001/*
002 * Java Genetic Algorithm Library (jenetics-8.3.0).
003 * Copyright (c) 2007-2025 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.rewriting;
021
022import static io.jenetics.internal.util.SerialIO.readInt;
023import static io.jenetics.internal.util.SerialIO.writeInt;
024
025import java.io.IOException;
026import java.io.InvalidObjectException;
027import java.io.ObjectInput;
028import java.io.ObjectInputStream;
029import java.io.ObjectOutput;
030import java.io.Serial;
031import java.io.Serializable;
032import java.util.Objects;
033import java.util.function.Function;
034import java.util.stream.Collectors;
035
036import io.jenetics.util.ISeq;
037import io.jenetics.util.MSeq;
038
039import io.jenetics.ext.util.TreeNode;
040
041/**
042 * This class represents a Tree Rewrite System, which consists of a set of
043 * Tree Rewrite Rules.
044 * {@snippet lang="java":
045 * final TRS<String> trs = TRS.parse(
046 *     "add(0,$x) -> $x",
047 *     "add(S($x),$y) -> S(add($x,$y))",
048 *     "mul(0,$x) -> 0",
049 *     "mul(S($x),$y) -> add(mul($x,$y),$y)"
050 * );
051 *
052 * // Converting the input tree into its normal form.
053 * final TreeNode<String> tree = TreeNode.parse("add(S(0),S(mul(S(0),S(S(0)))))");
054 * trs.rewrite(tree);
055 * assert tree.equals(TreeNode.parse("S(S(S(S(0))))"));
056 * }
057 *
058 * @see TreeRewriteRule
059 * @see <a href="https://en.wikipedia.org/wiki/Rewriting">TRS</a>
060 *
061 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
062 * @version 5.0
063 * @since 5.0
064 */
065public final class TRS<V> implements TreeRewriter<V>, Serializable {
066
067        @Serial
068        private static final long serialVersionUID = 1L;
069
070        private final ISeq<TreeRewriteRule<V>> _rules;
071
072        /**
073         * Create a new TRS from the given rewrite rules.
074         *
075         * @param rules the rewrite rules the TRS consists of
076         * @throws NullPointerException if the given {@code rules} are {@code null}
077         * @throws IllegalArgumentException if the given {@code rules} sequence is
078         *         empty
079         */
080        public TRS(final ISeq<TreeRewriteRule<V>> rules) {
081                if (rules.isEmpty()) {
082                        throw new IllegalArgumentException("Rewrite rules must not be empty.");
083                }
084                _rules = rules;
085        }
086
087        @Override
088        public int rewrite(final TreeNode<V> tree, final int limit) {
089                return TreeRewriter.rewrite(tree, limit, _rules);
090        }
091
092        /**
093         * Maps {@code this} TRS from type {@code V} to type {@code B}.
094         *
095         * @param mapper the type mapper
096         * @param <B> the target type
097         * @return a new TRS for the mapped type
098         * @throws NullPointerException if the {@code mapper} is {@code null}
099         */
100        public <B> TRS<B> map(final Function<? super V, ? extends B> mapper) {
101                return new TRS<>(_rules.map(rule -> rule.map(mapper)));
102        }
103
104        @Override
105        public int hashCode() {
106                return _rules.hashCode();
107        }
108
109        @Override
110        public boolean equals(final Object obj) {
111                return obj instanceof TRS<?> other &&
112                        _rules.equals(other._rules);
113        }
114
115        @Override
116        public String toString() {
117                return _rules.stream()
118                        .map(Objects::toString)
119                        .collect(Collectors.joining("; "));
120        }
121
122        /**
123         * Create a new TRS from the given rewrite rules and type mapper.
124         *
125         * @param mapper the tree value type mapper
126         * @param rules the rewrite rules
127         * @param <V> the tree value type the rewriter is working on
128         * @return a new TRS
129         * @throws NullPointerException if one of the arguments is {@code null}
130         * @throws IllegalArgumentException if the given {@code rules} sequence is
131         *         empty
132         */
133        public static <V> TRS<V> parse(
134                final Function<? super String, ? extends V> mapper,
135                final String... rules
136        ) {
137                return new TRS<>(
138                        ISeq.of(rules)
139                                .map(rule -> TreeRewriteRule.parse(rule, mapper))
140                );
141        }
142
143        /**
144         * Create a new TRS from the given rewrite rules.
145         *
146         * @param rules the rewrite rules
147         * @return a new TRS
148         * @throws NullPointerException if one of the arguments is {@code null}
149         * @throws IllegalArgumentException if the given {@code rules} sequence is
150         *         empty
151         */
152        public static TRS<String> parse(final String... rules) {
153                return parse(Function.identity(), rules);
154        }
155
156        /* *************************************************************************
157         *  Java object serialization
158         * ************************************************************************/
159
160        @Serial
161        private Object writeReplace() {
162                return new SerialProxy(SerialProxy.TRS_KEY, this);
163        }
164
165        @Serial
166        private void readObject(final ObjectInputStream stream)
167                throws InvalidObjectException
168        {
169                throw new InvalidObjectException("Serialization proxy required.");
170        }
171
172        void write(final ObjectOutput out) throws IOException {
173                writeInt(_rules.length(), out);
174                for (int i = 0; i < _rules.length(); ++i) {
175                        out.writeObject(_rules.get(i));
176                }
177        }
178
179        @SuppressWarnings({"unchecked", "rawtypes"})
180        static Object read(final ObjectInput in)
181                throws IOException, ClassNotFoundException
182        {
183                final var length = readInt(in);
184                final var rules = MSeq.ofLength(length);
185                for (int i = 0; i < length; ++i) {
186                        rules.set(i, in.readObject());
187                }
188
189                return new TRS(rules.toISeq());
190        }
191
192}