001 /*
002 * Java Genetic Algorithm Library (jenetics-5.1.0).
003 * Copyright (c) 2007-2019 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 */
020 package io.jenetics.ext.util;
021
022 import static java.lang.String.format;
023 import static java.util.Objects.requireNonNull;
024 import static io.jenetics.ext.util.ParenthesesTrees.ESCAPE_CHAR;
025 import static io.jenetics.ext.util.ParenthesesTrees.unescape;
026
027 import java.util.ArrayDeque;
028 import java.util.ArrayList;
029 import java.util.Deque;
030 import java.util.List;
031 import java.util.function.Function;
032
033 /**
034 * Parses an parentheses string into a {@code TreeNode<String>} object.
035 *
036 * @author <a href="mailto:franz.wilhelmstoetter@gmail.com">Franz Wilhelmstötter</a>
037 * @version 4.3
038 * @since 4.3
039 */
040 final class TreeParser {
041 private TreeParser() {}
042
043 /**
044 * Represents a parentheses tree string token.
045 */
046 final static class Token {
047 final String seq;
048 final int pos;
049
050 Token(final String seq, final int pos) {
051 this.seq = seq;
052 this.pos = pos;
053 }
054 }
055
056 /**
057 * Tokenize the given parentheses string.
058 *
059 * @param value the parentheses string
060 * @return the parentheses string tokens
061 * @throws NullPointerException if the given {@code value} is {@code null}
062 */
063 static List<Token> tokenize(final String value) {
064 final List<Token> tokens = new ArrayList<>();
065
066 char pc = '\0';
067 int pos = 0;
068 final StringBuilder token = new StringBuilder();
069 for (int i = 0; i < value.length(); ++i) {
070 final char c = value.charAt(i);
071
072 if (isTokenSeparator(c) && pc != ESCAPE_CHAR) {
073 tokens.add(new Token(unescape(token.toString()), pos));
074 tokens.add(new Token(Character.toString(c), i));
075 token.setLength(0);
076 pos = i;
077 } else {
078 token.append(c);
079 }
080
081 pc = c;
082 }
083
084 if (token.length() > 0) {
085 tokens.add(new Token(unescape(token.toString()), pos));
086 }
087
088 return tokens;
089 }
090
091 private static boolean isTokenSeparator(final char c) {
092 return c == '(' || c == ')' || c == ',';
093 }
094
095 /**
096 * Parses the given parentheses tree string
097 *
098 * @since 4.3
099 *
100 * @param <B> the tree node value type
101 * @param value the parentheses tree string
102 * @param mapper the mapper which converts the serialized string value to
103 * the desired type
104 * @return the parsed tree object
105 * @throws NullPointerException if one of the arguments is {@code null}
106 * @throws IllegalArgumentException if the given parentheses tree string
107 * doesn't represent a valid tree
108 */
109 static <B> TreeNode<B> parse(
110 final String value,
111 final Function<? super String, ? extends B> mapper
112 ) {
113 requireNonNull(value);
114 requireNonNull(mapper);
115
116 final TreeNode<B> root = TreeNode.of();
117 final Deque<TreeNode<B>> parents = new ArrayDeque<>();
118
119 TreeNode<B> current = root;
120 for (Token token : tokenize(value.trim())) {
121 switch (token.seq) {
122 case "(":
123 if (current == null) {
124 throw new IllegalArgumentException(format(
125 "Illegal parentheses tree string: '%s'.",
126 value
127 ));
128 }
129
130 final TreeNode<B> tn1 = TreeNode.of();
131 current.attach(tn1);
132 parents.push(current);
133 current = tn1;
134 break;
135 case ",":
136 if (parents.isEmpty()) {
137 throw new IllegalArgumentException(format(
138 "Expect '(' at position %d.",
139 token.pos
140 ));
141 }
142
143 final TreeNode<B> tn2 = TreeNode.of();
144 assert parents.peek() != null;
145 parents.peek().attach(tn2);
146 current = tn2;
147 break;
148 case ")":
149 if (parents.isEmpty()) {
150 throw new IllegalArgumentException(format(
151 "Unbalanced parentheses at position %d.",
152 token.pos
153 ));
154 }
155 current = parents.pop();
156 if (parents.isEmpty()) {
157 current = null;
158 }
159 break;
160 default:
161 if (current == null) {
162 throw new IllegalArgumentException(format(
163 "More than one root element at pos %d: '%s'.",
164 token.pos, value
165 ));
166 }
167 if (current.getValue() == null) {
168 current.setValue(mapper.apply(token.seq));
169 }
170 break;
171 }
172 }
173
174 if (!parents.isEmpty()) {
175 throw new IllegalArgumentException(
176 "Unbalanced parentheses: " + value
177 );
178 }
179
180 return root;
181 }
182
183 }
|