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