001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *      Copyright 2015 ForgeRock AS
024 */
025package org.opends.server.types;
026
027import static org.opends.server.util.StaticUtils.*;
028
029import java.util.Collection;
030import java.util.Iterator;
031import java.util.Set;
032import java.util.SortedSet;
033import java.util.TreeSet;
034
035import org.forgerock.util.Reject;
036
037/** Temporary class until we move to {@link org.forgerock.opendj.ldap.AttributeDescription}. */
038public final class AttributeDescription implements Comparable<AttributeDescription>
039{
040  private final AttributeType attributeType;
041  private final Set<String> options;
042
043  private AttributeDescription(AttributeType attributeType, Set<String> options)
044  {
045    Reject.ifNull(attributeType);
046    Reject.ifNull(options);
047    this.attributeType = attributeType;
048    this.options = options;
049  }
050
051  /**
052   * Creates an attribute description with the attribute type and options of the provided
053   * {@link Attribute}.
054   *
055   * @param attr
056   *          The attribute.
057   * @return The attribute description.
058   * @throws NullPointerException
059   *           If {@code attributeType} or {@code options} was {@code null}.
060   */
061  public static AttributeDescription create(Attribute attr)
062  {
063    return create(attr.getAttributeType(), attr.getOptions());
064  }
065
066  /**
067   * Creates an attribute description having the provided attribute type and options.
068   *
069   * @param attributeType
070   *          The attribute type.
071   * @param options
072   *          The attribute options.
073   * @return The attribute description.
074   * @throws NullPointerException
075   *           If {@code attributeType} or {@code options} was {@code null}.
076   */
077  public static AttributeDescription create(AttributeType attributeType, Set<String> options)
078  {
079    return new AttributeDescription(attributeType, options);
080  }
081
082  /**
083   * Returns the attribute type associated with this attribute description.
084   *
085   * @return The attribute type associated with this attribute description.
086   */
087  public AttributeType getAttributeType()
088  {
089    return attributeType;
090  }
091
092  /**
093   * Returns the set of not normalized options contained in this attribute description.
094   *
095   * @return A set containing the not normalized options.
096   */
097  public Set<String> getOptions()
098  {
099    return options;
100  }
101
102  /**
103   * Indicates whether the provided set of not normalized options contains the provided option.
104   *
105   * @param options
106   *          The set of not normalized options where to do the search.
107   * @param optionToFind
108   *          The option for which to make the determination.
109   * @return {@code true} if the provided set of options has the provided option, or {@code false}
110   *         if not.
111   */
112  public static boolean containsOption(Set<String> options, String optionToFind)
113  {
114    String normToFind = toLowerCase(optionToFind);
115
116    // Cannot use Set.contains() because the options are not normalized.
117    for (String o : options)
118    {
119      String norm = toLowerCase(o);
120      if (norm.equals(normToFind))
121      {
122        return true;
123      }
124    }
125    return false;
126  }
127
128  /**
129   * Indicates whether the provided first set of not normalized options contains all values from the
130   * second set of not normalized options.
131   *
132   * @param options1
133   *          The first set of not normalized options where to do the search.
134   * @param options2
135   *          The second set of not normalized options that must all be found.
136   * @return {@code true} if the first provided set of options has all the options from the second
137   *         provided set of options.
138   */
139  public static boolean containsAllOptions(Collection<String> options1, Collection<String> options2)
140  {
141    if (options1 == options2)
142    {
143      return true;
144    }
145    else if (isEmpty(options2))
146    {
147      return true;
148    }
149    else if (isEmpty(options1))
150    {
151      return false;
152    }
153    // normalize all options before calling containsAll()
154    Set<String> set1 = toLowercaseSet(options1);
155    Set<String> set2 = toLowercaseSet(options2);
156    return set1.size() >= set2.size() && set1.containsAll(set2);
157  }
158
159  /**
160   * Indicates whether the provided first set of not normalized options equals the second set of not
161   * normalized options.
162   *
163   * @param options1
164   *          The first set of not normalized options.
165   * @param options2
166   *          The second set of not normalized options.
167   * @return {@code true} if the first provided set of options equals the second provided set of
168   *         options.
169   */
170  public static boolean optionsEqual(Set<String> options1, Set<String> options2)
171  {
172    if (options1 == options2)
173    {
174      return true;
175    }
176    else if (isEmpty(options2))
177    {
178      return isEmpty(options1);
179    }
180    else if (isEmpty(options1))
181    {
182      return false;
183    }
184    // normalize all options before calling containsAll()
185    Set<String> set1 = toLowercaseSet(options1);
186    Set<String> set2 = toLowercaseSet(options2);
187    return set1.equals(set2);
188  }
189
190  private static boolean isEmpty(Collection<String> col)
191  {
192    return col == null || col.isEmpty();
193  }
194
195  private static SortedSet<String> toLowercaseSet(Collection<String> strings)
196  {
197    final SortedSet<String> results = new TreeSet<>();
198    for (String s : strings)
199    {
200      results.add(toLowerCase(s));
201    }
202    return results;
203  }
204
205  @Override
206  public int compareTo(AttributeDescription other)
207  {
208    if (this == other)
209    {
210      return 0;
211    }
212    return compare(attributeType, options, other.attributeType, other.options);
213  }
214
215  /**
216   * Compares the first attribute type and options to the second attribute type and options, as if
217   * they were both instances of {@link AttributeDescription}.
218   * <p>
219   * The attribute types are compared first and then, if equal, the options are normalized, sorted,
220   * and compared.
221   *
222   * @param attrType1
223   *          The first attribute type to be compared.
224   * @param options1
225   *          The first options to be compared.
226   * @param attrType2
227   *          The second attribute type to be compared.
228   * @param options2
229   *          The second options to be compared.
230   * @return A negative integer, zero, or a positive integer as this attribute description is less
231   *         than, equal to, or greater than the specified attribute description.
232   * @throws NullPointerException
233   *           If {@code name} was {@code null}.
234   * @see AttributeDescription#compareTo(AttributeDescription)
235   */
236  public static int compare(AttributeType attrType1, Set<String> options1,
237      AttributeType attrType2, Set<String> options2)
238  {
239    int cmp = attrType1.compareTo(attrType2);
240    if (cmp != 0)
241    {
242      return cmp;
243    }
244    if (options1 == options2)
245    {
246      return 0;
247    }
248    return compare(toLowercaseSet(options1), toLowercaseSet(options2));
249  }
250
251  private static int compare(SortedSet<String> options1, SortedSet<String> options2)
252  {
253    Iterator<String> it1 = options1.iterator();
254    Iterator<String> it2 = options2.iterator();
255    while (it1.hasNext() && it2.hasNext())
256    {
257      int cmp = it1.next().compareTo(it2.next());
258      if (cmp != 0)
259      {
260        return cmp;
261      }
262    }
263    if (it1.hasNext())
264    {
265      return 1;
266    }
267    else if (it2.hasNext())
268    {
269      return -1;
270    }
271    return 0;
272  }
273
274  @Override
275  public boolean equals(Object obj)
276  {
277    if (obj == this)
278    {
279      return true;
280    }
281    if (!(obj instanceof AttributeDescription))
282    {
283      return false;
284    }
285    final AttributeDescription other = (AttributeDescription) obj;
286    return attributeType.equals(other.attributeType) && optionsEqual(options, other.options);
287  }
288
289  @Override
290  public int hashCode()
291  {
292    final int prime = 31;
293    int result = 1;
294    result = prime * result + ((attributeType == null) ? 0 : attributeType.hashCode());
295    result = prime * result + ((options == null) ? 0 : options.hashCode());
296    return result;
297  }
298
299  @Override
300  public String toString()
301  {
302    final StringBuilder buffer = new StringBuilder();
303    buffer.append(attributeType.getNameOrOID());
304    for (String option : options)
305    {
306      buffer.append(';');
307      buffer.append(option);
308    }
309    return buffer.toString();
310  }
311}