]> git.basschouten.com Git - openhab-addons.git/blob
00c4ec3e5337759f5ba0b824b3655417eddd702e
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.deutschebahn.internal.filter;
14
15 import java.util.List;
16
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
19
20 /**
21  * Parses a {@link FilterToken}-Sequence into a {@link TimetableStopPredicate}.
22  * 
23  * @author Sönke Küper - Initial contribution.
24  */
25 @NonNullByDefault
26 public final class FilterParser {
27
28     /**
29      * Parser's state.
30      */
31     private abstract static class State implements FilterTokenVisitor<State> {
32
33         @Nullable
34         private final State previousState;
35
36         public State(@Nullable State previousState) {
37             this.previousState = previousState;
38         }
39
40         private final State handle(FilterToken token) throws FilterParserException {
41             return token.accept(this);
42         }
43
44         protected abstract State handleChildResult(TimetableStopPredicate predicate) throws FilterParserException;
45
46         @Override
47         public final State handle(ChannelNameEquals channelEquals) throws FilterParserException {
48             final TimetableStopByStringEventAttributeFilter predicate = channelEquals.mapToPredicate();
49             return this.handleChildResult(predicate);
50         }
51
52         protected final State publishResultToPrevious(TimetableStopPredicate predicate) throws FilterParserException {
53             return this.getPreviousState().handleChildResult(predicate);
54         }
55
56         protected State getPreviousState() throws FilterParserException {
57             final State previousStateValue = this.previousState;
58             if (previousStateValue == null) {
59                 throw new FilterParserException("Invalid filter");
60             } else {
61                 return previousStateValue;
62             }
63         }
64
65         /**
66          * Returns the result.
67          */
68         public abstract TimetableStopPredicate getResult() throws FilterParserException;
69     }
70
71     /**
72      * Initial state for the parser.
73      */
74     private static final class InitialState extends State {
75
76         @Nullable
77         private TimetableStopPredicate result;
78
79         public InitialState() {
80             super(null);
81         }
82
83         @Override
84         public State handle(OrOperator operator) throws FilterParserException {
85             final TimetableStopPredicate currentResult = this.result;
86             this.result = null;
87             if (currentResult == null) {
88                 throw new FilterParserException(
89                         "Invalid filter: first argument missing for '|' at " + operator.getPosition());
90             }
91             return new OrState(this, currentResult);
92         }
93
94         @Override
95         public State handle(AndOperator operator) throws FilterParserException {
96             final TimetableStopPredicate currentResult = this.result;
97             this.result = null;
98             if (currentResult == null) {
99                 throw new FilterParserException(
100                         "Invalid filter: first argument missing for '&' at " + operator.getPosition());
101             }
102             return new AndState(this, currentResult);
103         }
104
105         @Override
106         public State handle(BracketOpenToken token) throws FilterParserException {
107             this.result = null;
108             return new SubQueryState(this);
109         }
110
111         @Override
112         public State handle(BracketCloseToken token) throws FilterParserException {
113             throw new FilterParserException("Unexpected token " + token + " at " + token.getPosition());
114         }
115
116         @Override
117         protected State handleChildResult(TimetableStopPredicate predicate) throws FilterParserException {
118             if (this.result == null) {
119                 this.result = predicate;
120                 return this;
121             } else {
122                 throw new FilterParserException("Invalid filter: Operator for multiple filters missing.");
123             }
124         }
125
126         @Override
127         public TimetableStopPredicate getResult() throws FilterParserException {
128             final TimetableStopPredicate currentResult = this.result;
129             if (currentResult != null) {
130                 return currentResult;
131             }
132             throw new FilterParserException("Invalid filter.");
133         }
134     }
135
136     /**
137      * State while parsing a conjunction.
138      */
139     private static final class AndState extends State {
140
141         private final TimetableStopPredicate first;
142
143         public AndState(State previousState, final TimetableStopPredicate first) {
144             super(previousState);
145             this.first = first;
146         }
147
148         @Override
149         public State handle(OrOperator operator) throws FilterParserException {
150             throw new FilterParserException(
151                     "Invalid second argument for '&' operator " + operator + " at " + operator.getPosition());
152         }
153
154         @Override
155         public State handle(AndOperator operator) throws FilterParserException {
156             throw new FilterParserException(
157                     "Invalid second argument for '&' operator " + operator + " at " + operator.getPosition());
158         }
159
160         @Override
161         public State handle(BracketOpenToken token) throws FilterParserException {
162             return new SubQueryState(this);
163         }
164
165         @Override
166         public State handle(BracketCloseToken token) throws FilterParserException {
167             throw new FilterParserException(
168                     "Invalid second argument for '&' operator " + token + " at " + token.getPosition());
169         }
170
171         @Override
172         protected State handleChildResult(TimetableStopPredicate predicate) throws FilterParserException {
173             return this.publishResultToPrevious(new AndPredicate(first, predicate));
174         }
175
176         @Override
177         public TimetableStopPredicate getResult() throws FilterParserException {
178             throw new FilterParserException("Invalid filter");
179         }
180     }
181
182     /**
183      * State while parsing a disjunction.
184      */
185     private static final class OrState extends State {
186
187         private final TimetableStopPredicate first;
188
189         public OrState(State previousState, final TimetableStopPredicate first) {
190             super(previousState);
191             this.first = first;
192         }
193
194         @Override
195         public State handle(OrOperator operator) throws FilterParserException {
196             throw new FilterParserException(
197                     "Invalid second argument for '|' operator " + operator + " at " + operator.getPosition());
198         }
199
200         @Override
201         public State handle(AndOperator operator) throws FilterParserException {
202             throw new FilterParserException(
203                     "Invalid second argument for '|' operator " + operator + " at " + operator.getPosition());
204         }
205
206         @Override
207         public State handle(BracketOpenToken token) throws FilterParserException {
208             return new SubQueryState(this);
209         }
210
211         @Override
212         public State handle(BracketCloseToken token) throws FilterParserException {
213             throw new FilterParserException(
214                     "Invalid second argument for '|' operator " + token + " at " + token.getPosition());
215         }
216
217         @Override
218         protected State handleChildResult(TimetableStopPredicate second) throws FilterParserException {
219             return this.publishResultToPrevious(new OrPredicate(first, second));
220         }
221
222         @Override
223         public TimetableStopPredicate getResult() throws FilterParserException {
224             throw new FilterParserException("Invalid filter");
225         }
226     }
227
228     /**
229      * State while parsing a Subquery.
230      */
231     private static final class SubQueryState extends State {
232
233         @Nullable
234         private TimetableStopPredicate currentResult;
235
236         public SubQueryState(State previousState) {
237             super(previousState);
238         }
239
240         @Override
241         public State handle(OrOperator operator) throws FilterParserException {
242             TimetableStopPredicate result = this.currentResult;
243             if (result == null) {
244                 throw new FilterParserException(
245                         "Operator '|' at " + operator.getPosition() + " must not be first element in subquery.");
246             }
247             return new OrState(this, result);
248         }
249
250         @Override
251         public State handle(AndOperator operator) throws FilterParserException {
252             TimetableStopPredicate result = this.currentResult;
253             if (result == null) {
254                 throw new FilterParserException(
255                         "Operator '&' at" + operator.getPosition() + " must not be first element in subquery.");
256             }
257             return new AndState(this, result);
258         }
259
260         @Override
261         public State handle(BracketOpenToken token) throws FilterParserException {
262             return new SubQueryState(this);
263         }
264
265         @Override
266         public State handle(BracketCloseToken token) throws FilterParserException {
267             TimetableStopPredicate result = this.currentResult;
268             if (result == null) {
269                 throw new FilterParserException("Subquery must not be empty at " + token.getPosition());
270             }
271             return publishResultToPrevious(result);
272         }
273
274         @Override
275         protected State handleChildResult(TimetableStopPredicate predicate) {
276             this.currentResult = predicate;
277             return this;
278         }
279
280         @Override
281         public TimetableStopPredicate getResult() throws FilterParserException {
282             throw new FilterParserException("Invalid filter");
283         }
284     }
285
286     private FilterParser() {
287     }
288
289     /**
290      * Parses the given {@link FilterToken} into a {@link TimetableStopPredicate}.
291      */
292     public static TimetableStopPredicate parse(final List<FilterToken> tokens) throws FilterParserException {
293         State state = new InitialState();
294         for (FilterToken token : tokens) {
295             state = state.handle(token);
296         }
297         return state.getResult();
298     }
299 }