001/* 002 * Copyright 2009-2023 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2009-2023 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2009-2023 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk.unboundidds.examples; 037 038 039 040import java.io.Serializable; 041import java.util.Arrays; 042import java.util.Comparator; 043import java.util.Iterator; 044import java.util.TreeSet; 045 046import com.unboundid.ldap.sdk.Filter; 047import com.unboundid.util.NotMutable; 048import com.unboundid.util.NotNull; 049import com.unboundid.util.Nullable; 050import com.unboundid.util.StaticUtils; 051import com.unboundid.util.ThreadSafety; 052import com.unboundid.util.ThreadSafetyLevel; 053import com.unboundid.util.Validator; 054 055 056 057/** 058 * This class provides a comparator that may be used to define a relative order 059 * for search filters. The filter order will be based first on the filter type 060 * (in the following order: AND, OR, NOT, equality, substring, 061 * greater-or-equal, less-or-equal, presence, approximate match, extensible 062 * match), then based on the attribute name, and then by the assertion value. 063 * For AND and OR filters, with all other things equal, a filter with fewer 064 * components will be ordered before one with more components. For a substring 065 * filter with all other things equal, then a filter missing a substring element 066 * will be ordered before one with that element, and one with fewer subAny 067 * elements will be ordered before one with more subAny elements. For an 068 * extensible match filter with all other things being equal, a filter without 069 * an element will be ordered before one with that element. 070 * <BR> 071 * <BLOCKQUOTE> 072 * <B>NOTE:</B> This class, and other classes within the 073 * {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only 074 * supported for use against Ping Identity, UnboundID, and 075 * Nokia/Alcatel-Lucent 8661 server products. These classes provide support 076 * for proprietary functionality or for external specifications that are not 077 * considered stable or mature enough to be guaranteed to work in an 078 * interoperable way with other types of LDAP servers. 079 * </BLOCKQUOTE> 080 */ 081@NotMutable() 082@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 083public final class FilterComparator 084 implements Comparator<Filter>, Serializable 085{ 086 /** 087 * The singleton instance of this comparator. 088 */ 089 @NotNull private static final FilterComparator INSTANCE = 090 new FilterComparator(); 091 092 093 094 /** 095 * The serial version UID for this serializable class. 096 */ 097 private static final long serialVersionUID = 7637416445464620770L; 098 099 100 101 /** 102 * Creates a new instance of this filter comparator. 103 */ 104 private FilterComparator() 105 { 106 // No implementation is required. 107 } 108 109 110 111 /** 112 * Retrieves the singleton instance of this filter comparator. 113 * 114 * @return The singleton instance of this filter comparator. 115 */ 116 @NotNull() 117 public static FilterComparator getInstance() 118 { 119 return INSTANCE; 120 } 121 122 123 124 /** 125 * Determines a relative order for the provided filter objects. 126 * 127 * @param f1 The first filter for which to make the determination. 128 * It must not be {@code null} 129 * @param f2 The second filter for which to make the determination. 130 * It must not be {@code null} 131 * 132 * @return A negative value if the first filter should be ordered before the 133 * second, a positive value if the first filter should be ordered 134 * after the second, or zero if there is no difference in their 135 * relative orders. 136 */ 137 @Override() 138 public int compare(@NotNull final Filter f1, @NotNull final Filter f2) 139 { 140 if (f1 == f2) 141 { 142 return 0; 143 } 144 145 Validator.ensureNotNull(f1, f2); 146 147 final byte type1 = f1.getFilterType(); 148 final byte type2 = f2.getFilterType(); 149 150 if (type1 != type2) 151 { 152 return ((type1 & 0x1F) - (type2 & 0x1F)); 153 } 154 155 final String name1 = StaticUtils.toLowerCase(f1.getAttributeName()); 156 final String name2 = StaticUtils.toLowerCase(f2.getAttributeName()); 157 if ((name1 != null) && (name2 != null)) 158 { 159 final int cmpValue = name1.compareTo(name2); 160 if (cmpValue != 0) 161 { 162 return cmpValue; 163 } 164 } 165 166 final byte[] value1 = f1.getAssertionValueBytes(); 167 if (value1 != null) 168 { 169 final byte[] value2 = f2.getAssertionValueBytes(); 170 final int cmpValue = compare(value1, value2); 171 if (cmpValue != 0) 172 { 173 return cmpValue; 174 } 175 } 176 177 switch (type1) 178 { 179 case Filter.FILTER_TYPE_AND: 180 case Filter.FILTER_TYPE_OR: 181 return compareANDOrOR(f1, f2); 182 183 case Filter.FILTER_TYPE_NOT: 184 return compare(f1.getNOTComponent(), f2.getNOTComponent()); 185 186 case Filter.FILTER_TYPE_PRESENCE: 187 case Filter.FILTER_TYPE_EQUALITY: 188 case Filter.FILTER_TYPE_GREATER_OR_EQUAL: 189 case Filter.FILTER_TYPE_LESS_OR_EQUAL: 190 case Filter.FILTER_TYPE_APPROXIMATE_MATCH: 191 // The necessary processing for these types has already been done. 192 return 0; 193 194 case Filter.FILTER_TYPE_SUBSTRING: 195 return compareSubstring(f1, f2); 196 197 case Filter.FILTER_TYPE_EXTENSIBLE_MATCH: 198 return compareExtensible(f1, f2); 199 200 default: 201 // This should never happen. 202 return 0; 203 } 204 } 205 206 207 208 /** 209 * Performs a comparison of the contents of AND or OR filters. 210 * 211 * @param f1 The first filter for which to make the determination. 212 * @param f2 The second filter for which to make the determination. 213 * 214 * @return A negative value if the first filter should be ordered before the 215 * second, a positive value if the first filter should be ordered 216 * after the second, or zero if there is no difference in their 217 * relative orders. 218 */ 219 private static int compareANDOrOR(@NotNull final Filter f1, 220 @NotNull final Filter f2) 221 { 222 final TreeSet<Filter> set1 = new TreeSet<>(INSTANCE); 223 final TreeSet<Filter> set2 = new TreeSet<>(INSTANCE); 224 225 set1.addAll(Arrays.asList(f1.getComponents())); 226 set2.addAll(Arrays.asList(f2.getComponents())); 227 228 final Iterator<Filter> iterator1 = set1.iterator(); 229 final Iterator<Filter> iterator2 = set2.iterator(); 230 while (true) 231 { 232 final Filter comp1; 233 if (iterator1.hasNext()) 234 { 235 comp1 = iterator1.next(); 236 } 237 else if (iterator2.hasNext()) 238 { 239 return -1; 240 } 241 else 242 { 243 return 0; 244 } 245 246 final Filter comp2; 247 if (iterator2.hasNext()) 248 { 249 comp2 = iterator2.next(); 250 } 251 else 252 { 253 return 1; 254 } 255 256 final int compValue = INSTANCE.compare(comp1, comp2); 257 if (compValue != 0) 258 { 259 return compValue; 260 } 261 } 262 } 263 264 265 266 /** 267 * Performs a comparison of the contents of substring filters. 268 * 269 * @param f1 The first filter for which to make the determination. 270 * @param f2 The second filter for which to make the determination. 271 * 272 * @return A negative value if the first filter should be ordered before the 273 * second, a positive value if the first filter should be ordered 274 * after the second, or zero if there is no difference in their 275 * relative orders. 276 */ 277 private static int compareSubstring(@NotNull final Filter f1, 278 @NotNull final Filter f2) 279 { 280 final byte[] sI1 = f1.getSubInitialBytes(); 281 final byte[] sI2 = f2.getSubInitialBytes(); 282 if (sI1 == null) 283 { 284 if (sI2 != null) 285 { 286 return -1; 287 } 288 } 289 else if (sI2 == null) 290 { 291 return 1; 292 } 293 else 294 { 295 final int cmpValue = compare(sI1, sI2); 296 if (cmpValue != 0) 297 { 298 return cmpValue; 299 } 300 } 301 302 final byte[][] sA1 = f1.getSubAnyBytes(); 303 final byte[][] sA2 = f2.getSubAnyBytes(); 304 if (sA1.length == 0) 305 { 306 if (sA2.length > 0) 307 { 308 return -1; 309 } 310 } 311 else if (sA2.length == 0) 312 { 313 return 1; 314 } 315 else 316 { 317 final int minLength = Math.min(sA1.length, sA2.length); 318 for (int i=0; i < minLength; i++) 319 { 320 final int cmpValue = compare(sA1[i], sA2[i]); 321 if (cmpValue != 0) 322 { 323 return cmpValue; 324 } 325 } 326 327 if (sA1.length < sA2.length) 328 { 329 return -1; 330 } 331 else if (sA2.length < sA1.length) 332 { 333 return 1; 334 } 335 } 336 337 final byte[] sF1 = f1.getSubFinalBytes(); 338 final byte[] sF2 = f2.getSubFinalBytes(); 339 if (sF1 == null) 340 { 341 if (sF2 != null) 342 { 343 return -1; 344 } 345 else 346 { 347 return 0; 348 } 349 } 350 else if (sF2 == null) 351 { 352 return 1; 353 } 354 else 355 { 356 return compare(sF1, sF2); 357 } 358 } 359 360 361 362 /** 363 * Performs a comparison of the contents of substring filters. 364 * 365 * @param f1 The first filter for which to make the determination. 366 * @param f2 The second filter for which to make the determination. 367 * 368 * @return A negative value if the first filter should be ordered before the 369 * second, a positive value if the first filter should be ordered 370 * after the second, or zero if there is no difference in their 371 * relative orders. 372 */ 373 private static int compareExtensible(@NotNull final Filter f1, 374 @NotNull final Filter f2) 375 { 376 final String name1 = f1.getAttributeName(); 377 final String name2 = f2.getAttributeName(); 378 if (name1 == null) 379 { 380 if (name2 != null) 381 { 382 return -1; 383 } 384 } 385 else if (name2 == null) 386 { 387 return 1; 388 } 389 390 final String mr1 = f1.getMatchingRuleID(); 391 final String mr2 = f2.getMatchingRuleID(); 392 if (mr1 == null) 393 { 394 if (mr2 != null) 395 { 396 return -1; 397 } 398 } 399 else if (mr2 == null) 400 { 401 return 1; 402 } 403 else 404 { 405 final int cmpValue = mr1.compareTo(mr2); 406 if (cmpValue != 0) 407 { 408 return cmpValue; 409 } 410 } 411 412 if (f1.getDNAttributes()) 413 { 414 if (f2.getDNAttributes()) 415 { 416 return 0; 417 } 418 else 419 { 420 return 1; 421 } 422 } 423 else if (f2.getDNAttributes()) 424 { 425 return -1; 426 } 427 else 428 { 429 return 0; 430 } 431 } 432 433 434 435 /** 436 * Performs a comparison of the contents of the provided byte arrays. 437 * 438 * @param a1 The first array to be compared. 439 * @param a2 The second array to be compared. 440 * 441 * @return An integer value denoting the comparison value. 442 */ 443 private static int compare(@NotNull final byte[] a1, 444 @NotNull final byte[] a2) 445 { 446 final int length = Math.min(a1.length, a2.length); 447 448 for (int i=0; i < length; i++) 449 { 450 final int b1 = 0xFF & a1[i]; 451 final int b2 = 0xFF & a2[i]; 452 if (b1 != b2) 453 { 454 return b1 - b2; 455 } 456 } 457 458 return (a1.length - a2.length); 459 } 460 461 462 463 /** 464 * Retrieves a hash code for this filter comparator. 465 * 466 * @return A hash code for this filter comparator. 467 */ 468 @Override() 469 public int hashCode() 470 { 471 return 0; 472 } 473 474 475 476 /** 477 * Indicates whether the provided object is equal to this filter comparator. 478 * 479 * @param o The object for which to make the determination. 480 * 481 * @return {@code true} if the provided object is equal to this filter 482 * comparator, or {@code false} if not. 483 */ 484 @Override() 485 public boolean equals(@Nullable final Object o) 486 { 487 return ((o != null) && (o instanceof FilterComparator)); 488 } 489}