From 046430396dd40a63d6f574bf53c4d3ad2977a130 Mon Sep 17 00:00:00 2001
From: Matthew Swift <matthew.swift@forgerock.com>
Date: Mon, 07 May 2012 16:48:34 +0000
Subject: [PATCH] Initial implementation for OPENDJ-355: Add fluent API for decoding attributes
---
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractEntry.java | 14
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entry.java | 28
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableAddRequestImpl.java | 10
opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/GeneralizedTime.java | 1299 ++++++++++++++++++++++++
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractAttribute.java | 7
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java | 8
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/UnmodifiableSearchResultEntryImpl.java | 9
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AddRequestImpl.java | 15
opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/Functions.java | 46
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attribute.java | 8
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeEqualityMatchingRuleImpl.java | 5
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeParser.java | 589 ++++++++++
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeOrderingMatchingRuleImpl.java | 5
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/SearchResultEntryImpl.java | 15
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeSyntaxImpl.java | 1136 --------------------
opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java | 14
16 files changed, 2,076 insertions(+), 1,132 deletions(-)
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/Functions.java b/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/Functions.java
index 8fb41d3..64c21f8 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/Functions.java
+++ b/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}.
*
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/GeneralizedTime.java b/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/GeneralizedTime.java
new file mode 100644
index 0000000..953e92a
--- /dev/null
+++ b/opendj3/opendj-ldap-sdk/src/main/java/com/forgerock/opendj/util/GeneralizedTime.java
@@ -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.
+ }
+
+}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractAttribute.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractAttribute.java
index 81dec63..2c2a2c3 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractAttribute.java
+++ b/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();
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractEntry.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractEntry.java
index 37664b5..9aa3359 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AbstractEntry.java
+++ b/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);
}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attribute.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attribute.java
index 2ef52e0..931e041 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attribute.java
+++ b/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}.
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeParser.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeParser.java
new file mode 100644
index 0000000..c34923a
--- /dev/null
+++ b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/AttributeParser.java
@@ -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<DN> 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;
+ }
+ }
+}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java
index 34f707c..75e044f 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Attributes.java
+++ b/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();
}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
index 1c6f67f..afa54b4 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entries.java
+++ b/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) {
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entry.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entry.java
index 0873122..3c1c382 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/Entry.java
+++ b/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.
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AddRequestImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AddRequestImpl.java
index 35b24eb..97c8a6c 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/AddRequestImpl.java
+++ b/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);
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableAddRequestImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableAddRequestImpl.java
index 8a85be2..9f68d7a 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/requests/UnmodifiableAddRequestImpl.java
+++ b/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();
}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/SearchResultEntryImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/SearchResultEntryImpl.java
index f528a7f..6d9372b 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/SearchResultEntryImpl.java
+++ b/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);
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/UnmodifiableSearchResultEntryImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/UnmodifiableSearchResultEntryImpl.java
index d74f02f..5accd75 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/responses/UnmodifiableSearchResultEntryImpl.java
+++ b/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();
}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeEqualityMatchingRuleImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
index 6ff75c1..e7191f6 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
+++ b/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());
}
}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeOrderingMatchingRuleImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
index fba9ef9..c7cb533 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
+++ b/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());
}
}
diff --git a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeSyntaxImpl.java b/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeSyntaxImpl.java
index 21d9d8b..1b28a5b 100644
--- a/opendj3/opendj-ldap-sdk/src/main/java/org/forgerock/opendj/ldap/schema/GeneralizedTimeSyntaxImpl.java
+++ b/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());
--
Gitblit v1.10.0