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 *
024 *      Copyright 2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2015 ForgeRock AS
026 */
027package org.opends.server.admin.client.spi;
028
029import java.util.Collection;
030import java.util.Collections;
031import java.util.HashMap;
032import java.util.Map;
033import java.util.SortedSet;
034import java.util.TreeSet;
035
036import org.opends.server.admin.PropertyException;
037import org.opends.server.admin.PropertyDefinition;
038import org.opends.server.admin.PropertyOption;
039
040/**
041 * A set of properties. Instances of this class can be used as the
042 * core of a managed object implementation.
043 */
044public final class PropertySet {
045
046  /**
047   * Internal property implementation.
048   *
049   * @param <T>
050   *          The type of the property.
051   */
052  private static final class MyProperty<T> implements Property<T> {
053
054    /** The active set of values. */
055    private final SortedSet<T> activeValues;
056
057    /** The definition associated with this property. */
058    private final PropertyDefinition<T> d;
059
060    /** The default set of values (read-only). */
061    private final SortedSet<T> defaultValues;
062
063    /** The pending set of values. */
064    private final SortedSet<T> pendingValues;
065
066
067
068    /**
069     * Create a property with the provided sets of pre-validated
070     * default and active values.
071     *
072     * @param pd
073     *          The property definition.
074     * @param defaultValues
075     *          The set of default values for the property.
076     * @param activeValues
077     *          The set of active values for the property.
078     */
079    public MyProperty(PropertyDefinition<T> pd, Collection<T> defaultValues,
080        Collection<T> activeValues) {
081      this.d = pd;
082
083      SortedSet<T> sortedDefaultValues = new TreeSet<>(pd);
084      sortedDefaultValues.addAll(defaultValues);
085      this.defaultValues = Collections
086          .unmodifiableSortedSet(sortedDefaultValues);
087
088      this.activeValues = new TreeSet<>(pd);
089      this.activeValues.addAll(activeValues);
090
091      // Initially the pending values is the same as the active values.
092      this.pendingValues = new TreeSet<>(this.activeValues);
093    }
094
095    /** Makes the pending values active. */
096    public void commit() {
097      activeValues.clear();
098      activeValues.addAll(pendingValues);
099    }
100
101    /** {@inheritDoc} */
102    public SortedSet<T> getActiveValues() {
103      return Collections.unmodifiableSortedSet(activeValues);
104    }
105
106    /** {@inheritDoc} */
107    public SortedSet<T> getDefaultValues() {
108      return defaultValues;
109    }
110
111    /** {@inheritDoc} */
112    public SortedSet<T> getEffectiveValues() {
113      SortedSet<T> values = getPendingValues();
114
115      if (values.isEmpty()) {
116        values = getDefaultValues();
117      }
118
119      return values;
120    }
121
122
123
124    /** {@inheritDoc} */
125    public SortedSet<T> getPendingValues() {
126      return Collections.unmodifiableSortedSet(pendingValues);
127    }
128
129
130
131    /** {@inheritDoc} */
132    public PropertyDefinition<T> getPropertyDefinition() {
133      return d;
134    }
135
136
137
138    /** {@inheritDoc} */
139    public boolean isEmpty() {
140      return pendingValues.isEmpty();
141    }
142
143
144
145    /** {@inheritDoc} */
146    public boolean isModified() {
147      return activeValues.size() != pendingValues.size()
148          || !activeValues.containsAll(pendingValues);
149    }
150
151
152
153    /**
154     * Replace all pending values of this property with the provided
155     * values.
156     *
157     * @param c
158     *          The new set of pending property values.
159     */
160    public void setPendingValues(Collection<T> c) {
161      pendingValues.clear();
162      pendingValues.addAll(c);
163    }
164
165
166
167    /** {@inheritDoc} */
168    @Override
169    public String toString() {
170      return getEffectiveValues().toString();
171    }
172
173
174
175    /** {@inheritDoc} */
176    public boolean wasEmpty() {
177      return activeValues.isEmpty();
178    }
179  }
180
181  /** The properties. */
182  private final Map<PropertyDefinition<?>, MyProperty<?>> properties = new HashMap<>();
183
184  /** Creates a new empty property set. */
185  public PropertySet() {
186  }
187
188  /**
189   * Creates a property with the provided sets of pre-validated
190   * default and active values.
191   *
192   * @param <T>
193   *          The type of the property.
194   * @param pd
195   *          The property definition.
196   * @param defaultValues
197   *          The set of default values for the property.
198   * @param activeValues
199   *          The set of active values for the property.
200   */
201  public <T> void addProperty(PropertyDefinition<T> pd,
202      Collection<T> defaultValues, Collection<T> activeValues) {
203    MyProperty<T> p = new MyProperty<>(pd, defaultValues, activeValues);
204    properties.put(pd, p);
205  }
206
207
208
209  /**
210   * Get the property associated with the specified property
211   * definition.
212   *
213   * @param <T>
214   *          The underlying type of the property.
215   * @param d
216   *          The Property definition.
217   * @return Returns the property associated with the specified
218   *         property definition.
219   * @throws IllegalArgumentException
220   *           If this property provider does not recognise the
221   *           requested property definition.
222   */
223  @SuppressWarnings("unchecked")
224  public <T> Property<T> getProperty(PropertyDefinition<T> d)
225      throws IllegalArgumentException {
226    if (!properties.containsKey(d)) {
227      throw new IllegalArgumentException("Unknown property " + d.getName());
228    }
229
230    return (Property<T>) properties.get(d);
231  }
232
233
234
235  /** {@inheritDoc} */
236  @Override
237  public String toString() {
238    StringBuilder builder = new StringBuilder();
239    builder.append('{');
240    for (Map.Entry<PropertyDefinition<?>, MyProperty<?>> entry : properties.entrySet()) {
241      builder.append(entry.getKey().getName());
242      builder.append('=');
243      builder.append(entry.getValue());
244      builder.append(' ');
245    }
246    builder.append('}');
247    return builder.toString();
248  }
249
250
251
252  /**
253   * Makes all pending values active.
254   */
255  void commit()
256  {
257    for (MyProperty<?> p : properties.values())
258    {
259      p.commit();
260    }
261  }
262
263
264
265  /**
266   * Set a new pending values for the specified property.
267   * <p>
268   * See the class description for more information regarding pending values.
269   *
270   * @param <T>
271   *          The type of the property to be modified.
272   * @param d
273   *          The property to be modified.
274   * @param values
275   *          A non-<code>null</code> set of new pending values for the property
276   *          (an empty set indicates that the property should be reset to its
277   *          default behavior). The set will not be referenced by this managed
278   *          object.
279   * @throws PropertyException
280   *           If a new pending value is deemed to be invalid according to the
281   *           property definition, or if an attempt was made to add multiple
282   *           pending values to a single-valued property, or if an attempt was
283   *           made to remove a mandatory property.
284   * @throws IllegalArgumentException
285   *           If the specified property definition is not associated with this
286   *           managed object.
287   */
288  <T> void setPropertyValues(PropertyDefinition<T> d, Collection<T> values)
289      throws PropertyException, IllegalArgumentException
290  {
291    MyProperty<T> property = (MyProperty<T>) getProperty(d);
292
293    if (values.size() > 1 && !d.hasOption(PropertyOption.MULTI_VALUED)) {
294      throw PropertyException.propertyIsSingleValuedException(d);
295    }
296
297    if (values.isEmpty()
298        && d.hasOption(PropertyOption.MANDATORY)
299        // But only if there are no default values.
300        && property.getDefaultValues().isEmpty()) {
301      throw PropertyException.propertyIsMandatoryException(d);
302    }
303
304    // Validate each value.
305    for (T e : values) {
306      if (e == null) {
307        throw new NullPointerException();
308      }
309
310      d.validateValue(e);
311    }
312
313    // Update the property.
314    property.setPendingValues(values);
315  }
316}