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

Matthew Swift
07.48.2012 046430396dd40a63d6f574bf53c4d3ad2977a130
opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/Functions.java
@@ -22,13 +22,18 @@
 *
 *
 *      Copyright 2009-2010 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 */
package com.forgerock.opendj.util;
import java.util.Calendar;
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.schema.Schema;
/**
@@ -64,6 +69,14 @@
                }
            };
    private static final Function<ByteString, String, Void> BYTESTRING_TO_BASE64 =
            new Function<ByteString, String, Void>() {
                public String apply(final ByteString value, final Void p) {
                    return Base64.encode(value);
                }
            };
    private static final Function<ByteString, Boolean, Void> BYTESTRING_TO_BOOLEAN =
            new Function<ByteString, Boolean, Void>() {
@@ -83,6 +96,18 @@
                }
            };
    private static final Function<ByteString, Calendar, Void> BYTESTRING_TO_CALENDAR =
            new Function<ByteString, Calendar, Void>() {
                public Calendar apply(final ByteString value, final Void p) {
                    try {
                        return GeneralizedTime.decode(value);
                    } catch (DecodeException e) {
                        throw new LocalizedIllegalArgumentException(e.getMessageObject(), e);
                    }
                }
            };
    private static final Function<ByteString, DN, Schema> BYTESTRING_TO_DN =
            new Function<ByteString, DN, Schema>() {
@@ -236,6 +261,15 @@
    }
    /**
     * Returns a function which encodes a {@code ByteString} as {@code Base64}.
     *
     * @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},
@@ -251,6 +285,18 @@
    /**
     * 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}.
     *
     * @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}.
     *
opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/GeneralizedTime.java
New file
@@ -0,0 +1,1299 @@
/*
 * 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 com.forgerock.opendj.util;
import static org.forgerock.opendj.ldap.CoreMessages.*;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
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;
/**
 * Utility class for encoding and decoding generalized time syntax values.
 */
public final class 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}.
     *
     * @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.
     */
    public static Calendar decode(final ByteSequence value) throws DecodeException {
        int year = 0;
        int month = 0;
        int day = 0;
        int hour = 0;
        int minute = 0;
        int second = 0;
        // 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 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;
        }
        // The first four characters are the century and year, and they must
        // be numeric digits between 0 and 9.
        for (int i = 0; i < 4; i++) {
            switch (valueString.charAt(i)) {
            case '0':
                year = year * 10;
                break;
            case '1':
                year = (year * 10) + 1;
                break;
            case '2':
                year = (year * 10) + 2;
                break;
            case '3':
                year = (year * 10) + 3;
                break;
            case '4':
                year = (year * 10) + 4;
                break;
            case '5':
                year = (year * 10) + 5;
                break;
            case '6':
                year = (year * 10) + 6;
                break;
            case '7':
                year = (year * 10) + 7;
                break;
            case '8':
                year = (year * 10) + 8;
                break;
            case '9':
                year = (year * 10) + 9;
                break;
            default:
                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;
            }
        }
        // The next two characters are the month, and they must form the
        // string representation of an integer between 01 and 12.
        char m1 = valueString.charAt(4);
        final char m2 = valueString.charAt(5);
        switch (m1) {
        case '0':
            // m2 must be a digit between 1 and 9.
            switch (m2) {
            case '1':
                month = Calendar.JANUARY;
                break;
            case '2':
                month = Calendar.FEBRUARY;
                break;
            case '3':
                month = Calendar.MARCH;
                break;
            case '4':
                month = Calendar.APRIL;
                break;
            case '5':
                month = Calendar.MAY;
                break;
            case '6':
                month = Calendar.JUNE;
                break;
            case '7':
                month = Calendar.JULY;
                break;
            case '8':
                month = Calendar.AUGUST;
                break;
            case '9':
                month = Calendar.SEPTEMBER;
                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;
            }
            break;
        case '1':
            // m2 must be a digit between 0 and 2.
            switch (m2) {
            case '0':
                month = Calendar.OCTOBER;
                break;
            case '1':
                month = Calendar.NOVEMBER;
                break;
            case '2':
                month = Calendar.DECEMBER;
                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;
            }
            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;
        }
        // The next two characters should be the day of the month, and they
        // must form the string representation of an integer between 01 and
        // 31. This doesn't do any validation against the year or month, so
        // it will allow dates like April 31, or February 29 in a non-leap
        // year, but we'll let those slide.
        final char d1 = valueString.charAt(6);
        final char d2 = valueString.charAt(7);
        switch (d1) {
        case '0':
            // d2 must be a digit between 1 and 9.
            switch (d2) {
            case '1':
                day = 1;
                break;
            case '2':
                day = 2;
                break;
            case '3':
                day = 3;
                break;
            case '4':
                day = 4;
                break;
            case '5':
                day = 5;
                break;
            case '6':
                day = 6;
                break;
            case '7':
                day = 7;
                break;
            case '8':
                day = 8;
                break;
            case '9':
                day = 9;
                break;
            default:
                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;
            }
            break;
        case '1':
            // d2 must be a digit between 0 and 9.
            switch (d2) {
            case '0':
                day = 10;
                break;
            case '1':
                day = 11;
                break;
            case '2':
                day = 12;
                break;
            case '3':
                day = 13;
                break;
            case '4':
                day = 14;
                break;
            case '5':
                day = 15;
                break;
            case '6':
                day = 16;
                break;
            case '7':
                day = 17;
                break;
            case '8':
                day = 18;
                break;
            case '9':
                day = 19;
                break;
            default:
                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;
            }
            break;
        case '2':
            // d2 must be a digit between 0 and 9.
            switch (d2) {
            case '0':
                day = 20;
                break;
            case '1':
                day = 21;
                break;
            case '2':
                day = 22;
                break;
            case '3':
                day = 23;
                break;
            case '4':
                day = 24;
                break;
            case '5':
                day = 25;
                break;
            case '6':
                day = 26;
                break;
            case '7':
                day = 27;
                break;
            case '8':
                day = 28;
                break;
            case '9':
                day = 29;
                break;
            default:
                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;
            }
            break;
        case '3':
            // d2 must be either 0 or 1.
            switch (d2) {
            case '0':
                day = 30;
                break;
            case '1':
                day = 31;
                break;
            default:
                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;
            }
            break;
        default:
            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;
        }
        // The next two characters must be the hour, and they must form the
        // string representation of an integer between 00 and 23.
        final char h1 = valueString.charAt(8);
        final char h2 = valueString.charAt(9);
        switch (h1) {
        case '0':
            switch (h2) {
            case '0':
                hour = 0;
                break;
            case '1':
                hour = 1;
                break;
            case '2':
                hour = 2;
                break;
            case '3':
                hour = 3;
                break;
            case '4':
                hour = 4;
                break;
            case '5':
                hour = 5;
                break;
            case '6':
                hour = 6;
                break;
            case '7':
                hour = 7;
                break;
            case '8':
                hour = 8;
                break;
            case '9':
                hour = 9;
                break;
            default:
                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;
            }
            break;
        case '1':
            switch (h2) {
            case '0':
                hour = 10;
                break;
            case '1':
                hour = 11;
                break;
            case '2':
                hour = 12;
                break;
            case '3':
                hour = 13;
                break;
            case '4':
                hour = 14;
                break;
            case '5':
                hour = 15;
                break;
            case '6':
                hour = 16;
                break;
            case '7':
                hour = 17;
                break;
            case '8':
                hour = 18;
                break;
            case '9':
                hour = 19;
                break;
            default:
                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;
            }
            break;
        case '2':
            switch (h2) {
            case '0':
                hour = 20;
                break;
            case '1':
                hour = 21;
                break;
            case '2':
                hour = 22;
                break;
            case '3':
                hour = 23;
                break;
            default:
                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;
            }
            break;
        default:
            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;
        }
        // Next, there should be either two digits comprising an integer
        // between 00 and 59 (for the minute), a letter 'Z' (for the UTC
        // specifier), a plus or minus sign followed by two or four digits
        // (for the UTC offset), or a period or comma representing the
        // fraction.
        m1 = valueString.charAt(10);
        switch (m1) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
            // There must be at least two more characters, and the next one
            // must be a digit between 0 and 9.
            if (length < 13) {
                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;
            }
            minute = 10 * (m1 - '0');
            switch (valueString.charAt(11)) {
            case '0':
                break;
            case '1':
                minute += 1;
                break;
            case '2':
                minute += 2;
                break;
            case '3':
                minute += 3;
                break;
            case '4':
                minute += 4;
                break;
            case '5':
                minute += 5;
                break;
            case '6':
                minute += 6;
                break;
            case '7':
                minute += 7;
                break;
            case '8':
                minute += 8;
                break;
            case '9':
                minute += 9;
                break;
            default:
                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;
            }
            break;
        case 'Z':
            // This is fine only if we are at the end of the value.
            if (length == 11) {
                final TimeZone tz = TIME_ZONE_UTC_OBJ;
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '+':
        case '-':
            // These are fine only if there are exactly two or four more
            // digits that specify a valid offset.
            if ((length == 13) || (length == 15)) {
                final TimeZone tz = getTimeZoneForOffset(valueString, 10);
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '.':
        case ',':
            return finishDecodingFraction(valueString, 11, year, month, day, hour, minute, second,
                    3600000);
        default:
            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;
        }
        // Next, there should be either two digits comprising an integer
        // between 00 and 60 (for the second, including a possible leap
        // second), a letter 'Z' (for the UTC specifier), a plus or minus
        // sign followed by two or four digits (for the UTC offset), or a
        // period or comma to start the fraction.
        final char s1 = valueString.charAt(12);
        switch (s1) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
            // There must be at least two more characters, and the next one
            // must be a digit between 0 and 9.
            if (length < 15) {
                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;
            }
            second = 10 * (s1 - '0');
            switch (valueString.charAt(13)) {
            case '0':
                break;
            case '1':
                second += 1;
                break;
            case '2':
                second += 2;
                break;
            case '3':
                second += 3;
                break;
            case '4':
                second += 4;
                break;
            case '5':
                second += 5;
                break;
            case '6':
                second += 6;
                break;
            case '7':
                second += 7;
                break;
            case '8':
                second += 8;
                break;
            case '9':
                second += 9;
                break;
            default:
                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;
            }
            break;
        case '6':
            // There must be at least two more characters and the next one
            // must be a 0.
            if (length < 15) {
                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;
            }
            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;
            }
            second = 60;
            break;
        case 'Z':
            // This is fine only if we are at the end of the value.
            if (length == 13) {
                final TimeZone tz = TIME_ZONE_UTC_OBJ;
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '+':
        case '-':
            // These are fine only if there are exactly two or four more
            // digits that specify a valid offset.
            if ((length == 15) || (length == 17)) {
                final TimeZone tz = getTimeZoneForOffset(valueString, 12);
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '.':
        case ',':
            return finishDecodingFraction(valueString, 13, year, month, day, hour, minute, second,
                    60000);
        default:
            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;
        }
        // Next, there should be either a period or comma followed by
        // between one and three digits (to specify the sub-second), a
        // letter 'Z' (for the UTC specifier), or a plus or minus sign
        // followed by two our four digits (for the UTC offset).
        switch (valueString.charAt(14)) {
        case '.':
        case ',':
            return finishDecodingFraction(valueString, 15, year, month, day, hour, minute, second,
                    1000);
        case 'Z':
            // This is fine only if we are at the end of the value.
            if (length == 15) {
                final TimeZone tz = TIME_ZONE_UTC_OBJ;
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '+':
        case '-':
            // These are fine only if there are exactly two or four more
            // digits that specify a valid offset.
            if ((length == 17) || (length == 19)) {
                final TimeZone tz = getTimeZoneForOffset(valueString, 14);
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        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;
        }
    }
    /**
     * 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
     * parameters.
     *
     * @param value
     *            The generalized time string representation.
     * @param year
     *            The year.
     * @param month
     *            The month.
     * @param day
     *            The day.
     * @param hour
     *            The hour.
     * @param minute
     *            The minute.
     * @param second
     *            The second.
     * @param tz
     *            The timezone.
     * @return A Calendar object representing the provided date / time
     *         parameters.
     * @throws DecodeException
     *             If the calendar 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 {
        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;
        } 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;
        }
    }
    /**
     * Completes decoding the generalized time value containing a fractional
     * component. It will also decode the trailing 'Z' or offset.
     *
     * @param value
     *            The whole value, including the fractional component and time
     *            zone information.
     * @param startPos
     *            The position of the first character after the period in the
     *            value string.
     * @param year
     *            The year decoded from the provided value.
     * @param month
     *            The month decoded from the provided value.
     * @param day
     *            The day decoded from the provided value.
     * @param hour
     *            The hour decoded from the provided value.
     * @param minute
     *            The minute decoded from the provided value.
     * @param second
     *            The second decoded from the provided value.
     * @param multiplier
     *            The multiplier value that should be used to scale the fraction
     *            appropriately. If it's a fraction of an hour, then it should
     *            be 3600000 (60*60*1000). If it's a fraction of a minute, then
     *            it should be 60000. If it's a fraction of a second, then it
     *            should be 1000.
     * @return The timestamp created from the provided generalized time value
     *         including the fractional element.
     * @throws DecodeException
     *             If the provided value cannot be parsed as a valid generalized
     *             time string.
     */
    private static Calendar 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 length = value.length();
        final StringBuilder fractionBuffer = new StringBuilder((2 + length) - startPos);
        fractionBuffer.append("0.");
        TimeZone timeZone = null;
    outerLoop:
        for (int i = startPos; i < length; i++) {
            final char c = value.charAt(i);
            switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                fractionBuffer.append(c);
                break;
            case 'Z':
                // This is only acceptable if we're at the end of the value.
                if (i != (value.length() - 1)) {
                    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;
                }
                timeZone = TIME_ZONE_UTC_OBJ;
                break outerLoop;
            case '+':
            case '-':
                timeZone = getTimeZoneForOffset(value, i);
                break outerLoop;
            default:
                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;
            }
        }
        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;
        }
        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;
        }
        final Double fractionValue = Double.parseDouble(fractionBuffer.toString());
        final int additionalMilliseconds = (int) Math.round(fractionValue * multiplier);
        try {
            final GregorianCalendar calendar = new GregorianCalendar();
            calendar.setLenient(false);
            calendar.setTimeZone(timeZone);
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, additionalMilliseconds);
            return calendar;
        } 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;
        }
    }
    /**
     * Decodes a time zone offset from the provided value.
     *
     * @param value
     *            The whole value, including the offset.
     * @param startPos
     *            The position of the first character that is contained in the
     *            offset. This should be the position of the plus or minus
     *            character.
     * @return The {@code TimeZone} object representing the decoded time zone.
     * @throws DecodeException
     *             If the provided value does not contain a valid offset.
     */
    private static TimeZone getTimeZoneForOffset(final String value, final int startPos)
            throws DecodeException {
        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;
        }
        // The first character must be either a plus or minus.
        switch (offSetStr.charAt(0)) {
        case '+':
        case '-':
            // These are OK.
            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;
        }
        // The first two characters must be an integer between 00 and 23.
        switch (offSetStr.charAt(1)) {
        case '0':
        case '1':
            switch (offSetStr.charAt(2)) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                // These are all fine.
                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;
            }
            break;
        case '2':
            switch (offSetStr.charAt(2)) {
            case '0':
            case '1':
            case '2':
            case '3':
                // These are all fine.
                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;
            }
            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;
        }
        // If there are two more characters, then they must be an integer
        // between 00 and 59.
        if (offSetStr.length() == 5) {
            switch (offSetStr.charAt(3)) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
                switch (offSetStr.charAt(4)) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    // These are all fine.
                    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;
                }
                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;
            }
        }
        // If we've gotten here, then it looks like a valid offset. We can
        // create a time zone by using "GMT" followed by the offset.
        return TimeZone.getTimeZone("GMT" + offSetStr);
    }
    private GeneralizedTime() {
        // Prevent instantiation.
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractAttribute.java
@@ -265,6 +265,13 @@
    /**
     * {@inheritDoc}
     */
    public AttributeParser parse() {
        return AttributeParser.parseAttribute(this);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public abstract Iterator<ByteString> iterator();
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractEntry.java
@@ -234,6 +234,20 @@
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
        return AttributeParser.parseAttribute(getAttribute(attributeDescription));
    }
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(String attributeDescription) {
        return AttributeParser.parseAttribute(getAttribute(attributeDescription));
    }
    /**
     * {@inheritDoc}
     */
    public boolean removeAttribute(final AttributeDescription attributeDescription) {
        return removeAttribute(Attributes.emptyAttribute(attributeDescription), null);
    }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attribute.java
@@ -256,6 +256,14 @@
    Iterator<ByteString> iterator();
    /**
     * Returns a parser for this attribute which can be used for decoding values
     * as different types of object.
     *
     * @return A parser for this attribute.
     */
    AttributeParser parse();
    /**
     * Removes {@code value} from this attribute if it is present (optional
     * operation). If this attribute does not contain {@code value}, the call
     * leaves the attribute unchanged and returns {@code false}.
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeParser.java
New file
@@ -0,0 +1,589 @@
/*
 * 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 java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
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;
/**
 * A fluent API for parsing attributes as different types of value. An attribute
 * parser is obtained from an entry using the method
 * {@link Entry#parseAttribute} or from an attribute using
 * {@link Attribute#parse()}.
 * <p>
 * Methods throw an {@code IllegalArgumentException} when a value cannot be
 * parsed (e.g. because its syntax is invalid). Methods which return a
 * {@code Set} always return a modifiable non-{@code null} result.
 * <p>
 * Examples:
 *
 * <pre>
 * Entry entry = ...;
 *
 * Calendar timestamp = entry.parseAttribute("createTimestamp").asCalendar();
 * boolean isEnabled = entry.parseAttribute("enabled").asBoolean(false);
 *
 * Entry group = ...;
 * Schema schema = ...;
 *
 * Set&lt;DN&gt; members = group.parseAttribute("member").usingSchema(schema).asSetOfDN();
 * </pre>
 *
 * @see Entry#parseAttribute
 * @see Attribute#parse()
 */
public final class AttributeParser {
    // TODO: enums, filters, rdns?
    private static final AttributeParser NULL_INSTANCE = new AttributeParser(null);
    /**
     * Returns an attribute parser for the provided attribute. {@code null}
     * attributes are permitted and will be treated as if an empty attribute was
     * provided.
     *
     * @param attribute
     *            The attribute to be parsed, which may be {@code null}.
     * @return The attribute parser.
     */
    public static AttributeParser parseAttribute(final Attribute attribute) {
        return isEmpty(attribute) ? NULL_INSTANCE : new AttributeParser(attribute);
    }
    private static boolean isEmpty(final Attribute attribute) {
        return (attribute == null) || attribute.isEmpty();
    }
    private final Attribute attribute;
    private Schema schema;
    private AttributeParser(final Attribute attribute) {
        this.attribute = attribute;
    }
    /**
     * Returns the first value decoded as an {@code AttributeDescription} using
     * the schema associated with this parser, or {@code null} if the attribute
     * does not contain any values.
     *
     * @return The first value decoded as an {@code AttributeDescription}.
     */
    public AttributeDescription asAttributeDescription() {
        return asAttributeDescription(null);
    }
    /**
     * 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 decoded as an {@code AttributeDescription}.
     */
    public AttributeDescription asAttributeDescription(final AttributeDescription defaultValue) {
        return parseSingleValue(Functions.valueToAttributeDescription(getSchema()), defaultValue);
    }
    /**
     * 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
     * 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.
     */
    public String asBase64(final ByteString defaultValue) {
        return parseSingleValue(Functions.valueToBase64(), Base64.encode(defaultValue));
    }
    /**
     * Returns the first value decoded as a boolean, or {@code null} if the
     * attribute does not contain any values.
     *
     * @return The first value decoded as a boolean.
     */
    public Boolean asBoolean() {
        return isEmpty(attribute) ? null : asBoolean(false /* ignored */);
    }
    /**
     * Returns the first value decoded as an {@code Boolean}, 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 Boolean}.
     */
    public boolean asBoolean(final boolean defaultValue) {
        return parseSingleValue(Functions.valueToBoolean(), defaultValue);
    }
    /**
     * Returns the first value, or {@code null} if the attribute does not
     * contain any values.
     *
     * @return The first value.
     */
    public ByteString asByteString() {
        return asByteString(null);
    }
    /**
     * Returns the first value, 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.
     */
    public ByteString asByteString(final ByteString defaultValue) {
        return parseSingleValue(Functions.<ByteString> identityFunction(), defaultValue);
    }
    /**
     * 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.
     *
     * @return The first value decoded as a {@code DN}.
     */
    public DN asDN() {
        return asDN(null);
    }
    /**
     * 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 DN defaultValue) {
        return parseSingleValue(Functions.valueToDN(getSchema()), defaultValue);
    }
    /**
     * Returns the first value decoded as an {@code Integer}, or {@code null} if
     * the attribute does not contain any values.
     *
     * @return The first value decoded as an {@code Integer}.
     */
    public Integer asInteger() {
        return isEmpty(attribute) ? null : asInteger(0 /* ignored */);
    }
    /**
     * Returns the first value decoded as an {@code Integer}, 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 Integer}.
     */
    public int asInteger(final int defaultValue) {
        return parseSingleValue(Functions.valueToInteger(), defaultValue);
    }
    /**
     * Returns the first value decoded as a {@code Long}, or {@code null} if the
     * attribute does not contain any values.
     *
     * @return The first value decoded as a {@code Long}.
     */
    public Long asLong() {
        return isEmpty(attribute) ? null : asLong(0L /* ignored */);
    }
    /**
     * Returns the first value decoded as a {@code Long}, 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 Long}.
     */
    public long asLong(final long defaultValue) {
        return parseSingleValue(Functions.valueToLong(), defaultValue);
    }
    /**
     * 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 decoded as a set of {@code AttributeDescription}s.
     */
    public Set<AttributeDescription> asSetOfAttributeDescription(
            final AttributeDescription... defaultValues) {
        return asSetOfAttributeDescription(Arrays.asList(defaultValues));
    }
    /**
     * 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 decoded as a set of {@code AttributeDescription}s.
     */
    public Set<AttributeDescription> asSetOfAttributeDescription(
            final Collection<AttributeDescription> defaultValues) {
        return parseMultipleValues(Functions.valueToAttributeDescription(), defaultValues);
    }
    /**
     * 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 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));
    }
    /**
     * Returns the values decoded as a set of {@code Boolean}s, 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 Boolean}s.
     */
    public Set<Boolean> asSetOfBoolean(final Boolean... defaultValues) {
        return asSetOfBoolean(Arrays.asList(defaultValues));
    }
    /**
     * Returns the values decoded as a set of {@code Boolean}s, 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 Boolean}s.
     */
    public Set<Boolean> asSetOfBoolean(final Collection<Boolean> defaultValues) {
        return parseMultipleValues(Functions.valueToBoolean(), defaultValues);
    }
    /**
     * Returns the values contained in the attribute, 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.
     */
    public Set<ByteString> asSetOfByteString(final ByteString... defaultValues) {
        return asSetOfByteString(Arrays.asList(defaultValues));
    }
    /**
     * Returns the values contained in the attribute, 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.
     */
    public Set<ByteString> asSetOfByteString(final Collection<ByteString> defaultValues) {
        return parseMultipleValues(Functions.<ByteString> identityFunction(), 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 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);
    }
    /**
     * 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 Collection<DN> defaultValues) {
        return parseMultipleValues(Functions.valueToDN(), defaultValues);
    }
    /**
     * 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 DN... defaultValues) {
        return asSetOfDN(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.
     *
     * @param defaultValues
     *            The default values to return if the attribute is empty.
     * @return The values decoded as a set of {@code Integer}s.
     */
    public Set<Integer> asSetOfInteger(final Collection<Integer> defaultValues) {
        return parseMultipleValues(Functions.valueToInteger(), defaultValues);
    }
    /**
     * Returns the values decoded as a set of {@code Integer}s, 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 Integer}s.
     */
    public Set<Integer> asSetOfInteger(final Integer... defaultValues) {
        return asSetOfInteger(Arrays.asList(defaultValues));
    }
    /**
     * Returns the values decoded as a set of {@code Long}s, 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 Long}s.
     */
    public Set<Long> asSetOfLong(final Collection<Long> defaultValues) {
        return parseMultipleValues(Functions.valueToLong(), defaultValues);
    }
    /**
     * Returns the values decoded as a set of {@code Long}s, 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 Long}s.
     */
    public Set<Long> asSetOfLong(final Long... defaultValues) {
        return asSetOfLong(Arrays.asList(defaultValues));
    }
    /**
     * Returns the values decoded as a set of {@code String}s, 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 String}s.
     */
    public Set<String> asSetOfString(final Collection<String> defaultValues) {
        return parseMultipleValues(Functions.valueToString(), defaultValues);
    }
    /**
     * Returns the values decoded as a set of {@code String}s, 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 String}s.
     */
    public Set<String> asSetOfString(final String... defaultValues) {
        return asSetOfString(Arrays.asList(defaultValues));
    }
    /**
     * Returns the first value decoded as a {@code String}, or {@code null} if
     * the attribute does not contain any values.
     *
     * @return The first value decoded as a {@code String}.
     */
    public String asString() {
        return asString(null);
    }
    /**
     * Returns the first value decoded as a {@code String}, 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 String}.
     */
    public String asString(final String defaultValue) {
        return parseSingleValue(Functions.valueToString(), defaultValue);
    }
    /**
     * Throws a {@code NoSuchElementException} if the attribute referenced by
     * this parser is {@code null} or empty.
     *
     * @return A reference to this attribute parser.
     * @throws NoSuchElementException
     *             If the attribute referenced by this parser is {@code null} or
     *             empty.
     */
    public AttributeParser requireValue() {
        if (isEmpty(attribute)) {
            throw new NoSuchElementException();
        } else {
            return this;
        }
    }
    /**
     * Sets the {@code Schema} which will be used when parsing schema sensitive
     * values such as DNs and attribute descriptions.
     *
     * @param schema
     *            The {@code Schema} which will be used when parsing schema
     *            sensitive values.
     * @return This attribute parser.
     */
    public AttributeParser usingSchema(final Schema schema) {
        // Avoid modifying the null instance: a schema will not be needed
        // anyway.
        if (this != NULL_INSTANCE) {
            this.schema = schema;
        }
        return this;
    }
    private Schema getSchema() {
        return schema == null ? Schema.getDefaultSchema() : schema;
    }
    private <T> Set<T> parseMultipleValues(final Function<ByteString, T, ?> f,
            final Collection<? extends T> defaultValues) {
        if (!isEmpty(attribute)) {
            final LinkedHashSet<T> result = new LinkedHashSet<T>(attribute.size());
            for (final ByteString b : attribute) {
                result.add(f.apply(b, null));
            }
            return result;
        } else if (defaultValues != null) {
            return new LinkedHashSet<T>(defaultValues);
        } else {
            return new LinkedHashSet<T>(0);
        }
    }
    private <T> T parseSingleValue(final Function<ByteString, T, ?> f, final T defaultValue) {
        if (!isEmpty(attribute)) {
            return f.apply(attribute.firstValue(), null);
        } else {
            return defaultValue;
        }
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java
@@ -163,6 +163,10 @@
            return AbstractAttribute.hashCode(this);
        }
        public AttributeParser parse() {
            return attribute.parse();
        }
        public boolean isEmpty() {
            return attribute.isEmpty();
        }
@@ -286,6 +290,10 @@
            return Iterators.unmodifiableIterator(attribute.iterator());
        }
        public AttributeParser parse() {
            return attribute.parse();
        }
        public boolean remove(final Object value) {
            throw new UnsupportedOperationException();
        }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
@@ -180,6 +180,20 @@
        /**
         * {@inheritDoc}
         */
        public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
            return entry.parseAttribute(attributeDescription);
        }
        /**
         * {@inheritDoc}
         */
        public AttributeParser parseAttribute(String attributeDescription) {
            return entry.parseAttribute(attributeDescription);
        }
        /**
         * {@inheritDoc}
         */
        @Override
        public boolean removeAttribute(final Attribute attribute,
                final Collection<ByteString> missingValues) {
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entry.java
@@ -323,6 +323,34 @@
    int hashCode();
    /**
     * Returns a parser for the named attribute contained in this entry.
     *
     * @param attributeDescription
     *            The name of the attribute to be parsed.
     * @return A parser for the named attribute.
     * @throws NullPointerException
     *             If {@code attributeDescription} was {@code null}.
     */
    AttributeParser parseAttribute(AttributeDescription attributeDescription);
    /**
     * Returns a parser for the named attribute contained in this entry.
     * <p>
     * The attribute description will be decoded using the schema associated
     * with this entry (usually the default schema).
     *
     * @param attributeDescription
     *            The name of the attribute to be parsed.
     * @return A parser for the named attribute.
     * @throws LocalizedIllegalArgumentException
     *             If {@code attributeDescription} could not be decoded using
     *             the schema associated with this entry.
     * @throws NullPointerException
     *             If {@code attributeDescription} was {@code null}.
     */
    AttributeParser parseAttribute(String attributeDescription);
    /**
     * Removes all of the attribute values contained in {@code attribute} from
     * this entry if it is present (optional operation). If {@code attribute} is
     * empty then the entire attribute will be removed if it is present.
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AddRequestImpl.java
@@ -31,6 +31,7 @@
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.AttributeParser;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
@@ -263,6 +264,20 @@
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
        return entry.parseAttribute(attributeDescription);
    }
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(String attributeDescription) {
        return entry.parseAttribute(attributeDescription);
    }
    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(final Object object) {
        return entry.equals(object);
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableAddRequestImpl.java
@@ -22,6 +22,7 @@
 *
 *
 *      Copyright 2010 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.requests;
@@ -30,6 +31,7 @@
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.AttributeParser;
import org.forgerock.opendj.ldap.Attributes;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
@@ -125,6 +127,14 @@
        return impl.getName();
    }
    public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
        return impl.parseAttribute(attributeDescription);
    }
    public AttributeParser parseAttribute(String attributeDescription) {
        return impl.parseAttribute(attributeDescription);
    }
    public boolean removeAttribute(Attribute attribute, Collection<ByteString> missingValues) {
        throw new UnsupportedOperationException();
    }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/SearchResultEntryImpl.java
@@ -31,6 +31,7 @@
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.AttributeParser;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
@@ -172,6 +173,20 @@
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
        return entry.parseAttribute(attributeDescription);
    }
    /**
     * {@inheritDoc}
     */
    public AttributeParser parseAttribute(String attributeDescription) {
        return entry.parseAttribute(attributeDescription);
    }
    /**
     * {@inheritDoc}
     */
    public boolean removeAttribute(final Attribute attribute,
            final Collection<ByteString> missingValues) {
        return entry.removeAttribute(attribute, missingValues);
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/UnmodifiableSearchResultEntryImpl.java
@@ -31,6 +31,7 @@
import org.forgerock.opendj.ldap.Attribute;
import org.forgerock.opendj.ldap.AttributeDescription;
import org.forgerock.opendj.ldap.AttributeParser;
import org.forgerock.opendj.ldap.Attributes;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DN;
@@ -121,6 +122,14 @@
        return impl.getName();
    }
    public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
        return impl.parseAttribute(attributeDescription);
    }
    public AttributeParser parseAttribute(String attributeDescription) {
        return impl.parseAttribute(attributeDescription);
    }
    public boolean removeAttribute(Attribute attribute, Collection<ByteString> missingValues) {
        throw new UnsupportedOperationException();
    }
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
@@ -22,6 +22,7 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.schema;
@@ -29,6 +30,8 @@
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import com.forgerock.opendj.util.GeneralizedTime;
/**
 * This class defines the generalizedTimeMatch matching rule defined in X.520
 * and referenced in RFC 2252.
@@ -36,6 +39,6 @@
final class GeneralizedTimeEqualityMatchingRuleImpl extends AbstractMatchingRuleImpl {
    public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value)
            throws DecodeException {
        return ByteString.valueOf(GeneralizedTimeSyntaxImpl.decodeGeneralizedTimeValue(value));
        return ByteString.valueOf(GeneralizedTime.decode(value).getTimeInMillis());
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
@@ -22,6 +22,7 @@
 *
 *
 *      Copyright 2009 Sun Microsystems, Inc.
 *      Portions copyright 2012 ForgeRock AS.
 */
package org.forgerock.opendj.ldap.schema;
@@ -29,6 +30,8 @@
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import com.forgerock.opendj.util.GeneralizedTime;
/**
 * This class defines the generalizedTimeOrderingMatch matching rule defined in
 * X.520 and referenced in RFC 2252.
@@ -36,6 +39,6 @@
final class GeneralizedTimeOrderingMatchingRuleImpl extends AbstractOrderingMatchingRuleImpl {
    public ByteString normalizeAttributeValue(final Schema schema, final ByteSequence value)
            throws DecodeException {
        return ByteString.valueOf(GeneralizedTimeSyntaxImpl.decodeGeneralizedTimeValue(value));
        return ByteString.valueOf(GeneralizedTime.decode(value).getTimeInMillis());
    }
}
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeSyntaxImpl.java
@@ -27,19 +27,16 @@
package org.forgerock.opendj.ldap.schema;
import static org.forgerock.opendj.ldap.CoreMessages.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.*;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.EMR_GENERALIZED_TIME_OID;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.OMR_GENERALIZED_TIME_OID;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
import static org.forgerock.opendj.ldap.schema.SchemaConstants.SYNTAX_GENERALIZED_TIME_NAME;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
import org.forgerock.opendj.ldap.ByteSequence;
import org.forgerock.opendj.ldap.DecodeException;
import com.forgerock.opendj.util.StaticUtils;
import com.forgerock.opendj.util.GeneralizedTime;
/**
 * This class implements the fax attribute syntax. This should be restricted to
@@ -48,1127 +45,6 @@
 */
final class GeneralizedTimeSyntaxImpl extends AbstractSyntaxImpl {
    // UTC TimeZone is assumed to never change over JVM lifetime
    private static final TimeZone TIME_ZONE_UTC_OBJ = TimeZone.getTimeZone(TIME_ZONE_UTC);
    /**
     * Decodes the provided normalized value as a generalized time value and
     * retrieves a timestamp containing its representation.
     *
     * @param value
     *            The normalized value to decode using the generalized time
     *            syntax.
     * @return The timestamp created from the provided generalized time value.
     * @throws DecodeException
     *             If the provided value cannot be parsed as a valid generalized
     *             time string.
     */
    static long decodeGeneralizedTimeValue(final ByteSequence value) throws DecodeException {
        int year = 0;
        int month = 0;
        int day = 0;
        int hour = 0;
        int minute = 0;
        int second = 0;
        // 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 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;
        }
        // The first four characters are the century and year, and they must
        // be numeric digits between 0 and 9.
        for (int i = 0; i < 4; i++) {
            switch (valueString.charAt(i)) {
            case '0':
                year = year * 10;
                break;
            case '1':
                year = year * 10 + 1;
                break;
            case '2':
                year = year * 10 + 2;
                break;
            case '3':
                year = year * 10 + 3;
                break;
            case '4':
                year = year * 10 + 4;
                break;
            case '5':
                year = year * 10 + 5;
                break;
            case '6':
                year = year * 10 + 6;
                break;
            case '7':
                year = year * 10 + 7;
                break;
            case '8':
                year = year * 10 + 8;
                break;
            case '9':
                year = year * 10 + 9;
                break;
            default:
                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;
            }
        }
        // The next two characters are the month, and they must form the
        // string representation of an integer between 01 and 12.
        char m1 = valueString.charAt(4);
        final char m2 = valueString.charAt(5);
        switch (m1) {
        case '0':
            // m2 must be a digit between 1 and 9.
            switch (m2) {
            case '1':
                month = Calendar.JANUARY;
                break;
            case '2':
                month = Calendar.FEBRUARY;
                break;
            case '3':
                month = Calendar.MARCH;
                break;
            case '4':
                month = Calendar.APRIL;
                break;
            case '5':
                month = Calendar.MAY;
                break;
            case '6':
                month = Calendar.JUNE;
                break;
            case '7':
                month = Calendar.JULY;
                break;
            case '8':
                month = Calendar.AUGUST;
                break;
            case '9':
                month = Calendar.SEPTEMBER;
                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;
            }
            break;
        case '1':
            // m2 must be a digit between 0 and 2.
            switch (m2) {
            case '0':
                month = Calendar.OCTOBER;
                break;
            case '1':
                month = Calendar.NOVEMBER;
                break;
            case '2':
                month = Calendar.DECEMBER;
                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;
            }
            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;
        }
        // The next two characters should be the day of the month, and they
        // must form the string representation of an integer between 01 and
        // 31. This doesn't do any validation against the year or month, so
        // it will allow dates like April 31, or February 29 in a non-leap
        // year, but we'll let those slide.
        final char d1 = valueString.charAt(6);
        final char d2 = valueString.charAt(7);
        switch (d1) {
        case '0':
            // d2 must be a digit between 1 and 9.
            switch (d2) {
            case '1':
                day = 1;
                break;
            case '2':
                day = 2;
                break;
            case '3':
                day = 3;
                break;
            case '4':
                day = 4;
                break;
            case '5':
                day = 5;
                break;
            case '6':
                day = 6;
                break;
            case '7':
                day = 7;
                break;
            case '8':
                day = 8;
                break;
            case '9':
                day = 9;
                break;
            default:
                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;
            }
            break;
        case '1':
            // d2 must be a digit between 0 and 9.
            switch (d2) {
            case '0':
                day = 10;
                break;
            case '1':
                day = 11;
                break;
            case '2':
                day = 12;
                break;
            case '3':
                day = 13;
                break;
            case '4':
                day = 14;
                break;
            case '5':
                day = 15;
                break;
            case '6':
                day = 16;
                break;
            case '7':
                day = 17;
                break;
            case '8':
                day = 18;
                break;
            case '9':
                day = 19;
                break;
            default:
                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;
            }
            break;
        case '2':
            // d2 must be a digit between 0 and 9.
            switch (d2) {
            case '0':
                day = 20;
                break;
            case '1':
                day = 21;
                break;
            case '2':
                day = 22;
                break;
            case '3':
                day = 23;
                break;
            case '4':
                day = 24;
                break;
            case '5':
                day = 25;
                break;
            case '6':
                day = 26;
                break;
            case '7':
                day = 27;
                break;
            case '8':
                day = 28;
                break;
            case '9':
                day = 29;
                break;
            default:
                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;
            }
            break;
        case '3':
            // d2 must be either 0 or 1.
            switch (d2) {
            case '0':
                day = 30;
                break;
            case '1':
                day = 31;
                break;
            default:
                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;
            }
            break;
        default:
            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;
        }
        // The next two characters must be the hour, and they must form the
        // string representation of an integer between 00 and 23.
        final char h1 = valueString.charAt(8);
        final char h2 = valueString.charAt(9);
        switch (h1) {
        case '0':
            switch (h2) {
            case '0':
                hour = 0;
                break;
            case '1':
                hour = 1;
                break;
            case '2':
                hour = 2;
                break;
            case '3':
                hour = 3;
                break;
            case '4':
                hour = 4;
                break;
            case '5':
                hour = 5;
                break;
            case '6':
                hour = 6;
                break;
            case '7':
                hour = 7;
                break;
            case '8':
                hour = 8;
                break;
            case '9':
                hour = 9;
                break;
            default:
                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;
            }
            break;
        case '1':
            switch (h2) {
            case '0':
                hour = 10;
                break;
            case '1':
                hour = 11;
                break;
            case '2':
                hour = 12;
                break;
            case '3':
                hour = 13;
                break;
            case '4':
                hour = 14;
                break;
            case '5':
                hour = 15;
                break;
            case '6':
                hour = 16;
                break;
            case '7':
                hour = 17;
                break;
            case '8':
                hour = 18;
                break;
            case '9':
                hour = 19;
                break;
            default:
                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;
            }
            break;
        case '2':
            switch (h2) {
            case '0':
                hour = 20;
                break;
            case '1':
                hour = 21;
                break;
            case '2':
                hour = 22;
                break;
            case '3':
                hour = 23;
                break;
            default:
                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;
            }
            break;
        default:
            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;
        }
        // Next, there should be either two digits comprising an integer
        // between 00 and 59 (for the minute), a letter 'Z' (for the UTC
        // specifier), a plus or minus sign followed by two or four digits
        // (for the UTC offset), or a period or comma representing the
        // fraction.
        m1 = valueString.charAt(10);
        switch (m1) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
            // There must be at least two more characters, and the next one
            // must be a digit between 0 and 9.
            if (length < 13) {
                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;
            }
            minute = 10 * (m1 - '0');
            switch (valueString.charAt(11)) {
            case '0':
                break;
            case '1':
                minute += 1;
                break;
            case '2':
                minute += 2;
                break;
            case '3':
                minute += 3;
                break;
            case '4':
                minute += 4;
                break;
            case '5':
                minute += 5;
                break;
            case '6':
                minute += 6;
                break;
            case '7':
                minute += 7;
                break;
            case '8':
                minute += 8;
                break;
            case '9':
                minute += 9;
                break;
            default:
                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;
            }
            break;
        case 'Z':
            // This is fine only if we are at the end of the value.
            if (length == 11) {
                final TimeZone tz = TIME_ZONE_UTC_OBJ;
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '+':
        case '-':
            // These are fine only if there are exactly two or four more
            // digits that specify a valid offset.
            if (length == 13 || length == 15) {
                final TimeZone tz = getTimeZoneForOffset(valueString, 10);
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '.':
        case ',':
            return finishDecodingFraction(valueString, 11, year, month, day, hour, minute, second,
                    3600000);
        default:
            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;
        }
        // Next, there should be either two digits comprising an integer
        // between 00 and 60 (for the second, including a possible leap
        // second), a letter 'Z' (for the UTC specifier), a plus or minus
        // sign followed by two or four digits (for the UTC offset), or a
        // period or comma to start the fraction.
        final char s1 = valueString.charAt(12);
        switch (s1) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
            // There must be at least two more characters, and the next one
            // must be a digit between 0 and 9.
            if (length < 15) {
                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;
            }
            second = 10 * (s1 - '0');
            switch (valueString.charAt(13)) {
            case '0':
                break;
            case '1':
                second += 1;
                break;
            case '2':
                second += 2;
                break;
            case '3':
                second += 3;
                break;
            case '4':
                second += 4;
                break;
            case '5':
                second += 5;
                break;
            case '6':
                second += 6;
                break;
            case '7':
                second += 7;
                break;
            case '8':
                second += 8;
                break;
            case '9':
                second += 9;
                break;
            default:
                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;
            }
            break;
        case '6':
            // There must be at least two more characters and the next one
            // must be a 0.
            if (length < 15) {
                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;
            }
            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;
            }
            second = 60;
            break;
        case 'Z':
            // This is fine only if we are at the end of the value.
            if (length == 13) {
                final TimeZone tz = TIME_ZONE_UTC_OBJ;
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '+':
        case '-':
            // These are fine only if there are exactly two or four more
            // digits that specify a valid offset.
            if (length == 15 || length == 17) {
                final TimeZone tz = getTimeZoneForOffset(valueString, 12);
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '.':
        case ',':
            return finishDecodingFraction(valueString, 13, year, month, day, hour, minute, second,
                    60000);
        default:
            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;
        }
        // Next, there should be either a period or comma followed by
        // between one and three digits (to specify the sub-second), a
        // letter 'Z' (for the UTC specifier), or a plus or minus sign
        // followed by two our four digits (for the UTC offset).
        switch (valueString.charAt(14)) {
        case '.':
        case ',':
            return finishDecodingFraction(valueString, 15, year, month, day, hour, minute, second,
                    1000);
        case 'Z':
            // This is fine only if we are at the end of the value.
            if (length == 15) {
                final TimeZone tz = TIME_ZONE_UTC_OBJ;
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        case '+':
        case '-':
            // These are fine only if there are exactly two or four more
            // digits that specify a valid offset.
            if (length == 17 || length == 19) {
                final TimeZone tz = getTimeZoneForOffset(valueString, 14);
                return createTime(valueString, year, month, day, hour, minute, second, tz);
            } else {
                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;
            }
        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;
        }
    }
    private static long createTime(final String value, int year, int month, int day, int hour,
            int minute, int second, final TimeZone tz) throws DecodeException {
        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.getTimeInMillis();
        } 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;
        }
    }
    /**
     * Completes decoding the generalized time value containing a fractional
     * component. It will also decode the trailing 'Z' or offset.
     *
     * @param value
     *            The whole value, including the fractional component and time
     *            zone information.
     * @param startPos
     *            The position of the first character after the period in the
     *            value string.
     * @param year
     *            The year decoded from the provided value.
     * @param month
     *            The month decoded from the provided value.
     * @param day
     *            The day decoded from the provided value.
     * @param hour
     *            The hour decoded from the provided value.
     * @param minute
     *            The minute decoded from the provided value.
     * @param second
     *            The second decoded from the provided value.
     * @param multiplier
     *            The multiplier value that should be used to scale the fraction
     *            appropriately. If it's a fraction of an hour, then it should
     *            be 3600000 (60*60*1000). If it's a fraction of a minute, then
     *            it should be 60000. If it's a fraction of a second, then it
     *            should be 1000.
     * @return The timestamp created from the provided generalized time value
     *         including the fractional element.
     * @throws DecodeException
     *             If the provided value cannot be parsed as a valid generalized
     *             time string.
     */
    private static long 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 length = value.length();
        final StringBuilder fractionBuffer = new StringBuilder(2 + length - startPos);
        fractionBuffer.append("0.");
        TimeZone timeZone = null;
    outerLoop:
        for (int i = startPos; i < length; i++) {
            final char c = value.charAt(i);
            switch (c) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                fractionBuffer.append(c);
                break;
            case 'Z':
                // This is only acceptable if we're at the end of the value.
                if (i != value.length() - 1) {
                    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;
                }
                timeZone = TIME_ZONE_UTC_OBJ;
                break outerLoop;
            case '+':
            case '-':
                timeZone = getTimeZoneForOffset(value, i);
                break outerLoop;
            default:
                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;
            }
        }
        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;
        }
        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;
        }
        final Double fractionValue = Double.parseDouble(fractionBuffer.toString());
        final long additionalMilliseconds = Math.round(fractionValue * multiplier);
        try {
            final GregorianCalendar calendar = new GregorianCalendar();
            calendar.setLenient(false);
            calendar.setTimeZone(timeZone);
            calendar.set(year, month, day, hour, minute, second);
            calendar.set(Calendar.MILLISECOND, 0);
            return calendar.getTimeInMillis() + additionalMilliseconds;
        } 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;
        }
    }
    /**
     * Decodes a time zone offset from the provided value.
     *
     * @param value
     *            The whole value, including the offset.
     * @param startPos
     *            The position of the first character that is contained in the
     *            offset. This should be the position of the plus or minus
     *            character.
     * @return The {@code TimeZone} object representing the decoded time zone.
     * @throws DecodeException
     *             If the provided value does not contain a valid offset.
     */
    private static TimeZone getTimeZoneForOffset(final String value, final int startPos)
            throws DecodeException {
        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;
        }
        // The first character must be either a plus or minus.
        switch (offSetStr.charAt(0)) {
        case '+':
        case '-':
            // These are OK.
            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;
        }
        // The first two characters must be an integer between 00 and 23.
        switch (offSetStr.charAt(1)) {
        case '0':
        case '1':
            switch (offSetStr.charAt(2)) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                // These are all fine.
                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;
            }
            break;
        case '2':
            switch (offSetStr.charAt(2)) {
            case '0':
            case '1':
            case '2':
            case '3':
                // These are all fine.
                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;
            }
            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;
        }
        // If there are two more characters, then they must be an integer
        // between 00 and 59.
        if (offSetStr.length() == 5) {
            switch (offSetStr.charAt(3)) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
                switch (offSetStr.charAt(4)) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                    // These are all fine.
                    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;
                }
                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;
            }
        }
        // If we've gotten here, then it looks like a valid offset. We can
        // create a time zone by using "GMT" followed by the offset.
        return TimeZone.getTimeZone("GMT" + offSetStr);
    }
    @Override
    public String getEqualityMatchingRule() {
        return EMR_GENERALIZED_TIME_OID;
@@ -1209,7 +85,7 @@
    public boolean valueIsAcceptable(final Schema schema, final ByteSequence value,
            final LocalizableMessageBuilder invalidReason) {
        try {
            decodeGeneralizedTimeValue(value);
            GeneralizedTime.decode(value);
            return true;
        } catch (final DecodeException de) {
            invalidReason.append(de.getMessageObject());