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-2016 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        @Override
089        public SortedSet<T> getActiveValues() {
090            return Collections.unmodifiableSortedSet(activeValues);
091        }
092
093        @Override
094        public SortedSet<T> getDefaultValues() {
095            return defaultValues;
096        }
097
098        @Override
099        public SortedSet<T> getEffectiveValues() {
100            SortedSet<T> values = getPendingValues();
101
102            if (values.isEmpty()) {
103                values = getDefaultValues();
104            }
105
106            return values;
107        }
108
109        @Override
110        public SortedSet<T> getPendingValues() {
111            return Collections.unmodifiableSortedSet(pendingValues);
112        }
113
114        @Override
115        public PropertyDefinition<T> getPropertyDefinition() {
116            return d;
117        }
118
119        @Override
120        public boolean isEmpty() {
121            return pendingValues.isEmpty();
122        }
123
124        @Override
125        public boolean isModified() {
126            return activeValues.size() != pendingValues.size()
127                    || !activeValues.containsAll(pendingValues);
128        }
129
130        /**
131         * Replace all pending values of this property with the provided values.
132         *
133         * @param c
134         *            The new set of pending property values.
135         */
136        public void setPendingValues(Collection<T> c) {
137            pendingValues.clear();
138            pendingValues.addAll(c);
139        }
140
141        @Override
142        public String toString() {
143            return getEffectiveValues().toString();
144        }
145
146        @Override
147        public boolean wasEmpty() {
148            return activeValues.isEmpty();
149        }
150    }
151
152    /** The properties. */
153    private final Map<PropertyDefinition<?>, MyProperty<?>> properties = new HashMap<>();
154
155    /** Creates a new empty property set. */
156    public PropertySet() {
157    }
158
159    /**
160     * Creates a property with the provided sets of pre-validated default and
161     * active values.
162     *
163     * @param <T>
164     *            The type of the property.
165     * @param pd
166     *            The property definition.
167     * @param defaultValues
168     *            The set of default values for the property.
169     * @param activeValues
170     *            The set of active values for the property.
171     */
172    public <T> void addProperty(PropertyDefinition<T> pd, Collection<T> defaultValues, Collection<T> activeValues) {
173        MyProperty<T> p = new MyProperty<>(pd, defaultValues, activeValues);
174        properties.put(pd, p);
175    }
176
177    /**
178     * Get the property associated with the specified property definition.
179     *
180     * @param <T>
181     *            The underlying type of the property.
182     * @param d
183     *            The Property definition.
184     * @return Returns the property associated with the specified property
185     *         definition.
186     * @throws IllegalArgumentException
187     *             If this property provider does not recognise the requested
188     *             property definition.
189     */
190    @SuppressWarnings("unchecked")
191    public <T> Property<T> getProperty(PropertyDefinition<T> d) {
192        if (!properties.containsKey(d)) {
193            throw new IllegalArgumentException("Unknown property " + d.getName());
194        }
195
196        return (Property<T>) properties.get(d);
197    }
198
199    @Override
200    public String toString() {
201        StringBuilder builder = new StringBuilder();
202        builder.append('{');
203        for (Map.Entry<PropertyDefinition<?>, MyProperty<?>> entry : properties.entrySet()) {
204            builder.append(entry.getKey().getName());
205            builder.append('=');
206            builder.append(entry.getValue());
207            builder.append(' ');
208        }
209        builder.append('}');
210        return builder.toString();
211    }
212
213    /** Makes all pending values active. */
214    void commit() {
215        for (MyProperty<?> p : properties.values()) {
216            p.commit();
217        }
218    }
219
220    /**
221     * Set a new pending values for the specified property.
222     * <p>
223     * See the class description for more information regarding pending values.
224     *
225     * @param <T>
226     *            The type of the property to be modified.
227     * @param d
228     *            The property to be modified.
229     * @param values
230     *            A non-<code>null</code> set of new pending values for the
231     *            property (an empty set indicates that the property should be
232     *            reset to its default behavior). The set will not be referenced
233     *            by this managed object.
234     * @throws PropertyException
235     *             If a new pending value is deemed to be invalid according to
236     *             the property definition.
237     * @throws PropertyException
238     *             If an attempt was made to add multiple pending values to a
239     *             single-valued property.
240     * @throws PropertyException
241     *             If an attempt was made to remove a mandatory property.
242     * @throws IllegalArgumentException
243     *             If the specified property definition is not associated with
244     *             this managed object.
245     */
246    <T> void setPropertyValues(PropertyDefinition<T> d, Collection<T> values) {
247        MyProperty<T> property = (MyProperty<T>) getProperty(d);
248
249        if (values.size() > 1 && !d.hasOption(PropertyOption.MULTI_VALUED)) {
250            throw PropertyException.propertyIsSingleValuedException(d);
251        }
252
253        if (values.isEmpty() && d.hasOption(PropertyOption.MANDATORY) && property.getDefaultValues().isEmpty()) {
254            throw PropertyException.propertyIsMandatoryException(d);
255        }
256
257        // Validate each value.
258        for (T e : values) {
259            if (e == null) {
260                throw new NullPointerException();
261            }
262
263            d.validateValue(e);
264        }
265
266        // Update the property.
267        property.setPendingValues(values);
268    }
269}