/*
* This file is part of JGAP.
*
* JGAP offers a dual license model containing the LGPL as well as the MPL.
*
* For licensing information please see the file license.txt included with JGAP
* or have a look at the top of class org.jgap.Chromosome which representatively
* includes the JGAP license policy applicable for any file delivered with JGAP.
*/
package org.jgap.impl;
import java.util.*;
import org.jgap.*;
/**
* A Gene implementation that supports a string for its allele. The valid
* alphabet as well as the minimum and maximum length of the string can be
* specified.<p>
* An alphabet == null indicates that all characters are seen as valid.<br>
* An alphabet == "" indicates that no character is seen to be valid.<p>
* Partly copied from IntegerGene.
*
* @author Klaus Meffert
* @author Audrius Meskauskas
* @since 1.1
*/
public class StringGene
extends BaseGene implements IPersistentRepresentation, IBusinessKey {
//Constants for ready-to-use alphabets or serving as part of concetenation
public static final String ALPHABET_CHARACTERS_UPPER =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
public static final String ALPHABET_CHARACTERS_LOWER =
"abcdefghijklmnopqrstuvwxyz";
public static final String ALPHABET_CHARACTERS_DIGITS = "0123456789";
public static final String ALPHABET_CHARACTERS_SPECIAL = "+.*/\\,;@";
/** String containing the CVS revision. Read out via reflection!*/
private final static String CVS_REVISION = "$Revision: 1.58 $";
private int m_minLength;
private int m_maxLength;
private String m_alphabet;
/**
* References the internal String value (allele) of this Gene.
*/
private String m_value;
/**
* Default constructor, sets minimum and maximum length to arbitrary.
* You need to set the valid alphabet later!<p>
* Do not use this constructor with a sample chromosome set in the
* configuration.<p>
* Attention: The configuration used is the one set with the static method
* Genotype.setConfiguration.
* @throws InvalidConfigurationException
*
* @author Klaus Meffert
* @since 1.1
*/
public StringGene()
throws InvalidConfigurationException {
this(Genotype.getStaticConfiguration());
}
/**
* Default constructor, sets minimum and maximum length to arbitrary.
* You need to set the valid alphabet later!<p>
* Do not use this constructor with a sample chromosome set in the
* configuration.
*
* @param a_config the configuration to use
* @throws InvalidConfigurationException
*
* @author Klaus Meffert
* @since 3.0
*/
public StringGene(final Configuration a_config)
throws InvalidConfigurationException {
this(a_config, 0, 0);
}
/**
* Constructor, allows to specify minimum and maximum lengths of the string
* held by this gene. You need to set the valid alphabet later!<p>
* Do not use this constructor with a sample chromosome set in the
* configuration.
*
* @param a_config the configuration to use
* @param a_minLength minimum valid length of allele
* @param a_maxLength maximum valid length of allele
* @throws InvalidConfigurationException
*
* @author Klaus Meffert
* @since 1.1
*/
public StringGene(final Configuration a_config, final int a_minLength,
final int a_maxLength)
throws InvalidConfigurationException {
this(a_config, a_minLength, a_maxLength, null);
}
/**
* Constructor, allows to specify minimum and maximum lengths of the string
* held by this gene, as well as the valid alphabet. This constructor can be
* used to construct a sample chromosome with a configuration.
*
* @param a_config the configuration to use
* @param a_minLength minimum valid length of an allele
* @param a_maxLength maximum valid length of an allele
* @param a_alphabet valid alphabet for an allele
* @throws InvalidConfigurationException
*
* @author Klaus Meffert
* @since 2.0
*/
public StringGene(final Configuration a_config, final int a_minLength,
final int a_maxLength, final String a_alphabet)
throws InvalidConfigurationException {
super(a_config);
if (a_minLength < 0) {
throw new IllegalArgumentException(
"minimum length must be greater than"
+ " zero!");
}
if (a_maxLength < a_minLength) {
throw new IllegalArgumentException(
"minimum length must be smaller than"
+ " or equal to maximum length!");
}
m_minLength = a_minLength;
m_maxLength = a_maxLength;
setAlphabet(a_alphabet);
}
/**
* Sets the value (allele) of this Gene to a random String according to the
* valid alphabet and boundaries of length.
*
* @param a_numberGenerator the random number generator that should be used
* to create any random values. It's important to use this generator to
* maintain the user's flexibility to configure the genetic engine to use the
* random number generator of their choice
*
* @author Klaus Meffert
* @since 1.1
*/
public void setToRandomValue(final RandomGenerator a_numberGenerator) {
if (m_alphabet == null || m_alphabet.length() < 1) {
throw new IllegalStateException("The valid alphabet is empty!");
}
if (m_maxLength < m_minLength || m_maxLength < 1) {
throw new IllegalStateException(
"Illegal valid maximum and/or minimum "
+ "length of alphabet!");
}
//randomize length of string
//--------------------------
int length;
char value;
int index;
length = m_maxLength - m_minLength + 1;
int i = a_numberGenerator.nextInt() % length;
if (i < 0) {
i = -i;
}
length = m_minLength + i;
// For each character: randomize character value (which can be represented
// by an integer value).
//------------------------------------------------------------------------
String newAllele = "";
final int alphabetLength = m_alphabet.length();
for (int j = 0; j < length; j++) {
index = a_numberGenerator.nextInt(alphabetLength);
value = m_alphabet.charAt(index);
newAllele += value;
}
// Call setAllele to ensure extended verification.
// -----------------------------------------------
setAllele(newAllele);
}
/**
* Sets the value and internal state of this Gene from the string
* representation returned by a previous invocation of the
* getPersistentRepresentation() method. This is an optional method but,
* if not implemented, XML persistence and possibly other features will not
* be available. An UnsupportedOperationException should be thrown if no
* implementation is provided.
*
* @param a_representation the string representation retrieved from a prior
* call to the getPersistentRepresentation() method
*
* @throws UnsupportedRepresentationException if this Gene implementation
* does not support the given string representation
*
* @author Klaus Meffert
* @since 1.1
*/
public void setValueFromPersistentRepresentation(final String
a_representation)
throws UnsupportedRepresentationException {
if (a_representation != null) {
StringTokenizer tokenizer =
new StringTokenizer(a_representation,
PERSISTENT_FIELD_DELIMITER);
// Make sure the representation contains the correct number of
// fields. If not, throw an exception.
// -----------------------------------------------------------
if (tokenizer.countTokens() != 4) {
throw new UnsupportedRepresentationException(
"The format of the given persistent representation '"
+ a_representation + "'"
+ " is not recognized: it does not contain four tokens.");
}
String valueRepresentation;
String alphabetRepresentation;
String minLengthRepresentation;
String maxLengthRepresentation;
valueRepresentation = decode(tokenizer.nextToken());
minLengthRepresentation = tokenizer.nextToken();
maxLengthRepresentation = tokenizer.nextToken();
alphabetRepresentation = decode(tokenizer.nextToken());
// Now parse and set the minimum length.
// -------------------------------------
try {
m_minLength = Integer.parseInt(minLengthRepresentation);
} catch (NumberFormatException e) {
throw new UnsupportedRepresentationException(
"The format of the given persistent representation " +
"is not recognized: field 2 does not appear to be " +
"an integer value.");
}
// Now parse and set the maximum length.
// -------------------------------------
try {
m_maxLength = Integer.parseInt(maxLengthRepresentation);
} catch (NumberFormatException e) {
throw new UnsupportedRepresentationException(
"The format of the given persistent representation " +
"is not recognized: field 3 does not appear to be " +
"an integer value.");
}
String tempValue;
// Parse and set the representation of the value.
// ----------------------------------------------
if (valueRepresentation.equals("null")) {
tempValue = null;
}
else {
if (valueRepresentation.equals( ("\"\""))) {
tempValue = "";
}
else {
tempValue = valueRepresentation;
}
}
//check if minLength and maxLength are violated.
//----------------------------------------------
if (tempValue != null) {
if (m_minLength > tempValue.length()) {
throw new UnsupportedRepresentationException(
"The value given"
+ " is shorter than the allowed maximum length.");
}
if (m_maxLength < tempValue.length()) {
throw new UnsupportedRepresentationException(
"The value given"
+ " is longer than the allowed maximum length.");
}
}
//check if all characters are within the alphabet.
//------------------------------------------------
if (!isValidAlphabet(tempValue, alphabetRepresentation)) {
throw new UnsupportedRepresentationException("The value given"
+ " contains invalid characters.");
}
m_value = tempValue;
// Now set the alphabet that should be valid.
// ------------------------------------------
m_alphabet = alphabetRepresentation;
}
}
/**
* Retrieves a string representation of this Gene that includes any
* information required to reconstruct it at a later time, such as its
* value and internal state. This string will be used to represent this
* Gene in XML persistence. This is an optional method but, if not
* implemented, XML persistence and possibly other features will not be
* available. An UnsupportedOperationException should be thrown if no
* implementation is provided.
*
* @return string representation of this Gene's current state
* @throws UnsupportedOperationException to indicate that no implementation
* is provided for this method
*
* @author Klaus Meffert
* @since 1.1
*/
public String getPersistentRepresentation()
throws UnsupportedOperationException {
// The persistent representation includes the value, minimum length,
// maximum length and valid alphabet. Each is separated by a colon.
// -----------------------------------------------------------------
String s;
if (m_value == null) {
s = "null";
}
else {
if (m_value.equals("")) {
s = "\"\"";
}
else {
s = m_value;
}
}
return encode("" + s) +
PERSISTENT_FIELD_DELIMITER + m_minLength +
PERSISTENT_FIELD_DELIMITER + m_maxLength +
PERSISTENT_FIELD_DELIMITER +
encode("" + m_alphabet);
}
@Override
public String getBusinessKey() {
return m_value + PERSISTENT_FIELD_DELIMITER + m_minLength
+ PERSISTENT_FIELD_DELIMITER + m_maxLength;
}
/**
* Sets the value (allele) of this Gene to the new given value. This class
* expects the value to be a String instance. If the value is shorter or
* longer than the minimum or maximum length or any character is not within
* the valid alphabet an exception is thrown.
*
* @param a_newValue the new value of this Gene instance
*
* @author Klaus Meffert
* @since 1.1
*/
public void setAllele(final Object a_newValue) {
if (a_newValue != null) {
String temp = (String) a_newValue;
if (temp.length() < m_minLength ||
temp.length() > m_maxLength) {
throw new IllegalArgumentException(
"The given value is too short or too long!");
}
// Check for validity of alphabet.
// -------------------------------
if (!isValidAlphabet(temp, m_alphabet)) {
throw new IllegalArgumentException("The given value contains"
+ " at least one invalid character.");
}
if (getConstraintChecker() != null) {
if (!getConstraintChecker().verify(this, a_newValue, null, -1)) {
return;
}
}
m_value = temp;
}
else {
m_value = null;
}
}
/**
* Provides an implementation-independent means for creating new Gene
* instances.
*
* @return a new Gene instance of the same type and with the same setup as
* this concrete Gene
*
* @author Klaus Meffert
* @since 1.1
*/
protected Gene newGeneInternal() {
try {
StringGene result = new StringGene(getConfiguration(), m_minLength,
m_maxLength, m_alphabet);
result.setConstraintChecker(getConstraintChecker());
return result;
} catch (InvalidConfigurationException iex) {
throw new IllegalStateException(iex.getMessage());
}
}
/**
* Compares this StringGene with the specified object (which must also
* be a StringGene) for order, which is determined by the String
* value of this Gene compared to the one provided for comparison.
*
* @param a_other the StringGene to be compared to this StringGene
* @return a negative int, zero, or a positive int as this object
* is less than, equal to, or greater than the object provided for comparison
*
* @throws ClassCastException if the specified object's type prevents it
* from being compared to this StringGene
*
* @author Klaus Meffert
* @since 1.1
*/
public int compareTo(Object a_other) {
StringGene otherStringGene = (StringGene) a_other;
// First, if the other gene (or its value) is null, then this is
// the greater allele. Otherwise, just use the String's compareTo
// method to perform the comparison.
// ---------------------------------------------------------------
if (otherStringGene == null) {
return 1;
}
else if (otherStringGene.m_value == null) {
// If our value is also null, then we're the same. Otherwise,
// this is the greater gene.
// ----------------------------------------------------------
if (m_value == null) {
if (isCompareApplicationData()) {
return compareApplicationData(getApplicationData(),
otherStringGene.getApplicationData());
}
else {
return 0;
}
}
else {
return 1;
}
}
else {
int res = m_value.compareTo(otherStringGene.m_value);
if (res == 0) {
if (isCompareApplicationData()) {
return compareApplicationData(getApplicationData(),
otherStringGene.getApplicationData());
}
else {
return 0;
}
}
else {
return res;
}
}
}
public int size() {
return m_value.length();
}
public int getMaxLength() {
return m_maxLength;
}
public int getMinLength() {
return m_minLength;
}
public void setMinLength(int m_minLength) {
this.m_minLength = m_minLength;
}
public void setMaxLength(int m_maxLength) {
this.m_maxLength = m_maxLength;
}
public String getAlphabet() {
return m_alphabet;
}
/**
* Sets the valid alphabet of the StringGene. The caller needs to care that
* there are no doublettes in the alphabet. Otherwise there is no guarantee
* for correct functioning of the class!
* @param a_alphabet valid alphabet for allele
*
* @author Klaus Meffert
* @since 1.1
*/
public void setAlphabet(String a_alphabet) {
m_alphabet = a_alphabet;
}
/**
* Retrieves a string representation of this StringGene's value that
* may be useful for display purposes.
*
* @return a string representation of this StringGene's value
*
* @author Klaus Meffert
* @since 1.1
*/
public String toString() {
String s = "StringGene=";
if (m_value == null) {
s += "null";
}
else {
if (m_value.equals("")) {
s += "\"\"";
}
else {
s += m_value;
}
}
return s;
}
/**
* Retrieves the String value of this Gene, which may be more convenient in
* some cases than the more general getAllele() method.
*
* @return the String value of this Gene
*
* @since 1.1
*/
public String stringValue() {
return m_value;
}
/**
* Checks whether a string value is valid concerning a given alphabet.
* @param a_value the value to check
* @param a_alphabet the valid alphabet to check against
* @return true: given string value is valid
*
* @author Klaus Meffert
* @since 1.1
*/
private boolean isValidAlphabet(String a_value, String a_alphabet) {
if (a_value == null || a_value.length() < 1) {
return true;
}
if (a_alphabet == null) {
return true;
}
if (a_alphabet.length() < 1) {
return false;
}
// Loop over all characters of a_value.
// ------------------------------------
int length = a_value.length();
char c;
for (int i = 0; i < length; i++) {
c = a_value.charAt(i);
if (a_alphabet.indexOf(c) < 0) {
return false;
}
}
return true;
}
/**
* Applies a mutation of a given intensity (percentage) onto the atomic
* element at given index (NumberGenes only have one atomic element).
* @param index index of atomic element, between 0 and size()-1
* @param a_percentage percentage of mutation (greater than -1 and smaller
* than 1).
*
* @author Klaus Meffert
* @since 1.1
*/
public void applyMutation(int index, double a_percentage) {
String s = stringValue();
int index2 = -1;
boolean randomize;
int len = 0;
if (m_alphabet != null) {
len = m_alphabet.length();
if (len < 1) {
// Does mutation make sense here?
// ------------------------------
randomize = true;
}
else {
randomize = false;
}
}
else {
randomize = true;
}
char newValue;
RandomGenerator rn = getConfiguration().getRandomGenerator();
if (!randomize) {
int indexC = m_alphabet.indexOf(s.charAt(index));
index2 = indexC + (int) Math.round(len * a_percentage);
// If index of new character out of bounds then randomly choose a new
// character. This randomness is assumed to help in the process of
// evolution.
// ------------------------------------------------------------------
if (index2 < 0 || index2 >= len) {
index2 = rn.nextInt(len);
}
newValue = m_alphabet.charAt(index2);
}
else {
index2 = rn.nextInt(256);
newValue = (char) index2;
}
// Set mutated character by concatenating the String with it.
// ----------------------------------------------------------
if (s == null) {
s = "" + newValue;
}
else {
s = s.substring(0, index) + newValue + s.substring(index + 1);
}
setAllele(s);
}
protected Object getInternalValue() {
return m_value;
}
}