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.ext;
021
022import static java.lang.String.format;
023import static java.util.Objects.requireNonNull;
024import static io.jenetics.internal.util.Hashes.hash;
025
026import java.io.Serial;
027import java.io.Serializable;
028import java.util.Objects;
029import java.util.Optional;
030
031import io.jenetics.util.BaseSeq;
032import io.jenetics.util.ISeq;
033
034/**
035 * Abstract implementation of the {@link TreeGene} interface..
036 *
037 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
038 * @version 6.0
039 * @since 3.9
040 */
041public abstract class AbstractTreeGene<A, G extends AbstractTreeGene<A, G>>
042        implements TreeGene<A, G>, Serializable
043{
044
045        @Serial
046        private static final long serialVersionUID = 1L;
047
048        /**
049         * The allele of the tree-gene.
050         */
051        private final A _allele;
052        private final int _childOffset;
053        private final int _childCount;
054
055        private BaseSeq<G> _genes;
056
057        /**
058         * Creates a new tree-gene from the given data.
059         *
060         * @param allele the actual value (allele) of the tree-gene
061         * @param childOffset the offset index of the child in the containing
062         *        chromosome. If this node has no child, the value should be set
063         *        to zero.
064         * @param childCount the number of children of this gene
065         * @throws IllegalArgumentException if the {@code childCount} is smaller
066         *         than zero
067         */
068        protected AbstractTreeGene(
069                final A allele,
070                final int childOffset,
071                final int childCount
072        ) {
073                if (childCount < 0) {
074                        throw new IllegalArgumentException(format(
075                                "Child count smaller than zero: %s", childCount
076                        ));
077                }
078
079                _allele = allele;
080                _childOffset = childOffset;
081                _childCount = childCount;
082        }
083
084        /**
085         * Return the whole flattened tree values in breadth-first order. This method
086         * will always return the same {@code ISeq} instance.
087         *
088         * @return the whole flattened tree values
089         */
090        @Override
091        public ISeq<G> flattenedNodes() {
092                return ISeq.of(_genes);
093        }
094
095        @Override
096        public G root() {
097                return _genes.get(0);
098        }
099
100        @Override
101        public boolean isRoot() {
102                return root() == this;
103        }
104
105        @Override
106        public int size() {
107                return isRoot() ? _genes.length() : TreeGene.super.size();
108        }
109
110        protected void checkTreeState() {
111                if (_genes == null) {
112                        throw new IllegalStateException(
113                                "Gene is not attached to a chromosome."
114                        );
115                }
116        }
117
118        /**
119         * This method is used by the {@code AbstractTreeChromosome} to attach
120         * itself to this gene.
121         *
122         * @param genes the genes of the attached chromosome
123         */
124        protected void bind(final BaseSeq<G> genes) {
125                _genes = requireNonNull(genes);
126        }
127
128        @Override
129        public int childOffset() {
130                return _childOffset;
131        }
132
133        @Override
134        public A allele() {
135                return _allele;
136        }
137
138        /**
139         * Return the <em>parent</em> node of this tree node.
140         *
141         * @return the parent node, or {@code Optional.empty()} if this node is the
142         *         root of the tree
143         * @throws IllegalStateException if this gene is not part of a chromosome
144         */
145        @Override
146        public Optional<G> parent() {
147                checkTreeState();
148
149                return _genes.stream()
150                        .filter(g -> g.childStream().anyMatch(this::identical))
151                        .findFirst();
152        }
153
154        /**
155         * Return the child gene with the given index.
156         *
157         * @param index the child index
158         * @return the child node with the given index
159         * @throws IndexOutOfBoundsException  if the {@code index} is out of
160         *         bounds ({@code [0, childCount())})
161         * @throws IllegalStateException if this gene is not part of a chromosome
162         */
163        @Override
164        public G childAt(final int index) {
165                checkTreeState();
166                if (index < 0 || index >= childCount()) {
167                        throw new IndexOutOfBoundsException(format(
168                                "Child index out of bounds: %s", index
169                        ));
170                }
171
172                assert _genes != null;
173                return _genes.get(_childOffset + index);
174        }
175
176        @Override
177        public int childCount() {
178                return _childCount;
179        }
180
181        @Override
182        public boolean isValid() {
183                return _genes != null;
184        }
185
186        @Override
187        public int hashCode() {
188                return hash(_allele, hash(_childOffset, hash(_childCount)));
189        }
190
191        @Override
192        public boolean equals(final Object obj) {
193                return obj == this ||
194                        obj instanceof AbstractTreeGene<?, ?> other &&
195                        Objects.equals(other._allele, _allele) &&
196                        other._childOffset == _childOffset &&
197                        other._childCount == _childCount;
198        }
199
200        @Override
201        public String toString() {
202                return Objects.toString(_allele);
203        }
204
205}