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}