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