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