001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.utils; 014 015import static java.util.Collections.*; 016import static org.apache.juneau.internal.StringUtils.*; 017 018import java.util.*; 019 020import org.apache.juneau.*; 021import org.apache.juneau.internal.*; 022 023/** 024 * Encapsulates arguments for basic search/view/sort/position/limit functionality. 025 */ 026public class SearchArgs { 027 028 /** 029 * Default search args. 030 */ 031 public static SearchArgs DEFAULT = SearchArgs.builder().build(); 032 033 private final Map<String,String> search; 034 private final List<String> view; 035 private final Map<String,Boolean> sort; 036 private final int position, limit; 037 private final boolean ignoreCase; 038 039 SearchArgs(Builder b) { 040 this.search = unmodifiableMap(new LinkedHashMap<>(b.search)); 041 this.view = unmodifiableList(new ArrayList<>(b.view)); 042 this.sort = unmodifiableMap(new LinkedHashMap<>(b.sort)); 043 this.position = b.position; 044 this.limit = b.limit; 045 this.ignoreCase = b.ignoreCase; 046 } 047 048 /** 049 * Creates a new builder for {@link SearchArgs} 050 * 051 * @return A new builder for {@link SearchArgs} 052 */ 053 public static Builder builder() { 054 return new Builder(); 055 } 056 057 /** 058 * Builder for {@link SearchArgs} class. 059 */ 060 public static final class Builder { 061 Map<String,String> search = new LinkedHashMap<>(); 062 List<String> view = new ArrayList<>(); 063 Map<String,Boolean> sort = new LinkedHashMap<>(); 064 int position, limit; 065 boolean ignoreCase; 066 067 /** 068 * Adds search terms to this builder. 069 * 070 * <p> 071 * The search terms are a comma-delimited list of key/value pairs of column-names and search tokens. 072 * 073 * <p> 074 * For example: 075 * <p class='bcode w800'> 076 * builder.search(<js>"column1=foo*, column2=bar baz"</js>); 077 * </p> 078 * 079 * <p> 080 * It's up to implementers to decide the syntax and meaning of the search terms. 081 * 082 * <p> 083 * Whitespace is trimmed from column names and search tokens. 084 * 085 * @param searchTerms 086 * The search terms string. 087 * Can be <jk>null</jk>. 088 * @return This object (for method chaining). 089 */ 090 public Builder search(String searchTerms) { 091 if (searchTerms != null) { 092 for (String s : StringUtils.split(searchTerms)) { 093 int i = StringUtils.indexOf(s, '=', '>', '<'); 094 if (i == -1) 095 throw new BasicRuntimeException("Invalid search terms: ''{0}''", searchTerms); 096 char c = s.charAt(i); 097 search(s.substring(0, i).trim(), s.substring(c == '=' ? i+1 : i).trim()); 098 } 099 } 100 return this; 101 } 102 103 /** 104 * Adds a search term to this builder. 105 * 106 * <p> 107 * It's up to implementers to decide the syntax and meaning of the search term. 108 * 109 * @param column The column being searched. 110 * @param searchTerm The search term. 111 * @return This object (for method chaining). 112 */ 113 public Builder search(String column, String searchTerm) { 114 this.search.put(column, searchTerm); 115 return this; 116 } 117 118 /** 119 * Specifies the list of columns to view. 120 * 121 * <p> 122 * The columns argument is a simple comma-delimited list of column names. 123 * 124 * <p> 125 * For example: 126 * <p class='bcode w800'> 127 * builder.view(<js>"column1, column2"</js>); 128 * </p> 129 * 130 * <p> 131 * Whitespace is trimmed from column names. 132 * 133 * <p> 134 * Empty view columns imply view all columns. 135 * 136 * @param columns 137 * The columns being viewed. 138 * Can be <jk>null</jk>. 139 * @return This object (for method chaining). 140 */ 141 public Builder view(String columns) { 142 if (columns != null) 143 Collections.addAll(this.view, StringUtils.split(columns)); 144 return this; 145 } 146 147 /** 148 * Specifies the list of columns to view. 149 * 150 * <p> 151 * Empty view columns imply view all columns. 152 * 153 * @param columns The columns being viewed. 154 * @return This object (for method chaining). 155 */ 156 public Builder view(Collection<String> columns) { 157 this.view.addAll(columns); 158 return this; 159 } 160 161 /** 162 * Specifies the sort arguments. 163 * 164 * <p> 165 * The sort argument is a simple comma-delimited list of column names. 166 * <br>Column names can be suffixed with <js>'+'</js> or <js>'-'</js> to indicate ascending or descending order. 167 * <br>No suffix implies ascending order. 168 * 169 * <p> 170 * For example: 171 * <p class='bcode w800'> 172 * <jc>// Order by column1 ascending, then column2 descending.</jc> 173 * builder.sort(<js>"column1, column2-"</js>); 174 * </p> 175 * 176 * <p> 177 * Note that the order of the order arguments is important. 178 * 179 * <p> 180 * Whitespace is trimmed from column names. 181 * 182 * @param sortArgs 183 * The columns to sort by. 184 * Can be <jk>null</jk>. 185 * @return This object (for method chaining). 186 */ 187 public Builder sort(String sortArgs) { 188 if (sortArgs != null) 189 sort(Arrays.asList(StringUtils.split(sortArgs))); 190 return this; 191 } 192 193 /** 194 * Specifies the sort arguments. 195 * 196 * <p> 197 * Column names can be suffixed with <js>'+'</js> or <js>'-'</js> to indicate ascending or descending order. 198 * <br>No suffix implies ascending order. 199 * 200 * <p> 201 * Note that the order of the sort is important. 202 * 203 * @param sortArgs 204 * The columns to sort by. 205 * Can be <jk>null</jk>. 206 * @return This object (for method chaining). 207 */ 208 public Builder sort(Collection<String> sortArgs) { 209 for (String s : sortArgs) { 210 boolean isDesc = false; 211 if (endsWith(s, '-', '+')) { 212 isDesc = endsWith(s, '-'); 213 s = s.substring(0, s.length()-1); 214 } 215 this.sort.put(s, isDesc); 216 } 217 return this; 218 } 219 220 /** 221 * Specifies the starting line number. 222 * 223 * @param position The zero-indexed position. 224 * @return This object (for method chaining). 225 */ 226 public Builder position(int position) { 227 this.position = position; 228 return this; 229 } 230 231 /** 232 * Specifies the number of rows to return. 233 * 234 * @param limit 235 * The number of rows to return. 236 * If <c><=0</c>, all rows should be returned. 237 * @return This object (for method chaining). 238 */ 239 public Builder limit(int limit) { 240 this.limit = limit; 241 return this; 242 } 243 244 /** 245 * Specifies whether case-insensitive search should be used. 246 * 247 * <p> 248 * The default is <jk>false</jk>. 249 * 250 * @param value The ignore-case flag value. 251 * @return This object (for method chaining). 252 */ 253 public Builder ignoreCase(boolean value) { 254 this.ignoreCase = value; 255 return this; 256 } 257 258 /** 259 * Construct the {@link SearchArgs} object. 260 * 261 * <p> 262 * This method can be called multiple times to construct new objects. 263 * 264 * @return A new {@link SearchArgs} object initialized with values in this builder. 265 */ 266 public SearchArgs build() { 267 return new SearchArgs(this); 268 } 269 } 270 271 /** 272 * The query search terms. 273 * 274 * <p> 275 * The search terms are key/value pairs consisting of column-names and search tokens. 276 * 277 * <p> 278 * It's up to implementers to decide the syntax and meaning of the search term. 279 * 280 * @return An unmodifiable map of query search terms. 281 */ 282 public Map<String,String> getSearch() { 283 return search; 284 } 285 286 /** 287 * The view columns. 288 * 289 * <p> 290 * The view columns are the list of columns that should be displayed. 291 * An empty list implies all columns should be displayed. 292 * 293 * @return An unmodifiable list of columns to view. 294 */ 295 public List<String> getView() { 296 return view; 297 } 298 299 /** 300 * The sort columns. 301 * 302 * <p> 303 * The sort columns are key/value pairs consisting of column-names and direction flags 304 * (<jk>false</jk> = ascending, <jk>true</jk> = descending). 305 * 306 * @return An unmodifiable ordered map of sort columns and directions. 307 */ 308 public Map<String,Boolean> getSort() { 309 return sort; 310 } 311 312 /** 313 * The first-row position. 314 * 315 * @return 316 * The zero-indexed row number of the first row to display. 317 * Default is <c>0</c> 318 */ 319 public int getPosition() { 320 return position; 321 } 322 323 /** 324 * The number of rows to return. 325 * 326 * @return 327 * The number of rows to return in the result. 328 * Default is <c>0</c> which means return all rows. 329 */ 330 public int getLimit() { 331 return limit; 332 } 333 334 /** 335 * The ignore-case flag. 336 * 337 * <p> 338 * Used in conjunction with {@link #getSearch()} to specify whether case-insensitive searches should be performed. 339 * 340 * @return 341 * The number of rows to return in the result. 342 * Default is <jk>false</jk>. 343 */ 344 public boolean isIgnoreCase() { 345 return ignoreCase; 346 } 347}