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}