001/*
002 * Copyright 2009-2018 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2015-2018 Ping Identity Corporation
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU General Public License (GPLv2 only)
010 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
011 * as published by the Free Software Foundation.
012 *
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Public License for more details.
017 *
018 * You should have received a copy of the GNU General Public License
019 * along with this program; if not, see <http://www.gnu.org/licenses>.
020 */
021package com.unboundid.ldap.sdk.unboundidds.examples;
022
023
024
025import java.io.Serializable;
026import java.util.Arrays;
027import java.util.TreeSet;
028
029import com.unboundid.ldap.sdk.Filter;
030import com.unboundid.util.NotMutable;
031import com.unboundid.util.StaticUtils;
032import com.unboundid.util.ThreadSafety;
033import com.unboundid.util.ThreadSafetyLevel;
034
035
036
037/**
038 * This class provides a data structure for representing search filters in a
039 * generic way.
040 * <BR>
041 * <BLOCKQUOTE>
042 *   <B>NOTE:</B>  This class, and other classes within the
043 *   {@code com.unboundid.ldap.sdk.unboundidds} package structure, are only
044 *   supported for use against Ping Identity, UnboundID, and
045 *   Nokia/Alcatel-Lucent 8661 server products.  These classes provide support
046 *   for proprietary functionality or for external specifications that are not
047 *   considered stable or mature enough to be guaranteed to work in an
048 *   interoperable way with other types of LDAP servers.
049 * </BLOCKQUOTE>
050 * <BR>
051 * This includes:
052 * <UL>
053 *   <LI>Using a consistent order for AND and OR components.</LI>
054 *   <LI>Converting all attribute names to lowercase.</LI>
055 *   <LI>Replacing the assertion value with a "?" character for equality,
056 *       greater-or-equal, less-or-equal, approximate match, and extensible
057 *       match filters.</LI>
058 *   <LI>Replacing all subInitial, subAny, and subFinal elements with "?"
059 *       characters in substring filters.</LI>
060 * </UL>
061 */
062@NotMutable()
063@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
064public final class GenericFilter
065       implements Serializable
066{
067  /**
068   * The serial version UID for this serializable class.
069   */
070  private static final long serialVersionUID = -7875317078624475546L;
071
072
073
074  // The hash code for this generic filter.
075  private final int hashCode;
076
077  // The string representation for this filter.
078  private final String filterString;
079
080
081
082  /**
083   * Creates a new generic filter from the provided search filter.
084   *
085   * @param  f  The filter to use to create a generic filter.
086   */
087  public GenericFilter(final Filter f)
088  {
089    final StringBuilder b = new StringBuilder();
090    b.append('(');
091
092    switch (f.getFilterType())
093    {
094      case Filter.FILTER_TYPE_AND:
095      case Filter.FILTER_TYPE_OR:
096        appendComponents(f, b);
097        break;
098
099      case Filter.FILTER_TYPE_NOT:
100        b.append('!');
101        b.append(new GenericFilter(f.getNOTComponent()).toString());
102        break;
103
104      case Filter.FILTER_TYPE_EQUALITY:
105        b.append(StaticUtils.toLowerCase(f.getAttributeName()));
106        b.append("=?");
107        break;
108
109      case Filter.FILTER_TYPE_SUBSTRING:
110        b.append(StaticUtils.toLowerCase(f.getAttributeName()));
111        b.append('=');
112        if (f.getRawSubInitialValue() != null)
113        {
114          b.append('?');
115        }
116        for (int i=0; i < f.getRawSubAnyValues().length; i++)
117        {
118          b.append("*?");
119        }
120        b.append('*');
121        if (f.getRawSubFinalValue() != null)
122        {
123          b.append('?');
124        }
125        break;
126
127      case Filter.FILTER_TYPE_GREATER_OR_EQUAL:
128        b.append(StaticUtils.toLowerCase(f.getAttributeName()));
129        b.append(">=?");
130        break;
131
132      case Filter.FILTER_TYPE_LESS_OR_EQUAL:
133        b.append(StaticUtils.toLowerCase(f.getAttributeName()));
134        b.append("<=?");
135        break;
136
137      case Filter.FILTER_TYPE_PRESENCE:
138        b.append(StaticUtils.toLowerCase(f.getAttributeName()));
139        b.append("=*");
140        break;
141
142      case Filter.FILTER_TYPE_APPROXIMATE_MATCH:
143        b.append(StaticUtils.toLowerCase(f.getAttributeName()));
144        b.append("~=?");
145        break;
146
147      case Filter.FILTER_TYPE_EXTENSIBLE_MATCH:
148        final String attrName = StaticUtils.toLowerCase(f.getAttributeName());
149        final String mrID     = StaticUtils.toLowerCase(f.getMatchingRuleID());
150        if (attrName != null)
151        {
152          b.append(attrName);
153        }
154        if (f.getDNAttributes())
155        {
156          b.append(":dn");
157        }
158        if (mrID != null)
159        {
160          b.append(':');
161          b.append(mrID);
162        }
163        b.append(":=?");
164        break;
165    }
166
167    b.append(')');
168
169    filterString = b.toString();
170    hashCode     = filterString.hashCode();
171  }
172
173
174
175  /**
176   * Appends a string representation of the provided AND or OR filter to the
177   * given buffer.
178   *
179   * @param  f  The filter for which to provide the string representation.
180   * @param  b  The buffer to which to append the string representation.
181   */
182  private static void appendComponents(final Filter f, final StringBuilder b)
183  {
184    if (f.getFilterType() == Filter.FILTER_TYPE_AND)
185    {
186      b.append('&');
187    }
188    else
189    {
190      b.append('|');
191    }
192
193    final TreeSet<Filter> compSet =
194         new TreeSet<>(FilterComparator.getInstance());
195    compSet.addAll(Arrays.asList(f.getComponents()));
196    for (final Filter fc : compSet)
197    {
198      b.append(new GenericFilter(fc).toString());
199    }
200  }
201
202
203
204  /**
205   * Retrieves a hash code for this generic filter.
206   *
207   * @return  A hash code for this generic filter.
208   */
209  @Override()
210  public int hashCode()
211  {
212    return hashCode;
213  }
214
215
216
217  /**
218   * Indicates whether the provided object is equal to this generic filter.
219   *
220   * @param  o  The object for which to make the determination.
221   *
222   * @return  {@code true} the provided object is equal to this generic filter,
223   *          or {@code false} if not.
224   */
225  @Override()
226  public boolean equals(final Object o)
227  {
228    if (o == null)
229    {
230      return false;
231    }
232
233    if (o == this)
234    {
235      return true;
236    }
237
238    return ((o instanceof GenericFilter) &&
239            filterString.equals(((GenericFilter) o).filterString));
240  }
241
242
243
244  /**
245   * Retrieves a string representation of this generic filter.
246   *
247   * @return  A string representation of this generic filter.
248   */
249  @Override()
250  public String toString()
251  {
252    return filterString;
253  }
254}