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;
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    @Override
275    public int compare(T o1, T o2) {
276        Reject.ifNull(o1);
277        Reject.ifNull(o2);
278
279        String s1 = normalizeValue(o1);
280        String s2 = normalizeValue(o2);
281
282        return s1.compareTo(s2);
283    }
284
285    /**
286     * Compares this property definition with the specified property definition
287     * for order. Returns a negative integer, zero, or a positive integer if
288     * this property definition is less than, equal to, or greater than the
289     * specified property definition.
290     * <p>
291     * The ordering must be determined first from the property name and then
292     * base on the underlying value type.
293     *
294     * @param o
295     *            The reference property definition with which to compare.
296     * @return Returns a negative integer, zero, or a positive integer if this
297     *         property definition is less than, equal to, or greater than the
298     *         specified property definition.
299     */
300    @Override
301    public final int compareTo(PropertyDefinition<?> o) {
302        int rc = propertyName.compareTo(o.propertyName);
303        if (rc == 0) {
304            rc = theClass.getName().compareTo(o.theClass.getName());
305        }
306        return rc;
307    }
308
309    /**
310     * Parse and validate a string representation of a property value.
311     *
312     * @param value
313     *            The property string value (must not be <code>null</code>).
314     * @return Returns the decoded property value.
315     * @throws PropertyException
316     *             If the property value string is invalid.
317     */
318    public abstract T decodeValue(String value);
319
320    /**
321     * Encode the provided property value into its string representation.
322     * <p>
323     * This default implementation simply returns invokes the
324     * {@link Object#toString()} method on the provided value.
325     *
326     * @param value
327     *            The property value (must not be <code>null</code>).
328     * @return Returns the encoded property string value.
329     * @throws PropertyException
330     *             If the property value is invalid.
331     */
332    public String encodeValue(T value) {
333        Reject.ifNull(value);
334
335        return value.toString();
336    }
337
338    /**
339     * Indicates whether some other object is &quot;equal to&quot; this property
340     * definition. This method must obey the general contract of
341     * <tt>Object.equals(Object)</tt>. Additionally, this method can return
342     * <tt>true</tt> <i>only</i> if the specified Object is also a property
343     * definition and it has the same name, as returned by {@link #getName()},
344     * and also is deemed to be &quot;compatible&quot; with this property
345     * definition. Compatibility means that the two property definitions share
346     * the same underlying value type and provide similar comparator
347     * implementations.
348     *
349     * @param o
350     *            The reference object with which to compare.
351     * @return Returns <code>true</code> only if the specified object is also a
352     *         property definition and it has the same name and is compatible
353     *         with this property definition.
354     * @see java.lang.Object#equals(java.lang.Object)
355     * @see java.lang.Object#hashCode()
356     */
357    @Override
358    public final boolean equals(Object o) {
359        if (this == o) {
360            return true;
361        } else if (o instanceof PropertyDefinition) {
362            PropertyDefinition<?> other = (PropertyDefinition<?>) o;
363            return propertyName.equals(other.propertyName)
364                    && theClass.equals(other.theClass);
365        } else {
366            return false;
367        }
368    }
369
370    /**
371     * Get the administrator action associated with this property definition.
372     * The administrator action describes any action which the administrator
373     * must perform in order for changes to this property to take effect.
374     *
375     * @return Returns the administrator action associated with this property
376     *         definition.
377     */
378    public final AdministratorAction getAdministratorAction() {
379        return adminAction;
380    }
381
382    /**
383     * Get the default behavior provider associated with this property
384     * definition.
385     *
386     * @return Returns the default behavior provider associated with this
387     *         property definition.
388     */
389    public final DefaultBehaviorProvider<T> getDefaultBehaviorProvider() {
390        return defaultBehavior;
391    }
392
393    /**
394     * Gets the optional description of this property definition in the default
395     * locale.
396     *
397     * @return Returns the description of this property definition in the
398     *         default locale, or <code>null</code> if there is no description.
399     */
400    public final LocalizableMessage getDescription() {
401        return getDescription(Locale.getDefault());
402    }
403
404    /**
405     * Gets the optional description of this property definition in the
406     * specified locale.
407     *
408     * @param locale
409     *            The locale.
410     * @return Returns the description of this property definition in the
411     *         specified locale, or <code>null</code> if there is no
412     *         description.
413     */
414    public final LocalizableMessage getDescription(Locale locale) {
415        ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance();
416        String property = "property." + propertyName + ".description";
417        try {
418            return resource.getMessage(definition, property, locale);
419        } catch (MissingResourceException e) {
420            return null;
421        }
422    }
423
424    /**
425     * Gets the managed object definition associated with this property
426     * definition.
427     *
428     * @return Returns the managed object definition associated with this
429     *         property definition.
430     */
431    public final AbstractManagedObjectDefinition<?, ?> getManagedObjectDefinition() {
432        return definition;
433    }
434
435    /**
436     * Get the name of the property.
437     *
438     * @return Returns the name of the property.
439     */
440    public final String getName() {
441        return propertyName;
442    }
443
444    /**
445     * Gets the synopsis of this property definition in the default locale.
446     *
447     * @return Returns the synopsis of this property definition in the default
448     *         locale.
449     */
450    public final LocalizableMessage getSynopsis() {
451        return getSynopsis(Locale.getDefault());
452    }
453
454    /**
455     * Gets the synopsis of this property definition in the specified locale.
456     *
457     * @param locale
458     *            The locale.
459     * @return Returns the synopsis of this property definition in the specified
460     *         locale.
461     */
462    public final LocalizableMessage getSynopsis(Locale locale) {
463        ManagedObjectDefinitionI18NResource resource = ManagedObjectDefinitionI18NResource.getInstance();
464        String property = "property." + propertyName + ".synopsis";
465        return resource.getMessage(definition, property, locale);
466    }
467
468    /**
469     * Returns a hash code value for this property definition. The hash code
470     * should be derived from the property name and the type of values handled
471     * by this property definition.
472     *
473     * @return Returns the hash code value for this property definition.
474     */
475    @Override
476    public final int hashCode() {
477        int rc = 17 + propertyName.hashCode();
478        return 37 * rc + theClass.hashCode();
479    }
480
481    /**
482     * Check if the specified option is set for this property definition.
483     *
484     * @param option
485     *            The option to test.
486     * @return Returns <code>true</code> if the option is set, or
487     *         <code>false</code> otherwise.
488     */
489    public final boolean hasOption(PropertyOption option) {
490        return options.contains(option);
491    }
492
493    /**
494     * Get a normalized string representation of a property value. This can then
495     * be used for comparisons and for generating hash-codes.
496     * <p>
497     * This method may throw an exception if the provided value is invalid.
498     * However, applications should not assume that implementations of this
499     * method will always validate a value. This task is the responsibility of
500     * {@link #validateValue(Object)}.
501     * <p>
502     * This default implementation simply returns the string representation of
503     * the provided value. Sub-classes might want to override this method if
504     * this behavior is insufficient (for example, a string property definition
505     * might strip white-space and convert characters to lower-case).
506     *
507     * @param value
508     *            The property value to be normalized.
509     * @return Returns the normalized property value.
510     * @throws PropertyException
511     *             If the property value is invalid.
512     */
513    public String normalizeValue(T value) {
514        Reject.ifNull(value);
515
516        return encodeValue(value);
517    }
518
519    /**
520     * Returns a string representation of this property definition.
521     *
522     * @return Returns a string representation of this property definition.
523     * @see Object#toString()
524     */
525    @Override
526    public final String toString() {
527        StringBuilder builder = new StringBuilder();
528        toString(builder);
529        return builder.toString();
530    }
531
532    /**
533     * Append a string representation of the property definition to the provided
534     * string builder.
535     * <p>
536     * This simple implementation just outputs the propertyName of the property
537     * definition. Sub-classes should override this method to provide more
538     * complete string representations.
539     *
540     * @param builder
541     *            The string builder where the string representation should be
542     *            appended.
543     */
544    public void toString(StringBuilder builder) {
545        builder.append(propertyName);
546    }
547
548    /**
549     * Determine if the provided property value is valid according to this
550     * property definition.
551     *
552     * @param value
553     *            The property value (must not be <code>null</code>).
554     * @throws PropertyException
555     *             If the property value is invalid.
556     */
557    public abstract void validateValue(T value);
558
559    /**
560     * Performs any run-time initialization required by this property
561     * definition. This may include resolving managed object paths and property
562     * names.
563     *
564     * @throws Exception
565     *             If this property definition could not be initialized.
566     */
567    protected void initialize() throws Exception {
568        // No implementation required.
569    }
570}