2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.deutschebahn.internal.filter;
15 import java.util.List;
17 import org.eclipse.jdt.annotation.NonNullByDefault;
18 import org.eclipse.jdt.annotation.Nullable;
21 * Parses a {@link FilterToken}-Sequence into a {@link TimetableStopPredicate}.
23 * @author Sönke Küper - Initial contribution.
26 public final class FilterParser {
31 private abstract static class State implements FilterTokenVisitor<State> {
34 private final State previousState;
36 public State(@Nullable State previousState) {
37 this.previousState = previousState;
40 private final State handle(FilterToken token) throws FilterParserException {
41 return token.accept(this);
44 protected abstract State handleChildResult(TimetableStopPredicate predicate) throws FilterParserException;
47 public final State handle(ChannelNameEquals channelEquals) throws FilterParserException {
48 final TimetableStopByStringEventAttributeFilter predicate = channelEquals.mapToPredicate();
49 return this.handleChildResult(predicate);
52 protected final State publishResultToPrevious(TimetableStopPredicate predicate) throws FilterParserException {
53 return this.getPreviousState().handleChildResult(predicate);
56 protected State getPreviousState() throws FilterParserException {
57 final State previousStateValue = this.previousState;
58 if (previousStateValue == null) {
59 throw new FilterParserException("Invalid filter");
61 return previousStateValue;
68 public abstract TimetableStopPredicate getResult() throws FilterParserException;
72 * Initial state for the parser.
74 private static final class InitialState extends State {
77 private TimetableStopPredicate result;
79 public InitialState() {
84 public State handle(OrOperator operator) throws FilterParserException {
85 final TimetableStopPredicate currentResult = this.result;
87 if (currentResult == null) {
88 throw new FilterParserException(
89 "Invalid filter: first argument missing for '|' at " + operator.getPosition());
91 return new OrState(this, currentResult);
95 public State handle(AndOperator operator) throws FilterParserException {
96 final TimetableStopPredicate currentResult = this.result;
98 if (currentResult == null) {
99 throw new FilterParserException(
100 "Invalid filter: first argument missing for '&' at " + operator.getPosition());
102 return new AndState(this, currentResult);
106 public State handle(BracketOpenToken token) throws FilterParserException {
108 return new SubQueryState(this);
112 public State handle(BracketCloseToken token) throws FilterParserException {
113 throw new FilterParserException("Unexpected token " + token + " at " + token.getPosition());
117 protected State handleChildResult(TimetableStopPredicate predicate) throws FilterParserException {
118 if (this.result == null) {
119 this.result = predicate;
122 throw new FilterParserException("Invalid filter: Operator for multiple filters missing.");
127 public TimetableStopPredicate getResult() throws FilterParserException {
128 final TimetableStopPredicate currentResult = this.result;
129 if (currentResult != null) {
130 return currentResult;
132 throw new FilterParserException("Invalid filter.");
137 * State while parsing a conjunction.
139 private static final class AndState extends State {
141 private final TimetableStopPredicate first;
143 public AndState(State previousState, final TimetableStopPredicate first) {
144 super(previousState);
149 public State handle(OrOperator operator) throws FilterParserException {
150 throw new FilterParserException(
151 "Invalid second argument for '&' operator " + operator + " at " + operator.getPosition());
155 public State handle(AndOperator operator) throws FilterParserException {
156 throw new FilterParserException(
157 "Invalid second argument for '&' operator " + operator + " at " + operator.getPosition());
161 public State handle(BracketOpenToken token) throws FilterParserException {
162 return new SubQueryState(this);
166 public State handle(BracketCloseToken token) throws FilterParserException {
167 throw new FilterParserException(
168 "Invalid second argument for '&' operator " + token + " at " + token.getPosition());
172 protected State handleChildResult(TimetableStopPredicate predicate) throws FilterParserException {
173 return this.publishResultToPrevious(new AndPredicate(first, predicate));
177 public TimetableStopPredicate getResult() throws FilterParserException {
178 throw new FilterParserException("Invalid filter");
183 * State while parsing a disjunction.
185 private static final class OrState extends State {
187 private final TimetableStopPredicate first;
189 public OrState(State previousState, final TimetableStopPredicate first) {
190 super(previousState);
195 public State handle(OrOperator operator) throws FilterParserException {
196 throw new FilterParserException(
197 "Invalid second argument for '|' operator " + operator + " at " + operator.getPosition());
201 public State handle(AndOperator operator) throws FilterParserException {
202 throw new FilterParserException(
203 "Invalid second argument for '|' operator " + operator + " at " + operator.getPosition());
207 public State handle(BracketOpenToken token) throws FilterParserException {
208 return new SubQueryState(this);
212 public State handle(BracketCloseToken token) throws FilterParserException {
213 throw new FilterParserException(
214 "Invalid second argument for '|' operator " + token + " at " + token.getPosition());
218 protected State handleChildResult(TimetableStopPredicate second) throws FilterParserException {
219 return this.publishResultToPrevious(new OrPredicate(first, second));
223 public TimetableStopPredicate getResult() throws FilterParserException {
224 throw new FilterParserException("Invalid filter");
229 * State while parsing a Subquery.
231 private static final class SubQueryState extends State {
234 private TimetableStopPredicate currentResult;
236 public SubQueryState(State previousState) {
237 super(previousState);
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.");
247 return new OrState(this, result);
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.");
257 return new AndState(this, result);
261 public State handle(BracketOpenToken token) throws FilterParserException {
262 return new SubQueryState(this);
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());
271 return publishResultToPrevious(result);
275 protected State handleChildResult(TimetableStopPredicate predicate) {
276 this.currentResult = predicate;
281 public TimetableStopPredicate getResult() throws FilterParserException {
282 throw new FilterParserException("Invalid filter");
286 private FilterParser() {
290 * Parses the given {@link FilterToken} into a {@link TimetableStopPredicate}.
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);
297 return state.getResult();