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 }