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