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 java.util.*;
020
021/**
022 * A {@link BeanQuery} after validation: the engine-neutral input to every engine hook.
023 *
024 * <p>
025 * Produced only by {@link BeanQueryContext#resolve(BeanQuery)} and {@link BeanQuerySession#resolve(BeanQuery)}.
026 * Every column in it is allowed, every expression is parsed, depth-checked and type-checked, the limit is clamped,
027 * and {@link #counts()} is never {@link CountPolicy#IF_REQUESTED}.  Engines never see raw request strings.
028 *
029 * <h5 class='section'>Example:</h5>
030 * <p class='bjava'>
031 *    <jc>// Validate without executing, e.g. to fail fast or to inspect what a request will do.</jc>
032 *    ResolvedQuery <jv>q</jv> = <jv>context</jv>.resolve(BeanQuery.<jsm>create</jsm>().gt(<js>"age"</js>, 21).sortDesc(<js>"name"</js>).build());
033 *    <jk>if</jk> (<jv>q</jv>.filter() <jk>instanceof</jk> Filter.Leaf <jv>leaf</jv>)
034 *       <jv>log</jv>.fine(<jv>leaf</jv>.column() + <js>" ("</js> + <jv>leaf</jv>.type() + <js>"): "</js> + <jv>leaf</jv>.expression());
035 *    <jk>boolean</jk> <jv>newestFirst</jv> = <jv>q</jv>.sort().get(0).descending();
036 * </p>
037 *
038 * <p>
039 * The nested {@link SortKey} type is covered by this example.
040 *
041 * @since 10.0.0
042 */
043public final class ResolvedQuery {
044
045   /** One sort key: its column, the column's value type, and the direction. */
046   public static final class SortKey {
047      private final String column;
048      private final SearchType type;
049      private final boolean descending;
050
051      SortKey(String column, SearchType type, boolean descending) {
052         this.column = column;
053         this.type = type;
054         this.descending = descending;
055      }
056
057      /** @return The column name. */
058      public String column() {
059         return column;
060      }
061
062      /** @return The column's value type. */
063      public SearchType type() {
064         return type;
065      }
066
067      /** @return <jk>true</jk> for descending order. */
068      public boolean descending() {
069         return descending;
070      }
071
072      @Override /* Object */
073      public String toString() {
074         return column + (descending ? ":desc" : "");
075      }
076   }
077
078   private final Filter filter;
079   private final List<SortKey> sort;
080   private final List<String> view;
081   private final int position;
082   private final Integer limit;
083   private final CountPolicy counts;
084   private final BeanQueryContext<?> origin;
085
086   ResolvedQuery(Filter filter, List<SortKey> sort, List<String> view, int position, Integer limit, CountPolicy counts, BeanQueryContext<?> origin) {
087      this.filter = filter;
088      this.sort = List.copyOf(sort);
089      this.view = List.copyOf(view);
090      this.position = position;
091      this.limit = limit;
092      this.counts = counts;
093      this.origin = origin;
094   }
095
096   /**
097    * @return The resolved search tree, AND-ed and OR-ed across columns as the request's search expressed it;
098    *    never <jk>null</jk> &mdash; an empty search resolves to an empty {@link Filter.And}, which matches every row.
099    */
100   public Filter filter() {
101      return filter;
102   }
103
104   /** @return The sort keys, most significant first (empty for source order). */
105   public List<SortKey> sort() {
106      return sort;
107   }
108
109   /** @return The view columns; never empty (defaults to every allowed column in declaration order). */
110   public List<String> view() {
111      return view;
112   }
113
114   /** @return The zero-based offset of the first row (never negative). */
115   public int position() {
116      return position;
117   }
118
119   /** @return The maximum number of rows, or <jk>null</jk> for all rows (only when {@code maxLimit} is unset). */
120   public Integer limit() {
121      return limit;
122   }
123
124   /** @return The effective count policy: {@link CountPolicy#NONE}, {@link CountPolicy#MATCHED} or {@link CountPolicy#BOTH}. */
125   public CountPolicy counts() {
126      return counts;
127   }
128
129   @Override /* Object */
130   public String toString() {
131      return "ResolvedQuery(filter=" + filter + ", sort=" + sort + ", view=" + view + ", position=" + position + ", limit=" + limit + ", counts=" + counts + ")";
132   }
133
134   /**
135    * @param context The context to check.
136    * @return <jk>true</jk> if the given context is the one that resolved this query (directly, or through a session built
137    *    from it).  Not public: an engine's rendering/execution code uses this only to confirm a caller-supplied query
138    *    matches the context it is about to be used against; it is not part of this type's engine-neutral public contract.
139    */
140   boolean isResolvedBy(BeanQueryContext<?> context) {
141      return origin == context;
142   }
143}