001/*
002 * Java Genetic Algorithm Library (jenetics-9.1.0).
003 * Copyright (c) 2007-2026 Franz Wilhelmstötter
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 * Author:
018 *    Franz Wilhelmstötter (franz.wilhelmstoetter@gmail.com)
019 */
020package io.jenetics;
021
022import static java.util.Objects.requireNonNull;
023import static io.jenetics.util.RandomRegistry.random;
024
025import java.io.Serial;
026import java.io.Serializable;
027import java.util.Objects;
028
029import io.jenetics.util.CharSeq;
030import io.jenetics.util.ISeq;
031import io.jenetics.util.IntRange;
032import io.jenetics.util.MSeq;
033import io.jenetics.util.RandomRegistry;
034
035/**
036 * Character gene implementation.
037 *
038 * <p>This is a <a href="https://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html">
039 * value-based</a> class; use of identity-sensitive operations (including
040 * reference equality ({@code ==}), identity hash code, or synchronization) on
041 * instances of {@code CharacterGene} may have unpredictable results and should
042 * be avoided.
043 *
044 * @see CharacterChromosome
045 *
046 * @implNote
047 * This class is immutable and thread-safe.
048 *
049 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
050 * @since 1.0
051 * @version 6.0
052 */
053public final class CharacterGene
054        implements
055                Gene<Character, CharacterGene>,
056                Comparable<CharacterGene>,
057                Serializable
058{
059        @Serial
060        private static final long serialVersionUID = 3L;
061
062        /**
063         * The default character set used by this gene.
064         */
065        public static final CharSeq DEFAULT_CHARACTERS = new CharSeq("""
066                0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ \
067                !"$%&/()=?`{[]}\\+~*#';.:,-_<>|@^'"""
068        );
069
070        private final char _allele;
071        private final CharSeq _validCharacters;
072
073        private CharacterGene(final CharSeq chars, final int index) {
074                _allele = chars.get(index);
075                _validCharacters = chars;
076        }
077
078        /**
079         * Create a new character gene from the given {@code character} and the
080         * given set of valid characters.
081         *
082         * @param allele the char this gene represents
083         * @param validChars the set of valid characters.
084         * @throws NullPointerException if one of the arguments is {@code null}.
085         */
086        private CharacterGene(final char allele, final CharSeq validChars) {
087                _allele = allele;
088                _validCharacters = requireNonNull(validChars);
089        }
090
091        @Override
092        public boolean isValid() {
093                return _validCharacters.contains(_allele);
094        }
095
096        @Override
097        public Character allele() {
098                return _allele;
099        }
100
101        /**
102         * Return the {@code char} value of this character gene.
103         *
104         * @return the {@code char} value.
105         */
106        public char charValue() {
107                return _allele;
108        }
109
110        /**
111         * Test, if the given character is valid.
112         *
113         * @param allele The character to test.
114         * @return true if the character is valid, false otherwise.
115         */
116        public boolean isValidCharacter(final Character allele) {
117                return _validCharacters.contains(allele);
118        }
119
120        /**
121         * Return a (unmodifiable) set of valid characters.
122         *
123         * @return the {@link CharSeq} of valid characters.
124         */
125        public CharSeq validChars() {
126                return _validCharacters;
127        }
128
129        /**
130         * @see java.lang.Character#compareTo(java.lang.Character)
131         * @param that The other gene to compare.
132         * @return value 0 if the argument Character is equal to this Character;
133         *         a value less than 0 if this Character is numerically less than
134         *         the Character argument; and a value greater than 0 if this
135         *         Character is numerically greater than the Character argument
136         *         (unsigned comparison). Note that this is strictly a numerical
137         *         comparison; it is not local-dependent.
138         */
139        @Override
140        public int compareTo(final CharacterGene that) {
141                return Character.compare(_allele, that._allele);
142        }
143
144        @Override
145        public int hashCode() {
146                return Objects.hash(_allele, _validCharacters);
147        }
148
149        @Override
150        public boolean equals(final Object obj) {
151                return obj instanceof CharacterGene other &&
152                        other._allele == _allele &&
153                        Objects.equals(other._validCharacters, _validCharacters);
154        }
155
156        @Override
157        public String toString() {
158                return Character.toString(_allele);
159        }
160
161
162        /* *************************************************************************
163         *  Factory methods
164         * ************************************************************************/
165
166        @Override
167        public CharacterGene newInstance() {
168                return of(_validCharacters);
169        }
170
171        /**
172         * Create a new character gene from the given character. If the character
173         * is not within the {@link #validChars()}, an invalid gene will be
174         * created.
175         *
176         * @param allele the character value of the created gene.
177         * @return a new character gene.
178         * @throws NullPointerException if the given {@code character} is
179         *         {@code null}.
180         */
181        @Override
182        public CharacterGene newInstance(final Character allele) {
183                return of(allele, _validCharacters);
184        }
185
186
187        /* *************************************************************************
188         *  Static object creation methods
189         * ************************************************************************/
190
191        /**
192         * Create a new CharacterGene with a randomly chosen character from the
193         * set of valid characters.
194         *
195         * @param validCharacters the valid characters for this gene.
196         * @return a new valid, <em>random</em> gene,
197         * @throws NullPointerException if the {@code validCharacters} are
198         *         {@code null}.
199         */
200        public static CharacterGene of(final CharSeq validCharacters) {
201                return new CharacterGene(
202                        validCharacters,
203                        RandomRegistry.random().nextInt(validCharacters.length())
204                );
205        }
206
207        /**
208         * Create a new character gene from the given character. If the character
209         * is not within the {@link #DEFAULT_CHARACTERS}, an invalid gene will be
210         * created.
211         *
212         * @param allele the character value of the created gene.
213         * @return a new character gene.
214         */
215        public static CharacterGene of(final char allele) {
216                return new CharacterGene(allele, DEFAULT_CHARACTERS);
217        }
218
219        /**
220         * Create a new random character gene, chosen from the
221         * {@link #DEFAULT_CHARACTERS}.
222         *
223         * @return a new random character gene.
224         */
225        public static CharacterGene of() {
226                return new CharacterGene(
227                        DEFAULT_CHARACTERS,
228                        RandomRegistry.random().nextInt(DEFAULT_CHARACTERS.length())
229                );
230        }
231
232        /**
233         * Create a new CharacterGene from the give character.
234         *
235         * @param allele The allele.
236         * @param validCharacters the valid characters for the new gene
237         * @return a new {@code CharacterGene} with the given parameter
238         * @throws NullPointerException if one of the arguments is {@code null}.
239         * @throws IllegalArgumentException if the {@code validCharacters} are empty.
240         */
241        public static CharacterGene of(
242                final char allele,
243                final CharSeq validCharacters
244        ) {
245                return new CharacterGene(allele, validCharacters);
246        }
247
248        static ISeq<CharacterGene> seq(
249                final CharSeq chars,
250                final IntRange lengthRange
251        ) {
252                final var random = random();
253                final var length = random.nextInt(lengthRange.min(), lengthRange.max());
254
255                return MSeq.<CharacterGene>ofLength(length)
256                        .fill(() -> new CharacterGene(chars, random.nextInt(chars.length())))
257                        .toISeq();
258        }
259
260}