1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 package org.apache.commons.math3.geometry.partitioning;
18 
19 import java.io.IOException;
20 import java.text.ParseException;
21 import java.util.StringTokenizer;
22 
23 import org.apache.commons.math3.geometry.Space;
24 import org.apache.commons.math3.geometry.euclidean.oned.Euclidean1D;
25 import org.apache.commons.math3.geometry.euclidean.oned.IntervalsSet;
26 import org.apache.commons.math3.geometry.euclidean.oned.OrientedPoint;
27 import org.apache.commons.math3.geometry.euclidean.oned.Vector1D;
28 import org.apache.commons.math3.geometry.euclidean.threed.Euclidean3D;
29 import org.apache.commons.math3.geometry.euclidean.threed.Plane;
30 import org.apache.commons.math3.geometry.euclidean.threed.PolyhedronsSet;
31 import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
32 import org.apache.commons.math3.geometry.euclidean.twod.Euclidean2D;
33 import org.apache.commons.math3.geometry.euclidean.twod.Line;
34 import org.apache.commons.math3.geometry.euclidean.twod.PolygonsSet;
35 import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
36 import org.apache.commons.math3.geometry.spherical.oned.ArcsSet;
37 import org.apache.commons.math3.geometry.spherical.oned.LimitAngle;
38 import org.apache.commons.math3.geometry.spherical.oned.S1Point;
39 import org.apache.commons.math3.geometry.spherical.oned.Sphere1D;
40 import org.apache.commons.math3.geometry.spherical.twod.Circle;
41 import org.apache.commons.math3.geometry.spherical.twod.Sphere2D;
42 import org.apache.commons.math3.geometry.spherical.twod.SphericalPolygonsSet;
43 
44 /** Class parsing a string representation of an {@link AbstractRegion}.
45  * <p>
46  * This class is intended for tests and debug purposes only.
47  * </p>
48  * @see RegionDumper
49  * @since 3.5
50  */
51 public class RegionParser {
52 
53     /** Private constructor for a utility class
54      */
RegionParser()55     private RegionParser() {
56     }
57 
58     /** Parse a string representation of an {@link ArcsSet}.
59      * @param s string to parse
60      * @return parsed region
61      * @exception IOException if the string cannot be read
62      * @exception ParseException if the string cannot be parsed
63      */
parseArcsSet(final String s)64     public static ArcsSet parseArcsSet(final String s)
65         throws IOException, ParseException {
66         final TreeBuilder<Sphere1D> builder = new TreeBuilder<Sphere1D>("ArcsSet", s) {
67 
68             /** {@inheritDoc} */
69             @Override
70             protected LimitAngle parseHyperplane()
71                 throws IOException, ParseException {
72                 return new LimitAngle(new S1Point(getNumber()), getBoolean(), getNumber());
73             }
74 
75         };
76         return new ArcsSet(builder.getTree(), builder.getTolerance());
77     }
78 
79     /** Parse a string representation of a {@link SphericalPolygonsSet}.
80      * @param s string to parse
81      * @return parsed region
82      * @exception IOException if the string cannot be read
83      * @exception ParseException if the string cannot be parsed
84      */
parseSphericalPolygonsSet(final String s)85     public static SphericalPolygonsSet parseSphericalPolygonsSet(final String s)
86         throws IOException, ParseException {
87         final TreeBuilder<Sphere2D> builder = new TreeBuilder<Sphere2D>("SphericalPolygonsSet", s) {
88 
89             /** {@inheritDoc} */
90             @Override
91             public Circle parseHyperplane()
92                 throws IOException, ParseException {
93                 return new Circle(new Vector3D(getNumber(), getNumber(), getNumber()), getNumber());
94             }
95 
96         };
97         return new SphericalPolygonsSet(builder.getTree(), builder.getTolerance());
98     }
99 
100     /** Parse a string representation of an {@link IntervalsSet}.
101      * @param s string to parse
102      * @return parsed region
103      * @exception IOException if the string cannot be read
104      * @exception ParseException if the string cannot be parsed
105      */
parseIntervalsSet(final String s)106     public static IntervalsSet parseIntervalsSet(final String s)
107         throws IOException, ParseException {
108         final TreeBuilder<Euclidean1D> builder = new TreeBuilder<Euclidean1D>("IntervalsSet", s) {
109 
110             /** {@inheritDoc} */
111             @Override
112             public OrientedPoint parseHyperplane()
113                 throws IOException, ParseException {
114                 return new OrientedPoint(new Vector1D(getNumber()), getBoolean(), getNumber());
115             }
116 
117         };
118         return new IntervalsSet(builder.getTree(), builder.getTolerance());
119     }
120 
121     /** Parse a string representation of a {@link PolygonsSet}.
122      * @param s string to parse
123      * @return parsed region
124      * @exception IOException if the string cannot be read
125      * @exception ParseException if the string cannot be parsed
126      */
parsePolygonsSet(final String s)127     public static PolygonsSet parsePolygonsSet(final String s)
128         throws IOException, ParseException {
129         final TreeBuilder<Euclidean2D> builder = new TreeBuilder<Euclidean2D>("PolygonsSet", s) {
130 
131             /** {@inheritDoc} */
132             @Override
133             public Line parseHyperplane()
134                 throws IOException, ParseException {
135                 return new Line(new Vector2D(getNumber(), getNumber()), getNumber(), getNumber());
136             }
137 
138         };
139         return new PolygonsSet(builder.getTree(), builder.getTolerance());
140     }
141 
142     /** Parse a string representation of a {@link PolyhedronsSet}.
143      * @param s string to parse
144      * @return parsed region
145      * @exception IOException if the string cannot be read
146      * @exception ParseException if the string cannot be parsed
147      */
parsePolyhedronsSet(final String s)148     public static PolyhedronsSet parsePolyhedronsSet(final String s)
149         throws IOException, ParseException {
150         final TreeBuilder<Euclidean3D> builder = new TreeBuilder<Euclidean3D>("PolyhedronsSet", s) {
151 
152             /** {@inheritDoc} */
153             @Override
154             public Plane parseHyperplane()
155                 throws IOException, ParseException {
156                 return new Plane(new Vector3D(getNumber(), getNumber(), getNumber()),
157                                  new Vector3D(getNumber(), getNumber(), getNumber()),
158                                  getNumber());
159             }
160 
161         };
162         return new PolyhedronsSet(builder.getTree(), builder.getTolerance());
163     }
164 
165     /** Local class for building an {@link AbstractRegion} tree.
166      * @param <S> Type of the space.
167      */
168     private abstract static class TreeBuilder<S extends Space> {
169 
170         /** Keyword for tolerance. */
171         private static final String TOLERANCE = "tolerance";
172 
173         /** Keyword for internal nodes. */
174         private static final String INTERNAL  = "internal";
175 
176         /** Keyword for leaf nodes. */
177         private static final String LEAF      = "leaf";
178 
179         /** Keyword for plus children trees. */
180         private static final String PLUS      = "plus";
181 
182         /** Keyword for minus children trees. */
183         private static final String MINUS     = "minus";
184 
185         /** Keyword for true flags. */
186         private static final String TRUE      = "true";
187 
188         /** Keyword for false flags. */
189         private static final String FALSE     = "false";
190 
191         /** Tree root. */
192         private BSPTree<S> root;
193 
194         /** Tolerance. */
195         private final double tolerance;
196 
197         /** Tokenizer parsing string representation. */
198         private final StringTokenizer tokenizer;
199 
200         /** Simple constructor.
201          * @param type type of the expected representation
202          * @param reader reader for the string representation
203          * @exception IOException if the string cannot be read
204          * @exception ParseException if the string cannot be parsed
205          */
TreeBuilder(final String type, final String s)206         public TreeBuilder(final String type, final String s)
207             throws IOException, ParseException {
208             root = null;
209             tokenizer = new StringTokenizer(s);
210             getWord(type);
211             getWord(TOLERANCE);
212             tolerance = getNumber();
213             getWord(PLUS);
214             root = new BSPTree<S>();
215             parseTree(root);
216             if (tokenizer.hasMoreTokens()) {
217                 throw new ParseException("unexpected " + tokenizer.nextToken(), 0);
218             }
219         }
220 
221         /** Parse a tree.
222          * @param node start node
223          * @exception IOException if the string cannot be read
224          * @exception ParseException if the string cannot be parsed
225          */
parseTree(final BSPTree<S> node)226         private void parseTree(final BSPTree<S> node)
227             throws IOException, ParseException {
228             if (INTERNAL.equals(getWord(INTERNAL, LEAF))) {
229                 // this is an internal node, it has a cut sub-hyperplane (stored as a whole hyperplane)
230                 // then a minus tree, then a plus tree
231                 node.insertCut(parseHyperplane());
232                 getWord(MINUS);
233                 parseTree(node.getMinus());
234                 getWord(PLUS);
235                 parseTree(node.getPlus());
236             } else {
237                 // this is a leaf node, it has only an inside/outside flag
238                 node.setAttribute(getBoolean());
239             }
240         }
241 
242         /** Get next word.
243          * @param allowed allowed values
244          * @return parsed word
245          * @exception IOException if the string cannot be read
246          * @exception ParseException if the string cannot be parsed
247          */
getWord(final String ... allowed)248         protected String getWord(final String ... allowed)
249             throws IOException, ParseException {
250             final String token = tokenizer.nextToken();
251             for (final String a : allowed) {
252                 if (a.equals(token)) {
253                     return token;
254                 }
255             }
256             throw new ParseException(token + " != " + allowed[0], 0);
257         }
258 
259         /** Get next number.
260          * @return parsed number
261          * @exception IOException if the string cannot be read
262          * @exception NumberFormatException if the string cannot be parsed
263          */
getNumber()264         protected double getNumber()
265             throws IOException, NumberFormatException {
266             return Double.parseDouble(tokenizer.nextToken());
267         }
268 
269         /** Get next boolean.
270          * @return parsed boolean
271          * @exception IOException if the string cannot be read
272          * @exception ParseException if the string cannot be parsed
273          */
getBoolean()274         protected boolean getBoolean()
275             throws IOException, ParseException {
276             return getWord(TRUE, FALSE).equals(TRUE);
277         }
278 
279         /** Get the built tree.
280          * @return built tree
281          */
getTree()282         public BSPTree<S> getTree() {
283             return root;
284         }
285 
286         /** Get the tolerance.
287          * @return tolerance
288          */
getTolerance()289         public double getTolerance() {
290             return tolerance;
291         }
292 
293         /** Parse an hyperplane.
294          * @return next hyperplane from the stream
295          * @exception IOException if the string cannot be read
296          * @exception ParseException if the string cannot be parsed
297          */
parseHyperplane()298         protected abstract Hyperplane<S> parseHyperplane()
299             throws IOException, ParseException;
300 
301     }
302 
303 }
304