001/*
002 * Java Genetic Algorithm Library (jenetics-7.0.0).
003 * Copyright (c) 2007-2022 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.util;
021
022import static java.util.Objects.requireNonNull;
023import static io.jenetics.internal.util.SerialIO.readIntArray;
024import static io.jenetics.internal.util.SerialIO.readObjectArray;
025import static io.jenetics.internal.util.SerialIO.writeIntArray;
026import static io.jenetics.internal.util.SerialIO.writeObjectArray;
027
028import java.io.IOException;
029import java.io.InvalidObjectException;
030import java.io.ObjectInput;
031import java.io.ObjectInputStream;
032import java.io.ObjectOutput;
033import java.io.Serial;
034import java.io.Serializable;
035import java.util.Arrays;
036import java.util.Iterator;
037import java.util.Optional;
038import java.util.function.Function;
039import java.util.stream.IntStream;
040import java.util.stream.Stream;
041
042import io.jenetics.util.ISeq;
043
044/**
045 * Default implementation of the {@link FlatTree} interface. Beside the
046 * flattened and dense layout it is also an <em>immutable</em> implementation of
047 * the {@link Tree} interface. It can only be created from an existing tree.
048 *
049 * <pre>{@code
050 * final Tree<String, ?> immutable = FlatTreeNode.ofTree(TreeNode.parse(...));
051 * }</pre>
052 *
053 * @implNote
054 * This class is immutable and thread-safe.
055 *
056 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
057 * @version 6.0
058 * @since 3.9
059 */
060public final class FlatTreeNode<V>
061        implements
062                FlatTree<V, FlatTreeNode<V>>,
063                Serializable
064{
065
066        /**
067         * The flattened tree nodes.
068         */
069        private record Nodes(Object[] values, int[] childOffsets, int[] childCounts) {
070        }
071
072        @Serial
073        private static final long serialVersionUID = 3L;
074
075        private static final int NULL_INDEX = -1;
076
077        private final Nodes _nodes;
078        private final int _index;
079
080        private FlatTreeNode(final Nodes nodes, final int index) {
081                _nodes = requireNonNull(nodes);
082                _index = index;
083        }
084
085        private FlatTreeNode(final Nodes nodes) {
086                this(nodes, 0);
087        }
088
089        /**
090         * Returns the root of the tree that contains this node. The root is the
091         * ancestor with no parent. This implementation has a runtime complexity
092         * of O(1).
093         *
094         * @return the root of the tree that contains this node
095         */
096        @Override
097        public FlatTreeNode<V> root() {
098                return nodeAt(0);
099        }
100
101        @Override
102        public boolean isRoot() {
103                return _index == 0;
104        }
105
106        private FlatTreeNode<V> nodeAt(final int index) {
107                return new FlatTreeNode<>(_nodes, index);
108        }
109
110        @SuppressWarnings("unchecked")
111        @Override
112        public V value() {
113                return (V)_nodes.values[_index];
114        }
115
116        @Override
117        public Optional<FlatTreeNode<V>> parent() {
118                int index = NULL_INDEX;
119                for (int i = _index; --i >= 0 && index == NULL_INDEX;) {
120                        if (isParent(i)) {
121                                index = i;
122                        }
123                }
124
125                return index != NULL_INDEX
126                        ? Optional.of(nodeAt(index))
127                        : Optional.empty();
128        }
129
130        private boolean isParent(final int index) {
131                return _nodes.childCounts[index] > 0 &&
132                        _nodes.childOffsets[index] <= _index &&
133                        _nodes.childOffsets[index] + _nodes.childCounts[index] > _index;
134        }
135
136        @Override
137        public FlatTreeNode<V> childAt(final int index) {
138                if (index < 0 || index >= childCount()) {
139                        throw new IndexOutOfBoundsException(Integer.toString(index));
140                }
141
142                return nodeAt(childOffset() + index);
143        }
144
145        @Override
146        public int childCount() {
147                return _nodes.childCounts[_index];
148        }
149
150        /**
151         * Return the index of the first child node in the underlying node array.
152         * {@code -1} is returned if {@code this} node is a leaf.
153         *
154         * @return Return the index of the first child node in the underlying node
155         *         array, or {@code -1} if {@code this} node is a leaf
156         */
157        @Override
158        public int childOffset() {
159                return _nodes.childOffsets[_index];
160        }
161
162        @Override
163        public ISeq<FlatTreeNode<V>> flattenedNodes() {
164                return stream().collect(ISeq.toISeq());
165        }
166
167        @Override
168        public Iterator<FlatTreeNode<V>> breadthFirstIterator() {
169                return _index == 0
170                        ? new IntFunctionIterator<>(this::nodeAt, _nodes.values.length)
171                        : FlatTree.super.breadthFirstIterator();
172        }
173
174        @Override
175        public Stream<FlatTreeNode<V>> breadthFirstStream() {
176                return _index == 0
177                        ? IntStream.range(0, _nodes.values.length).mapToObj(this::nodeAt)
178                        : FlatTree.super.breadthFirstStream();
179        }
180
181        /**
182         * Return a sequence of all <em>mapped</em> nodes of the whole underlying
183         * tree. This is a convenient method for
184         * <pre>{@code
185         * final ISeq<B> seq = stream()
186         *     .map(mapper)
187         *     .collect(ISeq.toISeq())
188         * }</pre>
189         *
190         * @param mapper the mapper function
191         * @param <B> the mapped type
192         * @return a sequence of all <em>mapped</em> nodes
193         */
194        public <B> ISeq<B>
195        map(final Function<? super FlatTreeNode<V>, ? extends B> mapper) {
196                return stream()
197                        .map(mapper)
198                        .collect(ISeq.toISeq());
199        }
200
201        @Override
202        public boolean identical(final Tree<?, ?> other) {
203                return other == this ||
204                        other instanceof FlatTreeNode<?> node &&
205                        node._index == _index &&
206                        node._nodes == _nodes;
207        }
208
209        @Override
210        public int hashCode() {
211                return Tree.hashCode(this);
212        }
213
214        @Override
215        public boolean equals(final Object obj) {
216                return obj == this ||
217                        obj instanceof FlatTreeNode<?> other &&
218                        (equals(other) || Tree.equals(other, this));
219        }
220
221        private boolean equals(final FlatTreeNode<?> tree) {
222                return tree._index == _index &&
223                        Arrays.equals(tree._nodes.values, _nodes.values) &&
224                        Arrays.equals(tree._nodes.childCounts, _nodes.childCounts) &&
225                        Arrays.equals(tree._nodes.childOffsets, _nodes.childOffsets);
226        }
227
228        @Override
229        public String toString() {
230                return toParenthesesString();
231        }
232
233        @Override
234        public int size() {
235                return _index == 0
236                        ? _nodes.values.length
237                        : countChildren(_index) + 1;
238        }
239
240        private int countChildren(final int index) {
241                int count = _nodes.childCounts[index];
242                for (int i = 0; i < _nodes.childCounts[index]; ++i) {
243                        count += countChildren(_nodes.childOffsets[index] + i);
244                }
245                return count;
246        }
247
248        /* *************************************************************************
249         *  Static factories
250         * ************************************************************************/
251
252        /**
253         * Create a new, immutable {@code FlatTreeNode} from the given {@code tree}.
254         *
255         * @param tree the source tree
256         * @param <V> the tree value types
257         * @return a new {@code FlatTreeNode} from the given {@code tree}
258         * @throws NullPointerException if the given {@code tree} is {@code null}
259         */
260        public static <V> FlatTreeNode<V> ofTree(final Tree<? extends V, ?> tree) {
261                requireNonNull(tree);
262
263                final int size = tree.size();
264                assert size >= 1;
265
266                final var nodes = new Nodes(new Object[size], new int[size], new int[size]);
267
268                int childOffset = 1;
269                int index = 0;
270
271                for (var node : tree) {
272                        nodes.values[index] = node.value();
273                        nodes.childCounts[index] = node.childCount();
274                        nodes.childOffsets[index] = node.isLeaf() ? NULL_INDEX : childOffset;
275
276                        childOffset += node.childCount();
277                        ++index;
278                }
279                assert index == size;
280
281                return new FlatTreeNode<>(nodes);
282        }
283
284        /**
285         * Parses a (parentheses) tree string, created with
286         * {@link Tree#toParenthesesString()}. The tree string might look like this:
287         * <pre>
288         *  mul(div(cos(1.0),cos(π)),sin(mul(1.0,z)))
289         * </pre>
290         *
291         * @see Tree#toParenthesesString(Function)
292         * @see Tree#toParenthesesString()
293         * @see TreeNode#parse(String)
294         *
295         * @since 5.0
296         *
297         * @param tree the parentheses tree string
298         * @return the parsed tree
299         * @throws NullPointerException if the given {@code tree} string is
300         *         {@code null}
301         * @throws IllegalArgumentException if the given tree string could not be
302         *         parsed
303         */
304        public static FlatTreeNode<String> parse(final String tree) {
305                return ofTree(ParenthesesTreeParser.parse(tree, Function.identity()));
306        }
307
308        /**
309         * Parses a (parentheses) tree string, created with
310         * {@link Tree#toParenthesesString()}. The tree string might look like this
311         * <pre>
312         *  0(1(4,5),2(6),3(7(10,11),8,9))
313         * </pre>
314         * and can be parsed to an integer tree with the following code:
315         * <pre>{@code
316         * final Tree<Integer, ?> tree = FlatTreeNode.parse(
317         *     "0(1(4,5),2(6),3(7(10,11),8,9))",
318         *     Integer::parseInt
319         * );
320         * }</pre>
321         *
322         * @see Tree#toParenthesesString(Function)
323         * @see Tree#toParenthesesString()
324         * @see TreeNode#parse(String, Function)
325         *
326         * @since 5.0
327         *
328         * @param <B> the tree node value type
329         * @param tree the parentheses tree string
330         * @param mapper the mapper which converts the serialized string value to
331         *        the desired type
332         * @return the parsed tree object
333         * @throws NullPointerException if one of the arguments is {@code null}
334         * @throws IllegalArgumentException if the given parentheses tree string
335         *         doesn't represent a valid tree
336         */
337        public static <B> FlatTreeNode<B> parse(
338                final String tree,
339                final Function<? super String, ? extends B> mapper
340        ) {
341                return ofTree(ParenthesesTreeParser.parse(tree, mapper));
342        }
343
344
345        /* *************************************************************************
346         *  Java object serialization
347         * ************************************************************************/
348
349        @Serial
350        private Object writeReplace() {
351                return new SerialProxy(SerialProxy.FLAT_TREE_NODE, this);
352        }
353
354        @Serial
355        private void readObject(final ObjectInputStream stream)
356                throws InvalidObjectException
357        {
358                throw new InvalidObjectException("Serialization proxy required.");
359        }
360
361
362        void write(final ObjectOutput out) throws IOException {
363                final FlatTreeNode<V> node = _index == 0
364                        ? this
365                        : FlatTreeNode.ofTree(this);
366
367                writeObjectArray(node._nodes.values, out);
368                writeIntArray(node._nodes.childOffsets, out);
369                writeIntArray(node._nodes.childCounts, out);
370        }
371
372        @SuppressWarnings("rawtypes")
373        static FlatTreeNode read(final ObjectInput in)
374                throws IOException, ClassNotFoundException
375        {
376                return new FlatTreeNode(new Nodes(
377                        readObjectArray(in),
378                        readIntArray(in),
379                        readIntArray(in)
380                ));
381        }
382
383}