mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Matthew Swift
09.28.2012 e399742925d1a8a1ae3dae4a86bf25d3d02e8f9c
Fix OPENDJ-355: Add fluent API for decoding attributes

* finish API
* add support for parsing generalized time values
* add unit tests.
2 files added
1 files renamed
6 files modified
1750 ■■■■ changed files
opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/Functions.java 435 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeParser.java 194 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/GeneralizedTime.java 594 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeEqualityMatchingRuleImpl.java 10 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeOrderingMatchingRuleImpl.java 10 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeSyntaxImpl.java 11 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties 4 ●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AttributeParserTestCase.java 360 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/GeneralizedTimeTest.java 132 ●●●●● patch | view | raw | blame | history
opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/Functions.java
@@ -27,13 +27,16 @@
package com.forgerock.opendj.util;
import java.util.Calendar;
import static org.forgerock.opendj.ldap.CoreMessages.FUNCTIONS_TO_INTEGER_FAIL;
import static org.forgerock.opendj.ldap.CoreMessages.FUNCTIONS_TO_LONG_FAIL;
import static org.forgerock.opendj.ldap.CoreMessages.WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.GeneralizedTime;
import org.forgerock.opendj.ldap.schema.Schema;
/**
@@ -60,28 +63,45 @@
    }
    private static final Function<ByteString, AttributeDescription, Schema> BYTESTRING_TO_ATTRIBUTE_DESCRIPTION =
            new Function<ByteString, AttributeDescription, Schema>() {
                public AttributeDescription apply(final ByteString value, final Schema p) {
                    // FIXME: what should we do if parsing fails?
                    return AttributeDescription.valueOf(value.toString(), p);
                }
            };
    private static final Function<ByteString, String, Void> BYTESTRING_TO_BASE64 =
    private static final Function<ByteString, String, Void> BYTESTRING_TO_STRING =
            new Function<ByteString, String, Void>() {
                public String apply(final ByteString value, final Void p) {
                    return Base64.encode(value);
                    return value.toString();
                }
            };
    private static final Function<ByteString, Boolean, Void> BYTESTRING_TO_BOOLEAN =
            new Function<ByteString, Boolean, Void>() {
    private static final Function<Object, Object, Void> IDENTITY =
            new Function<Object, Object, Void>() {
                public Object apply(final Object value, final Void p) {
                    return value;
                }
            };
                public Boolean apply(final ByteString value, final Void p) {
                    final String valueString = StaticUtils.toLowerCase(value.toString());
    private static final Function<String, String, Void> NORMALIZE_STRING =
            new Function<String, String, Void>() {
                public String apply(final String value, final Void p) {
                    return StaticUtils.toLowerCase(value).trim();
                }
            };
    private static final Function<Object, ByteString, Void> OBJECT_TO_BYTESTRING =
            new Function<Object, ByteString, Void>() {
                public ByteString apply(final Object value, final Void p) {
                    return ByteString.valueOf(value);
                }
            };
    private static final Function<String, AttributeDescription, Schema> STRING_TO_ATTRIBUTE_DESCRIPTION =
            new Function<String, AttributeDescription, Schema>() {
                public AttributeDescription apply(final String value, final Schema p) {
                    return AttributeDescription.valueOf(value, p);
                }
            };
    private static final Function<String, Boolean, Void> STRING_TO_BOOLEAN =
            new Function<String, Boolean, Void>() {
                public Boolean apply(final String value, final Void p) {
                    final String valueString = StaticUtils.toLowerCase(value);
                    if (valueString.equals("true") || valueString.equals("yes")
                            || valueString.equals("on") || valueString.equals("1")) {
@@ -90,86 +110,159 @@
                            || valueString.equals("off") || valueString.equals("0")) {
                        return Boolean.FALSE;
                    } else {
                        throw new NumberFormatException("Invalid boolean value \"" + valueString
                                + "\"");
                        final LocalizableMessage message =
                                WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN.get(valueString);
                        throw new LocalizedIllegalArgumentException(message);
                    }
                }
            };
    private static final Function<ByteString, Calendar, Void> BYTESTRING_TO_CALENDAR =
            new Function<ByteString, Calendar, Void>() {
    private static final Function<String, DN, Schema> STRING_TO_DN =
            new Function<String, DN, Schema>() {
                public DN apply(final String value, final Schema p) {
                    return DN.valueOf(value, p);
                }
            };
                public Calendar apply(final ByteString value, final Void p) {
    private static final Function<String, GeneralizedTime, Void> STRING_TO_GENERALIZED_TIME =
            new Function<String, GeneralizedTime, Void>() {
                public GeneralizedTime apply(final String value, final Void p) {
                    return GeneralizedTime.valueOf(value);
                }
            };
    private static final Function<String, Integer, Void> STRING_TO_INTEGER =
            new Function<String, Integer, Void>() {
                public Integer apply(final String value, final Void p) {
                    try {
                        return GeneralizedTime.decode(value);
                    } catch (DecodeException e) {
                        throw new LocalizedIllegalArgumentException(e.getMessageObject(), e);
                        return Integer.valueOf(value);
                    } catch (final NumberFormatException e) {
                        final LocalizableMessage message = FUNCTIONS_TO_INTEGER_FAIL.get(value);
                        throw new LocalizedIllegalArgumentException(message);
                    }
                }
            };
    private static final Function<ByteString, DN, Schema> BYTESTRING_TO_DN =
            new Function<ByteString, DN, Schema>() {
                public DN apply(final ByteString value, final Schema p) {
                    // FIXME: what should we do if parsing fails?
                    // FIXME: we should have a ByteString valueOf
                    // implementation.
                    return DN.valueOf(value.toString(), p);
    private static final Function<String, Long, Void> STRING_TO_LONG =
            new Function<String, Long, Void>() {
                public Long apply(final String value, final Void p) {
                    try {
                        return Long.valueOf(value);
                    } catch (final NumberFormatException e) {
                        final LocalizableMessage message = FUNCTIONS_TO_LONG_FAIL.get(value);
                        throw new LocalizedIllegalArgumentException(message);
                    }
                }
            };
    private static final Function<ByteString, Integer, Void> BYTESTRING_TO_INTEGER =
            new Function<ByteString, Integer, Void>() {
    private static final Function<ByteString, AttributeDescription, Schema> BYTESTRING_TO_ATTRIBUTE_DESCRIPTION =
            composeSecondP(valueToString(), STRING_TO_ATTRIBUTE_DESCRIPTION);
                public Integer apply(final ByteString value, final Void p) {
                    // We do not use ByteString.toInt() as we are string based.
                    return Integer.valueOf(value.toString());
                }
    private static final Function<ByteString, Boolean, Void> BYTESTRING_TO_BOOLEAN = compose(
            valueToString(), STRING_TO_BOOLEAN);
    private static final Function<ByteString, DN, Schema> BYTESTRING_TO_DN = composeSecondP(
            valueToString(), STRING_TO_DN);
    private static final Function<ByteString, GeneralizedTime, Void> BYTESTRING_TO_GENERALIZED_TIME =
            compose(valueToString(), STRING_TO_GENERALIZED_TIME);
    private static final Function<ByteString, Integer, Void> BYTESTRING_TO_INTEGER = compose(
            valueToString(), STRING_TO_INTEGER);
    private static final Function<ByteString, Long, Void> BYTESTRING_TO_LONG = compose(
            valueToString(), STRING_TO_LONG);
    /**
     * Returns the composition of two functions. The result of the first
     * function will be passed to the second.
     *
     * @param <M>
     *            The type of input values transformed by this function.
     * @param <N>
     *            The type of output values returned by this function.
     * @param <X>
     *            The type of intermediate values passed between the two
     *            functions.
     * @param first
     *            The first function which will consume the input.
     * @param second
     *            The second function which will produce the result.
     * @return The composition.
     */
    public static <M, X, N> Function<M, N, Void> compose(final Function<M, X, Void> first,
            final Function<X, N, Void> second) {
        return new Function<M, N, Void>() {
            public N apply(final M value, final Void p) {
                final X tmp = first.apply(value, p);
                return second.apply(tmp, p);
            };
        };
    }
    private static final Function<ByteString, Long, Void> BYTESTRING_TO_LONG =
            new Function<ByteString, Long, Void>() {
                public Long apply(final ByteString value, final Void p) {
                    // We do not use ByteString.toLong() as we are string based.
                    return Long.valueOf(value.toString());
                }
    /**
     * Returns the composition of two functions. The result of the first
     * function will be passed to the second. The first function will be passed
     * an additional parameter.
     *
     * @param <M>
     *            The type of input values transformed by this function.
     * @param <N>
     *            The type of output values returned by this function.
     * @param <X>
     *            The type of intermediate values passed between the two
     *            functions.
     * @param <P>
     *            The type of the additional parameter to the first function's
     *            {@code apply} method. Use {@link java.lang.Void} for functions
     *            that do not need an additional parameter.
     * @param first
     *            The first function which will consume the input.
     * @param second
     *            The second function which will produce the result.
     * @return The composition.
     */
    public static <M, X, N, P> Function<M, N, P> composeFirstP(final Function<M, X, P> first,
            final Function<X, N, Void> second) {
        return new Function<M, N, P>() {
            public N apply(final M value, final P p) {
                final X tmp = first.apply(value, p);
                return second.apply(tmp, null);
            };
        };
    }
    private static final Function<ByteString, String, Void> BYTESTRING_TO_STRING =
            new Function<ByteString, String, Void>() {
                public String apply(final ByteString value, final Void p) {
                    return value.toString();
                }
    /**
     * Returns the composition of two functions. The result of the first
     * function will be passed to the second. The second function will be passed
     * an additional parameter.
     *
     * @param <M>
     *            The type of input values transformed by this function.
     * @param <N>
     *            The type of output values returned by this function.
     * @param <X>
     *            The type of intermediate values passed between the two
     *            functions.
     * @param <P>
     *            The type of the additional parameter to the second function's
     *            {@code apply} method. Use {@link java.lang.Void} for functions
     *            that do not need an additional parameter.
     * @param first
     *            The first function which will consume the input.
     * @param second
     *            The second function which will produce the result.
     * @return The composition.
     */
    public static <M, X, N, P> Function<M, N, P> composeSecondP(final Function<M, X, Void> first,
            final Function<X, N, P> second) {
        return new Function<M, N, P>() {
            public N apply(final M value, final P p) {
                final X tmp = first.apply(value, null);
                return second.apply(tmp, p);
            };
    private static final Function<Object, ByteString, Void> OBJECT_TO_BYTESTRING =
            new Function<Object, ByteString, Void>() {
                public ByteString apply(final Object value, final Void p) {
                    return ByteString.valueOf(value);
                }
            };
    private static final Function<String, String, Void> NORMALIZE_STRING =
            new Function<String, String, Void>() {
                public String apply(final String value, final Void p) {
                    return StaticUtils.toLowerCase(value).trim();
                }
            };
    private static final Function<Object, Object, Void> IDENTITY =
            new Function<Object, Object, Void>() {
                public Object apply(Object value, Void p) {
                    return value;
                }
            };
        };
    }
    /**
     * Returns a function which which always invokes {@code function} with
@@ -232,28 +325,115 @@
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} as an {@code AttributeDescription} using the default
     * schema. Invalid values will result in a
     * Returns a function which parses {@code AttributeDescription}s using the
     * default schema. Invalid values will result in a
     * {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses the string representation of a
     *         {@code ByteString} as an {@code AttributeDescription}.
     * @return A function which parses {@code AttributeDescription}s.
     */
    public static Function<String, AttributeDescription, Void> stringToAttributeDescription() {
        return fixedFunction(STRING_TO_ATTRIBUTE_DESCRIPTION, Schema.getDefaultSchema());
    }
    /**
     * Returns a function which parses {@code AttributeDescription}s using the
     * provided schema. Invalid values will result in a
     * {@code LocalizedIllegalArgumentException}.
     *
     * @param schema
     *            The schema to use for decoding attribute descriptions.
     * @return A function which parses {@code AttributeDescription}s.
     */
    public static Function<String, AttributeDescription, Void> stringToAttributeDescription(
            final Schema schema) {
        return fixedFunction(STRING_TO_ATTRIBUTE_DESCRIPTION, schema);
    }
    /**
     * Returns a function which parses {@code Boolean} values. The function will
     * accept the values {@code 0}, {@code false}, {@code no}, {@code off},
     * {@code 1}, {@code true}, {@code yes}, {@code on}. All other values will
     * result in a {@code NumberFormatException}.
     *
     * @return A function which parses {@code Boolean} values.
     */
    public static Function<String, Boolean, Void> stringToBoolean() {
        return STRING_TO_BOOLEAN;
    }
    /**
     * Returns a function which parses {@code DN}s using the default schema.
     * Invalid values will result in a {@code LocalizedIllegalArgumentException}
     * .
     *
     * @return A function which parses {@code DN}s.
     */
    public static Function<String, DN, Void> stringToDN() {
        return fixedFunction(STRING_TO_DN, Schema.getDefaultSchema());
    }
    /**
     * Returns a function which parses {@code DN}s using the provided schema.
     * Invalid values will result in a {@code LocalizedIllegalArgumentException}
     * .
     *
     * @param schema
     *            The schema to use for decoding DNs.
     * @return A function which parses {@code DN}s.
     */
    public static Function<String, DN, Void> stringToDN(final Schema schema) {
        return fixedFunction(STRING_TO_DN, schema);
    }
    /**
     * Returns a function which parses generalized time strings. Invalid values
     * will result in a {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses generalized time strings.
     */
    public static Function<String, GeneralizedTime, Void> stringToGeneralizedTime() {
        return STRING_TO_GENERALIZED_TIME;
    }
    /**
     * Returns a function which parses {@code Integer} string values. Invalid
     * values will result in a {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses {@code Integer} string values.
     */
    public static Function<String, Integer, Void> stringToInteger() {
        return STRING_TO_INTEGER;
    }
    /**
     * Returns a function which parses {@code Long} string values. Invalid
     * values will result in a {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses {@code Long} string values.
     */
    public static Function<String, Long, Void> stringToLong() {
        return STRING_TO_LONG;
    }
    /**
     * Returns a function which parses {@code AttributeDescription}s using the
     * default schema. Invalid values will result in a
     * {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses {@code AttributeDescription}s.
     */
    public static Function<ByteString, AttributeDescription, Void> valueToAttributeDescription() {
        return fixedFunction(BYTESTRING_TO_ATTRIBUTE_DESCRIPTION, Schema.getDefaultSchema());
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} as an {@code AttributeDescription} using the provided
     * schema. Invalid values will result in a
     * Returns a function which parses {@code AttributeDescription}s using the
     * provided schema. Invalid values will result in a
     * {@code LocalizedIllegalArgumentException}.
     *
     * @param schema
     *            The schema to use for decoding attribute descriptions.
     * @return A function which parses the string representation of a
     *         {@code ByteString} as an {@code AttributeDescription}.
     * @return A function which parses {@code AttributeDescription}s.
     */
    public static Function<ByteString, AttributeDescription, Void> valueToAttributeDescription(
            final Schema schema) {
@@ -261,85 +441,66 @@
    }
    /**
     * Returns a function which encodes a {@code ByteString} as {@code Base64}.
     * Returns a function which parses {@code Boolean} values. The function will
     * accept the values {@code 0}, {@code false}, {@code no}, {@code off},
     * {@code 1}, {@code true}, {@code yes}, {@code on}. All other values will
     * result in a {@code NumberFormatException}.
     *
     * @return A function which encodes a {@code ByteString} as {@code Base64}.
     */
    public static Function<ByteString, String, Void> valueToBase64() {
        return BYTESTRING_TO_BASE64;
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} to a {@code Boolean}. The function will accept the
     * values {@code 0}, {@code false}, {@code no}, {@code off}, {@code 1},
     * {@code true}, {@code yes}, {@code on}. All other values will result in a
     * {@code NumberFormatException}.
     *
     * @return A function which transforms a {@code ByteString} to a
     *         {@code Boolean}.
     * @return A function which parses {@code Boolean} values.
     */
    public static Function<ByteString, Boolean, Void> valueToBoolean() {
        return BYTESTRING_TO_BOOLEAN;
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} as a generalized time syntax. Invalid values will
     * result in a {@code LocalizedIllegalArgumentException}.
     * Returns a function which parses {@code DN}s using the default schema.
     * Invalid values will result in a {@code LocalizedIllegalArgumentException}
     * .
     *
     * @return A function which parses the string representation of a
     *         {@code ByteString} as generalized time syntax.
     */
    public static Function<ByteString, Calendar, Void> valueToCalendar() {
        return BYTESTRING_TO_CALENDAR;
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} as a {@code DN} using the default schema. Invalid
     * values will result in a {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses the string representation of a
     *         {@code ByteString} as an {@code DN}.
     * @return A function which parses {@code DN}s.
     */
    public static Function<ByteString, DN, Void> valueToDN() {
        return fixedFunction(BYTESTRING_TO_DN, Schema.getDefaultSchema());
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} as a {@code DN} using the provided schema. Invalid
     * values will result in a {@code LocalizedIllegalArgumentException}.
     * Returns a function which parses {@code DN}s using the provided schema.
     * Invalid values will result in a {@code LocalizedIllegalArgumentException}
     * .
     *
     * @param schema
     *            The schema to use for decoding DNs.
     * @return A function which parses the string representation of a
     *         {@code ByteString} as an {@code DN}.
     * @return A function which parses {@code DN}s.
     */
    public static Function<ByteString, DN, Void> valueToDN(final Schema schema) {
        return fixedFunction(BYTESTRING_TO_DN, schema);
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} as an {@code Integer}. Invalid values will result in a
     * {@code NumberFormatException}.
     * Returns a function which parses generalized time strings. Invalid values
     * will result in a {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses the string representation of a
     *         {@code ByteString} as an {@code Integer}.
     * @return A function which parses generalized time strings.
     */
    public static Function<ByteString, GeneralizedTime, Void> valueToGeneralizedTime() {
        return BYTESTRING_TO_GENERALIZED_TIME;
    }
    /**
     * Returns a function which parses {@code Integer} string values. Invalid
     * values will result in a {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses {@code Integer} string values.
     */
    public static Function<ByteString, Integer, Void> valueToInteger() {
        return BYTESTRING_TO_INTEGER;
    }
    /**
     * Returns a function which parses the string representation of a
     * {@code ByteString} as a {@code Long}. Invalid values will result in a
     * {@code NumberFormatException}.
     * Returns a function which parses {@code Long} string values. Invalid
     * values will result in a {@code LocalizedIllegalArgumentException}.
     *
     * @return A function which parses the string representation of a
     *         {@code ByteString} as a {@code Long}.
     * @return A function which parses {@code Long} string values.
     */
    public static Function<ByteString, Long, Void> valueToLong() {
        return BYTESTRING_TO_LONG;
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeParser.java
@@ -26,17 +26,17 @@
package org.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.Collections2.transformedCollection;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.NoSuchElementException;
import java.util.Set;
import org.forgerock.opendj.ldap.schema.Schema;
import com.forgerock.opendj.util.Base64;
import com.forgerock.opendj.util.Collections2;
import com.forgerock.opendj.util.Function;
import com.forgerock.opendj.util.Functions;
@@ -105,7 +105,7 @@
     * @return The first value decoded as an {@code AttributeDescription}.
     */
    public AttributeDescription asAttributeDescription() {
        return asAttributeDescription(null);
        return asAttributeDescription((AttributeDescription) null);
    }
    /**
@@ -122,25 +122,16 @@
    }
    /**
     * Returns the first value encoded as base64, or {@code null} if the
     * attribute does not contain any values.
     *
     * @return The first value encoded as base64.
     */
    public String asBase64() {
        return asBase64(null);
    }
    /**
     * Returns the first value encoded as base64, or {@code defaultValue} if the
     * Returns the first value decoded as an {@code AttributeDescription} using
     * the schema associated with this parser, or {@code defaultValue} if the
     * attribute does not contain any values.
     *
     * @param defaultValue
     *            The default value to return if the attribute is empty.
     * @return The first value encoded as base64.
     * @return The first value decoded as an {@code AttributeDescription}.
     */
    public String asBase64(final ByteString defaultValue) {
        return parseSingleValue(Functions.valueToBase64(), Base64.encode(defaultValue));
    public AttributeDescription asAttributeDescription(final String defaultValue) {
        return asAttributeDescription(AttributeDescription.valueOf(defaultValue, getSchema()));
    }
    /**
@@ -188,30 +179,6 @@
    }
    /**
     * Returns the first value decoded as a {@code Calendar} using the
     * generalized time syntax, or {@code null} if the attribute does not
     * contain any values.
     *
     * @return The first value decoded as a {@code Calendar}.
     */
    public Calendar asCalendar() {
        return asCalendar(null);
    }
    /**
     * Returns the first value decoded as an {@code Calendar} using the
     * generalized time syntax, or {@code defaultValue} if the attribute does
     * not contain any values.
     *
     * @param defaultValue
     *            The default value to return if the attribute is empty.
     * @return The first value decoded as an {@code Calendar}.
     */
    public Calendar asCalendar(final Calendar defaultValue) {
        return parseSingleValue(Functions.valueToCalendar(), defaultValue);
    }
    /**
     * Returns the first value decoded as a {@code DN} using the schema
     * associated with this parser, or {@code null} if the attribute does not
     * contain any values.
@@ -219,7 +186,7 @@
     * @return The first value decoded as a {@code DN}.
     */
    public DN asDN() {
        return asDN(null);
        return asDN((DN) null);
    }
    /**
@@ -236,6 +203,43 @@
    }
    /**
     * Returns the first value decoded as a {@code DN} using the schema
     * associated with this parser, or {@code defaultValue} if the attribute
     * does not contain any values.
     *
     * @param defaultValue
     *            The default value to return if the attribute is empty.
     * @return The first value decoded as a {@code DN}.
     */
    public DN asDN(final String defaultValue) {
        return asDN(DN.valueOf(defaultValue, getSchema()));
    }
    /**
     * Returns the first value decoded as a {@code GeneralizedTime} using the
     * generalized time syntax, or {@code null} if the attribute does not
     * contain any values.
     *
     * @return The first value decoded as a {@code GeneralizedTime}.
     */
    public GeneralizedTime asGeneralizedTime() {
        return asGeneralizedTime(null);
    }
    /**
     * Returns the first value decoded as an {@code GeneralizedTime} using the
     * generalized time syntax, or {@code defaultValue} if the attribute does
     * not contain any values.
     *
     * @param defaultValue
     *            The default value to return if the attribute is empty.
     * @return The first value decoded as an {@code GeneralizedTime}.
     */
    public GeneralizedTime asGeneralizedTime(final GeneralizedTime defaultValue) {
        return parseSingleValue(Functions.valueToGeneralizedTime(), defaultValue);
    }
    /**
     * Returns the first value decoded as an {@code Integer}, or {@code null} if
     * the attribute does not contain any values.
     *
@@ -281,6 +285,17 @@
    /**
     * Returns the values decoded as a set of {@code AttributeDescription}s
     * using the schema associated with this parser, or an empty set if the
     * attribute does not contain any values.
     *
     * @return The values decoded as a set of {@code AttributeDescription}s.
     */
    public Set<AttributeDescription> asSetOfAttributeDescription() {
        return asSetOfAttributeDescription(Collections.<AttributeDescription> emptySet());
    }
    /**
     * Returns the values decoded as a set of {@code AttributeDescription}s
     * using the schema associated with this parser, or {@code defaultValues} if
     * the attribute does not contain any values.
     *
@@ -308,28 +323,17 @@
    }
    /**
     * Returns the values contained in the attribute encoded as base64, or
     * {@code defaultValues} if the attribute does not contain any values.
     * Returns the values decoded as a set of {@code AttributeDescription}s
     * using the schema associated with this parser, or {@code defaultValues} if
     * the attribute does not contain any values.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values contained in the attribute encoded as base64.
     * @return The values decoded as a set of {@code AttributeDescription}s.
     */
    public Set<String> asSetOfBase64(final Collection<ByteString> defaultValues) {
        return parseMultipleValues(Functions.valueToString(), Collections2.transformedCollection(
                defaultValues, Functions.valueToBase64(), null));
    }
    /**
     * Returns the values contained in the attribute encoded as base64, or
     * {@code defaultValues} if the attribute does not contain any values.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values contained in the attribute encoded as base64.
     */
    public Set<String> asSetOfBase64(final String... defaultValues) {
        return asSetOfString(Arrays.asList(defaultValues));
    public Set<AttributeDescription> asSetOfAttributeDescription(final String... defaultValues) {
        return asSetOfAttributeDescription(transformedCollection(Arrays.asList(defaultValues),
                Functions.stringToAttributeDescription(getSchema()), null));
    }
    /**
@@ -381,29 +385,14 @@
    }
    /**
     * Returns the values decoded as a set of {@code Calendar}s using the
     * generalized time syntax, or {@code defaultValues} if the attribute does
     * not contain any values.
     * Returns the values decoded as a set of {@code DN}s using the schema
     * associated with this parser, or an empty set if the attribute does not
     * contain any values.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values decoded as a set of {@code Calendar}s.
     * @return The values decoded as a set of {@code DN}s.
     */
    public Set<Calendar> asSetOfCalendar(final Calendar... defaultValues) {
        return asSetOfCalendar(Arrays.asList(defaultValues));
    }
    /**
     * Returns the values decoded as a set of {@code Calendar}s using the
     * generalized time syntax, or {@code defaultValues} if the attribute does
     * not contain any values.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values decoded as a set of {@code Calendar}s.
     */
    public Set<Calendar> asSetOfCalendar(final Collection<Calendar> defaultValues) {
        return parseMultipleValues(Functions.valueToCalendar(), defaultValues);
    public Set<DN> asSetOfDN() {
        return asSetOfDN(Collections.<DN> emptySet());
    }
    /**
@@ -433,6 +422,47 @@
    }
    /**
     * Returns the values decoded as a set of {@code DN}s using the schema
     * associated with this parser, or {@code defaultValues} if the attribute
     * does not contain any values.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values decoded as a set of {@code DN}s.
     */
    public Set<DN> asSetOfDN(final String... defaultValues) {
        return asSetOfDN(transformedCollection(Arrays.asList(defaultValues), Functions
                .stringToDN(getSchema()), null));
    }
    /**
     * Returns the values decoded as a set of {@code GeneralizedTime}s using the
     * generalized time syntax, or {@code defaultValues} if the attribute does
     * not contain any values.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values decoded as a set of {@code GeneralizedTime}s.
     */
    public Set<GeneralizedTime> asSetOfGeneralizedTime(
            final Collection<GeneralizedTime> defaultValues) {
        return parseMultipleValues(Functions.valueToGeneralizedTime(), defaultValues);
    }
    /**
     * Returns the values decoded as a set of {@code GeneralizedTime}s using the
     * generalized time syntax, or {@code defaultValues} if the attribute does
     * not contain any values.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values decoded as a set of {@code GeneralizedTime}s.
     */
    public Set<GeneralizedTime> asSetOfGeneralizedTime(final GeneralizedTime... defaultValues) {
        return asSetOfGeneralizedTime(Arrays.asList(defaultValues));
    }
    /**
     * Returns the values decoded as a set of {@code Integer}s, or
     * {@code defaultValues} if the attribute does not contain any values.
     *
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/GeneralizedTime.java
File was renamed from opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/GeneralizedTime.java
@@ -23,7 +23,7 @@
 *
 *      Copyright 2012 ForgeRock AS.
 */
package com.forgerock.opendj.util;
package org.forgerock.opendj.ldap;
import static org.forgerock.opendj.ldap.CoreMessages.*;
@@ -33,30 +33,88 @@
import java.util.TimeZone;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import com.forgerock.opendj.util.Validator;
/**
 * Utility class for encoding and decoding generalized time syntax values.
 * An LDAP generalized time as defined in RFC 4517. This class facilitates
 * parsing of generalized time values to and from {@link Date} and
 * {@link Calendar} classes.
 * <p>
 * The following are examples of generalized time values:
 *
 * <pre>
 * 199412161032Z
 * 199412160532-0500
 * </pre>
 *
 * @see <a href="http://tools.ietf.org/html/rfc4517#section-3.3.13">RFC 4517 -
 *      Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching
 *      Rules </a>
 */
public final class GeneralizedTime {
public final class GeneralizedTime implements Comparable<GeneralizedTime> {
    // UTC TimeZone is assumed to never change over JVM lifetime
    private static final TimeZone TIME_ZONE_UTC_OBJ = TimeZone.getTimeZone("UTC");
    /**
     * Returns the provided generalized time syntax value decoded as a
     * {@code Calendar}.
     * Returns a generalized time representing the provided {@code Calendar}.
     * <p>
     * The provided calendar will be defensively copied in order to preserve
     * immutability.
     *
     * @param value
     *            The generalized time value to be decoded.
     * @return The decoded {@code Calendar}.
     * @throws DecodeException
     *             If the provided value cannot be parsed as a valid generalized
     *             time string.
     * @param calendar
     *            The calendar to be converted to a generalized time.
     * @return A generalized time representing the provided {@code Calendar}.
     */
    public static Calendar decode(final ByteSequence value) throws DecodeException {
    public static GeneralizedTime valueOf(final Calendar calendar) {
        Validator.ensureNotNull(calendar);
        return new GeneralizedTime((Calendar) calendar.clone(), null, -1L, null);
    }
    /**
     * Returns a generalized time representing the provided {@code Date}.
     * <p>
     * The provided date will be defensively copied in order to preserve
     * immutability.
     *
     * @param date
     *            The date to be converted to a generalized time.
     * @return A generalized time representing the provided {@code Date}.
     */
    public static GeneralizedTime valueOf(final Date date) {
        Validator.ensureNotNull(date);
        return new GeneralizedTime(null, (Date) date.clone(), -1L, null);
    }
    /**
     * Returns a generalized time representing the provided time in milliseconds
     * since the epoch.
     *
     * @param timeMS
     *            The time to be converted to a generalized time.
     * @return A generalized time representing the provided time in milliseconds
     *         since the epoch.
     */
    public static GeneralizedTime valueOf(final long timeMS) {
        Validator.ensureTrue(timeMS >= 0, "timeMS must be >= 0");
        return new GeneralizedTime(null, null, timeMS, null);
    }
    /**
     * Parses the provided string as an LDAP generalized time.
     *
     * @param time
     *            The generalized time value to be parsed.
     * @return The parsed generalized time.
     * @throws LocalizedIllegalArgumentException
     *             If {@code time} cannot be parsed as a valid generalized time
     *             string.
     * @throws NullPointerException
     *             If {@code time} was {@code null}.
     */
    public static GeneralizedTime valueOf(final String time) {
        int year = 0;
        int month = 0;
        int day = 0;
@@ -66,14 +124,12 @@
        // Get the value as a string and verify that it is at least long
        // enough for "YYYYMMDDhhZ", which is the shortest allowed value.
        final String valueString = value.toString().toUpperCase();
        final String valueString = time.toUpperCase();
        final int length = valueString.length();
        if (length < 11) {
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_TOO_SHORT.get(valueString);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // The first four characters are the century and year, and they must
@@ -124,9 +180,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_YEAR.get(valueString, String
                                .valueOf(valueString.charAt(i)));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        }
@@ -178,9 +232,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(valueString,
                                valueString.substring(4, 6));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
        case '1':
@@ -202,18 +254,14 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(valueString,
                                valueString.substring(4, 6));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
        default:
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MONTH.get(valueString, valueString
                            .substring(4, 6));
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // The next two characters should be the day of the month, and they
@@ -267,9 +315,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, valueString
                                .substring(6, 8));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -320,9 +366,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, valueString
                                .substring(6, 8));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -373,9 +417,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, valueString
                                .substring(6, 8));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -394,9 +436,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, valueString
                                .substring(6, 8));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -404,9 +444,7 @@
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_DAY.get(valueString, valueString
                            .substring(6, 8));
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // The next two characters must be the hour, and they must form the
@@ -460,9 +498,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, valueString
                                .substring(8, 10));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -512,9 +548,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, valueString
                                .substring(8, 10));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -540,9 +574,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, valueString
                                .substring(8, 10));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -550,9 +582,7 @@
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_HOUR.get(valueString, valueString
                            .substring(8, 10));
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // Next, there should be either two digits comprising an integer
@@ -574,9 +604,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(m1), 10);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            minute = 10 * (m1 - '0');
@@ -625,9 +653,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE.get(valueString,
                                valueString.substring(10, 12));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -641,9 +667,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(m1), 10);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        case '+':
@@ -657,9 +681,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(m1), 10);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        case '.':
@@ -671,9 +693,7 @@
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                            .valueOf(m1), 10);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // Next, there should be either two digits comprising an integer
@@ -695,9 +715,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(s1), 12);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            second = 10 * (s1 - '0');
@@ -746,9 +764,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_MINUTE.get(valueString,
                                valueString.substring(12, 14));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -760,18 +776,14 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(s1), 12);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            if (valueString.charAt(13) != '0') {
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_SECOND.get(valueString,
                                valueString.substring(12, 14));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            second = 60;
@@ -786,9 +798,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(s1), 12);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        case '+':
@@ -802,9 +812,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(s1), 12);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        case '.':
@@ -816,9 +824,7 @@
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                            .valueOf(s1), 12);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // Next, there should be either a period or comma followed by
@@ -840,9 +846,7 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(valueString.charAt(14)), 14);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        case '+':
@@ -856,129 +860,19 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                                .valueOf(valueString.charAt(14)), 14);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        default:
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_CHAR.get(valueString, String
                            .valueOf(valueString.charAt(14)), 14);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
    }
    /**
     * Returns the generalized time syntax encoding of the provided
     * {@code Calendar}.
     *
     * @param value
     *            The calendar to be encoded.
     * @return The generalized time syntax encoding.
     */
    public static ByteString encode(final Calendar value) {
        return encode(value.getTimeInMillis());
    }
    /**
     * Returns the generalized time syntax encoding of the provided {@code Date}
     * .
     *
     * @param value
     *            The date to be encoded.
     * @return The generalized time syntax encoding.
     */
    public static ByteString encode(final Date value) {
        return encode(value.getTime());
    }
    /**
     * Returns the generalized time syntax encoding of the provided date
     * represented as milliseconds since the epoch.
     *
     * @param value
     *            The date in milli-seconds since the epoch.
     * @return The generalized time syntax encoding.
     */
    public static ByteString encode(final long value) {
        // Generalized time has the format yyyyMMddHHmmss.SSS'Z'
        // Do this in a thread-safe non-synchronized fashion.
        // (Simple)DateFormat is neither fast nor thread-safe.
        final StringBuilder sb = new StringBuilder(19);
        final GregorianCalendar calendar = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
        calendar.setLenient(false);
        calendar.setTimeInMillis(value);
        // Format the year yyyy.
        int n = calendar.get(Calendar.YEAR);
        if (n < 0) {
            throw new IllegalArgumentException("Year cannot be < 0:" + n);
        } else if (n < 10) {
            sb.append("000");
        } else if (n < 100) {
            sb.append("00");
        } else if (n < 1000) {
            sb.append("0");
        }
        sb.append(n);
        // Format the month MM.
        n = calendar.get(Calendar.MONTH) + 1;
        if (n < 10) {
            sb.append("0");
        }
        sb.append(n);
        // Format the day dd.
        n = calendar.get(Calendar.DAY_OF_MONTH);
        if (n < 10) {
            sb.append("0");
        }
        sb.append(n);
        // Format the hour HH.
        n = calendar.get(Calendar.HOUR_OF_DAY);
        if (n < 10) {
            sb.append("0");
        }
        sb.append(n);
        // Format the minute mm.
        n = calendar.get(Calendar.MINUTE);
        if (n < 10) {
            sb.append("0");
        }
        sb.append(n);
        // Format the seconds ss.
        n = calendar.get(Calendar.SECOND);
        if (n < 10) {
            sb.append("0");
        }
        sb.append(n);
        // Format the milli-seconds.
        sb.append('.');
        n = calendar.get(Calendar.MILLISECOND);
        if (n < 10) {
            sb.append("00");
        } else if (n < 100) {
            sb.append("0");
        }
        sb.append(n);
        // Format the timezone (always Z).
        sb.append('Z');
        return ByteString.valueOf(sb.toString());
    }
    /**
     * Returns a Calendar object representing the provided date / time
     * Returns a generalized time object representing the provided date / time
     * parameters.
     *
     * @param value
@@ -997,29 +891,26 @@
     *            The second.
     * @param tz
     *            The timezone.
     * @return A Calendar object representing the provided date / time
     * @return A generalized time representing the provided date / time
     *         parameters.
     * @throws DecodeException
     *             If the calendar could not be created.
     * @throws LocalizedIllegalArgumentException
     *             If the generalized time could not be created.
     */
    private static Calendar createTime(final String value, final int year, final int month,
            final int day, final int hour, final int minute, final int second, final TimeZone tz)
            throws DecodeException {
    private static GeneralizedTime createTime(final String value, final int year, final int month,
            final int day, final int hour, final int minute, final int second, final TimeZone tz) {
        try {
            final GregorianCalendar calendar = new GregorianCalendar();
            calendar.setLenient(false);
            calendar.setTimeZone(tz);
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar;
            return new GeneralizedTime(calendar, null, -1L, value);
        } catch (final Exception e) {
            // This should only happen if the provided date wasn't legal
            // (e.g., September 31).
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(value, String.valueOf(e));
            final DecodeException de = DecodeException.error(message, e);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", de);
            throw de;
            throw new LocalizedIllegalArgumentException(message, e);
        }
    }
@@ -1053,13 +944,13 @@
     *            should be 1000.
     * @return The timestamp created from the provided generalized time value
     *         including the fractional element.
     * @throws DecodeException
     * @throws LocalizedIllegalArgumentException
     *             If the provided value cannot be parsed as a valid generalized
     *             time string.
     */
    private static Calendar finishDecodingFraction(final String value, final int startPos,
    private static GeneralizedTime finishDecodingFraction(final String value, final int startPos,
            final int year, final int month, final int day, final int hour, final int minute,
            final int second, final int multiplier) throws DecodeException {
            final int second, final int multiplier) {
        final int length = value.length();
        final StringBuilder fractionBuffer = new StringBuilder((2 + length) - startPos);
        fractionBuffer.append("0.");
@@ -1089,10 +980,7 @@
                    final LocalizableMessage message =
                            WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR.get(value,
                                    String.valueOf(c));
                    final DecodeException e = DecodeException.error(message);
                    StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
                            "finishDecodingFraction", e);
                    throw e;
                    throw new LocalizedIllegalArgumentException(message);
                }
                timeZone = TIME_ZONE_UTC_OBJ;
@@ -1107,27 +995,20 @@
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_FRACTION_CHAR.get(value, String
                                .valueOf(c));
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG
                        .throwing("GeneralizedTimeSyntax", "finishDecodingFraction", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        }
        if (fractionBuffer.length() == 2) {
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_EMPTY_FRACTION.get(value);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "finishDecodingFraction", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        if (timeZone == null) {
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_NO_TIME_ZONE_INFO.get(value);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "finishDecodingFraction", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        final Double fractionValue = Double.parseDouble(fractionBuffer.toString());
@@ -1139,16 +1020,13 @@
            calendar.setTimeZone(timeZone);
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, additionalMilliseconds);
            return calendar;
            return new GeneralizedTime(calendar, null, -1L, value);
        } catch (final Exception e) {
            // This should only happen if the provided date wasn't legal
            // (e.g., September 31).
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(value, String.valueOf(e));
            final DecodeException de = DecodeException.error(message, e);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "valueIsAcceptable", de);
            throw de;
            throw new LocalizedIllegalArgumentException(message, e);
        }
    }
@@ -1165,15 +1043,12 @@
     * @throws DecodeException
     *             If the provided value does not contain a valid offset.
     */
    private static TimeZone getTimeZoneForOffset(final String value, final int startPos)
            throws DecodeException {
    private static TimeZone getTimeZoneForOffset(final String value, final int startPos) {
        final String offSetStr = value.substring(startPos);
        if ((offSetStr.length() != 3) && (offSetStr.length() != 5)) {
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value, offSetStr);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "getTimeZoneForOffset", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // The first character must be either a plus or minus.
@@ -1186,9 +1061,7 @@
        default:
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value, offSetStr);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "getTimeZoneForOffset", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // The first two characters must be an integer between 00 and 23.
@@ -1212,9 +1085,7 @@
            default:
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value, offSetStr);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "getTimeZoneForOffset", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
@@ -1230,18 +1101,14 @@
            default:
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value, offSetStr);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "getTimeZoneForOffset", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
            break;
        default:
            final LocalizableMessage message =
                    WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value, offSetStr);
            final DecodeException e = DecodeException.error(message);
            StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "getTimeZoneForOffset", e);
            throw e;
            throw new LocalizedIllegalArgumentException(message);
        }
        // If there are two more characters, then they must be an integer
@@ -1271,19 +1138,14 @@
                default:
                    final LocalizableMessage message =
                            WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value, offSetStr);
                    final DecodeException e = DecodeException.error(message);
                    StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "getTimeZoneForOffset",
                            e);
                    throw e;
                    throw new LocalizedIllegalArgumentException(message);
                }
                break;
            default:
                final LocalizableMessage message =
                        WARN_ATTR_SYNTAX_GENERALIZED_TIME_INVALID_OFFSET.get(value, offSetStr);
                final DecodeException e = DecodeException.error(message);
                StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "getTimeZoneForOffset", e);
                throw e;
                throw new LocalizedIllegalArgumentException(message);
            }
        }
@@ -1292,8 +1154,204 @@
        return TimeZone.getTimeZone("GMT" + offSetStr);
    }
    private GeneralizedTime() {
        // Prevent instantiation.
    // Lazily constructed internal representations.
    private volatile Calendar calendar;
    private volatile Date date;
    private volatile String stringValue;
    private volatile long timeMS;
    private GeneralizedTime(final Calendar calendar, final Date date, final long time,
            final String stringValue) {
        this.calendar = calendar;
        this.date = date;
        this.timeMS = time;
        this.stringValue = stringValue;
    }
    /**
     * {@inheritDoc}
     */
    public int compareTo(final GeneralizedTime o) {
        final Long timeMS1 = getTimeInMillis();
        final Long timeMS2 = o.getTimeInMillis();
        return timeMS1.compareTo(timeMS2);
    }
    /**
     * {@inheritDoc}
     */
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        } else if (obj instanceof GeneralizedTime) {
            return getTimeInMillis() == ((GeneralizedTime) obj).getTimeInMillis();
        } else {
            return false;
        }
    }
    /**
     * Returns the value of this generalized time in milliseconds since the
     * epoch.
     *
     * @return The value of this generalized time in milliseconds since the
     *         epoch.
     */
    public long getTimeInMillis() {
        long tmpTimeMS = timeMS;
        if (tmpTimeMS == -1) {
            if (date != null) {
                tmpTimeMS = date.getTime();
            } else {
                tmpTimeMS = calendar.getTimeInMillis();
            }
            timeMS = tmpTimeMS;
        }
        return tmpTimeMS;
    }
    /**
     * {@inheritDoc}
     */
    public int hashCode() {
        return ((Long) getTimeInMillis()).hashCode();
    }
    /**
     * Returns a {@code Calendar} representation of this generalized time.
     * <p>
     * Subsequent modifications to the returned calendar will not alter the
     * internal state of this generalized time.
     *
     * @return A {@code Calendar} representation of this generalized time.
     */
    public Calendar toCalendar() {
        Calendar tmpCalendar = calendar;
        if (tmpCalendar == null) {
            tmpCalendar = new GregorianCalendar(TIME_ZONE_UTC_OBJ);
            tmpCalendar.setLenient(false);
            tmpCalendar.setTimeInMillis(getTimeInMillis());
            calendar = tmpCalendar;
        }
        return (Calendar) tmpCalendar.clone();
    }
    /**
     * Returns a {@code Date} representation of this generalized time.
     * <p>
     * Subsequent modifications to the returned date will not alter the internal
     * state of this generalized time.
     *
     * @return A {@code Date} representation of this generalized time.
     */
    public Date toDate() {
        Date tmpDate = date;
        if (tmpDate == null) {
            tmpDate = new Date(getTimeInMillis());
            date = tmpDate;
        }
        return (Date) tmpDate.clone();
    }
    /**
     * {@inheritDoc}
     */
    public String toString() {
        String tmpString = stringValue;
        if (tmpString == null) {
            // Do this in a thread-safe non-synchronized fashion.
            // (Simple)DateFormat is neither fast nor thread-safe.
            final StringBuilder sb = new StringBuilder(19);
            final Calendar tmpCalendar = toCalendar();
            // Format the year yyyy.
            int n = tmpCalendar.get(Calendar.YEAR);
            if (n < 0) {
                throw new IllegalArgumentException("Year cannot be < 0:" + n);
            } else if (n < 10) {
                sb.append("000");
            } else if (n < 100) {
                sb.append("00");
            } else if (n < 1000) {
                sb.append("0");
            }
            sb.append(n);
            // Format the month MM.
            n = tmpCalendar.get(Calendar.MONTH) + 1;
            if (n < 10) {
                sb.append("0");
            }
            sb.append(n);
            // Format the day dd.
            n = tmpCalendar.get(Calendar.DAY_OF_MONTH);
            if (n < 10) {
                sb.append("0");
            }
            sb.append(n);
            // Format the hour HH.
            n = tmpCalendar.get(Calendar.HOUR_OF_DAY);
            if (n < 10) {
                sb.append("0");
            }
            sb.append(n);
            // Format the minute mm.
            n = tmpCalendar.get(Calendar.MINUTE);
            if (n < 10) {
                sb.append("0");
            }
            sb.append(n);
            // Format the seconds ss.
            n = tmpCalendar.get(Calendar.SECOND);
            if (n < 10) {
                sb.append("0");
            }
            sb.append(n);
            // Format the milli-seconds.
            n = tmpCalendar.get(Calendar.MILLISECOND);
            if (n != 0) {
                sb.append('.');
                if (n < 10) {
                    sb.append("00");
                } else if (n < 100) {
                    sb.append("0");
                }
                sb.append(n);
            }
            // Format the timezone.
            n = tmpCalendar.get(Calendar.ZONE_OFFSET); /* ms */
            if (n == 0) {
                sb.append('Z');
            } else {
                if (n < 0) {
                    sb.append('-');
                    n = -n;
                } else {
                    sb.append('+');
                }
                n = n / 60000; // Minutes.
                final int h = n / 60;
                if (h < 10) {
                    sb.append("0");
                }
                sb.append(h);
                final int m = n % 60;
                if (m < 10) {
                    sb.append("0");
                }
                sb.append(m);
            }
            tmpString = sb.toString();
            stringValue = tmpString;
        }
        return stringValue;
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
@@ -26,11 +26,11 @@
 */
package org.forgerock.opendj.ldap.schema;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import com.forgerock.opendj.util.GeneralizedTime;
import org.forgerock.opendj.ldap.GeneralizedTime;
/**
 * This class defines the generalizedTimeMatch matching rule defined in X.520
@@ -39,6 +39,10 @@
final class GeneralizedTimeEqualityMatchingRuleImpl extends AbstractMatchingRuleImpl {
    public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value)
            throws DecodeException {
        return ByteString.valueOf(GeneralizedTime.decode(value).getTimeInMillis());
        try {
            return ByteString.valueOf(GeneralizedTime.valueOf(value.toString()).getTimeInMillis());
        } catch (LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
        }
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
@@ -26,11 +26,11 @@
 */
package org.forgerock.opendj.ldap.schema;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import com.forgerock.opendj.util.GeneralizedTime;
import org.forgerock.opendj.ldap.GeneralizedTime;
/**
 * This class defines the generalizedTimeOrderingMatch matching rule defined in
@@ -39,6 +39,10 @@
final class GeneralizedTimeOrderingMatchingRuleImpl extends AbstractOrderingMatchingRuleImpl {
    public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value)
            throws DecodeException {
        return ByteString.valueOf(GeneralizedTime.decode(value).getTimeInMillis());
        try {
            return ByteString.valueOf(GeneralizedTime.valueOf(value.toString()).getTimeInMillis());
        } catch (LocalizedIllegalArgumentException e) {
            throw DecodeException.error(e.getMessageObject());
        }
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeSyntaxImpl.java
@@ -33,10 +33,9 @@
import static org.forgerock.opendj.ldap.schema.SchemaConstants.SYNTAX_GENERALIZED_TIME_NAME;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.DecodeException;
import com.forgerock.opendj.util.GeneralizedTime;
import org.forgerock.opendj.ldap.GeneralizedTime;
/**
 * This class implements the fax attribute syntax. This should be restricted to
@@ -85,10 +84,10 @@
    public boolean valueIsAcceptable(final Schema schema, final ByteSequence value,
            final LocalizableMessageBuilder invalidReason) {
        try {
            GeneralizedTime.decode(value);
            GeneralizedTime.valueOf(value.toString());
            return true;
        } catch (final DecodeException de) {
            invalidReason.append(de.getMessageObject());
        } catch (final LocalizedIllegalArgumentException e) {
            invalidReason.append(e.getMessageObject());
            return false;
        }
    }
opendj3/opendj-ldap-sdk/src/main/resources/org/forgerock/opendj/ldap/core.properties
@@ -1399,3 +1399,7 @@
 entry does not exist 
REJECTED_CHANGE_FAIL_MODIFYDN_DUPE=The entry "%s" could not be renamed because \
 there is already an entry with the same name
FUNCTIONS_TO_INTEGER_FAIL=The provided value "%s" could not be parsed as an \
 integer
FUNCTIONS_TO_LONG_FAIL=The provided value "%s" could not be parsed as an \
 long
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/AttributeParserTestCase.java
New file
@@ -0,0 +1,360 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2012 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import static org.fest.assertions.Assertions.assertThat;
import java.util.NoSuchElementException;
import org.fest.util.Collections;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.testng.annotations.Test;
/**
 * Test {@code AttributeParser}.
 */
@SuppressWarnings("javadoc")
public final class AttributeParserTestCase extends SdkTestCase {
    @Test
    public void testAsBooleanTrue() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "enabled: true");
        assertThat(e.parseAttribute("enabled").asBoolean()).isTrue();
    }
    @Test
    public void testAsBooleanFalse() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "enabled: false");
        assertThat(e.parseAttribute("enabled").asBoolean()).isFalse();
    }
    @Test
    public void testAsBooleanTrueDefaultFalse() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "enabled: true");
        assertThat(e.parseAttribute("enabled").asBoolean(false)).isTrue();
    }
    @Test
    public void testAsBooleanFalseDefaultTrue() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "enabled: false");
        assertThat(e.parseAttribute("enabled").asBoolean(true)).isFalse();
    }
    @Test
    public void testAsBooleanMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("enabled").asBoolean()).isNull();
    }
    @Test
    public void testAsBooleanMissingDefaultTrue() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("enabled").asBoolean(true)).isTrue();
    }
    @Test
    public void testAsBooleanMissingDefaultFalse() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("enabled").asBoolean(false)).isFalse();
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsBooleanMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        e.parseAttribute("enabled").requireValue().asBoolean();
    }
    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testAsBooleanInvalid() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "enabled: xxx");
        e.parseAttribute("enabled").asBoolean();
    }
    @Test
    public void testAsInteger99() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "age: 99");
        assertThat(e.parseAttribute("age").asInteger()).isEqualTo(99);
    }
    @Test
    public void testAsInteger99Default100() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "age: 99");
        assertThat(e.parseAttribute("age").asInteger(100)).isEqualTo(99);
    }
    @Test
    public void testAsIntegerMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("age").asInteger()).isNull();
    }
    @Test
    public void testAsIntegerMissingDefault100() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("age").asInteger(100)).isEqualTo(100);
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsIntegerMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        e.parseAttribute("age").requireValue().asInteger();
    }
    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testAsIntegerInvalid() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "age: xxx");
        e.parseAttribute("age").asInteger();
    }
    @Test
    public void testAsLong99() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "age: 99");
        assertThat(e.parseAttribute("age").asLong()).isEqualTo(99);
    }
    @Test
    public void testAsLong99Default100() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "age: 99");
        assertThat(e.parseAttribute("age").asLong(100)).isEqualTo(99);
    }
    @Test
    public void testAsLongMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("age").asLong()).isNull();
    }
    @Test
    public void testAsLongMissingDefault100() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("age").asLong(100)).isEqualTo(100);
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsLongMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        e.parseAttribute("age").requireValue().asLong();
    }
    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testAsLongInvalid() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "age: xxx");
        e.parseAttribute("age").asLong();
    }
    @Test
    public void testAsDN() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "manager: cn=manager");
        assertThat((Object) e.parseAttribute("manager").asDN()).isEqualTo(DN.valueOf("cn=manager"));
    }
    @Test
    public void testAsDNDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "manager: cn=manager");
        assertThat((Object) e.parseAttribute("manager").asDN("cn=boss")).isEqualTo(
                DN.valueOf("cn=manager"));
    }
    @Test
    public void testAsDNMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("manager").asDN()).isNull();
    }
    @Test
    public void testAsDNMissingDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat((Object) e.parseAttribute("manager").asDN(DN.valueOf("cn=boss"))).isEqualTo(
                DN.valueOf("cn=boss"));
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsDNMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        e.parseAttribute("manager").requireValue().asDN();
    }
    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testAsDNInvalid() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "manager: xxx");
        e.parseAttribute("manager").asDN();
    }
    @Test
    public void testAsAttributeDescription() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "type: cn");
        assertThat(e.parseAttribute("type").asAttributeDescription()).isEqualTo(
                AttributeDescription.valueOf("cn"));
    }
    @Test
    public void testAsAttributeDescriptionDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "type: cn");
        assertThat(e.parseAttribute("type").asAttributeDescription("sn")).isEqualTo(
                AttributeDescription.valueOf("cn"));
    }
    @Test
    public void testAsAttributeDescriptionMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("type").asAttributeDescription()).isNull();
    }
    @Test
    public void testAsAttributeDescriptionMissingDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(
                e.parseAttribute("type").asAttributeDescription(AttributeDescription.valueOf("sn")))
                .isEqualTo(AttributeDescription.valueOf("sn"));
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsAttributeDescriptionMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        e.parseAttribute("type").requireValue().asAttributeDescription();
    }
    @Test(expectedExceptions = { IllegalArgumentException.class })
    public void testAsAttributeDescriptionInvalid() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "type: ;x");
        e.parseAttribute("type").asAttributeDescription();
    }
    @Test
    public void testAsString() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "type: cn");
        assertThat(e.parseAttribute("type").asString()).isEqualTo(String.valueOf("cn"));
    }
    @Test
    public void testAsStringDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "type: cn");
        assertThat(e.parseAttribute("type").asString("sn")).isEqualTo(String.valueOf("cn"));
    }
    @Test
    public void testAsStringMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("type").asString()).isNull();
    }
    @Test
    public void testAsStringMissingDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("type").asString(String.valueOf("sn"))).isEqualTo(
                String.valueOf("sn"));
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsStringMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        e.parseAttribute("type").requireValue().asString();
    }
    @Test
    public void testAsByteString() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "type: cn");
        assertThat(e.parseAttribute("type").asByteString()).isEqualTo(ByteString.valueOf("cn"));
    }
    @Test
    public void testAsByteStringDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test", "type: cn");
        assertThat(e.parseAttribute("type").asByteString(ByteString.valueOf("sn"))).isEqualTo(
                ByteString.valueOf("cn"));
    }
    @Test
    public void testAsByteStringMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("type").asByteString()).isNull();
    }
    @Test
    public void testAsByteStringMissingDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        assertThat(e.parseAttribute("type").asByteString(ByteString.valueOf("sn"))).isEqualTo(
                ByteString.valueOf("sn"));
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsByteStringMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=test", "objectClass: test");
        e.parseAttribute("type").requireValue().asByteString();
    }
    // Smoke test for set of methods: use one type only since the code is common
    // and we've already tested the parsing.
    @Test
    public void testAsSetOfDN() {
        Entry e =
                new LinkedHashMapEntry("dn: cn=group", "objectClass: group", "member: cn=member1",
                        "member: cn=member2", "member: cn=member3");
        assertThat(e.parseAttribute("member").asSetOfDN()).isEqualTo(
                Collections.set(DN.valueOf("cn=member1"), DN.valueOf("cn=member2"), DN
                        .valueOf("cn=member3")));
    }
    @Test
    public void testAsSetOfDNDefault() {
        Entry e =
                new LinkedHashMapEntry("dn: cn=group", "objectClass: group", "member: cn=member1",
                        "member: cn=member2", "member: cn=member3");
        assertThat(e.parseAttribute("member").asSetOfDN("cn=dummy1", "cn=dummy2")).isEqualTo(
                Collections.set(DN.valueOf("cn=member1"), DN.valueOf("cn=member2"), DN
                        .valueOf("cn=member3")));
    }
    @Test
    public void testAsSetOfDNMissing() {
        Entry e = new LinkedHashMapEntry("dn: cn=group", "objectClass: group");
        assertThat(e.parseAttribute("member").asSetOfDN()).isEqualTo(
                java.util.Collections.emptySet());
    }
    @Test
    public void testAsSetOfDNMissingDefault() {
        Entry e = new LinkedHashMapEntry("dn: cn=group", "objectClass: group");
        assertThat(e.parseAttribute("member").asSetOfDN("cn=dummy1", "cn=dummy2")).isEqualTo(
                Collections.set(DN.valueOf("cn=dummy1"), DN.valueOf("cn=dummy2")));
    }
    @Test(expectedExceptions = { NoSuchElementException.class })
    public void testAsSetOfDNMissingRequired() {
        Entry e = new LinkedHashMapEntry("dn: cn=group", "objectClass: group");
        e.parseAttribute("member").requireValue().asSetOfDN();
    }
    @Test(expectedExceptions = { LocalizedIllegalArgumentException.class })
    public void testAsSetOfDNInvalid() {
        Entry e =
                new LinkedHashMapEntry("dn: cn=group", "objectClass: group", "member: cn=member1",
                        "member: xxxx");
        e.parseAttribute("member").asSetOfDN();
    }
}
opendj3/opendj-ldap-sdk/src/test/java/org/forgerock/opendj/ldap/GeneralizedTimeTest.java
New file
@@ -0,0 +1,132 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 */
package org.forgerock.opendj.ldap;
import java.util.Calendar;
import java.util.Date;
import org.forgerock.i18n.LocalizedIllegalArgumentException;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import static org.fest.assertions.Assertions.assertThat;
/**
 * Generalized time tests.
 */
@SuppressWarnings("javadoc")
public class GeneralizedTimeTest extends SdkTestCase {
    @DataProvider
    public Object[][] validStrings() {
        return new Object[][] { { "2006090613Z" }, { "20060906135030+01" }, { "200609061350Z" },
            { "20060906135030Z" }, { "20061116135030Z" }, { "20061126135030Z" },
            { "20061231235959Z" }, { "20060906135030+0101" }, { "20060906135030+2359" }, };
    }
    @DataProvider
    public Object[][] invalidStrings() {
        return new Object[][] { { "20060906135030+3359" }, { "20060906135030+2389" },
            { "20060906135030+2361" }, { "20060906135030+" }, { "20060906135030+0" },
            { "20060906135030+010" }, { "20061200235959Z" }, { "2006121a235959Z" },
            { "2006122a235959Z" }, { "20060031235959Z" }, { "20061331235959Z" },
            { "20062231235959Z" }, { "20061232235959Z" }, { "2006123123595aZ" },
            { "200a1231235959Z" }, { "2006j231235959Z" }, { "200612-1235959Z" },
            { "20061231#35959Z" }, { "2006" }, };
    }
    @Test(expectedExceptions = { LocalizedIllegalArgumentException.class },
            dataProvider = "invalidStrings")
    public void testValueOfInvalidString(String s) {
        GeneralizedTime.valueOf(s);
    }
    @Test(dataProvider = "validStrings")
    public void testValueOfValidString(String s) {
        assertThat(GeneralizedTime.valueOf(s).toString()).isEqualTo(s);
    }
    @Test
    public void testValueOfLong() {
        Date date = new Date();
        GeneralizedTime time = GeneralizedTime.valueOf(date.getTime());
        assertThat(time.getTimeInMillis()).isEqualTo(date.getTime());
        assertThat(time.toDate()).isEqualTo(date);
    }
    @Test
    public void testValueOfDate() {
        Date date = new Date();
        GeneralizedTime time = GeneralizedTime.valueOf(date);
        assertThat(time.getTimeInMillis()).isEqualTo(date.getTime());
        assertThat(time.toDate()).isEqualTo(date);
    }
    @Test
    public void testValueOfCalendar() {
        Calendar calendar = Calendar.getInstance();
        GeneralizedTime time = GeneralizedTime.valueOf(calendar);
        assertThat(time.getTimeInMillis()).isEqualTo(calendar.getTimeInMillis());
        assertThat(time.toCalendar()).isEqualTo(calendar);
        assertThat(time.toDate()).isEqualTo(calendar.getTime());
    }
    @Test
    public void testEqualsTrue() {
        GeneralizedTime gt1 = GeneralizedTime.valueOf("20060906135030+01");
        GeneralizedTime gt2 = GeneralizedTime.valueOf("20060906125030Z");
        assertThat(gt1).isEqualTo(gt2);
    }
    @Test
    public void testEqualsFalse() {
        GeneralizedTime gt1 = GeneralizedTime.valueOf("20060906135030Z");
        GeneralizedTime gt2 = GeneralizedTime.valueOf("20060906135030+01");
        assertThat(gt1).isNotEqualTo(gt2);
    }
    @Test
    public void testCompareEquals() {
        GeneralizedTime gt1 = GeneralizedTime.valueOf("20060906135030+01");
        GeneralizedTime gt2 = GeneralizedTime.valueOf("20060906125030Z");
        assertThat(gt1.compareTo(gt2)).isEqualTo(0);
    }
    @Test
    public void testCompareLessThan() {
        GeneralizedTime gt1 = GeneralizedTime.valueOf("20060906135030+01");
        GeneralizedTime gt2 = GeneralizedTime.valueOf("20060906135030Z");
        assertThat(gt1.compareTo(gt2) < 0).isTrue();
    }
    @Test
    public void testCompareGreaterThan() {
        GeneralizedTime gt1 = GeneralizedTime.valueOf("20060906135030Z");
        GeneralizedTime gt2 = GeneralizedTime.valueOf("20060906135030+01");
        assertThat(gt1.compareTo(gt2) > 0).isTrue();
    }
}