001/*
002 * Copyright 2007-2017 UnboundID Corp.
003 * All Rights Reserved.
004 */
005/*
006 * Copyright (C) 2008-2017 UnboundID Corp.
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.controls;
022
023
024
025import java.io.Serializable;
026import java.util.ArrayList;
027
028import com.unboundid.asn1.ASN1Boolean;
029import com.unboundid.asn1.ASN1Element;
030import com.unboundid.asn1.ASN1OctetString;
031import com.unboundid.asn1.ASN1Sequence;
032import com.unboundid.ldap.sdk.LDAPException;
033import com.unboundid.ldap.sdk.ResultCode;
034import com.unboundid.util.NotMutable;
035import com.unboundid.util.ThreadSafety;
036import com.unboundid.util.ThreadSafetyLevel;
037
038import static com.unboundid.ldap.sdk.controls.ControlMessages.*;
039import static com.unboundid.util.Debug.*;
040import static com.unboundid.util.StaticUtils.*;
041import static com.unboundid.util.Validator.*;
042
043
044
045/**
046 * This class provides a data structure for representing a sort key that is to
047 * be used in conjunction with the {@link ServerSideSortRequestControl} for
048 * requesting that the server sort the results before returning them to the
049 * client.
050 * <BR><BR>
051 * A sort key includes the following elements:
052 * <UL>
053 *   <LI>The name of the attribute for which sorting is to be performed.</LI>
054 *   <LI>A {@code reverseOrder} flag that indicates whether the results should
055 *       be sorted in ascending order (if the value is {@code false}) or
056 *       descending order (if the value is {@code true}).</LI>
057 *   <LI>An optional matching rule ID, which specifies the ordering matching
058 *       rule that should be used to perform the sorting.  If this is not
059 *       provided, then the default ordering matching rule for the specified
060 *       attribute will be used.</LI>
061 * </UL>
062 */
063@NotMutable()
064@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
065public final class SortKey
066       implements Serializable
067{
068  /**
069   * The BER type that should be used for the matching rule ID element.
070   */
071  private static final byte TYPE_MATCHING_RULE_ID = (byte) 0x80;
072
073
074
075  /**
076   * The BER type that should be used for the reverse order element.
077   */
078  private static final byte TYPE_REVERSE_ORDER = (byte) 0x81;
079
080
081
082  /**
083   * The serial version UID for this serializable class.
084   */
085  private static final long serialVersionUID = -8631224188301402858L;
086
087
088
089  // Indicates whether the sort should be performed in reverse order.
090  private final boolean reverseOrder;
091
092  // The attribute name for this sort key.
093  private final String attributeName;
094
095  // The matching rule ID for this sort key.
096  private final String matchingRuleID;
097
098
099
100  /**
101   * Creates a new sort key with the specified attribute name.  It will use the
102   * default ordering matching rule associated with that attribute, and it will
103   * not use reverse order.
104   *
105   * @param  attributeName  The attribute name for this sort key.  It must not
106   *                        be {@code null}.
107   */
108  public SortKey(final String attributeName)
109  {
110    this(attributeName, null, false);
111  }
112
113
114
115  /**
116   * Creates a new sort key with the specified attribute name.  It will use the
117   * default ordering matching rule associated with that attribute.
118   *
119   * @param  attributeName  The attribute name for this sort key.  It must not
120   *                        be {@code null}.
121   * @param  reverseOrder   Indicates whether the sort should be performed in
122   *                        reverse order.
123   */
124  public SortKey(final String attributeName, final boolean reverseOrder)
125  {
126    this(attributeName, null, reverseOrder);
127  }
128
129
130
131  /**
132   * Creates a new sort key with the provided information.
133   *
134   * @param  attributeName   The attribute name for this sort key.  It must not
135   *                         be {@code null}.
136   * @param  matchingRuleID  The name or OID of the ordering matching rule that
137   *                         should be used to perform the sort.  It may be
138   *                         {@code null} if the default ordering matching rule
139   *                         for the specified attribute is to be used.
140   * @param  reverseOrder    Indicates whether the sort should be performed in
141   *                         reverse order.
142   */
143  public SortKey(final String attributeName, final String matchingRuleID,
144                 final boolean reverseOrder)
145  {
146    ensureNotNull(attributeName);
147
148    this.attributeName  = attributeName;
149    this.matchingRuleID = matchingRuleID;
150    this.reverseOrder   = reverseOrder;
151  }
152
153
154
155  /**
156   * Retrieves the attribute name for this sort key.
157   *
158   * @return  The attribute name for this sort key.
159   */
160  public String getAttributeName()
161  {
162    return attributeName;
163  }
164
165
166
167  /**
168   * Retrieves the name or OID of the ordering matching rule that should be used
169   * to perform the sort, if defined.
170   *
171   * @return  The name or OID of the ordering matching rule that should be used
172   *          to perform the sort, or {@code null} if the sort should use the
173   *          default ordering matching rule associated with the specified
174   *          attribute.
175   */
176  public String getMatchingRuleID()
177  {
178    return matchingRuleID;
179  }
180
181
182
183  /**
184   * Indicates whether the sort should be performed in reverse order.
185   *
186   * @return  {@code true} if the sort should be performed in reverse order, or
187   *          {@code false} if it should be performed in the standard order for
188   *          the associated ordering matching rule.
189   */
190  public boolean reverseOrder()
191  {
192    return reverseOrder;
193  }
194
195
196
197  /**
198   * Encodes this sort key into an ASN.1 sequence suitable for use in the
199   * server-side sort control.
200   *
201   * @return  An ASN.1 sequence containing the encoded representation of this
202   *          sort key.
203   */
204  ASN1Sequence encode()
205  {
206    final ArrayList<ASN1Element> elements = new ArrayList<ASN1Element>(3);
207    elements.add(new ASN1OctetString(attributeName));
208
209    if (matchingRuleID != null)
210    {
211      elements.add(new ASN1OctetString(TYPE_MATCHING_RULE_ID, matchingRuleID));
212    }
213
214    if (reverseOrder)
215    {
216      elements.add(new ASN1Boolean(TYPE_REVERSE_ORDER, reverseOrder));
217    }
218
219    return new ASN1Sequence(elements);
220  }
221
222
223
224  /**
225   * Decodes the provided ASN.1 element as a sort key.
226   *
227   * @param  element  The ASN.1 element to decode as a sort key.
228   *
229   * @return  The decoded sort key.
230   *
231   * @throws  LDAPException  If the provided ASN.1 element cannot be decoded as
232   *                         a sort key.
233   */
234  public static SortKey decode(final ASN1Element element)
235         throws LDAPException
236  {
237    final ASN1Element[] elements;
238    try
239    {
240      elements = ASN1Sequence.decodeAsSequence(element).elements();
241    }
242    catch (Exception e)
243    {
244      debugException(e);
245      throw new LDAPException(ResultCode.DECODING_ERROR,
246                              ERR_SORT_KEY_NOT_SEQUENCE.get(e), e);
247    }
248
249    if ((elements.length < 1) || (elements.length > 3))
250    {
251      throw new LDAPException(ResultCode.DECODING_ERROR,
252                              ERR_SORT_KEY_INVALID_ELEMENT_COUNT.get(
253                                   elements.length));
254    }
255
256    boolean reverseOrder   = false;
257    String  matchingRuleID = null;
258    final String  attributeName  =
259         ASN1OctetString.decodeAsOctetString(elements[0]).stringValue();
260    for (int i=1; i < elements.length; i++)
261    {
262      switch (elements[i].getType())
263      {
264        case TYPE_MATCHING_RULE_ID:
265          matchingRuleID =
266               ASN1OctetString.decodeAsOctetString(elements[i]).stringValue();
267          break;
268
269        case TYPE_REVERSE_ORDER:
270          try
271          {
272            reverseOrder =
273                 ASN1Boolean.decodeAsBoolean(elements[i]).booleanValue();
274          }
275          catch (Exception e)
276          {
277            debugException(e);
278            throw new LDAPException(ResultCode.DECODING_ERROR,
279                                    ERR_SORT_KEY_REVERSE_NOT_BOOLEAN.get(e), e);
280          }
281          break;
282
283        default:
284          throw new LDAPException(ResultCode.DECODING_ERROR,
285                                  ERR_SORT_KEY_ELEMENT_INVALID_TYPE.get(
286                                       toHex(elements[i].getType())));
287      }
288    }
289
290    return new SortKey(attributeName, matchingRuleID, reverseOrder);
291  }
292
293
294
295  /**
296   * Retrieves a string representation of this sort key.
297   *
298   * @return  A string representation of this sort key.
299   */
300  @Override()
301  public String toString()
302  {
303    final StringBuilder buffer = new StringBuilder();
304    toString(buffer);
305    return buffer.toString();
306  }
307
308
309
310  /**
311   * Appends a string representation of this sort key to the provided buffer.
312   *
313   * @param  buffer  The buffer to which to append a string representation of
314   *                 this sort key.
315   */
316  public void toString(final StringBuilder buffer)
317  {
318    buffer.append("SortKey(attributeName=");
319    buffer.append(attributeName);
320
321    if (matchingRuleID != null)
322    {
323      buffer.append(", matchingRuleID=");
324      buffer.append(matchingRuleID);
325    }
326
327    buffer.append(", reverseOrder=");
328    buffer.append(reverseOrder);
329    buffer.append(')');
330  }
331}