001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  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 */
017package org.apache.juneau.commons.beanquery;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.stream.*;
023
024/**
025 * One node of the parse tree produced by {@link SearchExpressionParser}: the resolved, validated form of a column's
026 * {@code $}-search expression that a context walks to filter rows or build SQL.
027 *
028 * <p>
029 * A node is one of two shapes:
030 * <ul>
031 *    <li>A <b>literal</b> ({@link #isLiteral()}): a bare pattern token or a literal argument of a leaf operator
032 *       (for example {@code OPEN} in {@code $eq(OPEN)}).  Its text is {@link #value()}; it has no {@link #args()}.
033 *    <li>A <b>function</b> ({@link #isFunction()}): a {@code $}-operator invocation.  Its {@link #name()} is the
034 *       {@code $}-name, {@link #operator()} is the resolved {@link SearchOperator}, and {@link #args()} are the
035 *       argument nodes &mdash; literal nodes for a leaf operator, or nested function nodes for a combinator.
036 * </ul>
037 *
038 * <p>
039 * This tree is the parser's output, not a wire format: a {@link BeanQuery} carries only raw expression strings, and
040 * engines call the parser themselves.  See {@link SearchExpressionParser}.
041 *
042 * <h5 class='section'>Example:</h5>
043 * <p class='bjava'>
044 *    SearchExpression <jv>node</jv> = SearchExpressionParser.<jsm>parse</jsm>(<js>"$contains(err)"</js>, SearchOperatorSet.<jsm>standard</jsm>());
045 *    <jk>if</jk> (<jv>node</jv>.isFunction())
046 *       <jsm>evaluate</jsm>(<jv>node</jv>.name(), <jv>node</jv>.literalArgs());  <jc>// name() == "$contains", literalArgs() == ["err"]</jc>
047 * </p>
048 *
049 * <h5 class='section'>Example (typed values):</h5>
050 * <p class='bjava'>
051 *    SearchExpression <jv>e</jv> = SearchExpressionParser.<jsm>parse</jsm>(<js>"$eq(42)"</js>, SearchOperatorSet.<jsm>standard</jsm>());
052 *    <jc>// After QueryResolver types it against a NUMERIC column, the arg's typedValue() is a BigDecimal, not "42".</jc>
053 *    Object <jv>arg</jv> = <jv>e</jv>.typedArgs().get(0);
054 * </p>
055 *
056 * @since 10.0.0
057 */
058public final class SearchExpression {
059
060   private final String name;
061   private final String value;
062   private final boolean quoted;
063   private final SearchOperator operator;
064   private final List<SearchExpression> args;
065   private final Object typed;
066
067   private SearchExpression(String name, String value, boolean quoted, SearchOperator operator, List<SearchExpression> args, Object typed) {
068      this.name = name;
069      this.value = value;
070      this.quoted = quoted;
071      this.operator = operator;
072      this.args = args;
073      this.typed = typed;
074   }
075
076   /** Creates a literal node (a bare pattern token or a leaf argument). */
077   static SearchExpression literal(String value, boolean quoted) {
078      return new SearchExpression(null, value, quoted, null, List.of(), null);
079   }
080
081   /** Creates a function node (a resolved {@code $}-operator invocation). */
082   static SearchExpression func(SearchOperator operator, List<SearchExpression> args) {
083      return new SearchExpression(operator.name(), null, false, operator, List.copyOf(args), null);
084   }
085
086   /**
087    * Whether this is a literal (a bare pattern token or a leaf argument).
088    *
089    * @return <jk>true</jk> if a literal.
090    */
091   public boolean isLiteral() {
092      return name == null;
093   }
094
095   /**
096    * Whether this is a {@code $}-operator invocation.
097    *
098    * @return <jk>true</jk> if a function.
099    */
100   public boolean isFunction() {
101      return name != null;
102   }
103
104   /**
105    * The operator {@code $}-name, or <jk>null</jk> for a literal.
106    *
107    * @return The name, or <jk>null</jk>.
108    */
109   public String name() {
110      return name;
111   }
112
113   /**
114    * The literal text, or <jk>null</jk> for a function.
115    *
116    * @return The value, or <jk>null</jk>.
117    */
118   public String value() {
119      return value;
120   }
121
122   /**
123    * Whether this literal was written as a quoted value ({@code "a*"} or {@code 'a*'}) in the search string.
124    *
125    * <p>
126    * A quoted literal is never wildcard-interpreted: {@code name="a*"} matches the literal text {@code a*}
127    * (case-insensitive substring, like any bare value without wildcards), while {@code name=a*} treats {@code *} as
128    * a wildcard. Always <jk>false</jk> for a function node.
129    *
130    * @return <jk>true</jk> if this literal was quoted in the source search string.
131    */
132   public boolean isQuoted() {
133      return quoted;
134   }
135
136   /**
137    * The resolved operator, or <jk>null</jk> for a literal.
138    *
139    * @return The operator, or <jk>null</jk>.
140    */
141   public SearchOperator operator() {
142      return operator;
143   }
144
145   /**
146    * The argument nodes (empty for a literal or a zero-arg function such as {@code $blank()}).
147    *
148    * @return An unmodifiable list of argument nodes.
149    */
150   public List<SearchExpression> args() {
151      return args;
152   }
153
154   /**
155    * The number of argument nodes.
156    *
157    * @return The argument count.
158    */
159   public int argCount() {
160      return args.size();
161   }
162
163   /**
164    * This function's arguments as literal strings &mdash; the shape a leaf operator (built-in or custom) consumes.
165    *
166    * <p>
167    * A non-literal (nested function) argument contributes its {@code $}-name so the list length always equals
168    * {@link #argCount()}.
169    *
170    * @return An unmodifiable list of the literal argument strings.
171    */
172   public List<String> literalArgs() {
173      return args.stream().map(x -> x.isLiteral() ? x.value : x.name).collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
174   }
175
176   /**
177    * The parsed, type-aware value for this node.
178    *
179    * <p>
180    * For a literal node that {@link QueryResolver} typed (a bare literal, or an argument of a value operator such
181    * as {@code $eq}/{@code $in}/{@code $gt}/{@code $between}), this is the {@link SearchType#parse(String) parsed}
182    * object ({@link java.math.BigDecimal}, {@link Boolean}, or a {@code java.time} type).  For every other node
183    * (untyped literals, function nodes) this returns {@link #value()} unchanged, so callers can read
184    * {@code typedValue()} instead of {@code value()} without a null check.
185    *
186    * @return The typed value, or the literal text.
187    */
188   public Object typedValue() {
189      return typed != null ? typed : value;
190   }
191
192   /**
193    * Returns a copy of this literal node carrying a parsed value, leaving this node unchanged.
194    *
195    * <p>
196    * Called only by {@link QueryResolver}, the sole place a raw string is known to belong to a specific
197    * {@link SearchType}.
198    *
199    * @param value The parsed value.  Must not be <jk>null</jk>.
200    * @throws IllegalStateException If this is a function node.
201    * @return A new node equal to this one except for {@link #typedValue()}.
202    */
203   SearchExpression withTypedValue(Object value) {
204      if (isFunction())
205         throw new IllegalStateException("Cannot attach a typed value to function node '" + name + "'.");
206      return new SearchExpression(name, this.value, quoted, operator, args, Objects.requireNonNull(value, "value"));
207   }
208
209   /**
210    * This function's arguments' {@link #typedValue() typed values}, in order - the shape a value operator, or a
211    * custom operator opting into {@link SearchOperator.Builder#typedArgs(boolean) typedArgs(true)}, consumes instead
212    * of {@link #literalArgs()}.
213    *
214    * <p>
215    * A nested function argument contributes its {@code $}-name, exactly as in {@link #literalArgs()}, so the list
216    * never contains <jk>null</jk> for a function argument and its length always equals {@link #argCount()}.
217    *
218    * @return An unmodifiable list of the typed argument values, one per {@link #args()} entry.
219    */
220   public List<Object> typedArgs() {
221      return args.stream().map(x -> x.isLiteral() ? x.typedValue() : (Object)x.name).collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
222   }
223
224   @Override /* Object */
225   public boolean equals(Object o) {
226      if (this == o)
227         return true;
228      if (! (o instanceof SearchExpression other))
229         return false;
230      return quoted == other.quoted && eq(name, other.name) && eq(value, other.value) && eq(operator, other.operator)
231         && eq(args, other.args) && eq(typed, other.typed);
232   }
233
234   @Override /* Object */
235   public int hashCode() {
236      return h(name, value, quoted, operator, args, typed);
237   }
238
239   @Override /* Object */
240   public String toString() {
241      return isLiteral() ? value : (name + "(" + args.stream().map(SearchExpression::toString).collect(Collectors.joining(",")) + ")");
242   }
243}