001: /*
002: * This file is part of JGAP.
003: *
004: * JGAP offers a dual license model containing the LGPL as well as the MPL.
005: *
006: * For licensing information please see the file license.txt included with JGAP
007: * or have a look at the top of class org.jgap.Chromosome which representatively
008: * includes the JGAP license policy applicable for any file delivered with JGAP.
009: */
010: package org.jgap.impl;
011:
012: import java.util.*;
013: import org.jgap.*;
014:
015: /**
016: * A Gene implementation that supports a string for its allele. The valid
017: * alphabet as well as the minimum and maximum length of the string can be
018: * specified.<p>
019: * An alphabet == null indicates that all characters are seen as valid.<br>
020: * An alphabet == "" indicates that no character is seen to be valid.<p>
021: * Partly copied from IntegerGene.
022: *
023: * @author Klaus Meffert
024: * @author Audrius Meskauskas
025: * @since 1.1
026: */
027: public class StringGene extends BaseGene implements
028: IPersistentRepresentation, IBusinessKey {
029: //Constants for ready-to-use alphabets or serving as part of concetenation
030: public static final String ALPHABET_CHARACTERS_UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
031:
032: public static final String ALPHABET_CHARACTERS_LOWER = "abcdefghijklmnopqrstuvwxyz";
033:
034: public static final String ALPHABET_CHARACTERS_DIGITS = "0123456789";
035:
036: public static final String ALPHABET_CHARACTERS_SPECIAL = "+.*/\\,;@";
037:
038: /** String containing the CVS revision. Read out via reflection!*/
039: private final static String CVS_REVISION = "$Revision: 1.58 $";
040:
041: private int m_minLength;
042:
043: private int m_maxLength;
044:
045: private String m_alphabet;
046:
047: /**
048: * References the internal String value (allele) of this Gene.
049: */
050: private String m_value;
051:
052: /**
053: * Default constructor, sets minimum and maximum length to arbitrary.
054: * You need to set the valid alphabet later!<p>
055: * Do not use this constructor with a sample chromosome set in the
056: * configuration.<p>
057: * Attention: The configuration used is the one set with the static method
058: * Genotype.setConfiguration.
059: * @throws InvalidConfigurationException
060: *
061: * @author Klaus Meffert
062: * @since 1.1
063: */
064: public StringGene() throws InvalidConfigurationException {
065: this (Genotype.getStaticConfiguration());
066: }
067:
068: /**
069: * Default constructor, sets minimum and maximum length to arbitrary.
070: * You need to set the valid alphabet later!<p>
071: * Do not use this constructor with a sample chromosome set in the
072: * configuration.
073: *
074: * @param a_config the configuration to use
075: * @throws InvalidConfigurationException
076: *
077: * @author Klaus Meffert
078: * @since 3.0
079: */
080: public StringGene(final Configuration a_config)
081: throws InvalidConfigurationException {
082: this (a_config, 0, 0);
083: }
084:
085: /**
086: * Constructor, allows to specify minimum and maximum lengths of the string
087: * held by this gene. You need to set the valid alphabet later!<p>
088: * Do not use this constructor with a sample chromosome set in the
089: * configuration.
090: *
091: * @param a_config the configuration to use
092: * @param a_minLength minimum valid length of allele
093: * @param a_maxLength maximum valid length of allele
094: * @throws InvalidConfigurationException
095: *
096: * @author Klaus Meffert
097: * @since 1.1
098: */
099: public StringGene(final Configuration a_config,
100: final int a_minLength, final int a_maxLength)
101: throws InvalidConfigurationException {
102: this (a_config, a_minLength, a_maxLength, null);
103: }
104:
105: /**
106: * Constructor, allows to specify minimum and maximum lengths of the string
107: * held by this gene, as well as the valid alphabet. This constructor can be
108: * used to construct a sample chromosome with a configuration.
109: *
110: * @param a_config the configuration to use
111: * @param a_minLength minimum valid length of an allele
112: * @param a_maxLength maximum valid length of an allele
113: * @param a_alphabet valid alphabet for an allele
114: * @throws InvalidConfigurationException
115: *
116: * @author Klaus Meffert
117: * @since 2.0
118: */
119: public StringGene(final Configuration a_config,
120: final int a_minLength, final int a_maxLength,
121: final String a_alphabet)
122: throws InvalidConfigurationException {
123: super (a_config);
124: if (a_minLength < 0) {
125: throw new IllegalArgumentException(
126: "minimum length must be greater than" + " zero!");
127: }
128: if (a_maxLength < a_minLength) {
129: throw new IllegalArgumentException(
130: "minimum length must be smaller than"
131: + " or equal to maximum length!");
132: }
133: m_minLength = a_minLength;
134: m_maxLength = a_maxLength;
135: setAlphabet(a_alphabet);
136: }
137:
138: /**
139: * Sets the value (allele) of this Gene to a random String according to the
140: * valid alphabet and boundaries of length.
141: *
142: * @param a_numberGenerator the random number generator that should be used
143: * to create any random values. It's important to use this generator to
144: * maintain the user's flexibility to configure the genetic engine to use the
145: * random number generator of their choice
146: *
147: * @author Klaus Meffert
148: * @since 1.1
149: */
150: public void setToRandomValue(final RandomGenerator a_numberGenerator) {
151: if (m_alphabet == null || m_alphabet.length() < 1) {
152: throw new IllegalStateException(
153: "The valid alphabet is empty!");
154: }
155: if (m_maxLength < m_minLength || m_maxLength < 1) {
156: throw new IllegalStateException(
157: "Illegal valid maximum and/or minimum "
158: + "length of alphabet!");
159: }
160: //randomize length of string
161: //--------------------------
162: int length;
163: char value;
164: int index;
165: length = m_maxLength - m_minLength + 1;
166: int i = a_numberGenerator.nextInt() % length;
167: if (i < 0) {
168: i = -i;
169: }
170: length = m_minLength + i;
171: // For each character: randomize character value (which can be represented
172: // by an integer value).
173: //------------------------------------------------------------------------
174: String newAllele = "";
175: final int alphabetLength = m_alphabet.length();
176: for (int j = 0; j < length; j++) {
177: index = a_numberGenerator.nextInt(alphabetLength);
178: value = m_alphabet.charAt(index);
179: newAllele += value;
180: }
181: // Call setAllele to ensure extended verification.
182: // -----------------------------------------------
183: setAllele(newAllele);
184: }
185:
186: /**
187: * Sets the value and internal state of this Gene from the string
188: * representation returned by a previous invocation of the
189: * getPersistentRepresentation() method. This is an optional method but,
190: * if not implemented, XML persistence and possibly other features will not
191: * be available. An UnsupportedOperationException should be thrown if no
192: * implementation is provided.
193: *
194: * @param a_representation the string representation retrieved from a prior
195: * call to the getPersistentRepresentation() method
196: *
197: * @throws UnsupportedRepresentationException if this Gene implementation
198: * does not support the given string representation
199: *
200: * @author Klaus Meffert
201: * @since 1.1
202: */
203: public void setValueFromPersistentRepresentation(
204: final String a_representation)
205: throws UnsupportedRepresentationException {
206: if (a_representation != null) {
207: StringTokenizer tokenizer = new StringTokenizer(
208: a_representation, PERSISTENT_FIELD_DELIMITER);
209: // Make sure the representation contains the correct number of
210: // fields. If not, throw an exception.
211: // -----------------------------------------------------------
212: if (tokenizer.countTokens() != 4) {
213: throw new UnsupportedRepresentationException(
214: "The format of the given persistent representation '"
215: + a_representation
216: + "'"
217: + " is not recognized: it does not contain four tokens.");
218: }
219: String valueRepresentation;
220: String alphabetRepresentation;
221: String minLengthRepresentation;
222: String maxLengthRepresentation;
223: valueRepresentation = decode(tokenizer.nextToken());
224: minLengthRepresentation = tokenizer.nextToken();
225: maxLengthRepresentation = tokenizer.nextToken();
226: alphabetRepresentation = decode(tokenizer.nextToken());
227: // Now parse and set the minimum length.
228: // -------------------------------------
229: try {
230: m_minLength = Integer.parseInt(minLengthRepresentation);
231: } catch (NumberFormatException e) {
232: throw new UnsupportedRepresentationException(
233: "The format of the given persistent representation "
234: + "is not recognized: field 2 does not appear to be "
235: + "an integer value.");
236: }
237: // Now parse and set the maximum length.
238: // -------------------------------------
239: try {
240: m_maxLength = Integer.parseInt(maxLengthRepresentation);
241: } catch (NumberFormatException e) {
242: throw new UnsupportedRepresentationException(
243: "The format of the given persistent representation "
244: + "is not recognized: field 3 does not appear to be "
245: + "an integer value.");
246: }
247: String tempValue;
248: // Parse and set the representation of the value.
249: // ----------------------------------------------
250: if (valueRepresentation.equals("null")) {
251: tempValue = null;
252: } else {
253: if (valueRepresentation.equals(("\"\""))) {
254: tempValue = "";
255: } else {
256: tempValue = valueRepresentation;
257: }
258: }
259: //check if minLength and maxLength are violated.
260: //----------------------------------------------
261: if (tempValue != null) {
262: if (m_minLength > tempValue.length()) {
263: throw new UnsupportedRepresentationException(
264: "The value given"
265: + " is shorter than the allowed maximum length.");
266: }
267: if (m_maxLength < tempValue.length()) {
268: throw new UnsupportedRepresentationException(
269: "The value given"
270: + " is longer than the allowed maximum length.");
271: }
272: }
273: //check if all characters are within the alphabet.
274: //------------------------------------------------
275: if (!isValidAlphabet(tempValue, alphabetRepresentation)) {
276: throw new UnsupportedRepresentationException(
277: "The value given"
278: + " contains invalid characters.");
279: }
280: m_value = tempValue;
281: // Now set the alphabet that should be valid.
282: // ------------------------------------------
283: m_alphabet = alphabetRepresentation;
284: }
285: }
286:
287: /**
288: * Retrieves a string representation of this Gene that includes any
289: * information required to reconstruct it at a later time, such as its
290: * value and internal state. This string will be used to represent this
291: * Gene in XML persistence. This is an optional method but, if not
292: * implemented, XML persistence and possibly other features will not be
293: * available. An UnsupportedOperationException should be thrown if no
294: * implementation is provided.
295: *
296: * @return string representation of this Gene's current state
297: * @throws UnsupportedOperationException to indicate that no implementation
298: * is provided for this method
299: *
300: * @author Klaus Meffert
301: * @since 1.1
302: */
303: public String getPersistentRepresentation()
304: throws UnsupportedOperationException {
305: // The persistent representation includes the value, minimum length,
306: // maximum length and valid alphabet. Each is separated by a colon.
307: // -----------------------------------------------------------------
308: String s;
309: if (m_value == null) {
310: s = "null";
311: } else {
312: if (m_value.equals("")) {
313: s = "\"\"";
314: } else {
315: s = m_value;
316: }
317: }
318: return encode("" + s) + PERSISTENT_FIELD_DELIMITER
319: + m_minLength + PERSISTENT_FIELD_DELIMITER
320: + m_maxLength + PERSISTENT_FIELD_DELIMITER
321: + encode("" + m_alphabet);
322: }
323:
324: @Override
325: public String getBusinessKey() {
326: return m_value + PERSISTENT_FIELD_DELIMITER + m_minLength
327: + PERSISTENT_FIELD_DELIMITER + m_maxLength;
328: }
329:
330: /**
331: * Sets the value (allele) of this Gene to the new given value. This class
332: * expects the value to be a String instance. If the value is shorter or
333: * longer than the minimum or maximum length or any character is not within
334: * the valid alphabet an exception is thrown.
335: *
336: * @param a_newValue the new value of this Gene instance
337: *
338: * @author Klaus Meffert
339: * @since 1.1
340: */
341: public void setAllele(final Object a_newValue) {
342: if (a_newValue != null) {
343: String temp = (String) a_newValue;
344: if (temp.length() < m_minLength
345: || temp.length() > m_maxLength) {
346: throw new IllegalArgumentException(
347: "The given value is too short or too long!");
348: }
349: // Check for validity of alphabet.
350: // -------------------------------
351: if (!isValidAlphabet(temp, m_alphabet)) {
352: throw new IllegalArgumentException(
353: "The given value contains"
354: + " at least one invalid character.");
355: }
356: if (getConstraintChecker() != null) {
357: if (!getConstraintChecker().verify(this , a_newValue,
358: null, -1)) {
359: return;
360: }
361: }
362: m_value = temp;
363: } else {
364: m_value = null;
365: }
366: }
367:
368: /**
369: * Provides an implementation-independent means for creating new Gene
370: * instances.
371: *
372: * @return a new Gene instance of the same type and with the same setup as
373: * this concrete Gene
374: *
375: * @author Klaus Meffert
376: * @since 1.1
377: */
378: protected Gene newGeneInternal() {
379: try {
380: StringGene result = new StringGene(getConfiguration(),
381: m_minLength, m_maxLength, m_alphabet);
382: result.setConstraintChecker(getConstraintChecker());
383: return result;
384: } catch (InvalidConfigurationException iex) {
385: throw new IllegalStateException(iex.getMessage());
386: }
387: }
388:
389: /**
390: * Compares this StringGene with the specified object (which must also
391: * be a StringGene) for order, which is determined by the String
392: * value of this Gene compared to the one provided for comparison.
393: *
394: * @param a_other the StringGene to be compared to this StringGene
395: * @return a negative int, zero, or a positive int as this object
396: * is less than, equal to, or greater than the object provided for comparison
397: *
398: * @throws ClassCastException if the specified object's type prevents it
399: * from being compared to this StringGene
400: *
401: * @author Klaus Meffert
402: * @since 1.1
403: */
404: public int compareTo(Object a_other) {
405: StringGene otherStringGene = (StringGene) a_other;
406: // First, if the other gene (or its value) is null, then this is
407: // the greater allele. Otherwise, just use the String's compareTo
408: // method to perform the comparison.
409: // ---------------------------------------------------------------
410: if (otherStringGene == null) {
411: return 1;
412: } else if (otherStringGene.m_value == null) {
413: // If our value is also null, then we're the same. Otherwise,
414: // this is the greater gene.
415: // ----------------------------------------------------------
416: if (m_value == null) {
417: if (isCompareApplicationData()) {
418: return compareApplicationData(getApplicationData(),
419: otherStringGene.getApplicationData());
420: } else {
421: return 0;
422: }
423: } else {
424: return 1;
425: }
426: } else {
427: int res = m_value.compareTo(otherStringGene.m_value);
428: if (res == 0) {
429: if (isCompareApplicationData()) {
430: return compareApplicationData(getApplicationData(),
431: otherStringGene.getApplicationData());
432: } else {
433: return 0;
434: }
435: } else {
436: return res;
437: }
438: }
439: }
440:
441: public int size() {
442: return m_value.length();
443: }
444:
445: public int getMaxLength() {
446: return m_maxLength;
447: }
448:
449: public int getMinLength() {
450: return m_minLength;
451: }
452:
453: public void setMinLength(int m_minLength) {
454: this .m_minLength = m_minLength;
455: }
456:
457: public void setMaxLength(int m_maxLength) {
458: this .m_maxLength = m_maxLength;
459: }
460:
461: public String getAlphabet() {
462: return m_alphabet;
463: }
464:
465: /**
466: * Sets the valid alphabet of the StringGene. The caller needs to care that
467: * there are no doublettes in the alphabet. Otherwise there is no guarantee
468: * for correct functioning of the class!
469: * @param a_alphabet valid alphabet for allele
470: *
471: * @author Klaus Meffert
472: * @since 1.1
473: */
474: public void setAlphabet(String a_alphabet) {
475: m_alphabet = a_alphabet;
476: }
477:
478: /**
479: * Retrieves a string representation of this StringGene's value that
480: * may be useful for display purposes.
481: *
482: * @return a string representation of this StringGene's value
483: *
484: * @author Klaus Meffert
485: * @since 1.1
486: */
487: public String toString() {
488: String s = "StringGene=";
489: if (m_value == null) {
490: s += "null";
491: } else {
492: if (m_value.equals("")) {
493: s += "\"\"";
494: } else {
495: s += m_value;
496: }
497: }
498: return s;
499: }
500:
501: /**
502: * Retrieves the String value of this Gene, which may be more convenient in
503: * some cases than the more general getAllele() method.
504: *
505: * @return the String value of this Gene
506: *
507: * @since 1.1
508: */
509: public String stringValue() {
510: return m_value;
511: }
512:
513: /**
514: * Checks whether a string value is valid concerning a given alphabet.
515: * @param a_value the value to check
516: * @param a_alphabet the valid alphabet to check against
517: * @return true: given string value is valid
518: *
519: * @author Klaus Meffert
520: * @since 1.1
521: */
522: private boolean isValidAlphabet(String a_value, String a_alphabet) {
523: if (a_value == null || a_value.length() < 1) {
524: return true;
525: }
526: if (a_alphabet == null) {
527: return true;
528: }
529: if (a_alphabet.length() < 1) {
530: return false;
531: }
532: // Loop over all characters of a_value.
533: // ------------------------------------
534: int length = a_value.length();
535: char c;
536: for (int i = 0; i < length; i++) {
537: c = a_value.charAt(i);
538: if (a_alphabet.indexOf(c) < 0) {
539: return false;
540: }
541: }
542: return true;
543: }
544:
545: /**
546: * Applies a mutation of a given intensity (percentage) onto the atomic
547: * element at given index (NumberGenes only have one atomic element).
548: * @param index index of atomic element, between 0 and size()-1
549: * @param a_percentage percentage of mutation (greater than -1 and smaller
550: * than 1).
551: *
552: * @author Klaus Meffert
553: * @since 1.1
554: */
555: public void applyMutation(int index, double a_percentage) {
556: String s = stringValue();
557: int index2 = -1;
558: boolean randomize;
559: int len = 0;
560: if (m_alphabet != null) {
561: len = m_alphabet.length();
562: if (len < 1) {
563: // Does mutation make sense here?
564: // ------------------------------
565: randomize = true;
566: } else {
567: randomize = false;
568: }
569: } else {
570: randomize = true;
571: }
572: char newValue;
573: RandomGenerator rn = getConfiguration().getRandomGenerator();
574: if (!randomize) {
575: int indexC = m_alphabet.indexOf(s.charAt(index));
576: index2 = indexC + (int) Math.round(len * a_percentage);
577: // If index of new character out of bounds then randomly choose a new
578: // character. This randomness is assumed to help in the process of
579: // evolution.
580: // ------------------------------------------------------------------
581: if (index2 < 0 || index2 >= len) {
582: index2 = rn.nextInt(len);
583: }
584: newValue = m_alphabet.charAt(index2);
585: } else {
586: index2 = rn.nextInt(256);
587: newValue = (char) index2;
588: }
589: // Set mutated character by concatenating the String with it.
590: // ----------------------------------------------------------
591: if (s == null) {
592: s = "" + newValue;
593: } else {
594: s = s.substring(0, index) + newValue
595: + s.substring(index + 1);
596: }
597: setAllele(s);
598: }
599:
600: protected Object getInternalValue() {
601: return m_value;
602: }
603: }
|