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