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.*;
022
023/**
024 * The immutable definition of a single {@code $}-search operator — its {@code $}-name, arity, the value types
025 * it applies to, and its popup help text.
026 *
027 * <p>
028 * A custom operator always carries its own help text; the help text of a built-in ({@link SearchOperatorSet#standard()})
029 * operator is owned by the browser runtime, so {@link #help()} is <jk>null</jk> for built-ins.  A custom operator
030 * additionally may carry a {@link SearchPredicate} the in-memory context calls when it hits
031 * that {@code $}-name &mdash; Juneau never invents one.  Anything else a downstream module (for example a SQL
032 * renderer) needs to attach lives in the typed {@link #extension(Class) extension} slot: it is keyed by the
033 * extension's own {@link Class}, so this module (commons) never needs to depend on that module's types (design
034 * §5.3).
035 *
036 * <p>
037 * Instances are built with {@link Builder}: {@link #create(String, String)} starts a new custom operator, and
038 * {@link #copy()} starts a modified copy of an existing one (built-in or custom).
039 *
040 * <h5 class='section'>Combinators vs. leaves</h5>
041 * <p>
042 * {@code $and}/{@code $or}/{@code $not} are {@link #isCombinator() combinators}: their children are sub-expressions,
043 * not literal value arguments, and they apply to every type.  Every other operator is a leaf whose arguments are
044 * literal values and whose {@link #appliesTo(SearchType) applicability} is a fixed type set (an <b>empty</b> type set
045 * means "applies to all types").
046 *
047 * <h5 class='section'>Example (custom operator, with a typed extension):</h5>
048 * <p class='bjava'>
049 *    SearchOperator <jv>op</jv> = SearchOperator.<jsm>create</jsm>(<js>"$near"</js>, <js>"Fuzzy match. Example: $near(jhon)"</js>)
050 *       .types(SearchType.<jsf>TEXT</jsf>)
051 *       .predicate((<jv>cell</jv>, <jv>args</jv>) -> <jsm>fuzzy</jsm>(<jv>cell</jv>, <jv>args</jv>.get(0)))
052 *       .extension(MySqlRenderer.<jk>class</jk>, <jv>myRenderer</jv>)
053 *       .build();
054 *
055 *    <jc>// A later consumer that knows about MySqlRenderer retrieves it back, type-safe, with no cast.</jc>
056 *    Optional&lt;MySqlRenderer&gt; <jv>renderer</jv> = <jv>op</jv>.extension(MySqlRenderer.<jk>class</jk>);
057 * </p>
058 *
059 * @since 10.0.0
060 */
061public final class SearchOperator {
062
063   private final String name;
064   private final String help;
065   private final int minArgs;
066   private final int maxArgs;
067   private final Set<SearchType> types;
068   private final SearchPredicate predicate;
069   private final boolean custom;
070   private final boolean combinator;
071   private final boolean typedArgs;
072   private final Map<Class<?>,Object> extensions;
073
074   private SearchOperator(Builder b) {
075      name = b.name;
076      help = b.help;
077      minArgs = b.minArgs;
078      maxArgs = b.maxArgs;
079      types = Set.copyOf(b.types);
080      predicate = b.predicate;
081      custom = b.custom;
082      combinator = b.combinator;
083      typedArgs = b.typedArgs;
084      extensions = u(new LinkedHashMap<>(b.extensions));
085   }
086
087   /**
088    * Creates a built-in operator definition.  Package-private &mdash; the canonical set is
089    * {@link SearchOperatorSet#standard()}; applications add operators through {@link #create(String, String)}.
090    */
091   static SearchOperator builtin(String name, int minArgs, int maxArgs, boolean combinator, SearchType...types) {
092      var b = new Builder();
093      b.name = name;
094      b.minArgs = minArgs;
095      b.maxArgs = maxArgs;
096      b.custom = false;
097      b.combinator = combinator;
098      b.types = types == null || types.length == 0 ? new LinkedHashSet<>() : new LinkedHashSet<>(l(types));
099      return b.build();
100   }
101
102   /**
103    * Starts a new <b>custom</b> (application-defined) leaf operator.
104    *
105    * <p>
106    * Defaults: arity {@code 1..*} (one or more arguments), applies to <b>all</b> value types (an empty type set),
107    * no predicate, no extensions.  Narrow it with {@link Builder#minArgs(int)}/{@link Builder#maxArgs(int)}/
108    * {@link Builder#types(SearchType...)} and attach an evaluator with {@link Builder#predicate(SearchPredicate)}
109    * (in-memory) and/or a typed {@link Builder#extension(Class, Object)} (for example, a SQL renderer).
110    *
111    * @param name The operator name, including the leading {@code $} (e.g. {@code "$near"}).  Must not be
112    *    <jk>null</jk> or blank and must start with {@code $}.
113    * @param helpText The popup help text: what the operator accepts and a short example.  Must not be
114    *    <jk>null</jk> or blank.
115    * @return A new {@link Builder} pre-filled with {@code name}/{@code helpText} and the defaults above.
116    * @throws IllegalArgumentException If {@code name} is not a valid {@code $}-name or {@code helpText} is blank.
117    */
118   public static Builder create(String name, String helpText) {
119      req(inb(name) && name.startsWith("$") && name.length() >= 2,
120         "SearchOperator name must start with '$' and not be blank: '%s'", name);
121      req(inb(helpText), "SearchOperator help text must not be null or blank (operator '%s').", name);
122      var b = new Builder();
123      b.name = name;
124      b.help = helpText;
125      b.custom = true;
126      return b;
127   }
128
129   /**
130    * Starts a {@link Builder} pre-filled with this operator's current state, for building a modified copy.
131    *
132    * <p>
133    * The returned builder is fully independent of this instance: mutating it (or the {@link SearchOperator} it
134    * eventually {@link Builder#build() builds}) never affects this instance.
135    *
136    * <p>
137    * <b>Renaming is only legal for a copy of a <i>custom</i> operator.</b>  Both evaluation engines dispatch a
138    * built-in purely by its {@link #name()} &mdash; the in-memory engine's leaf switch and
139    * {@code SqlDialect#builtinRenderer(String)} alike &mdash; so {@link Builder#name(String) changing the name} on a
140    * copy of a <i>custom</i> operator (one with its own {@link Builder#predicate(SearchPredicate) predicate}/
141    * {@link Builder#extension(Class, Object) extension}) is fine and gives it a fresh identity, but doing the same
142    * to a copy of a <b>built-in</b> operator throws {@link IllegalArgumentException} from {@link Builder#build()
143    * build()}.  A built-in's copy may still have its {@link Builder#help(String) help text} changed freely; only its
144    * name is fixed.
145    *
146    * @return A new builder seeded from this operator.
147    */
148   public Builder copy() {
149      var b = new Builder();
150      b.name = name;
151      b.help = help;
152      b.minArgs = minArgs;
153      b.maxArgs = maxArgs;
154      b.types = new LinkedHashSet<>(types);
155      b.predicate = predicate;
156      b.custom = custom;
157      b.combinator = combinator;
158      b.typedArgs = typedArgs;
159      b.extensions = new LinkedHashMap<>(extensions);
160      if (! custom)
161         b.builtinOriginalName = name;
162      return b;
163   }
164
165   /**
166    * The operator name, including the leading {@code $} (e.g. {@code "$eq"}).
167    *
168    * @return The operator name.
169    */
170   public String name() {
171      return name;
172   }
173
174   /**
175    * The human-readable help text for this operator, shown in the column-search popup.
176    *
177    * <p>
178    * Built-in operators return <jk>null</jk>: their help text is owned by <c>juneau-search.js</c>, which the popup
179    * falls back to.  Custom operators always return non-blank text, because {@link Builder#build()} rejects a custom
180    * operator without it.
181    *
182    * <h5 class='section'>Example:</h5>
183    * <p class='bjava'>
184    *    SearchOperator <jv>near</jv> = SearchOperator.<jsm>create</jsm>(<js>"$near"</js>, <js>"Within N units. Example: $near(5)"</js>).build();
185    *    <jv>near</jv>.help();                                                <jc>// "Within N units. Example: $near(5)"</jc>
186    *    SearchOperatorSet.<jsm>standard</jsm>().get(<js>"$eq"</js>).help();  <jc>// null</jc>
187    * </p>
188    *
189    * @return The help text, or <jk>null</jk> for a built-in operator.
190    */
191   public String help() {
192      return help;
193   }
194
195   /**
196    * Whether this is an application-defined custom operator (vs. a built-in).
197    *
198    * @return <jk>true</jk> if custom.
199    */
200   public boolean isCustom() {
201      return custom;
202   }
203
204   /**
205    * Whether this is a combinator ({@code $and}/{@code $or}/{@code $not}) whose children are sub-expressions.
206    *
207    * @return <jk>true</jk> if a combinator.
208    */
209   public boolean isCombinator() {
210      return combinator;
211   }
212
213   /**
214    * The minimum argument count (inclusive).
215    *
216    * @return The minimum arity.
217    */
218   public int minArgs() {
219      return minArgs;
220   }
221
222   /**
223    * The maximum argument count (inclusive), or {@code -1} for unbounded.
224    *
225    * @return The maximum arity, or {@code -1}.
226    */
227   public int maxArgs() {
228      return maxArgs;
229   }
230
231   /**
232    * The in-memory evaluator for a custom operator, if one was supplied.
233    *
234    * @return The predicate, or <jk>null</jk> for built-ins and for customs with no predicate.
235    */
236   public SearchPredicate predicate() {
237      return predicate;
238   }
239
240   /**
241    * The value types this operator applies to (combinators apply to all types regardless of this set).
242    *
243    * @return An unmodifiable view of the applicable types; empty means "applies to all types".
244    */
245   public Set<SearchType> types() {
246      return types;
247   }
248
249   /**
250    * Whether this operator applies to the specified value type.
251    *
252    * @param type The column value type.  Can be <jk>null</jk>.
253    * @return <jk>true</jk> if this operator is offered for that type (combinators, and operators with an empty
254    *    {@link #types() type set}, apply to every type).
255    */
256   public boolean appliesTo(SearchType type) {
257      return type != null && (combinator || types.isEmpty() || types.contains(type));
258   }
259
260   /**
261    * Whether the specified argument count satisfies this operator's arity.
262    *
263    * @param count The number of arguments supplied.
264    * @return <jk>true</jk> if {@code count} is within {@code [minArgs, maxArgs]} (upper bound ignored when unbounded).
265    */
266   public boolean acceptsArgCount(int count) {
267      return count >= minArgs && (maxArgs < 0 || count <= maxArgs);
268   }
269
270   /**
271    * Whether this operator's SQL renderer should be given {@link SearchExpression#typedArgs() typed values} rather
272    * than {@link SearchExpression#literalArgs() literal strings}.  Never affects the in-memory
273    * {@link #predicate() predicate}, which always receives literal strings.
274    *
275    * @return <jk>true</jk> if typed (see {@link Builder#typedArgs(boolean)}).
276    */
277   public boolean typedArgs() {
278      return typedArgs;
279   }
280
281   /**
282    * Looks up a typed extension previously attached with {@link Builder#extension(Class, Object)}.
283    *
284    * <p>
285    * This is how a downstream module (for example {@code juneau-beanquery-sql}'s SQL renderer) attaches its own
286    * metadata to a custom operator without this module (commons) ever depending on that module's types: the
287    * extension is keyed by its own {@link Class}, so retrieval is type-safe with no cast (design §5.3).
288    *
289    * @param <X> The extension type.
290    * @param type The extension's class, used as the lookup key.  Must not be <jk>null</jk>.
291    * @return The extension, or {@link Optional#empty()} if none was attached under that type.
292    */
293   public <X> Optional<X> extension(Class<X> type) {
294      reqnn("type", type);
295      return Optional.ofNullable(type.cast(extensions.get(type)));
296   }
297
298   @Override /* Object */
299   public boolean equals(Object o) {
300      if (this == o)
301         return true;
302      if (! (o instanceof SearchOperator other))
303         return false;
304      return eq(name, other.name) && eq(help, other.help) && minArgs == other.minArgs
305         && maxArgs == other.maxArgs && eq(types, other.types) && eq(predicate, other.predicate)
306         && custom == other.custom && combinator == other.combinator && typedArgs == other.typedArgs && eq(extensions, other.extensions);
307   }
308
309   @Override /* Object */
310   public int hashCode() {
311      return h(name, help, minArgs, maxArgs, types, predicate, custom, combinator, typedArgs, extensions);
312   }
313
314   @Override /* Object */
315   public String toString() {
316      return name;
317   }
318
319   /**
320    * Builder for {@link SearchOperator}.  See the class Javadoc for an example.  Start one with
321    * {@link SearchOperator#create(String, String)} or {@link SearchOperator#copy()}.
322    */
323   public static final class Builder {
324
325      private String name;
326      private String help;
327      private int minArgs = 1;
328      private int maxArgs = -1;
329      private Set<SearchType> types = new LinkedHashSet<>();
330      private SearchPredicate predicate;
331      private boolean custom;
332      private boolean combinator;
333      private boolean typedArgs;
334      private Map<Class<?>,Object> extensions = new LinkedHashMap<>();
335
336      // Set only by copy() when the source operator was a built-in (custom == false), to the built-in's original
337      // name. Left null for Builders created via create() (always custom) or the package-private builtin()
338      // factory (constructs a built-in directly; it never "renames" anything). build() compares this against the
339      // builder's current name only when non-null, so renaming a copy of a custom operator stays legal, and
340      // builtin()'s own one-time name assignment is never flagged.
341      private String builtinOriginalName;
342
343      private Builder() {}
344
345      /**
346       * Sets the operator name, including the leading {@code $} (e.g. {@code "$near"}).
347       *
348       * @param value The operator name.
349       * @return This object.
350       */
351      public Builder name(String value) {
352         name = value;
353         return this;
354      }
355
356      /**
357       * Sets the popup help text.
358       *
359       * @param value The help text.
360       * @return This object.
361       */
362      public Builder help(String value) {
363         help = value;
364         return this;
365      }
366
367      /**
368       * Sets the minimum argument count (inclusive).
369       *
370       * @param value The minimum number of arguments.
371       * @return This object.
372       */
373      public Builder minArgs(int value) {
374         minArgs = value;
375         return this;
376      }
377
378      /**
379       * Sets the maximum argument count (inclusive), or {@code -1} for unbounded.
380       *
381       * @param value The maximum number of arguments, or {@code -1} for no upper bound.
382       * @return This object.
383       */
384      public Builder maxArgs(int value) {
385         maxArgs = value;
386         return this;
387      }
388
389      /**
390       * Sets the value types this operator applies to.
391       *
392       * @param value The applicable types, or none to mean "applies to all types".
393       * @return This object.
394       */
395      public Builder types(SearchType...value) {
396         types = value == null || value.length == 0 ? new LinkedHashSet<>() : new LinkedHashSet<>(l(value));
397         return this;
398      }
399
400      /**
401       * Attaches the in-memory evaluator for this (custom) operator.
402       *
403       * @param value The predicate the in-memory context calls for this operator.  Can be <jk>null</jk> to leave it
404       *    with no in-memory evaluator.
405       * @return This object.
406       */
407      public Builder predicate(SearchPredicate value) {
408         predicate = value;
409         return this;
410      }
411
412      /**
413       * Opts this (custom) operator's SQL renderer into typed argument values.
414       *
415       * <p>
416       * Built-in value operators ({@code $eq}/{@code $ne}/{@code $in}/{@code $gt}/{@code $gte}/{@code $lt}/
417       * {@code $lte}/{@code $between}) always receive typed arguments from {@link QueryResolver}.  For a
418       * <b>custom</b> operator, this flag controls only its SQL renderer: when <jk>true</jk>, a SQL context passes
419       * {@link SearchExpression#typedArgs()} instead of {@link SearchExpression#literalArgs()}.  The in-memory
420       * {@link SearchPredicate} always receives literal strings regardless of this flag.
421       *
422       * @param value <jk>true</jk> for the SQL renderer to receive typed arguments.
423       * @return This object.
424       */
425      public Builder typedArgs(boolean value) {
426         typedArgs = value;
427         return this;
428      }
429
430      /**
431       * Attaches a typed extension, keyed by its own {@link Class}.
432       *
433       * <p>
434       * This is how a downstream module (for example {@code juneau-beanquery-sql}'s SQL renderer) attaches its own
435       * metadata to a custom operator without this module (commons) ever depending on that module's types (design
436       * §5.3).  Retrieve it later with {@link SearchOperator#extension(Class)}.
437       *
438       * @param <X> The extension type.
439       * @param type The extension's class, used as the lookup key.  Must not be <jk>null</jk>.
440       * @param value The extension value.  Can be <jk>null</jk>.
441       * @return This object.
442       */
443      public <X> Builder extension(Class<X> type, X value) {
444         reqnn("type", type);
445         extensions.put(type, value);
446         return this;
447      }
448
449      /**
450       * Builds the immutable {@link SearchOperator}.
451       *
452       * @return A new, immutable operator.
453       * @throws IllegalArgumentException If {@code name} is <jk>null</jk> or blank, {@code help} is <jk>null</jk> or
454       *    blank on a custom operator, {@code minArgs} is negative, {@code maxArgs} is neither {@code -1} nor {@code >= minArgs}, or this
455       *    builder was seeded from {@link SearchOperator#copy() copy()} of a built-in operator and {@code name} has
456       *    been changed since.
457       */
458      public SearchOperator build() {
459         reqnb("name", name);
460         if (custom)
461            reqnb("help", help);
462         req(minArgs >= 0, "SearchOperator '%s' minArgs must not be negative: %s", name, minArgs);
463         req(maxArgs == -1 || maxArgs >= minArgs,
464            "SearchOperator '%s' maxArgs (%s) must be -1 (unbounded) or >= minArgs (%s).", name, maxArgs, minArgs);
465         if (builtinOriginalName != null && neq(builtinOriginalName, name))
466            throw iaex("Built-in operator '%s' cannot be renamed; only help text may be changed on a built-in copy.", builtinOriginalName);
467         return new SearchOperator(this);
468      }
469   }
470}