View Javadoc

1   package uk.ac.ebi.intenz.domain.enzyme;
2   
3   import java.util.StringTokenizer;
4   
5   import uk.ac.ebi.biobabel.validator.DbIdentifierValidator;
6   import uk.ac.ebi.intenz.domain.DomainObject;
7   import uk.ac.ebi.intenz.domain.exceptions.EcException;
8   
9   /**
10   * This class represents an Enzyme Commission (EC) number.
11   * <p/>
12   * The general valid format looks as follows:<p/>
13   * <code>\d+(?:\.\d+(?:\.\d+(?:\.n?\d+){0,1}){0,1}){0,1}</code>
14   * <p/>
15   * <p/>
16   * For instance, the first enzyme has the EC <code>1.1.1.1</code>.
17   * <p/>
18   * The first digit is the number of the class the enzyme belongs to.<br>
19   * The second digit is the number of the subclass the enzyme belongs to.<br>
20   * The third digit is the number of the sub-subclass the enzyme belongs to.<br>
21   * The last digit is the number of the enzyme within the sub-subclass. In case
22   * of preliminary EC numbers, it is prefixed with <code>n</code>.
23   * <p/>
24   * Instances of this class are immutable.
25   *
26   * @author Michael Darsow
27   * @version $Revision: 1.2 $ $Date: 2008/01/28 12:33:00 $
28   */
29  public class EnzymeCommissionNumber extends DomainObject
30  implements Comparable<EnzymeCommissionNumber> {
31    /**
32     * Constant for an undefined EC which can be used for new enzyme suggestions where the EC number is not known (yet).
33     */
34    public final static EnzymeCommissionNumber UNDEF =
35  	  new EnzymeCommissionNumber(-1, -1, -1, -1, false);
36  
37    /**
38     * The character prefixing the fourth digit in preliminary EC numbers.
39     */
40    public static final char PRELIMINARY_PREFIX = 'n';
41    
42    /**
43     * Type of EC number.
44     * @author rafalcan
45     */
46    public static enum Type {
47  	  /** Only the first digit is > 0 which means that this EC is a class EC. */
48  	  CLASS,
49  	  /** Only the first two digits are > 0 which means that this EC is a subclass EC. */
50  	  SUBCLASS,
51  	  /** Only the first three digits are > 0 which means that this EC is a sub-subclass EC. */
52  	  SUBSUBCLASS,
53  	  /** All digits are > 0 which means that this EC is an enzyme EC. */
54  	  ENZYME,
55  	  /** Like {@link #ENZYME}, but not provided by NC-IUBMB. */
56  	  PRELIMINARY,
57  	  /** The EC number is undefined. */
58  	  UNDEF
59    }
60  
61    /**
62     * The enzyme's class number.
63     */
64    private int ec1;
65  
66    /**
67     * The enzyme's subclass number.
68     */
69    private int ec2;
70  
71    /**
72     * The enzyme's sub-subclass number.
73     */
74    private int ec3;
75  
76    /**
77     * The enzyme's number.
78     */
79    private int ec4;
80    
81    /**
82     * This EC number's type.
83     */
84    private Type type;
85  
86  
87    /**
88     * Object cannot be created outside this class.
89     *
90     * @param ec1 The enzyme's class number.
91     * @param ec2 The enzyme's subclass number.
92     * @param ec3 The enzyme's sub-subclass number.
93     * @param ec4 The enzyme's number.
94     * @param preliminary is this a preliminary EC number?
95     */
96    private EnzymeCommissionNumber(int ec1, int ec2, int ec3, int ec4, boolean preliminary) {
97      super();
98      this.ec1 = ec1;
99      this.ec2 = ec2;
100     this.ec3 = ec3;
101     this.ec4 = ec4;
102     this.type = (ec1 == -1)? Type.UNDEF:
103     	(ec2 == -1)? Type.CLASS:
104     		(ec3 == -1)? Type.SUBCLASS:
105     			(ec4 == -1)? Type.SUBSUBCLASS:
106     				preliminary? Type.PRELIMINARY : Type.ENZYME;
107   }
108 
109   /**
110    * Returns an <code>EnzymeCommissionNumber</code> instance defined by the given integer.
111    * <p/>
112    * If this requirement is met the EC number is valid. However, it does not implicitly mean, that
113    * this EC number is a valid number of the official Enzyme List. This has to be checked separately, outside this class.
114    *
115    * @param ec1 The enzyme's class number (must be a pos. integer).
116    * @return an <code>EnzymeCommissionNumber</code> instance.
117    * @throws EcException if one of the parameters is invalid.
118    */
119   public static EnzymeCommissionNumber valueOf(int ec1) throws EcException {
120     if (ec1 < 1) throw new EcException("The class number is invalid.");
121     return new EnzymeCommissionNumber(ec1, -1, -1, -1, false);
122   }
123 
124   /**
125    * Returns an <code>EnzymeCommissionNumber</code> instance defined by the given integers.
126    * <p/>
127    * All parameters have to be positive integers. A parameter can only be 0 if the succeeding parameter in
128    * the formal parameters list is > 0.
129    * <p/>
130    * Creating a class by giving 1,0 as parameters is alright, but it is invalid to create an EC number as follows:
131    * 0,1.<br/>
132    * If this requirement is met the EC number is valid. However, it does not implicitly mean, that
133    * this EC number is a valid number of the official Enzyme List. This has to be checked separately, outside this class.
134    *
135    * @param ec1 The enzyme's class number.
136    * @param ec2 The enzyme's subclass number.
137    * @return an <code>EnzymeCommissionNumber</code> instance.
138    * @throws EcException if one of the parameters is invalid.
139    */
140   public static EnzymeCommissionNumber valueOf(int ec1, int ec2) throws EcException {
141     if (ec1 < 1) throw new EcException("The class number is invalid.");
142     if (ec2 < 0) throw new EcException("The subclass number is invalid.");
143 
144     return new EnzymeCommissionNumber(ec1, ec2, -1, -1, false);
145   }
146 
147   /**
148    * Returns an <code>EnzymeCommissionNumber</code> instance defined by the given integers.
149    * <p/>
150    * All parameters have to be positive integers. A parameter can only be 0 if none of the succeeding parameters in
151    * the formal parameters list is > 0.
152    * <p/>
153    * Creating a class by giving 1,0,0 as parameters is alright, but it is invalid to create an EC number as follows:
154    * 0,1,1.<br/>
155    * If this requirement is met the EC number is valid. However, it does not implicitly mean, that
156    * this EC number is a valid number of the official Enzyme List. This has to be checked separately, outside this class.
157    *
158    * @param ec1 The enzyme's class number.
159    * @param ec2 The enzyme's subclass number.
160    * @param ec3 The enzyme's sub-subclass number.
161    * @return an <code>EnzymeCommissionNumber</code> instance.
162    * @throws EcException if one of the parameters is invalid.
163    */
164   public static EnzymeCommissionNumber valueOf(int ec1, int ec2, int ec3) throws EcException {
165     if (ec1 < 1) throw new EcException("The class number is invalid.");
166     if (ec2 < 0) throw new EcException("The subclass number is invalid.");
167     if (ec3 < 0) throw new EcException("The sub-subclass number is invalid.");
168     if (ec1 == 0) {
169       if (ec2 > 0 || ec3 > 0) throw new EcException("Invalid EC.");
170     }
171     if (ec2 == 0) {
172       if (ec3 > 0) throw new EcException("Invalid EC.");
173     }
174 
175     return new EnzymeCommissionNumber(ec1, ec2, ec3, -1, false);
176   }
177 
178   /**
179    * Returns an <code>EnzymeCommissionNumber</code> instance defined by the given integers.
180    * <p/>
181    * A parameter can only be 0 if none of the succeeding parameters in the formal parameters list is > 0.
182    * <p/>
183    * Creating a class by giving 1,0,0,0 as parameters is alright, but it is invalid to create an EC number as follows:
184    * 0,1,1,1.<br/>
185    * If this requirement is met the EC number is valid. However, it does not implicitly mean, that
186    * this EC number is a valid number of the official Enzyme List. This has to be checked separately, outside this class.
187    *
188    * @param ec1 The enzyme's class number.
189    * @param ec2 The enzyme's subclass number.
190    * @param ec3 The enzyme's sub-subclass number.
191    * @param ec4 The enzyme's number.
192    * @return an <code>EnzymeCommissionNumber</code> instance.
193    * @throws EcException if one of the parameters is invalid.
194    */
195   public static EnzymeCommissionNumber valueOf(int ec1, int ec2, int ec3, int ec4)
196   throws EcException {
197     return valueOf(ec1, ec2, ec3, ec4, false);
198   }
199   
200   /**
201    * Constructor allowing preliminary EC numbers.
202    * @param ec1
203    * @param ec2
204    * @param ec3
205    * @param ec4
206    * @param preliminary is this a preliminary EC number?
207    * @return An EC number instance.
208    * @throws EcException if the EC number is not valid.
209    */
210   public static EnzymeCommissionNumber valueOf(int ec1, int ec2, int ec3, int ec4,
211 		  boolean preliminary) throws EcException{
212 	    if (ec1 < 1) throw new EcException("The class number is invalid.");
213 	    if (ec2 == 0) {
214 	      if (ec3 > 0 || ec4 > 0) throw new EcException("Invalid EC.");
215 	    }
216 	    if (ec2 < 0) {
217 	      if (ec3 > -1 || ec4 > -1) throw new EcException("Invalid EC.");
218 	    }
219 	    if (ec3 == 0) {
220 	      if (ec4 > 0) throw new EcException("Invalid EC.");
221 	    }
222 	    if (ec3 < 0) {
223 	      if (ec4 > -1) throw new EcException("Invalid EC.");
224 	    }
225 	    if (preliminary && (ec1 < 1 || ec2 < 1 || ec3 < 1 || ec4 < 1))
226 	    	throw new EcException("Invalid preliminary EC");
227 	    return new EnzymeCommissionNumber(ec1, ec2, ec3, ec4, preliminary);
228   }
229 
230   /**
231    * Returns an <code>EnzymeCommissionNumber</code> instance defined by the given string.
232    * <p/>
233    * Calls {@link EnzymeCommissionNumber#valueOf(int, int, int, int, boolean)}.
234    *
235    * @param ecString A string representing an EC number.
236    * @throws NullPointerException  if <code>ecString</code> is <code>null</code>.
237    * @throws EcException           if one of the parameters is invalid.
238    * @throws NumberFormatException if <code>ecString</code> contained invalid characters.
239    */
240   public static EnzymeCommissionNumber valueOf(String ecString)
241   throws EcException, NumberFormatException {
242     if (ecString == null) 
243     	throw new NullPointerException("Parameter 'ecString' must not be null.");
244     int ec1 = -1, ec2 = -1, ec3 = -1, ec4 = -1;
245     boolean preliminary = false;
246     int iii = 1;
247     for (StringTokenizer st = new StringTokenizer(ecString.trim(), "."); st.hasMoreTokens();) {
248       String ecPart = st.nextToken();
249       switch (iii) {
250         case 1:
251           ec1 = Integer.parseInt(ecPart);
252           break;
253         case 2:
254           ec2 = Integer.parseInt(ecPart);
255           break;
256         case 3:
257           ec3 = Integer.parseInt(ecPart);
258           break;
259         case 4:
260         	if (ecPart.matches("\\*|-")){
261         		/**
262         		 * '*' is from ENZYME searches, that is a sub-subclass.
263         		 * '-' is a partial EC number for SIB, that is something
264         		 * which we do not know exactly what it does. It was also
265         		 * applied to cases for which there was not yet a complete
266         		 * EC number available (for this we now use preliminary EC
267         		 * numbers, e.g. EC 1.2.3.n1).
268         		 */
269     			// go to the sub-subclass
270         	} else if (ecPart.matches("\\d+")){
271 		    	ec4 = Integer.parseInt(ecPart);
272         	} else if (ecPart.matches("n\\d+")){
273 	    		preliminary = true;
274 		    	ec4 = Integer.parseInt(ecPart.substring(1));
275         	} else {
276         		throw new EcException("Invalid EC: " + ecString);
277         	}
278           break;
279       }
280       iii++;
281     }
282     return EnzymeCommissionNumber.valueOf(ec1, ec2, ec3, ec4, preliminary);
283   }
284 
285   /**
286    * Creates a copy of the given <code>EnzymeCommissionNumber</code>.
287    *
288    * @param ecToCopy The EC to be copied.
289    * @return a copy of the given <code>EnzymeCommissionNumber</code>.
290    * @throws NullPointerException if <code>ecToCopy</code> is <code>null</code>.
291    */
292   public static EnzymeCommissionNumber copy(EnzymeCommissionNumber ecToCopy) {
293     if (ecToCopy == null)
294     	throw new NullPointerException("Parameter 'ecToCopy' must not be null.");
295     return new EnzymeCommissionNumber(ecToCopy.ec1, ecToCopy.ec2, ecToCopy.ec3,
296     		ecToCopy.ec4, Type.PRELIMINARY.equals(ecToCopy.getType()));
297   }
298 
299   /**
300    * Compares this EC number with another one. The four digits are considered
301    * one after the other, but within the same subsubclass a preliminary EC
302    * is always lesser than an 'official' one, independently of the fourth digit.
303    * @param ec The object to be compared to this instance.
304    * @return a pos. integer if this instance is greater than, a neg. integer if it is less than or 0 if it equals o.
305    */
306   public int compareTo(EnzymeCommissionNumber ec) {
307     int ec1Diff = ec1 - ec.ec1;
308     if (ec1Diff != 0) return ec1Diff;
309 
310     int ec2Diff = ec2 - ec.ec2;
311     if (ec2Diff != 0) return ec2Diff;
312 
313     int ec3Diff = ec3 - ec.ec3;
314     if (ec3Diff != 0) return ec3Diff;
315 	  
316 	  int typeDiff = type.ordinal() - ec.type.ordinal();
317 	  if (typeDiff != 0) return typeDiff;
318 	  
319     int ec4Diff = ec4 - ec.ec4;
320     if (ec4Diff != 0) return ec4Diff;
321 
322     return type.ordinal() - ec.type.ordinal();
323   }
324 
325   /**
326    * Returns the type of this EC number.
327    * @return the type.
328    */
329   public Type getType() {
330 	return type;
331   }
332 
333   /**
334    * Checks whether a given EC number is valid.
335    *
336    * A valid EC number must be of the following format:<br>
337    *
338    * <code>\d+(?:\.\d+(?:\.\d+(?:\.\d+){0,1}){0,1}){0,1}</code>
339    *
340    * Examples:<br>
341    *
342    * <ul>
343    *   <li>1</li>
344    *   <li>2.3</li>
345    *   <li>4.2.14</li>
346    *   <li>1.2.2.126</li>
347    * </ul>
348    *
349    * @param ecString The EC string to be checked.
350    * @return <code>true</code>, if the EC is valid.
351    */
352   public static boolean isValid(String ecString) {
353 	  return DbIdentifierValidator.getInstance()
354 	  	.validate(ecString, DbIdentifierValidator.EC_NUMBER);
355   }
356   
357   /**
358    * Checks lazily if the given EC number is preliminary (according to UniProt
359    * format of preliminary EC numbers, see {@link #PRELIMINARY_PREFIX}).
360    * @param ecString
361    * @return <code>true</code> if the parameter contains the prefix for
362    * 	preliminary EC numbers.
363    */
364   public static boolean isPreliminary(String ecString){
365 	  return ecString.indexOf(PRELIMINARY_PREFIX) > -1;
366   }
367 
368   /**
369    * Returns a string representation of this EC number.
370    *
371    * @return the EC number as a string.
372    */
373   public String toString() {
374     StringBuffer ecString = new StringBuffer();
375 
376     if (ec1 == 0) {
377       ecString.append(ec1);
378       ecString.append(".");
379       ecString.append(ec2);
380       ecString.append(".");
381       ecString.append(ec3);
382       ecString.append(".");
383       ecString.append(ec4);
384     } else {
385       ecString.append(ec1);
386       if (ec2 > 0) {
387         ecString.append(".");
388         ecString.append(ec2);
389         if (ec3 > 0) {
390           ecString.append(".");
391           ecString.append(ec3);
392           if (ec4 > 0) {
393             ecString.append(".");
394             if (Type.PRELIMINARY.equals(type))
395             	ecString.append('n');
396             ecString.append(ec4);
397           }
398         }
399       }
400     }
401 
402     return ecString.toString();
403   }
404 
405   @Override
406 public int hashCode() {
407 	final int prime = 31;
408 	int result = super.hashCode();
409 	result = prime * result + ec1;
410 	result = prime * result + ec2;
411 	result = prime * result + ec3;
412 	result = prime * result + ec4;
413 	result = prime * result + ((type == null) ? 0 : type.hashCode());
414 	return result;
415 }
416 
417 @Override
418 public boolean equals(Object obj) {
419 	if (this == obj)
420 		return true;
421 	if (!super.equals(obj))
422 		return false;
423 	if (getClass() != obj.getClass())
424 		return false;
425 	EnzymeCommissionNumber other = (EnzymeCommissionNumber) obj;
426 	if (ec1 != other.ec1)
427 		return false;
428 	if (ec2 != other.ec2)
429 		return false;
430 	if (ec3 != other.ec3)
431 		return false;
432 	if (ec4 != other.ec4)
433 		return false;
434 	if (type == null) {
435 		if (other.type != null)
436 			return false;
437 	} else if (!type.equals(other.type))
438 		return false;
439 	return true;
440 }
441 
442 /**
443    * Returns the enzyme's class number.
444    *
445    * @return the enzyme's class number.
446    */
447   public int getEc1() {
448     return ec1;
449   }
450 
451   /**
452    * Returns the enzyme's subclass number.
453    *
454    * @return the enzyme's subclass number.
455    */
456   public int getEc2() {
457     return ec2;
458   }
459 
460   /**
461    * Returns the enzyme's sub-subclass number.
462    *
463    * @return the enzyme's sub-subclass number.
464    */
465   public int getEc3() {
466     return ec3;
467   }
468 
469   /**
470    * Returns the enzyme's number.
471    *
472    * @return the enzyme's number.
473    */
474   public int getEc4() {
475     return ec4;
476   }
477 
478   // ------------------- PRIVATE METHODS ------------------------
479 
480 }