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 — 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 — 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}