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.lang.String.format;
023
024import java.io.Serial;
025import java.io.Serializable;
026import java.util.Objects;
027
028import io.jenetics.util.ISeq;
029import io.jenetics.util.RandomRegistry;
030
031/**
032 * <p>
033 * Gene which holds enumerable (countable) genes. Will be used for combinatorial
034 * problems in combination with the {@link PermutationChromosome}.
035 * </p>
036 * The following code shows how to create a combinatorial genotype factory which
037 * can be used when creating an {@link io.jenetics.engine.Engine} instance.
038 * {@snippet lang="java":
039 * final ISeq<Integer> alleles = ISeq.of(1, 2, 3, 4, 5, 6, 7, 8);
040 * final Factory<Genotype<EnumGene<Integer>>> gtf = Genotype.of(
041 *     PermutationChromosome.of(alleles)
042 * );
043 * }
044 *
045 * The following code shows the assurances of the {@code EnumGene}.
046 * {@snippet lang="java":
047 * final ISeq<Integer> alleles = ISeq.of(1, 2, 3, 4, 5, 6, 7, 8);
048 * final EnumGene<Integer> gene = new EnumGene<>(5, alleles);
049 *
050 * assert(gene.alleleIndex() == 5);
051 * assert(gene.allele() == gene.validAlleles().get(5));
052 * assert(gene.validAlleles() == alleles);
053 * }
054 *
055 * @see PermutationChromosome
056 * @see PartiallyMatchedCrossover
057 *
058 * @implNote
059 * This class is immutable and thread-safe.
060 *
061 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
062 * @since 1.0
063 * @version 5.2
064 */
065public final class EnumGene<A>
066        implements
067                Gene<A, EnumGene<A>>,
068                Comparable<EnumGene<A>>,
069                Serializable
070{
071
072        @Serial
073        private static final long serialVersionUID = 2L;
074
075        private final ISeq<A> _validAlleles;
076        private final int _alleleIndex;
077
078        /**
079         * Create a new enum gene from the given valid genes and the chosen allele
080         * index.
081         *
082         * @param alleleIndex the index of the allele for this gene
083         * @param validAlleles the sequence of valid alleles
084         * @throws IllegalArgumentException if the give valid alleles sequence is
085         *         empty
086         * @throws NullPointerException if the valid alleles seq is {@code null}
087         */
088        EnumGene(final int alleleIndex, final ISeq<? extends A> validAlleles) {
089                if (validAlleles.isEmpty()) {
090                        throw new IllegalArgumentException(
091                                "Array of valid alleles must be greater than zero."
092                        );
093                }
094
095                if (alleleIndex < 0 || alleleIndex >= validAlleles.length()) {
096                        throw new IndexOutOfBoundsException(format(
097                                "Allele index is not in range [0, %d): %d.",
098                                validAlleles.length(),
099                                alleleIndex
100                        ));
101                }
102
103                _validAlleles = ISeq.upcast(validAlleles);
104                _alleleIndex = alleleIndex;
105        }
106
107        /**
108         * Return sequence of the valid alleles where this gene is a part of.
109         *
110         * @return the sequence of the valid alleles.
111         */
112        public ISeq<A> validAlleles() {
113                return _validAlleles;
114        }
115
116        /**
117         * Return the index of the allele this gene is representing.
118         *
119         * @return the index of the allele this gene is representing
120         */
121        public int alleleIndex() {
122                return _alleleIndex;
123        }
124
125        @Override
126        public A allele() {
127                return _validAlleles.get(_alleleIndex);
128        }
129
130        @Override
131        public boolean isValid() {
132                return _alleleIndex >= 0 && _alleleIndex < _validAlleles.length();
133        }
134
135        @Override
136        public EnumGene<A> newInstance() {
137                return new EnumGene<>(
138                        RandomRegistry.random().nextInt(_validAlleles.length()),
139                        _validAlleles
140                );
141        }
142
143        /**
144         * Create a new gene from the given {@code value} and the gene context.
145         *
146         * @since 1.6
147         *
148         * @param value the value of the new gene
149         * @return a new gene with the given value
150         */
151        public EnumGene<A> newInstance(final A value) {
152                return new EnumGene<>(
153                        _validAlleles.indexOf(value),
154                        _validAlleles
155                );
156        }
157
158        @Override
159        public int compareTo(final EnumGene<A> gene) {
160                int result = 0;
161                if (_alleleIndex > gene._alleleIndex) {
162                        result = 1;
163                } else if (_alleleIndex < gene._alleleIndex) {
164                        result = -1;
165                }
166
167                return result;
168        }
169
170        @Override
171        public int hashCode() {
172                return Objects.hash(_alleleIndex, _validAlleles);
173        }
174
175        @Override
176        public boolean equals(final Object obj) {
177                return obj instanceof EnumGene<?> other &&
178                        _alleleIndex == other._alleleIndex &&
179                        Objects.equals(_validAlleles, other._validAlleles);
180        }
181
182        @Override
183        public String toString() {
184                return Objects.toString(allele());
185        }
186
187
188        /* *************************************************************************
189         *  Static object creation methods
190         * ************************************************************************/
191
192        /**
193         * Create a new enum gene from the given valid genes and the chosen allele
194         * index.
195         *
196         * @since 3.4
197         *
198         * @param <A> the allele type
199         * @param alleleIndex the index of the allele for this gene
200         * @param validAlleles the sequence of valid alleles
201         * @return a new {@code EnumGene} with the given with the allele
202         *        {@code validAlleles.get(alleleIndex)}
203         * @throws IllegalArgumentException if the give valid alleles sequence is
204         *         empty
205         * @throws NullPointerException if the valid alleles seq is {@code null}.
206         */
207        public static <A> EnumGene<A> of(
208                final int alleleIndex,
209                final ISeq<? extends A> validAlleles
210        ) {
211                return new EnumGene<>(alleleIndex, validAlleles);
212        }
213
214        /**
215         * Return a new enum gene with an allele randomly chosen from the given
216         * valid alleles.
217         *
218         * @param <A> the allele type
219         * @param validAlleles the sequence of valid alleles.
220         * @return a new {@code EnumGene} with a randomly chosen allele from the
221         *         sequence of valid alleles
222         * @throws java.lang.IllegalArgumentException if the give valid alleles
223         *         sequence is empty
224         * @throws NullPointerException if the valid alleles seq is {@code null}.
225         */
226        public static <A> EnumGene<A> of(final ISeq<? extends A> validAlleles) {
227                return new EnumGene<>(
228                        RandomRegistry.random().nextInt(validAlleles.length()),
229                        validAlleles
230                );
231        }
232
233        /**
234         * Create a new enum gene from the given valid genes and the chosen allele
235         * index.
236         *
237         * @param <A> the allele type
238         * @param alleleIndex the index of the allele for this gene
239         * @param validAlleles the array of valid alleles.
240         * @return a new {@code EnumGene} with the given with the allele
241         *        {@code validAlleles[alleleIndex]}
242         * @throws java.lang.IllegalArgumentException if the give valid alleles
243         *         array is empty of the allele index is out of range.
244         */
245        @SafeVarargs
246        public static <A> EnumGene<A> of(
247                final int alleleIndex,
248                final A... validAlleles
249        ) {
250                return new EnumGene<>(alleleIndex, ISeq.of(validAlleles));
251        }
252
253        /**
254         * Return a new enum gene with an allele randomly chosen from the given
255         * valid alleles.
256         *
257         * @param <A> the allele type
258         * @param validAlleles the array of valid alleles
259         * @return a new {@code EnumGene} with a randomly chosen allele from the
260         *         sequence of valid alleles
261         * @throws IllegalArgumentException if the give valid alleles array is empty
262         */
263        @SafeVarargs
264        public static <A> EnumGene<A> of(final A... validAlleles) {
265                return EnumGene.of(ISeq.of(validAlleles));
266        }
267
268}