001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021package org.biojava.bio;
022
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026/**
027 * An ec (enzyme classification) number.
028 *
029 *
030 * Implementations of this interface should be imutable. This makes them much
031 * more usefull as keys in maps.
032 * 
033 * it is a good idea to validate that the data being passed in is a sane ec
034 * number.
035 *
036 * @author Matthew Pocock
037 * @since 1.4
038 */
039public interface EcNumber {
040  /**
041   * A Pattern that can be used to parse EC strings into the indiidual numbers.
042   */
043  public static final Pattern EC_PATTERN =
044    Pattern.compile("((\\d*)|(-))\\.((\\d*)|(-))\\.((\\d*)|(-))\\.((\\d*)|(-))");
045
046  /**
047   * Constant that represents EC number components that are not defined. This
048   * is often represented as a '-' in EC strings.
049   */
050  public static final int UNDEFINED = -1;
051
052  /**
053   * Constant that represents EC number components that are as yet unclassified.
054   * This is often represented as 99 in EC strings.
055   */
056  public static final int UNCLASSIFIED = 99;
057
058  /**
059   * Get the class number associated with the particular level of the ec number.
060   *
061   * <p>The index can be between 0 and 3 inclusive. 0 correxpons to the top
062   * level class, 1 to the sub-class and so on. A return value of UNDEFINED
063   * indicates that this field is not populated.</p>
064   *
065   * @param level  the level in the ec classification to return the number for
066   * @return the value at that level
067   */
068  public int getClassNumber(int level);
069
070  /**
071   * A simple implementation of EcNumber.
072   *
073   * @author Matthew Pocock
074   * @since 1.4
075   */
076  public static class Impl
077  implements EcNumber {
078    private int[] classes;
079
080    /**
081     * Make a new EcNumber.Impl with the data provided.
082     *
083     * @param mainClass     the main class number
084     * @param subClass      the sub class number
085     * @param subSubClass   the sub-sub class number
086     * @param group         the group number
087     */
088    public Impl(int mainClass, int subClass, int subSubClass, int group) {
089      this.classes = new int[] { mainClass, subClass, subSubClass, group };
090    }
091
092    private Impl(String ecString) {
093      Matcher matcher = EC_PATTERN.matcher(ecString);
094      if(!matcher.matches()) {
095        throw new IllegalArgumentException(
096          "Can't parse ec string: " + ecString );
097      }
098
099      classes = new int[] {
100        process(matcher.group(1)),
101        process(matcher.group(4)),
102        process(matcher.group(7)),
103        process(matcher.group(10))
104      };
105    }
106
107    private int process(String s) {
108      if(s.length() > 0) {
109        if(s.equals("-")) {
110          return UNDEFINED;
111        } else {
112          return Integer.parseInt(s);
113        }
114      } else {
115        return UNDEFINED;
116      }
117    }
118
119    public int getClassNumber(int level) {
120      return classes[level];
121    }
122
123    public String toString() {
124      StringBuffer sBuf = new StringBuffer();
125      sBuf.append(process(getClassNumber(0)));
126      for(int i = 1; i < 4; i++) {
127        sBuf.append(".");
128        sBuf.append(process(getClassNumber(i)));
129      }
130      return sBuf.toString();
131    }
132
133    private String process(int val) {
134      if(val == UNDEFINED) {
135        return "";
136      } else {
137        return Integer.toString(val);
138      }
139    }
140
141    public boolean equals(Object obj) {
142      if(obj instanceof EcNumber) {
143        EcNumber that = (EcNumber) obj;
144
145        for(int i = 0; i < 4; i++) {
146          if(this.getClassNumber(i) != that.getClassNumber(i)) {
147            return false;
148          }
149        }
150
151        return true;
152      }
153
154      return false;
155    }
156
157    public int hashCode() {
158      return
159        getClassNumber(0) * 1000000 +
160        getClassNumber(1) * 10000 +
161        getClassNumber(2) * 100 +
162        getClassNumber(3);
163    }
164
165    /**
166     * Process a string into an EcNumber.
167     *
168     * <p>
169     * This method uses the {@link EcNumber#EC_PATTERN} regular expression.
170     * </p>
171     *
172     * @param ecString  String to parse
173     * @return a new EcNumber
174     * @throws IllegalArgumentException  if ecString could not be parsed
175     */
176    public static Impl valueOf(String ecString) {
177      return new Impl(ecString);
178    }
179  }
180}