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;
019
020import org.forgerock.util.Reject;
021
022import java.util.Comparator;
023import java.util.EnumSet;
024import java.util.Locale;
025import java.util.MissingResourceException;
026import java.util.Set;
027
028import org.forgerock.i18n.LocalizableMessage;
029
030/**
031 * An interface for querying generic property definition features.
032 * <p>
033 * Property definitions are analogous to ConfigAttributes in the current model
034 * and will play a similar role. Eventually these will replace them.
035 * <p>
036 * Implementations <b>must</b> take care to implement the various comparison
037 * methods.
038 *
039 * @param <T>
040 *            The data-type of values of the property.
041 */
042public abstract class PropertyDefinition<T> implements Comparator<T>, Comparable<PropertyDefinition<?>> {
043
044    /**
045     * An interface for incrementally constructing property definitions.
046     *
047     * @param <T>
048     *            The data-type of values of the property.
049     * @param <D>
050     *            The type of property definition constructed by this builder.
051     */
052    protected static abstract class AbstractBuilder<T, D extends PropertyDefinition<T>> {
053
054        /** The administrator action. */
055        private AdministratorAction adminAction;
056
057        /** The default behavior provider. */
058        private DefaultBehaviorProvider<T> defaultBehavior;
059
060        /** The abstract managed object. */
061        private final AbstractManagedObjectDefinition<?, ?> definition;
062
063        /** The options applicable to this definition. */
064        private final EnumSet<PropertyOption> options;
065
066        /** The name of this property definition. */
067        private final String propertyName;
068
069        /**
070         * Create a property definition builder.
071         *
072         * @param d
073         *            The managed object definition associated with this
074         *            property definition.
075         * @param propertyName
076         *            The property name.
077         */
078        protected AbstractBuilder(AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
079            this.definition = d;
080            this.propertyName = propertyName;
081            this.options = EnumSet.noneOf(PropertyOption.class);
082            this.adminAction = new AdministratorAction(AdministratorAction.Type.NONE, d, propertyName);
083            this.defaultBehavior = new UndefinedDefaultBehaviorProvider<>();
084        }
085
086        /**
087         * Construct a property definition based on the properties of this
088         * builder.
089         *
090         * @return The new property definition.
091         */
092        public final D getInstance() {
093            return buildInstance(definition, propertyName, options, adminAction, defaultBehavior);
094        }
095
096        /**
097         * Set the administrator action.
098         *
099         * @param adminAction
100         *            The administrator action.
101         */
102        public final void setAdministratorAction(AdministratorAction adminAction) {
103            Reject.ifNull(adminAction);
104            this.adminAction = adminAction;
105        }
106
107        /**
108         * Set the default behavior provider.
109         *
110         * @param defaultBehavior
111         *            The default behavior provider.
112         */
113        public final void setDefaultBehaviorProvider(DefaultBehaviorProvider<T> defaultBehavior) {
114            Reject.ifNull(defaultBehavior);
115            this.defaultBehavior = defaultBehavior;
116        }
117
118        /**
119         * Add a property definition option.
120         *
121         * @param option
122         *            The property option.
123         */
124        public final void setOption(PropertyOption option) {
125            Reject.ifNull(option);
126            options.add(option);
127        }
128
129        /**
130         * Build a property definition based on the properties of this builder.
131         *
132         * @param d
133         *            The managed object definition associated with this
134         *            property definition.
135         * @param propertyName
136         *            The property name.
137         * @param options
138         *            Options applicable to this definition.
139         * @param adminAction
140         *            The administrator action.
141         * @param defaultBehavior
142         *            The default behavior provider.
143         * @return The new property definition.
144         */
145        protected abstract D buildInstance(AbstractManagedObjectDefinition<?, ?> d, String propertyName,
146            EnumSet<PropertyOption> options, AdministratorAction adminAction,
147            DefaultBehaviorProvider<T> defaultBehavior);
148    }
149
150    /** The administrator action. */
151    private final AdministratorAction adminAction;
152
153    /** The default behavior provider. */
154    private final DefaultBehaviorProvider<T> defaultBehavior;
155
156    /** The abstract managed object. */
157    private final AbstractManagedObjectDefinition<?, ?> definition;
158
159    /** Options applicable to this definition. */
160    private final Set<PropertyOption> options;
161
162    /** The property name. */
163    private final String propertyName;
164
165    /** The property value class. */
166    private final Class<T> theClass;
167
168    /**
169     * Create a property definition.
170     *
171     * @param d
172     *            The managed object definition associated with this property
173     *            definition.
174     * @param theClass
175     *            The property value class.
176     * @param propertyName
177     *            The property name.
178     * @param options
179     *            Options applicable to this definition.
180     * @param adminAction
181     *            The administrator action.
182     * @param defaultBehavior
183     *            The default behavior provider.
184     */
185    protected PropertyDefinition(AbstractManagedObjectDefinition<?, ?> d, Class<T> theClass, String propertyName,
186        EnumSet<PropertyOption> options, AdministratorAction adminAction, DefaultBehaviorProvider<T> defaultBehavior) {
187        Reject.ifNull(d, theClass, propertyName, options, adminAction, defaultBehavior);
188
189        this.definition = d;
190        this.theClass = theClass;
191        this.propertyName = propertyName;
192        this.options = EnumSet.copyOf(options);
193        this.adminAction = adminAction;
194        this.defaultBehavior = defaultBehavior;
195    }
196
197    /**
198     * Apply a visitor to this property definition.
199     *
200     * @param <R>
201     *            The return type of the visitor's methods.
202     * @param <P>
203     *            The type of the additional parameters to the visitor's
204     *            methods.
205     * @param v
206     *            The property definition visitor.
207     * @param p
208     *            Optional additional visitor parameter.
209     * @return Returns a result as specified by the visitor.
210     */
211    public abstract <R, P> R accept(PropertyDefinitionVisitor<R, P> v, P p);
212
213    /**
214     * Apply a visitor to a property value associated with this property
215     * definition.
216     *
217     * @param <R>
218     *            The return type of the visitor's methods.
219     * @param <P>
220     *            The type of the additional parameters to the visitor's
221     *            methods.
222     * @param v
223     *            The property value visitor.
224     * @param value
225     *            The property value.
226     * @param p
227     *            Optional additional visitor parameter.
228     * @return Returns a result as specified by the visitor.
229     */
230    public abstract <R, P> R accept(PropertyValueVisitor<R, P> v, T value, P p);
231
232    /**
233     * Cast the provided value to the type associated with this property
234     * definition.
235     * <p>
236     * This method only casts the object to the required type; it does not
237     * validate the value once it has been cast. Subsequent validation should be
238     * performed using the method {@link #validateValue(Object)}.
239     * <p>
240     * This method guarantees the following expression is always
241     * <code>true</code>:
242     *
243     * <pre>
244     *  PropertyDefinition d;
245     *  x == d.cast(x);
246     * </pre>
247     *
248     * @param object
249     *            The property value to be cast (can be <code>null</code>).
250     * @return Returns the property value cast to the correct type.
251     * @throws ClassCastException
252     *             If the provided property value did not have the correct type.
253     */
254    public final T castValue(Object object) {
255        return theClass.cast(object);
256    }
257
258    /**
259     * Compares two property values for order. Returns a negative integer, zero,
260     * or a positive integer as the first argument is less than, equal to, or
261     * greater than the second.
262     * <p>
263     * This default implementation normalizes both values using
264     * {@link #normalizeValue(Object)} and then performs a case-sensitive string
265     * comparison.
266     *
267     * @param o1
268     *            the first object to be compared.
269     * @param o2
270     *            the second object to be compared.
271     * @return a negative integer, zero, or a positive integer as the first
272     *         argument is less than, equal to, or greater than the second.
273     */
274    public int compare(T o1, T o2) {
275        Reject.ifNull(o1);
276        Reject.ifNull(o2);
277
278        String s1 = normalizeValue(o1);
279        String s2 = normalizeValue(o2);
280
281        return s1.compareTo(s2);
282    }
283
284    /**
285     * Compares this property definition with the specified property definition
286     * for order. Returns a negative integer, zero, or a positive integer if
287     * this property definition is less than, equal to, or greater than the
288     * specified property definition.
289     * <p>
290     * The ordering must be determined first from the property name and then
291     * base on the underlying value type.
292     *
293     * @param o
294     *            The reference property definition with which to compare.
295     * @return Returns a negative integer, zero, or a positive integer if this
296     *         property definition is less than, equal to, or greater than the
297     *         specified property definition.
298     */
299    public final int compareTo(PropertyDefinition<?> o) {
300        int rc = propertyName.compareTo(o.propertyName);
301        if (rc == 0) {
302            rc = theClass.getName().compareTo(o.theClass.getName());
303        }
304        return rc;
305    }
306
307    /**
308     * Parse and validate a string representation of a property value.
309     *
310     * @param value
311     *            The property string value (must not be <code>null</code>).
312     * @return Returns the decoded property value.
313     * @throws PropertyException
314     *             If the property value string is invalid.
315     */
316    public abstract T decodeValue(String value);
317
318    /**
319     * Encode the provided property value into its string representation.
320     * <p>
321     * This default implementation simply returns invokes the
322     * {@link Object#toString()} method on the provided value.
323     *
324     * @param value
325     *            The property value (must not be <code>null</code>).
326     * @return Returns the encoded property string value.
327     * @throws PropertyException
328     *             If the property value is invalid.
329     */
330    public String encodeValue(T value) {
331        Reject.ifNull(value);
332
333        return value.toString();
334    }
335
336    /**
337     * Indicates whether some other object is &quot;equal to&quot; this property
338     * definition. This method must obey the general contract of
339     * <tt>Object.equals(Object)</tt>. Additionally, this method can return
340     * <tt>true</tt> <i>only</i> if the specified Object is also a property
341     * definition and it has the same name, as returned by {@link #getName()},
342     * and also is deemed to be &quot;compatible&quot; with this property
343     * definition. Compatibility means that the two property definitions share
344     * the same underlying value type and provide similar comparator
345     * implementations.
346     *
347     * @param o
348     *            The reference object with which to compare.
349     * @return Returns <code>true</code> only if the specified object is also a
350     *         property definition and it has the same name and is compatible
351     *         with this property definition.
352     * @see java.lang.Object#equals(java.lang.Object)
353     * @see java.lang.Object#hashCode()
354     */
355    @Override
356    public final boolean equals(Object o) {
357        if (this == o) {
358            return true;
359        } else if (o instanceof PropertyDefinition) {
360            PropertyDefinition<?> other = (PropertyDefinition<?>) o;
361            return propertyName.equals(other.propertyName)
362                    && theClass.equals(other.theClass);
363        } else {
364            return false;
365        }
366    }
367
368    /**
369     * Get the administrator action associated with this property definition.
370     * The administrator action describes any action which the administrator
371     * must perform in order for changes to this property to take effect.
372     *
373     * @return Returns the administrator action associated with this property
374     *         definition.
375     */
376    public final AdministratorAction getAdministratorAction() {
377        return adminAction;
378    }
379
380    /**
381     * Get the default behavior provider associated with this property
382     * definition.
383     *
384     * @return Returns the default behavior provider associated with this
385     *         property definition.
386     */
387    public final DefaultBehaviorProvider<T> getDefaultBehaviorProvider() {
388        return defaultBehavior;
389    }
390
391    /**
392     * Gets the optional description of this property definition in the default
393     * locale.
394     *
395     * @return Returns the description of this property definition in the
396     *         default locale, or <code>null</code> if there is no description.
397     */
398    public final LocalizableMessage getDescription() {
399        return getDescription(Locale.getDefault());
400    }
401
402    /**
403     * Gets the optional description of this property definition in the
404     * specified locale.
405     *
406     * @param locale
407     *            The locale.
408     * @return Returns the description of this property definition in the
409     *         specified locale, or <code>null</code> if there is no
410     *         description.
411     */
412    public final LocalizableMessage getDescription(Locale locale) {
413        ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance();
414        String property = "property." + propertyName + ".description";
415        try {
416            return resource.getMessage(definition, property, locale);
417        } catch (MissingResourceException e) {
418            return null;
419        }
420    }
421
422    /**
423     * Gets the managed object definition associated with this property
424     * definition.
425     *
426     * @return Returns the managed object definition associated with this
427     *         property definition.
428     */
429    public final AbstractManagedObjectDefinition<?, ?> getManagedObjectDefinition() {
430        return definition;
431    }
432
433    /**
434     * Get the name of the property.
435     *
436     * @return Returns the name of the property.
437     */
438    public final String getName() {
439        return propertyName;
440    }
441
442    /**
443     * Gets the synopsis of this property definition in the default locale.
444     *
445     * @return Returns the synopsis of this property definition in the default
446     *         locale.
447     */
448    public final LocalizableMessage getSynopsis() {
449        return getSynopsis(Locale.getDefault());
450    }
451
452    /**
453     * Gets the synopsis of this property definition in the specified locale.
454     *
455     * @param locale
456     *            The locale.
457     * @return Returns the synopsis of this property definition in the specified
458     *         locale.
459     */
460    public final LocalizableMessage getSynopsis(Locale locale) {
461        ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance();
462        String property = "property." + propertyName + ".synopsis";
463        return resource.getMessage(definition, property, locale);
464    }
465
466    /**
467     * Returns a hash code value for this property definition. The hash code
468     * should be derived from the property name and the type of values handled
469     * by this property definition.
470     *
471     * @return Returns the hash code value for this property definition.
472     */
473    @Override
474    public final int hashCode() {
475        int rc = 17 + propertyName.hashCode();
476        return 37 * rc + theClass.hashCode();
477    }
478
479    /**
480     * Check if the specified option is set for this property definition.
481     *
482     * @param option
483     *            The option to test.
484     * @return Returns <code>true</code> if the option is set, or
485     *         <code>false</code> otherwise.
486     */
487    public final boolean hasOption(PropertyOption option) {
488        return options.contains(option);
489    }
490
491    /**
492     * Get a normalized string representation of a property value. This can then
493     * be used for comparisons and for generating hash-codes.
494     * <p>
495     * This method may throw an exception if the provided value is invalid.
496     * However, applications should not assume that implementations of this
497     * method will always validate a value. This task is the responsibility of
498     * {@link #validateValue(Object)}.
499     * <p>
500     * This default implementation simply returns the string representation of
501     * the provided value. Sub-classes might want to override this method if
502     * this behavior is insufficient (for example, a string property definition
503     * might strip white-space and convert characters to lower-case).
504     *
505     * @param value
506     *            The property value to be normalized.
507     * @return Returns the normalized property value.
508     * @throws PropertyException
509     *             If the property value is invalid.
510     */
511    public String normalizeValue(T value) {
512        Reject.ifNull(value);
513
514        return encodeValue(value);
515    }
516
517    /**
518     * Returns a string representation of this property definition.
519     *
520     * @return Returns a string representation of this property definition.
521     * @see Object#toString()
522     */
523    @Override
524    public final String toString() {
525        StringBuilder builder = new StringBuilder();
526        toString(builder);
527        return builder.toString();
528    }
529
530    /**
531     * Append a string representation of the property definition to the provided
532     * string builder.
533     * <p>
534     * This simple implementation just outputs the propertyName of the property
535     * definition. Sub-classes should override this method to provide more
536     * complete string representations.
537     *
538     * @param builder
539     *            The string builder where the string representation should be
540     *            appended.
541     */
542    public void toString(StringBuilder builder) {
543        builder.append(propertyName);
544    }
545
546    /**
547     * Determine if the provided property value is valid according to this
548     * property definition.
549     *
550     * @param value
551     *            The property value (must not be <code>null</code>).
552     * @throws PropertyException
553     *             If the property value is invalid.
554     */
555    public abstract void validateValue(T value);
556
557    /**
558     * Performs any run-time initialization required by this property
559     * definition. This may include resolving managed object paths and property
560     * names.
561     *
562     * @throws Exception
563     *             If this property definition could not be initialized.
564     */
565    protected void initialize() throws Exception {
566        // No implementation required.
567    }
568}