From 902747f3618c2ba285058670ee6d0cf57e51c34e Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 30 Nov 2009 16:27:13 +0000
Subject: [PATCH] Initial import of SDK source from data provider branch.

---
 sdk/src/org/opends/sdk/AttributeDescription.java                                          | 1496 
 sdk/src/org/opends/sdk/asn1/ASN1.java                                                     |  221 
 sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java          |  131 
 sdk/src/org/opends/sdk/tools/LDAPCompare.java                                             |  674 
 sdk/src/org/opends/sdk/responses/IntermediateResponse.java                                |  116 
 sdk/src/org/opends/sdk/ResultFuture.java                                                  |  153 
 sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java                                   |  563 
 sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java                   |   49 
 sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java                               |  188 
 sdk/src/org/opends/sdk/AbstractConnection.java                                            |  315 
 sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java                                 |  202 
 sdk/src/org/opends/sdk/SynchronousConnection.java                                         |  261 
 sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java                        |   98 
 sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java                           |  152 
 sdk/src/org/opends/sdk/util/Predicate.java                                                |   57 
 sdk/src/org/opends/sdk/schema/SchemaConstants.java                                        | 1623 
 sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java                              |  125 
 sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java                                   |  560 
 sdk/src/org/opends/sdk/requests/AbstractBindRequest.java                                  |   73 
 sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java                            |   95 
 sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java                           |  192 
 sdk/src/org/opends/sdk/responses/GenericExtendedResult.java                               |  198 
 sdk/src/org/opends/sdk/tools/ArgumentParser.java                                          | 2007 +
 sdk/src/org/opends/sdk/extensions/CancelRequest.java                                      |  171 
 sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java                                       |   86 
 sdk/src/org/opends/sdk/schema/Schema.java                                                 | 2531 +
 sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java                              |  175 
 sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java                |   67 
 sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java                           |  135 
 sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java                                 |   90 
 sdk/src/org/opends/sdk/ldif/ChangeRecord.java                                             |   71 
 sdk/src/org/opends/sdk/tools/ArgumentGroup.java                                           |  209 
 sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java                                    |  244 
 sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java                                        |  127 
 sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java               | 1077 
 sdk/src/org/opends/sdk/tools/Argument.java                                                |  808 
 sdk/src/org/opends/sdk/util/SubstringReader.java                                          |   84 
 sdk/src/org/opends/sdk/requests/UnbindRequest.java                                        |  119 
 sdk/src/org/opends/sdk/ldif/EntryReader.java                                              |   85 
 sdk/src/org/opends/sdk/util/Platform.java                                                 |  650 
 sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java                           |  150 
 sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java                                |  101 
 sdk/src/org/opends/sdk/controls/ServerSideSortControl.java                                |  549 
 sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java                                 |  215 
 sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java                                  |  230 
 sdk/src/org/opends/sdk/Assertion.java                                                     |   53 
 sdk/src/org/opends/sdk/ldap/ResolvedSchema.java                                           |   65 
 sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java                                    |  304 
 sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java                               |  285 
 sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java                               |  137 
 sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java                                     |  320 
 sdk/src/org/opends/sdk/controls/PreReadControl.java                                       |  447 
 sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java                               |  182 
 sdk/src/org/opends/sdk/sasl/SASLContext.java                                              |   77 
 sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java                                 |  212 
 sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java            |   85 
 sdk/src/org/opends/sdk/requests/Requests.java                                             |  699 
 sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java                      |  270 
 sdk/src/org/opends/sdk/RootDSE.java                                                       |  463 
 sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java                                 |  216 
 sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java                               |  132 
 sdk/src/org/opends/sdk/requests/AddRequest.java                                           |  341 
 sdk/src/org/opends/sdk/Attribute.java                                                     |  563 
 sdk/src/org/opends/sdk/schema/SchemaElement.java                                          |  185 
 sdk/src/org/opends/sdk/controls/PagedResultsControl.java                                  |  258 
 sdk/src/org/opends/sdk/responses/ExtendedResult.java                                      |  172 
 sdk/src/org/opends/sdk/schema/SyntaxImpl.java                                             |  144 
 sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java                                      |  475 
 sdk/src/org/opends/sdk/controls/GenericControl.java                                       |  157 
 sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java                      |  138 
 sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java                                 |   42 
 sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java                                  |  170 
 sdk/src/org/opends/sdk/tools/DataSource.java                                              |  481 
 sdk/src/org/opends/sdk/util/ByteSequenceReader.java                                       |  510 
 sdk/src/org/opends/sdk/AbstractEntry.java                                                 |  421 
 sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java                                       |  891 
 sdk/src/org/opends/sdk/util/ByteString.java                                               |  681 
 sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java                                  |   99 
 sdk/src/org/opends/sdk/Entry.java                                                         |  603 
 sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java                |  140 
 sdk/src/org/opends/sdk/ldif/EntryWriter.java                                              |  110 
 sdk/src/org/opends/sdk/schema/AttributeUsage.java                                         |  112 
 sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java                             |  231 
 sdk/src/org/opends/sdk/SearchScope.java                                                   |  207 
 sdk/src/org/opends/sdk/responses/SearchResultEntry.java                                   |  230 
 sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java               |  188 
 sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java                              |  203 
 sdk/src/org/opends/sdk/requests/CompareRequest.java                                       |  288 
 sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java                                  |   42 
 sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java                             |  205 
 sdk/src/org/opends/sdk/extensions/WhoAmIResult.java                                       |  113 
 sdk/src/org/opends/sdk/schema/SchemaLocal.java                                            |  130 
 sdk/src/org/opends/sdk/asn1/ASN1Reader.java                                               |  448 
 sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java                              |  122 
 sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java                                       |  185 
 sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java                  |  117 
 sdk/src/org/opends/sdk/ldap/SASLStreamReader.java                                         |  118 
 sdk/src/org/opends/sdk/controls/SortResult.java                                           |  113 
 sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java                               |  119 
 sdk/src/org/opends/sdk/Types.java                                                         |  985 
 sdk/src/org/opends/sdk/AsynchronousConnection.java                                        |  495 
 sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java                      |  167 
 sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java                    |  101 
 sdk/src/org/opends/sdk/util/StaticUtils.java                                              | 1998 +
 sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java                               |  377 
 sdk/src/org/opends/sdk/controls/PersistentSearchControl.java                              |  328 
 sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java                                 |  161 
 sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java                     |  133 
 sdk/src/org/opends/sdk/responses/SearchResultReference.java                               |  151 
 sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java                               |   73 
 sdk/src/org/opends/sdk/ErrorResultException.java                                          |  103 
 sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java                                       |  160 
 sdk/src/org/opends/sdk/util/Iterators.java                                                |  549 
 sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java              |  213 
 sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java                                      |   42 
 sdk/src/org/opends/sdk/requests/ModifyRequest.java                                        |  280 
 sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java                                    |  129 
 sdk/src/org/opends/sdk/responses/BindResultImpl.java                                      |  126 
 sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java                                         |  150 
 sdk/src/org/opends/sdk/LinkedAttribute.java                                               | 1063 
 sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java                                     |  415 
 sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java                           |  108 
 sdk/src/org/opends/sdk/SortedEntry.java                                                   |  295 
 sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java              |   62 
 sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java                        |   66 
 sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java                                      |  767 
 sdk/src/org/opends/sdk/util/Function.java                                                 |   58 
 sdk/src/org/opends/sdk/Filter.java                                                        | 1978 +
 sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java                     |  100 
 sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java                                 |  340 
 sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java                    |  137 
 sdk/src/org/opends/sdk/tools/ArgumentException.java                                       |   96 
 sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java                                    |  131 
 sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java                 |   59 
 sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java                           |  135 
 sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java                                |  412 
 sdk/src/org/opends/sdk/util/SSLUtils.java                                                 |   47 
 sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java                         |  940 
 sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java                          |  113 
 sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java                                |  106 
 sdk/src/org/opends/sdk/tools/Utils.java                                                   |  476 
 sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java                         |  295 
 sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java  |  140 
 sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java                 |  115 
 sdk/src/org/opends/sdk/ConnectionEventListener.java                                       |  109 
 sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java                                |  136 
 sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java                        |  184 
 sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java                                |   93 
 sdk/src/org/opends/sdk/schema/Syntax.java                                                 |  439 
 sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java                                    |  210 
 sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java                                       |   98 
 sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java                      |   83 
 sdk/src/org/opends/sdk/requests/GenericBindRequest.java                                   |  243 
 sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java                              |  278 
 sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java                                         | 1146 
 sdk/src/org/opends/sdk/requests/Request.java                                              |  124 
 sdk/src/org/opends/sdk/tools/LDAPSearch.java                                              | 1241 
 sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java                                     |  273 
 sdk/src/org/opends/sdk/controls/VLVTarget.java                                            |  191 
 sdk/src/org/opends/sdk/responses/Result.java                                              |  279 
 sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java                      |   89 
 sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java                                    |  133 
 sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java                               |   71 
 sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java                  |   98 
 sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java                           |  108 
 sdk/src/org/opends/sdk/responses/CompareResult.java                                       |  166 
 sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java                                    |  127 
 sdk/src/org/opends/sdk/controls/ControlDecoder.java                                       |   72 
 sdk/src/org/opends/sdk/ldap/LDAPUtils.java                                                |  738 
 sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java                   |   50 
 sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java                                          |  100 
 sdk/src/org/opends/sdk/sasl/SASLBindRequest.java                                          |   66 
 sdk/src/org/opends/sdk/tools/FileBasedArgument.java                                       |  283 
 sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java                                    |  156 
 sdk/src/org/opends/sdk/util/Functions.java                                                |  345 
 sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java                                     |   98 
 sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java                                |  137 
 sdk/src/org/opends/sdk/requests/SearchRequestImpl.java                                    |  416 
 sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java                              |  117 
 sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java                                    |   67 
 sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java                                      |  105 
 sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java                  |   60 
 sdk/src/org/opends/sdk/ResultHandler.java                                                 |   81 
 sdk/src/org/opends/sdk/controls/PostReadControl.java                                      |  447 
 sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java                             |  323 
 sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java                           |  144 
 sdk/src/org/opends/sdk/tools/BooleanArgument.java                                         |  125 
 sdk/src/org/opends/sdk/responses/CompareResultImpl.java                                   |   97 
 sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java                               |  172 
 sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java                    |   49 
 sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java                             |  140 
 sdk/src/org/opends/sdk/util/Validator.java                                                |  215 
 sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java                                |  412 
 sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java                                |  702 
 sdk/src/org/opends/sdk/requests/ModifyDNRequest.java                                      |  334 
 sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java                                       |  157 
 sdk/src/org/opends/sdk/controls/AssertionControl.java                                     |  201 
 sdk/src/org/opends/sdk/ldif/package-info.java                                             |   46 
 sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java                                   |  478 
 sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java                                      |  248 
 sdk/src/org/opends/sdk/schema/DITContentRule.java                                         |  623 
 sdk/src/org/opends/sdk/extensions/StartTLSRequest.java                                    |  120 
 sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java                                          |  108 
 sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java                                        |  430 
 sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java                                         |  675 
 sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java                                       |  190 
 sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java                                  |  244 
 sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java                                    |  789 
 sdk/src/org/opends/sdk/AbstractFilterVisitor.java                                         |  233 
 sdk/src/org/opends/sdk/ModificationType.java                                              |  212 
 sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java                              |   81 
 sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java                                      |  223 
 sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java                         |  137 
 sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java                              |  108 
 sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java                                    |  164 
 sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java               |   67 
 sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java                  |   59 
 sdk/src/org/opends/sdk/schema/ObjectClassType.java                                        |   82 
 sdk/src/org/opends/sdk/DN.java                                                            |  763 
 sdk/src/org/opends/sdk/requests/CompareRequestImpl.java                                   |  229 
 sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java                                     |   77 
 sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java                       |  104 
 sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java                          |   56 
 sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java                 |   71 
 sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java                |   50 
 sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java                                 |  112 
 sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java                                  |   80 
 sdk/src/org/opends/sdk/schema/ObjectClass.java                                            |  810 
 sdk/src/org/opends/sdk/ldap/LDAPConnection.java                                           | 1676 
 sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java                        |   64 
 sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java                              |  240 
 sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java                    |   49 
 sdk/src/org/opends/sdk/util/ByteSequence.java                                             |  348 
 sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java                              |   66 
 sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java                                          |  360 
 sdk/src/org/opends/sdk/requests/SearchRequest.java                                        |  527 
 sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java                              |  107 
 sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java                                         |   80 
 sdk/src/org/opends/sdk/package-info.java                                                  |   87 
 sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java                     |   83 
 sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java                        |  101 
 sdk/src/org/opends/sdk/SearchResultHandler.java                                           |   82 
 sdk/src/org/opends/sdk/schema/NameForm.java                                               |  452 
 sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java                            |  107 
 sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java                                   |  127 
 sdk/src/org/opends/sdk/ConditionResult.java                                               |  295 
 sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java                |   50 
 sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java                        |  236 
 sdk/src/org/opends/sdk/requests/BindRequest.java                                          |  137 
 sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java                     |   83 
 sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java                              |  217 
 sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java |  110 
 sdk/src/org/opends/sdk/requests/AddRequestImpl.java                                       |  387 
 sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java                           |  145 
 sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java                                  |  190 
 sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java                    |  271 
 sdk/src/org/opends/sdk/util/Base64.java                                                   |  388 
 sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java                       |  396 
 sdk/src/org/opends/sdk/ConnectionFuture.java                                              |  150 
 sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java                                |  184 
 sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java                                 |  177 
 sdk/src/org/opends/sdk/util/LocalizableException.java                                     |   48 
 sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java                                 |  436 
 sdk/src/org/opends/sdk/AbstractAttribute.java                                             |  512 
 sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java                               |  258 
 sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java                                   |  719 
 sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java                                      |  110 
 sdk/src/org/opends/sdk/controls/MatchedValuesControl.java                                 |  351 
 sdk/src/org/opends/sdk/AbstractConnectionFactory.java                                     |  125 
 sdk/src/org/opends/sdk/util/StringPrepProfile.java                                        |  696 
 sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java                                      |  228 
 sdk/src/org/opends/sdk/schema/CoreSchema.java                                             | 2666 +
 sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java                              |  128 
 sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java                              |  113 
 sdk/src/org/opends/sdk/schema/SchemaBuilder.java                                          | 3007 +
 sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java                                |  277 
 sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java                                       |  174 
 sdk/src/org/opends/sdk/tools/StringArgument.java                                          |  150 
 sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java                 |   67 
 sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java             | 1117 
 sdk/src/org/opends/sdk/DecodeException.java                                               |  154 
 sdk/src/org/opends/sdk/schema/MatchingRule.java                                           |  457 
 sdk/src/org/opends/sdk/ldap/LDAPConstants.java                                            |  332 
 sdk/src/org/opends/sdk/responses/ResultImpl.java                                          |   86 
 sdk/src/org/opends/sdk/controls/SortKey.java                                              |  144 
 sdk/src/org/opends/sdk/requests/DeleteRequest.java                                        |  190 
 sdk/src/org/opends/sdk/responses/BindResult.java                                          |  208 
 sdk/src/org/opends/sdk/schema/MatchingRuleUse.java                                        |  371 
 sdk/src/org/opends/sdk/asn1/ASN1Writer.java                                               |  401 
 sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java                              |   71 
 sdk/src/org/opends/sdk/responses/AbstractResultImpl.java                                  |  245 
 sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java                              |  691 
 sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java                                         |   87 
 sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java                   |   98 
 sdk/src/org/opends/sdk/ErrorResultIOException.java                                        |   75 
 sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java                                      |  110 
 sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java                                          |  432 
 sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java                                       |  545 
 sdk/src/org/opends/sdk/schema/AttributeType.java                                          |  908 
 sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java                        |   66 
 sdk/src/org/opends/sdk/ConnectionFactory.java                                             |   95 
 sdk/src/org/opends/sdk/ResultCode.java                                                    |  764 
 sdk/src/org/opends/sdk/util/ByteStringBuilder.java                                        | 1108 
 sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java                                   |  175 
 sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java                                    |  226 
 sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java                           |  184 
 sdk/src/org/opends/sdk/util/Iterables.java                                                |  397 
 sdk/src/org/opends/sdk/tools/ModRate.java                                                 |  431 
 sdk/src/org/opends/sdk/tools/PerformanceRunner.java                                       |  943 
 sdk/src/org/opends/sdk/ldap/SASLFilter.java                                               |  350 
 sdk/src/org/opends/sdk/controls/VLVResult.java                                            |  113 
 sdk/src/org/opends/sdk/util/ASCIICharProp.java                                            |  372 
 sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java                                   |  275 
 sdk/src/org/opends/sdk/Matcher.java                                                       |  871 
 sdk/src/org/opends/sdk/controls/Control.java                                              |   80 
 sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java                                         |   99 
 sdk/src/org/opends/sdk/ldap/LDAPEncoder.java                                              |  723 
 sdk/src/org/opends/sdk/requests/AbandonRequest.java                                       |  147 
 sdk/src/org/opends/sdk/asn1/ASN1Constants.java                                            |  167 
 sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java                               |  203 
 sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java                                     |  223 
 sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java                                |  636 
 sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java                                 |  259 
 sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java                          |  113 
 sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java                                         |  200 
 sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java                 |   96 
 sdk/src/org/opends/sdk/controls/VLVControl.java                                           |  654 
 sdk/src/org/opends/sdk/tools/SearchRate.java                                              |  519 
 sdk/src/org/opends/sdk/ConnectionResultHandler.java                                       |   81 
 sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java                      |   83 
 sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java                              |  250 
 sdk/src/org/opends/sdk/tools/LDAPModify.java                                              |  801 
 sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java            |   85 
 sdk/src/org/opends/sdk/tools/IntegerArgument.java                                         |  547 
 sdk/src/org/opends/sdk/util/SizeLimitInputStream.java                                     |  182 
 sdk/src/org/opends/sdk/Connection.java                                                    | 1084 
 sdk/src/org/opends/sdk/responses/Response.java                                            |  124 
 sdk/src/org/opends/sdk/ldap/LDAPDecoder.java                                              | 1922 +
 sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java                      |   58 
 sdk/src/org/opends/sdk/requests/SimpleBindRequest.java                                    |  254 
 sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java                                  |  107 
 sdk/src/org/opends/sdk/responses/Responses.java                                           |  278 
 sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java                                   |   95 
 sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java                                 |   64 
 sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java                              | 1463 
 sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java                                 |  113 
 sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java                                      |  519 
 sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java                                  |  214 
 sdk/src/org/opends/sdk/FilterVisitor.java                                                 |  238 
 sdk/src/org/opends/sdk/schema/SchemaException.java                                        |   89 
 sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java                                |  174 
 sdk/src/org/opends/sdk/schema/DITStructureRule.java                                       |  342 
 sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java              |   78 
 sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java                                         |  838 
 sdk/src/org/opends/sdk/extensions/ExtendedOperation.java                                  |   34 
 sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java                                       |  210 
 sdk/src/org/opends/sdk/RDN.java                                                           |  899 
 sdk/src/org/opends/sdk/Change.java                                                        |  126 
 sdk/src/org/opends/sdk/requests/ExtendedRequest.java                                      |  165 
 sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java                     |  271 
 sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java                                |  170 
 sdk/src/org/opends/sdk/schema/SchemaUtils.java                                            |  883 
 362 files changed, 114,483 insertions(+), 0 deletions(-)

diff --git a/sdk/src/org/opends/sdk/AbstractAttribute.java b/sdk/src/org/opends/sdk/AbstractAttribute.java
new file mode 100644
index 0000000..1e55080
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractAttribute.java
@@ -0,0 +1,512 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.*;
+
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.MatchingRule;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Function;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code
+ * Attribute} interface, to minimize the effort required to implement
+ * this interface.
+ */
+public abstract class AbstractAttribute extends AbstractSet<ByteString>
+    implements Attribute
+{
+
+  /**
+   * Returns {@code true} if {@code object} is an attribute which is
+   * equal to {@code attribute}. Two attributes are considered equal if
+   * their attribute descriptions are equal, they both have the same
+   * number of attribute values, and every attribute value contained in
+   * the first attribute is also contained in the second attribute.
+   *
+   * @param attribute
+   *          The attribute to be tested for equality.
+   * @param object
+   *          The object to be tested for equality with the attribute.
+   * @return {@code true} if {@code object} is an attribute which is
+   *         equal to {@code attribute}, or {@code false} if not.
+   */
+  static boolean equals(Attribute attribute, Object object)
+  {
+    if (attribute == object)
+    {
+      return true;
+    }
+
+    if (!(object instanceof Attribute))
+    {
+      return false;
+    }
+
+    Attribute other = (Attribute) object;
+    if (!attribute.getAttributeDescription().equals(
+        other.getAttributeDescription()))
+    {
+      return false;
+    }
+
+    // Attribute description is the same, compare values.
+    if (attribute.size() != other.size())
+    {
+      return false;
+    }
+
+    return attribute.containsAll(other);
+  }
+
+
+
+  /**
+   * Returns the hash code for {@code attribute}. It will be calculated
+   * as the sum of the hash codes of the attribute description and all
+   * of the attribute values.
+   *
+   * @param attribute
+   *          The attribute whose hash code should be calculated.
+   * @return The hash code for {@code attribute}.
+   */
+  static int hashCode(Attribute attribute)
+  {
+    int hashCode = attribute.getAttributeDescription().hashCode();
+    for (ByteString value : attribute)
+    {
+      hashCode += normalizeValue(attribute, value).hashCode();
+    }
+    return hashCode;
+  }
+
+
+
+  /**
+   * Returns the normalized form of {@code value} normalized using
+   * {@code attribute}'s equality matching rule.
+   *
+   * @param attribute
+   *          The attribute whose equality matching rule should be used
+   *          for normalization.
+   * @param value
+   *          The attribute value to be normalized.
+   * @return The normalized form of {@code value} normalized using
+   *         {@code attribute}'s equality matching rule.
+   */
+  static ByteString normalizeValue(Attribute attribute, ByteString value)
+  {
+    AttributeDescription attributeDescription = attribute
+        .getAttributeDescription();
+    AttributeType attributeType = attributeDescription
+        .getAttributeType();
+    MatchingRule matchingRule = attributeType.getEqualityMatchingRule();
+
+    try
+    {
+      return matchingRule.normalizeAttributeValue(value);
+    }
+    catch (Exception e)
+    {
+      // Fall back to provided value.
+      return value;
+    }
+  }
+
+
+
+  /**
+   * Returns a string representation of {@code attribute}.
+   *
+   * @param attribute
+   *          The attribute whose string representation should be
+   *          returned.
+   * @return The string representation of {@code attribute}.
+   */
+  static String toString(Attribute attribute)
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("Attribute(");
+    builder.append(attribute.getAttributeDescriptionAsString());
+    builder.append(", {");
+
+    boolean firstValue = true;
+    for (ByteString value : attribute)
+    {
+      if (!firstValue)
+      {
+        builder.append(", ");
+      }
+
+      builder.append(value);
+      firstValue = false;
+    }
+
+    builder.append("})");
+    return builder.toString();
+  }
+
+
+
+  /**
+   * Sole constructor.
+   */
+  protected AbstractAttribute()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract boolean add(ByteString value)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean add(Object firstValue, Object... remainingValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(firstValue);
+
+    boolean modified = add(ByteString.valueOf(firstValue));
+    if (remainingValues != null)
+    {
+      for (Object value : remainingValues)
+      {
+        modified |= add(ByteString.valueOf(value));
+      }
+    }
+    return modified;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAll(Collection<? extends ByteString> values)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return addAll(values, null);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAll(Collection<? extends ByteString> values,
+      Collection<? super ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    boolean modified = false;
+    for (ByteString value : values)
+    {
+      if (add(value))
+      {
+        modified = true;
+      }
+      else if (duplicateValues != null)
+      {
+        duplicateValues.add(value);
+      }
+    }
+    return modified;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract boolean contains(Object value)
+      throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAll(Collection<?> values)
+      throws NullPointerException
+  {
+    for (Object value : values)
+    {
+      if (!contains(value))
+      {
+        return false;
+      }
+    }
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object object)
+  {
+    return equals(this, object);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString firstValue() throws NoSuchElementException
+  {
+    return iterator().next();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <T> T firstValueAsObject(
+      Function<? super ByteString, T, Void> type)
+      throws NoSuchElementException
+  {
+    return type.apply(firstValue(), null);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <T, P> T firstValueAsObject(
+      Function<? super ByteString, T, P> type, P p)
+      throws NoSuchElementException
+  {
+    return type.apply(firstValue(), p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String firstValueAsString() throws NoSuchElementException
+  {
+    return firstValue().toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract AttributeDescription getAttributeDescription();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getAttributeDescriptionAsString()
+  {
+    return getAttributeDescription().toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    return hashCode(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract Iterator<ByteString> iterator();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract boolean remove(Object value)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAll(Collection<?> values)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return removeAll(values, null);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <T> boolean removeAll(Collection<T> values,
+      Collection<? super T> missingValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    boolean modified = false;
+    for (T value : values)
+    {
+      if (remove(value))
+      {
+        modified = true;
+      }
+      else if (missingValues != null)
+      {
+        missingValues.add(value);
+      }
+    }
+    return modified;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean retainAll(Collection<?> values)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return retainAll(values, null);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <T> boolean retainAll(Collection<T> values,
+      Collection<? super T> missingValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    if (values.isEmpty())
+    {
+      if (isEmpty())
+      {
+        return false;
+      }
+      else
+      {
+        clear();
+        return true;
+      }
+    }
+
+    if (isEmpty())
+    {
+      if (missingValues != null)
+      {
+        for (T value : values)
+        {
+          missingValues.add(value);
+        }
+      }
+      return false;
+    }
+
+    Map<ByteString, T> valuesToRetain = new HashMap<ByteString, T>(
+        values.size());
+    for (T value : values)
+    {
+      valuesToRetain.put(
+          normalizeValue(this, ByteString.valueOf(value)), value);
+    }
+
+    boolean modified = false;
+    Iterator<ByteString> iterator = iterator();
+    while (iterator.hasNext())
+    {
+      ByteString value = iterator.next();
+      ByteString normalizedValue = normalizeValue(this, value);
+      if (valuesToRetain.remove(normalizedValue) == null)
+      {
+        modified = true;
+        iterator.remove();
+      }
+    }
+
+    if (missingValues != null)
+    {
+      missingValues.addAll(valuesToRetain.values());
+    }
+
+    return modified;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract int size();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString[] toArray()
+  {
+    return toArray(new ByteString[size()]);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return toString(this);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/AbstractConnection.java b/sdk/src/org/opends/sdk/AbstractConnection.java
new file mode 100644
index 0000000..58d7e51
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractConnection.java
@@ -0,0 +1,315 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code
+ * Connection} interface, to minimize the effort required to implement
+ * this interface.
+ */
+public abstract class AbstractConnection implements Connection
+{
+
+  /**
+   * Creates a new abstract connection.
+   */
+  protected AbstractConnection()
+  {
+    // No implementation required.
+  }
+
+
+
+  private static final class SingleEntryHandler implements
+      SearchResultHandler<Void>
+  {
+    // FIXME: does this need to be thread safe?
+    private SearchResultEntry firstEntry = null;
+
+    private SearchResultReference firstReference = null;
+
+    private int entryCount = 0;
+
+
+
+    public void handleReference(Void p, SearchResultReference reference)
+    {
+      if (firstReference == null)
+      {
+        firstReference = reference;
+      }
+    }
+
+
+
+    public void handleEntry(Void p, SearchResultEntry entry)
+    {
+      if (firstEntry == null)
+      {
+        firstEntry = entry;
+      }
+      entryCount++;
+    }
+
+  }
+
+
+
+  public Result add(Entry entry) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    return add(Requests.newAddRequest(entry));
+  }
+
+
+
+  public Result add(String... ldifLines) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      LocalizedIllegalArgumentException, IllegalStateException,
+      NullPointerException
+  {
+    return add(Requests.newAddRequest(ldifLines));
+  }
+
+
+
+  public BindResult bind(String name, String password)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    return bind(Requests.newSimpleBindRequest(name, password));
+  }
+
+
+
+  public CompareResult compare(String name,
+      String attributeDescription, String assertionValue)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    return compare(Requests.newCompareRequest(name,
+        attributeDescription, assertionValue));
+  }
+
+
+
+  public Result delete(String name) throws ErrorResultException,
+      InterruptedException, LocalizedIllegalArgumentException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    return delete(Requests.newDeleteRequest(name));
+  }
+
+
+
+  public GenericExtendedResult extendedRequest(String requestName,
+      ByteString requestValue) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    return extendedRequest(Requests.newGenericExtendedRequest(
+        requestName, requestValue));
+  }
+
+
+
+  public Result modify(String... ldifLines)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, LocalizedIllegalArgumentException,
+      IllegalStateException, NullPointerException
+  {
+    return modify(Requests.newModifyRequest(ldifLines));
+  }
+
+
+
+  public Result modifyDN(String name, String newRDN)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    return modifyDN(Requests.newModifyDNRequest(name, newRDN));
+  }
+
+
+
+  public Result search(SearchRequest request,
+      final Collection<? super SearchResultEntry> entries,
+      final Collection<? super SearchResultReference> references)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(request, entries);
+
+    // FIXME: does this need to be thread safe?
+    SearchResultHandler<Void> handler = new SearchResultHandler<Void>()
+    {
+
+      public void handleReference(Void p,
+          SearchResultReference reference)
+      {
+        if (references != null)
+        {
+          references.add(reference);
+        }
+      }
+
+
+
+      public void handleEntry(Void p, SearchResultEntry entry)
+      {
+        entries.add(entry);
+      }
+    };
+
+    return search(request, handler, null);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Result search(SearchRequest request,
+      Collection<? super SearchResultEntry> entries)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    return search(request, entries, null);
+  }
+
+
+
+  public List<SearchResultEntry> search(String baseObject,
+      SearchScope scope, String filter, String... attributeDescriptions)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    List<SearchResultEntry> entries = new LinkedList<SearchResultEntry>();
+    SearchRequest request = Requests.newSearchRequest(baseObject,
+        scope, filter, attributeDescriptions);
+    search(request, entries);
+    return entries;
+  }
+
+
+
+  public SearchResultEntry searchSingleEntry(SearchRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    SingleEntryHandler handler = new SingleEntryHandler();
+    search(request, handler, null);
+    if (handler.entryCount > 1)
+    {
+      // Got more entries than expected.
+      Result result = Responses.newResult(
+          ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+          ERR_UNEXPECTED_SEARCH_RESULT_ENTRIES.get(handler.entryCount)
+              .toString());
+      throw new ErrorResultException(result);
+    }
+    else if (handler.firstReference != null)
+    {
+      // Got an unexpected search result reference.
+      Result result = Responses.newResult(
+          ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+          ERR_UNEXPECTED_SEARCH_RESULT_REFERENCES.get(
+              handler.firstReference.getURIs().iterator().next())
+              .toString());
+      throw new ErrorResultException(result);
+    }
+    else
+    {
+      return handler.firstEntry;
+    }
+  }
+
+
+
+  public SearchResultEntry searchSingleEntry(String baseObject,
+      SearchScope scope, String filter, String... attributeDescriptions)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    SearchRequest request = Requests.newSearchRequest(baseObject,
+        scope, filter, attributeDescriptions);
+    return searchSingleEntry(request);
+  }
+
+
+
+  public SearchResultEntry readEntry(String baseObject,
+      String... attributeDescriptions) throws ErrorResultException,
+      InterruptedException, LocalizedIllegalArgumentException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    return readEntry(DN.valueOf(baseObject));
+  }
+
+
+
+  public SearchResultEntry readEntry(DN baseObject,
+      String... attributeDescriptions) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    SearchRequest request = Requests.newSearchRequest(baseObject,
+        SearchScope.BASE_OBJECT, Filter.getObjectClassPresentFilter(),
+        attributeDescriptions);
+    return searchSingleEntry(request);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/AbstractConnectionFactory.java b/sdk/src/org/opends/sdk/AbstractConnectionFactory.java
new file mode 100644
index 0000000..f80baf0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractConnectionFactory.java
@@ -0,0 +1,125 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code
+ * ConnectionFactory} interface, to minimize the effort required to
+ * implement this interface.
+ *
+ * @param <C>
+ *          The type of asynchronous connection returned by this
+ *          connection factory.
+ */
+public abstract class AbstractConnectionFactory<C extends AsynchronousConnection>
+    implements ConnectionFactory<C>
+{
+  /**
+   * Creates a new abstract connection factory.
+   */
+  protected AbstractConnectionFactory()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract <P> ConnectionFuture<? extends C> getAsynchronousConnection(
+      ConnectionResultHandler<? super C, P> handler, P p);
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to convert the asynchronous
+   * connection returned from {@code
+   * blockingGetAsynchronousConnection()} to a synchronous connection
+   * using a {@link SynchronousConnection} as per the following code:
+   *
+   * <pre>
+   * return new SynchronousConnection(blockingGetAsynchronousConnection());
+   * </pre>
+   *
+   * Implementations should override this method if they wish to return
+   * a different type of synchronous connection.
+   *
+   * @return A connection to the Directory Server associated with this
+   *         connection factory.
+   * @throws ErrorResultException
+   *           If the connection request failed for some reason.
+   */
+  public Connection getConnection() throws ErrorResultException
+  {
+    return new SynchronousConnection(
+        blockingGetAsynchronousConnection());
+  }
+
+
+
+  /**
+   * Invokes {@code getAsynchronousConnection}, blocking until the
+   * asynchronous connection is obtained or the attempt fails.
+   *
+   * @return An asynchronous connection obtained using {@code
+   *         getAsynchronousConnection}.
+   * @throws ErrorResultException
+   *           If the connection request failed for some reason.
+   */
+  protected final C blockingGetAsynchronousConnection()
+      throws ErrorResultException
+  {
+    ConnectionFuture<? extends C> future =
+        getAsynchronousConnection(null, null);
+    try
+    {
+      return future.get();
+    }
+    catch (InterruptedException e)
+    {
+      // Cancel the request if possible.
+      future.cancel(false);
+
+      Result result =
+          Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR)
+              .setCause(e)
+              .setDiagnosticMessage(e.getLocalizedMessage());
+      throw new ErrorResultException(result);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/AbstractEntry.java b/sdk/src/org/opends/sdk/AbstractEntry.java
new file mode 100644
index 0000000..3732bc5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractEntry.java
@@ -0,0 +1,421 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * This class provides a skeletal implementation of the {@code Entry}
+ * interface, to minimize the effort required to implement this
+ * interface.
+ */
+public abstract class AbstractEntry implements Entry
+{
+
+  // Function used for getObjectClasses
+  private static final Function<ByteString, String, Void> BYTE_STRING_TO_STRING_FUNCTION = new Function<ByteString, String, Void>()
+  {
+
+    public String apply(ByteString value, Void p)
+    {
+      return value.toString();
+    }
+
+  };
+
+  // Predicate used for findAttributes.
+  private static final Predicate<Attribute, AttributeDescription> FIND_ATTRIBUTES_PREDICATE = new Predicate<Attribute, AttributeDescription>()
+  {
+
+    public boolean matches(Attribute value, AttributeDescription p)
+    {
+      return value.getAttributeDescription().isSubTypeOf(p);
+    }
+
+  };
+
+
+
+  /**
+   * Returns {@code true} if {@code object} is an entry which is equal
+   * to {@code entry}. Two entry are considered equal if their
+   * distinguished names are equal, they both have the same number of
+   * attributes, and every attribute contained in the first entry is
+   * also contained in the second entry.
+   *
+   * @param entry
+   *          The entry to be tested for equality.
+   * @param object
+   *          The object to be tested for equality with the entry.
+   * @return {@code true} if {@code object} is an entry which is equal
+   *         to {@code entry}, or {@code false} if not.
+   */
+  static boolean equals(Entry entry, Object object)
+  {
+    if (entry == object)
+    {
+      return true;
+    }
+
+    if (!(object instanceof Entry))
+    {
+      return false;
+    }
+
+    Entry other = (Entry) object;
+    if (!entry.getName().equals(other.getName()))
+    {
+      return false;
+    }
+
+    // Distinguished name is the same, compare attributes.
+    if (entry.getAttributeCount() != other.getAttributeCount())
+    {
+      return false;
+    }
+
+    for (Attribute attribute : entry.getAttributes())
+    {
+      Attribute otherAttribute = other.getAttribute(attribute
+          .getAttributeDescription());
+
+      if (!attribute.equals(otherAttribute))
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  /**
+   * Returns the hash code for {@code entry}. It will be calculated as
+   * the sum of the hash codes of the distinguished name and all of the
+   * attributes.
+   *
+   * @param entry
+   *          The entry whose hash code should be calculated.
+   * @return The hash code for {@code entry}.
+   */
+  static int hashCode(Entry entry)
+  {
+    int hashCode = entry.getName().hashCode();
+    for (Attribute attribute : entry.getAttributes())
+    {
+      hashCode += attribute.hashCode();
+    }
+    return hashCode;
+  }
+
+
+
+  /**
+   * Returns a string representation of {@code entry}.
+   *
+   * @param entry
+   *          The entry whose string representation should be returned.
+   * @return The string representation of {@code entry}.
+   */
+  static String toString(Entry entry)
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("Entry(");
+    builder.append(entry.getName());
+    builder.append(", {");
+
+    boolean firstValue = true;
+    for (Attribute attribute : entry.getAttributes())
+    {
+      if (!firstValue)
+      {
+        builder.append(", ");
+      }
+
+      builder.append(attribute);
+      firstValue = false;
+    }
+
+    builder.append("})");
+    return builder.toString();
+  }
+
+
+
+  /**
+   * Sole constructor.
+   */
+  protected AbstractEntry()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return addAttribute(attribute, null);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry addAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    addAttribute(new LinkedAttribute(attributeDescription, values), null);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return containsAttribute(AttributeDescription
+        .valueOf(attributeDescription));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsObjectClass(ObjectClass objectClass)
+      throws NullPointerException
+  {
+    return containsObjectClass(objectClass.getOID());
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsObjectClass(String objectClass)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(objectClass);
+
+    Attribute attribute = getAttribute(AttributeDescription
+        .objectClass());
+    return attribute != null ? attribute.contains(objectClass) : false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object object)
+  {
+    return equals(this, object);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> findAttributes(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+
+    return Iterables.filter(getAttributes(), FIND_ATTRIBUTES_PREDICATE,
+        attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> findAttributes(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return findAttributes(AttributeDescription
+        .valueOf(attributeDescription));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Attribute getAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return getAttribute(AttributeDescription
+        .valueOf(attributeDescription));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<String> getObjectClasses()
+  {
+    Attribute attribute = getAttribute(AttributeDescription
+        .objectClass());
+
+    if (attribute == null)
+    {
+      return Iterables.empty();
+    }
+    else
+    {
+      return Iterables.transform(attribute,
+          BYTE_STRING_TO_STRING_FUNCTION);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    return hashCode(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(
+      AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return removeAttribute(Types.emptyAttribute(attributeDescription),
+        null);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry removeAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    removeAttribute(new LinkedAttribute(attributeDescription), null);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry removeAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    removeAttribute(new LinkedAttribute(attributeDescription, values),
+        null);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean replaceAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    if (attribute.isEmpty())
+    {
+      return removeAttribute(attribute.getAttributeDescription());
+    }
+    else
+    {
+      removeAttribute(attribute.getAttributeDescription());
+      addAttribute(attribute);
+      return true;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry replaceAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    replaceAttribute(new LinkedAttribute(attributeDescription, values));
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    return setName(DN.valueOf(dn));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    return toString(this);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/AbstractFilterVisitor.java b/sdk/src/org/opends/sdk/AbstractFilterVisitor.java
new file mode 100644
index 0000000..7a62823
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AbstractFilterVisitor.java
@@ -0,0 +1,233 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.List;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+/**
+ * An abstract filter visitor whose default implementation for all
+ * {@code Visitor} methods is to invoke
+ * {@link #visitDefaultFilter(Object)}.
+ * <p>
+ * Implementations can override the methods on a case by case behavior.
+ *
+ * @param <R>
+ *          The return type of this visitor's methods. Use
+ *          {@link java.lang.Void} for visitors that do not need to
+ *          return results.
+ * @param <P>
+ *          The type of the additional parameter to this visitor's
+ *          methods. Use {@link java.lang.Void} for visitors that do not
+ *          need an additional parameter.
+ */
+public abstract class AbstractFilterVisitor<R, P> implements
+    FilterVisitor<R, P>
+{
+
+  /**
+   * Default constructor.
+   */
+  protected AbstractFilterVisitor()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitAndFilter(P p, List<Filter> subFilters)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitApproxMatchFilter(P p, String attributeDescription,
+      ByteSequence assertionValue)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * Visits any filters which are not explicitly handled by other
+   * visitor methods.
+   * <p>
+   * The default implementation of this method is to return {@code null}.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @return A visitor specified result.
+   */
+  public R visitDefaultFilter(P p)
+  {
+    return null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitEqualityMatchFilter(P p, String attributeDescription,
+      ByteSequence assertionValue)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitExtensibleMatchFilter(P p, String matchingRule,
+      String attributeDescription, ByteSequence assertionValue,
+      boolean dnAttributes)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitGreaterOrEqualFilter(P p, String attributeDescription,
+      ByteSequence assertionValue)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitLessOrEqualFilter(P p, String attributeDescription,
+      ByteSequence assertionValue)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitNotFilter(P p, Filter subFilter)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitOrFilter(P p, List<Filter> subFilters)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitPresentFilter(P p, String attributeDescription)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitSubstringsFilter(P p, String attributeDescription,
+      ByteSequence initialSubstring, List<ByteSequence> anySubstrings,
+      ByteSequence finalSubstring)
+  {
+    return visitDefaultFilter(p);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   * <p>
+   * The default implementation is to call
+   * {@link #visitDefaultFilter(Object)}.
+   */
+  public R visitUnrecognizedFilter(P p, byte filterTag,
+      ByteSequence filterBytes)
+  {
+    return visitDefaultFilter(p);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/Assertion.java b/sdk/src/org/opends/sdk/Assertion.java
new file mode 100644
index 0000000..96d052b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Assertion.java
@@ -0,0 +1,53 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * A compiled attribute value assertion.
+ */
+public interface Assertion
+{
+  /**
+   * Indicates whether the provided attribute value should be considered
+   * a match for this assertion value according to the matching rule.
+   *
+   * @param attributeValue
+   *          The attribute value.
+   * @return {@code TRUE} if the attribute value should be considered a
+   *         match for the provided assertion value, {@code FALSE} if it
+   *         does not match, or {@code UNDEFINED} if the result is
+   *         undefined.
+   */
+  public abstract ConditionResult matches(ByteSequence attributeValue);
+}
diff --git a/sdk/src/org/opends/sdk/AsynchronousConnection.java b/sdk/src/org/opends/sdk/AsynchronousConnection.java
new file mode 100644
index 0000000..ac0837c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AsynchronousConnection.java
@@ -0,0 +1,495 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.Closeable;
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+
+
+
+/**
+ * An asynchronous connection with a Directory Server over which read
+ * and update operations may be performed. See RFC 4511 for the LDAPv3
+ * protocol specification and more information about the types of
+ * operations defined in LDAP.
+ * <p>
+ * <h3>Operation processing</h3>
+ * <p>
+ * All operations are performed asynchronously and return a
+ * {@link ResultFuture} or sub-type thereof which can be used for
+ * retrieving the result using the {@link ResultFuture#get} method.
+ * Operation failures, for whatever reason, are signalled by the
+ * {@link ResultFuture#get()} method throwing an
+ * {@link ErrorResultException}.
+ * <p>
+ * Synchronous operations are easily simulated by immediately getting
+ * the result:
+ *
+ * <pre>
+ * Connection connection = ...;
+ * AddRequest request = ...;
+ * // Will block until operation completes, and
+ * // throws exception on failure.
+ * connection.add(request).get();
+ * </pre>
+ *
+ * Operations can be performed in parallel while taking advantage of the
+ * simplicity of a synchronous application design:
+ *
+ * <pre>
+ * Connection connection1 = ...;
+ * Connection connection2 = ...;
+ * AddRequest request = ...;
+ * // Add the entry to the first server (don't block).
+ * ResultFuture future1 = connection1.add(request);
+ * // Add the entry to the second server (in parallel).
+ * ResultFuture future2 = connection2.add(request);
+ * // Total time = is O(1) instead of O(n).
+ * future1.get();
+ * future2.get();
+ * </pre>
+ *
+ * More complex client applications can take advantage of a fully
+ * asynchronous event driven design using {@link ResultHandler}s:
+ *
+ * <pre>
+ * Connection connection = ...;
+ * SearchRequest request = ...;
+ * // Process results in the search result handler
+ * // in a separate thread.
+ * SearchResponseHandler handle = ...;
+ * connection.search(request, handler);
+ * </pre>
+ * <p>
+ * <h3>Closing connections</h3>
+ * <p>
+ * Applications must ensure that a connection is closed by calling
+ * {@link #close()} even if a fatal error occurs on the connection. Once
+ * a connection has been closed by the client application, any attempts
+ * to continue to use the connection will result in an
+ * {@link IllegalStateException} being thrown. Note that, if a fatal
+ * error is encountered on the connection, then the application can
+ * continue to use the connection. In this case all requests subsequent
+ * to the failure will fail with an appropriate
+ * {@link ErrorResultException} when their result is retrieved.
+ * <p>
+ * <h3>Event notification</h3>
+ * <p>
+ * Applications can choose to be notified when a connection is closed by
+ * the application, receives an unsolicited notification, or experiences
+ * a fatal error by registering a {@link ConnectionEventListener} with
+ * the connection using the {@link #addConnectionEventListener} method.
+ * <p>
+ * <h3>TO DO</h3>
+ * <p>
+ * <ul>
+ * <li>do we need isClosed() and isValid()?
+ * <li>do we need connection event notification of client close? JDBC
+ * and JCA have this functionality in their pooled (managed) connection
+ * APIs. We need some form of event notification at the app level for
+ * unsolicited notifications.
+ * <li>method for performing update operation (e.g. LDIF change
+ * records).
+ * <li>should unsupported methods throw UnsupportedOperationException or
+ * throw an ErrorResultException using an UnwillingToPerform result code
+ * (or something similar)?
+ * <li>Implementations should indicate whether or not they are thread
+ * safe and support concurrent requests.
+ * </ul>
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 -
+ *      Lightweight Directory Access Protocol (LDAP): The Protocol </a>
+ */
+public interface AsynchronousConnection extends Closeable
+{
+
+  /**
+   * Abandons the unfinished operation identified in the provided
+   * abandon request.
+   * <p>
+   * <b>Note:</b> a more convenient approach to abandoning unfinished
+   * operations is provided via the {@link ResultFuture#cancel(boolean)}
+   * method.
+   *
+   * @param request
+   *          The request identifying the operation to be abandoned.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support abandon operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  void abandon(AbandonRequest request)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Adds an entry to the Directory Server using the provided add
+   * request.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The add request.
+   * @param handler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support add operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> ResultFuture<Result> add(AddRequest request,
+      ResultHandler<Result, P> handler, P p)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Authenticates to the Directory Server using the provided bind
+   * request.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The bind request.
+   * @param handler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support bind operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> ResultFuture<BindResult> bind(BindRequest request,
+      ResultHandler<? super BindResult, P> handler, P p)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Releases any resources associated with this connection. For
+   * physical connections to a Directory Server this will mean that an
+   * unbind request is sent and the underlying socket is closed.
+   * <p>
+   * Other connection implementations may behave differently, and may
+   * choose not to send an unbind request if its use is inappropriate
+   * (for example a pooled connection will be released and returned to
+   * its connection pool without ever issuing an unbind request).
+   * <p>
+   * This method is semantically equivalent to the following code:
+   *
+   * <pre>
+   * UnbindRequest request = Requests.newUnbindRequest();
+   * connection.close(request);
+   * </pre>
+   *
+   * Calling {@code close} on a connection that is already closed has no
+   * effect.
+   */
+  void close();
+
+
+
+  /**
+   * Releases any resources associated with this connection. For
+   * physical connections to a Directory Server this will mean that the
+   * provided unbind request is sent and the underlying socket is
+   * closed.
+   * <p>
+   * Other connection implementations may behave differently, and may
+   * choose to ignore the provided unbind request if its use is
+   * inappropriate (for example a pooled connection will be released and
+   * returned to its connection pool without ever issuing an unbind
+   * request).
+   * <p>
+   * Calling {@code close} on a connection that is already closed has no
+   * effect.
+   *
+   * @param request
+   *          The unbind request to use in the case where a physical
+   *          connection is closed.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  void close(UnbindRequest request) throws NullPointerException;
+
+
+
+  /**
+   * Compares an entry in the Directory Server using the provided
+   * compare request.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The compare request.
+   * @param handler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support compare operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> ResultFuture<CompareResult> compare(CompareRequest request,
+      ResultHandler<? super CompareResult, P> handler, P p)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Deletes an entry from the Directory Server using the provided
+   * delete request.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The delete request.
+   * @param handler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support delete operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> ResultFuture<Result> delete(DeleteRequest request,
+      ResultHandler<Result, P> handler, P p)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Requests that the Directory Server performs the provided extended
+   * request.
+   *
+   * @param <R>
+   *          The type of result returned by the extended request.
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The extended request.
+   * @param handler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support extended operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <R extends Result, P> ResultFuture<R> extendedRequest(
+      ExtendedRequest<R> request, ResultHandler<? super R, P> handler,
+      P p) throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Modifies an entry in the Directory Server using the provided modify
+   * request.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The modify request.
+   * @param handler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support modify operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> ResultFuture<Result> modify(ModifyRequest request,
+      ResultHandler<Result, P> handler, P p)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Renames an entry in the Directory Server using the provided modify
+   * DN request.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The modify DN request.
+   * @param handler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support modify DN operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> ResultFuture<Result> modifyDN(ModifyDNRequest request,
+      ResultHandler<Result, P> handler, P p)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Searches the Directory Server using the provided search request.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The search request.
+   * @param resultHandler
+   *          A result handler which can be used to asynchronously
+   *          process the operation result when it is received, may be
+   *          {@code null}.
+   * @param searchResulthandler
+   *          A search result handler which can be used to
+   *          asynchronously process the search result entries and
+   *          references as they are received, may be {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future representing the result of the operation.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> ResultFuture<Result> search(SearchRequest request,
+      ResultHandler<Result, P> resultHandler,
+      SearchResultHandler<P> searchResulthandler, P p)
+      throws UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Registers the provided connection event listener so that it will be
+   * notified when this connection is closed by the application,
+   * receives an unsolicited notification, or experiences a fatal error.
+   *
+   * @param listener
+   *          The listener which wants to be notified when events occur
+   *          on this connection.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If the {@code listener} was {@code null}.
+   */
+  void addConnectionEventListener(ConnectionEventListener listener)
+      throws IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Removes the provided connection event listener from this connection
+   * so that it will no longer be notified when this connection is
+   * closed by the application, receives an unsolicited notification, or
+   * experiences a fatal error.
+   *
+   * @param listener
+   *          The listener which no longer wants to be notified when
+   *          events occur on this connection.
+   * @throws NullPointerException
+   *           If the {@code listener} was {@code null}.
+   */
+  void removeConnectionEventListener(ConnectionEventListener listener)
+      throws NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/Attribute.java b/sdk/src/org/opends/sdk/Attribute.java
new file mode 100644
index 0000000..d76a1da
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Attribute.java
@@ -0,0 +1,563 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Function;
+
+
+
+/**
+ * An attribute, comprising of an attribute description and zero or more
+ * attribute values.
+ * <p>
+ * Any methods which perform comparisons between attribute values use
+ * the equality matching rule associated with the attribute description.
+ * <p>
+ * Any methods which accept {@code Object} based attribute values
+ * convert the attribute values to instances of {@code ByteString} as
+ * follows:
+ *
+ * <pre>
+ * Object object = ...;
+ * ByteString value = null;
+ * if (object instanceof ByteSequence)
+ * {
+ *   value = ((ByteSequence)object).toByteString();
+ * }
+ * else
+ * {
+ *   value = ByteString.valueOf(object.toString());
+ * }
+ * </pre>
+ * <p>
+ * TODO: matching against attribute value assertions.
+ */
+public interface Attribute extends Set<ByteString>
+{
+  /**
+   * Adds {@code value} to this attribute if it is not already present
+   * (optional operation). If this attribute already contains {@code
+   * value}, the call leaves the attribute unchanged and returns {@code
+   * false}.
+   *
+   * @param value
+   *          The attribute value to be added to this attribute.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support addition of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code value} was {@code null}.
+   */
+  boolean add(ByteString value) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Adds all of the provided attribute values to this attribute if they
+   * are not already present (optional operation).
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param firstValue
+   *          The first attribute value to be added to this attribute.
+   * @param remainingValues
+   *          The remaining attribute values to be added to this
+   *          attribute.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support addition of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code firstValue} was {@code null}.
+   */
+  boolean add(Object firstValue, Object... remainingValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds all of the attribute values contained in {@code values} to
+   * this attribute if they are not already present (optional
+   * operation).
+   * <p>
+   * An invocation of this method is equivalent to:
+   *
+   * <pre>
+   * attribute.addAll(values, null);
+   * </pre>
+   *
+   * @param values
+   *          The attribute values to be added to this attribute.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support addition of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code values} was {@code null}.
+   */
+  boolean addAll(Collection<? extends ByteString> values)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds all of the attribute values contained in {@code values} to
+   * this attribute if they are not already present (optional
+   * operation). Any attribute values which are already present will be
+   * added to {@code duplicateValues} if specified.
+   *
+   * @param values
+   *          The attribute values to be added to this attribute.
+   * @param duplicateValues
+   *          A collection into which duplicate values will be added, or
+   *          {@code null} if duplicate values should not be saved.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support addition of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code values} was {@code null}.
+   */
+  boolean addAll(Collection<? extends ByteString> values,
+      Collection<? super ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all of the attribute values from this attribute (optional
+   * operation). This attribute will be empty after this call returns.
+   */
+  void clear() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns {@code true} if this attribute contains {@code value}.
+   * <p>
+   * If {@code value} is not an instance of {@code ByteString} then it
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param value
+   *          The attribute value whose presence in this attribute is to
+   *          be tested.
+   * @return {@code true} if this attribute contains {@code value}, or
+   *         {@code false} if not.
+   * @throws NullPointerException
+   *           If {@code value} was {@code null}.
+   */
+  boolean contains(Object value) throws NullPointerException;
+
+
+
+  /**
+   * Returns {@code true} if this attribute contains all of the
+   * attribute values contained in {@code values}.
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param values
+   *          The attribute values whose presence in this attribute is
+   *          to be tested.
+   * @return {@code true} if this attribute contains all of the
+   *         attribute values contained in {@code values}, or {@code
+   *         false} if not.
+   * @throws NullPointerException
+   *           If {@code values} was {@code null}.
+   */
+  boolean containsAll(Collection<?> values) throws NullPointerException;
+
+
+
+  /**
+   * Returns {@code true} if {@code object} is an attribute which is
+   * equal to this attribute. Two attributes are considered equal if
+   * their attribute descriptions are equal, they both have the same
+   * number of attribute values, and every attribute value contained in
+   * the first attribute is also contained in the second attribute.
+   *
+   * @param object
+   *          The object to be tested for equality with this attribute.
+   * @return {@code true} if {@code object} is an attribute which is
+   *         equal to this attribute, or {@code false} if not.
+   */
+  boolean equals(Object object);
+
+
+
+  /**
+   * Returns the first attribute value in this attribute.
+   *
+   * @return The first attribute value in this attribute.
+   * @throws NoSuchElementException
+   *           If this attribute is empty.
+   */
+  ByteString firstValue() throws NoSuchElementException;
+
+
+
+  /**
+   * Returns the first attribute value in this attribute converted to a
+   * object of type {@code T} using the function {@code type}. Any
+   * run-time exceptions thrown during the conversion will be passed
+   * back to the caller (e.g. {@code IllegalArgumentException}).
+   *
+   * @param <T>
+   *          The type of object to decode the first value as.
+   * @param type
+   *          The function to use for decoding the first attribute value
+   *          as a type {@code T}.
+   * @return The first attribute value in this attribute.
+   * @throws NoSuchElementException
+   *           If this attribute is empty.
+   * @throws NullPointerException
+   *           If {@code type} was {@code null}.
+   */
+  <T> T firstValueAsObject(Function<? super ByteString, T, Void> type)
+      throws NoSuchElementException;
+
+
+
+  /**
+   * Returns the first attribute value in this attribute converted to a
+   * object of type {@code T} using the function {@code type} and
+   * passing parameter {@code p}. Any run-time exceptions thrown during
+   * the conversion will be passed back to the caller (e.g. {@code
+   * IllegalArgumentException}).
+   *
+   * @param <T>
+   *          The type of object to decode the first value as.
+   * @param <P>
+   *          The type of the additional parameter to {@code type}'s
+   *          {@code apply} method. Use {@link java.lang.Void} for
+   *          functions that do not need an additional parameter.
+   * @param type
+   *          The function to use for decoding the first attribute value
+   *          as a type {@code T}.
+   * @param p
+   *          The parameter to pass to {@code type}.
+   * @return The first attribute value in this attribute.
+   * @throws NoSuchElementException
+   *           If this attribute is empty.
+   * @throws NullPointerException
+   *           If {@code type} was {@code null}.
+   */
+  <T, P> T firstValueAsObject(Function<? super ByteString, T, P> type,
+      P p) throws NoSuchElementException;
+
+
+
+  /**
+   * Returns the first attribute value in this attribute decoded as a
+   * UTF-8 string.
+   *
+   * @return The first attribute value in this attribute decoded as a
+   *         UTF-8 string.
+   * @throws NoSuchElementException
+   *           If this attribute is empty.
+   */
+  String firstValueAsString() throws NoSuchElementException;
+
+
+
+  /**
+   * Returns the attribute description of this attribute, which includes
+   * its attribute type and any options.
+   *
+   * @return The attribute description.
+   */
+  AttributeDescription getAttributeDescription();
+
+
+
+  /**
+   * Returns the string representation of the attribute description of
+   * this attribute, which includes its attribute type and any options.
+   *
+   * @return The string representation of the attribute description.
+   */
+  String getAttributeDescriptionAsString();
+
+
+
+  /**
+   * Returns the hash code for this attribute. It will be calculated as
+   * the sum of the hash codes of the attribute description and all of
+   * the attribute values.
+   *
+   * @return The hash code for this attribute.
+   */
+  int hashCode();
+
+
+
+  /**
+   * Returns {@code true} if this attribute contains no attribute
+   * values.
+   *
+   * @return {@code true} if this attribute contains no attribute
+   *         values.
+   */
+  boolean isEmpty();
+
+
+
+  /**
+   * Returns an iterator over the attribute values in this attribute.
+   * The attribute values are returned in no particular order, unless
+   * the implementation of this attribute provides such a guarantee.
+   *
+   * @return An iterator over the attribute values in this attribute.
+   */
+  Iterator<ByteString> iterator();
+
+
+
+  /**
+   * 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}.
+   * <p>
+   * If {@code value} is not an instance of {@code ByteString} then it
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param value
+   *          The attribute value to be removed from this attribute.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support removal of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code value} was {@code null}.
+   */
+  boolean remove(Object value) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Removes all of the attribute values contained in {@code values}
+   * from this attribute if they are present (optional operation).
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   * <p>
+   * An invocation of this method is equivalent to:
+   *
+   * <pre>
+   * attribute.removeAll(values, null);
+   * </pre>
+   *
+   * @param values
+   *          The attribute values to be removed from this attribute.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support removal of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code values} was {@code null}.
+   */
+  boolean removeAll(Collection<?> values)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all of the attribute values contained in {@code values}
+   * from this attribute if they are present (optional operation). Any
+   * attribute values which are not already present will be added to
+   * {@code missingValues} if specified.
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param <T>
+   *          The type of the attribute value objects being removed.
+   * @param values
+   *          The attribute values to be removed from this attribute.
+   * @param missingValues
+   *          A collection into which missing values will be added, or
+   *          {@code null} if missing values should not be saved.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support removal of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code values} was {@code null}.
+   */
+  <T> boolean removeAll(Collection<T> values,
+      Collection<? super T> missingValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Retains only the attribute values in this attribute which are
+   * contained in {@code values} (optional operation).
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   * <p>
+   * An invocation of this method is equivalent to:
+   *
+   * <pre>
+   * attribute.retainAll(values, null);
+   * </pre>
+   *
+   * @param values
+   *          The attribute values to be retained in this attribute.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support removal of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code values} was {@code null}.
+   */
+  boolean retainAll(Collection<?> values)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Retains only the attribute values in this attribute which are
+   * contained in {@code values} (optional operation). Any attribute
+   * values which are not already present will be added to {@code
+   * missingValues} if specified.
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param <T>
+   *          The type of the attribute value objects being retained.
+   * @param values
+   *          The attribute values to be retained in this attribute.
+   * @param missingValues
+   *          A collection into which missing values will be added, or
+   *          {@code null} if missing values should not be saved.
+   * @return {@code true} if this attribute changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this attribute does not support removal of attribute
+   *           values.
+   * @throws NullPointerException
+   *           If {@code values} was {@code null}.
+   */
+  <T> boolean retainAll(Collection<T> values,
+      Collection<? super T> missingValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Returns the number of attribute values in this attribute.
+   *
+   * @return The number of attribute values in this attribute.
+   */
+  int size();
+
+
+
+  /**
+   * Returns an array containing all of the attribute values contained
+   * in this attribute.
+   * <p>
+   * If this attribute makes any guarantees as to what order its
+   * attribute values are returned by its iterator, this method must
+   * return the attribute values in the same order.
+   * <p>
+   * The returned array will be "safe" in that no references to it are
+   * maintained by this attribute. The caller is thus free to modify the
+   * returned array.
+   */
+  ByteString[] toArray();
+
+
+
+  /**
+   * Returns an array containing all of the attribute values in this
+   * attribute; the runtime type of the returned array is that of the
+   * specified array.
+   * <p>
+   * If the set fits in the specified array, it is returned therein.
+   * Otherwise, a new array is allocated with the runtime type of the
+   * specified array and the size of this attribute. If this attribute
+   * fits in the specified array with room to spare (i.e., the array has
+   * more elements than this attribute), the elements in the array
+   * immediately following the end of the set is set to {@code null}.
+   * <p>
+   * If this attribute makes any guarantees as to what order its
+   * attribute values are returned by its iterator, this method must
+   * return the attribute values in the same order.
+   *
+   * @throws ArrayStoreException
+   *           If the runtime type of {@code array} is not a supertype
+   *           of {@code ByteString}.
+   * @throws NullPointerException
+   *           If {@code array} was {@code null}.
+   */
+  <T> T[] toArray(T[] array) throws ArrayStoreException,
+      NullPointerException;
+
+
+
+  /**
+   * Returns a string representation of this attribute.
+   *
+   * @return The string representation of this attribute.
+   */
+  String toString();
+}
diff --git a/sdk/src/org/opends/sdk/AttributeDescription.java b/sdk/src/org/opends/sdk/AttributeDescription.java
new file mode 100644
index 0000000..92dfd56
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AttributeDescription.java
@@ -0,0 +1,1496 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.util.StaticUtils.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.schema.UnknownSchemaElementException;
+import org.opends.sdk.util.ASCIICharProp;
+import org.opends.sdk.util.Iterators;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An attribute description as defined in RFC 4512 section 2.5.
+ * Attribute descriptions are used to identify an attribute in an entry
+ * and are composed of an attribute type and a set of zero or more
+ * attribute options.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.5">RFC
+ *      4512 - Lightweight Directory Access Protocol (LDAP): Directory
+ *      Information Models </a>
+ */
+public final class AttributeDescription implements
+    Comparable<AttributeDescription>
+{
+  private static abstract class Impl implements Iterable<String>
+  {
+    protected Impl()
+    {
+      // Nothing to do.
+    }
+
+
+
+    public abstract int compareTo(Impl other);
+
+
+
+    public abstract boolean containsOption(String normalizedOption);
+
+
+
+    public abstract boolean equals(Impl other);
+
+
+
+    public abstract String firstNormalizedOption();
+
+
+
+    @Override
+    public abstract int hashCode();
+
+
+
+    public abstract boolean hasOptions();
+
+
+
+    public abstract boolean isSubTypeOf(Impl other);
+
+
+
+    public abstract boolean isSuperTypeOf(Impl other);
+
+
+
+    public abstract int size();
+
+  }
+
+
+
+  private static final class MultiOptionImpl extends Impl
+  {
+
+    private final String[] normalizedOptions;
+
+    private final String[] options;
+
+
+
+    private MultiOptionImpl(String[] options, String[] normalizedOptions)
+    {
+      if (normalizedOptions.length < 2)
+      {
+        throw new AssertionError();
+      }
+
+      this.options = options;
+      this.normalizedOptions = normalizedOptions;
+    }
+
+
+
+    @Override
+    public int compareTo(Impl other)
+    {
+      final int thisSize = normalizedOptions.length;
+      final int otherSize = other.size();
+
+      if (thisSize < otherSize)
+      {
+        return -1;
+      }
+      else if (thisSize > otherSize)
+      {
+        return 1;
+      }
+      else
+      {
+        // Same number of options.
+        final MultiOptionImpl otherImpl = (MultiOptionImpl) other;
+        for (int i = 0; i < thisSize; i++)
+        {
+          final String o1 = normalizedOptions[i];
+          final String o2 = otherImpl.normalizedOptions[i];
+          final int result = o1.compareTo(o2);
+          if (result != 0)
+          {
+            return result;
+          }
+        }
+
+        // All options the same.
+        return 0;
+      }
+    }
+
+
+
+    @Override
+    public boolean containsOption(String normalizedOption)
+    {
+      final int sz = normalizedOptions.length;
+      for (int i = 0; i < sz; i++)
+      {
+        if (normalizedOptions[i].equals(normalizedOption))
+        {
+          return true;
+        }
+      }
+      return false;
+    }
+
+
+
+    @Override
+    public boolean equals(Impl other)
+    {
+      if (other instanceof MultiOptionImpl)
+      {
+        final MultiOptionImpl tmp = (MultiOptionImpl) other;
+        return Arrays.equals(normalizedOptions, tmp.normalizedOptions);
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+
+    @Override
+    public String firstNormalizedOption()
+    {
+      return normalizedOptions[0];
+    }
+
+
+
+    @Override
+    public int hashCode()
+    {
+      return Arrays.hashCode(normalizedOptions);
+    }
+
+
+
+    @Override
+    public boolean hasOptions()
+    {
+      return true;
+    }
+
+
+
+    @Override
+    public boolean isSubTypeOf(Impl other)
+    {
+      // Must contain a super-set of other's options.
+      if (other == ZERO_OPTION_IMPL)
+      {
+        return true;
+      }
+      else if (other.size() == 1)
+      {
+        return containsOption(other.firstNormalizedOption());
+      }
+      else if (other.size() > size())
+      {
+        return false;
+      }
+      else
+      {
+        // Check this contains other's options.
+        //
+        // This could be optimized more if required, but it's probably
+        // not worth it.
+        final MultiOptionImpl tmp = (MultiOptionImpl) other;
+        for (final String normalizedOption : tmp.normalizedOptions)
+        {
+          if (!containsOption(normalizedOption))
+          {
+            return false;
+          }
+        }
+        return true;
+      }
+    }
+
+
+
+    @Override
+    public boolean isSuperTypeOf(Impl other)
+    {
+      // Must contain a sub-set of other's options.
+      for (final String normalizedOption : normalizedOptions)
+      {
+        if (!other.containsOption(normalizedOption))
+        {
+          return false;
+        }
+      }
+      return true;
+    }
+
+
+
+    public Iterator<String> iterator()
+    {
+      return Iterators.arrayIterator(options);
+    }
+
+
+
+    @Override
+    public int size()
+    {
+      return normalizedOptions.length;
+    }
+
+  }
+
+
+
+  private static final class SingleOptionImpl extends Impl
+  {
+
+    private final String normalizedOption;
+
+    private final String option;
+
+
+
+    private SingleOptionImpl(String option, String normalizedOption)
+    {
+      this.option = option;
+      this.normalizedOption = normalizedOption;
+    }
+
+
+
+    @Override
+    public int compareTo(Impl other)
+    {
+      if (other == ZERO_OPTION_IMPL)
+      {
+        // If other has zero options then this sorts after.
+        return 1;
+      }
+      else if (other.size() == 1)
+      {
+        // Same number of options, so compare.
+        return normalizedOption
+            .compareTo(other.firstNormalizedOption());
+      }
+      else
+      {
+        // Other has more options, so comes after.
+        return -1;
+      }
+    }
+
+
+
+    @Override
+    public boolean containsOption(String normalizedOption)
+    {
+      return this.normalizedOption.equals(normalizedOption);
+    }
+
+
+
+    @Override
+    public boolean equals(Impl other)
+    {
+      return other.size() == 1
+          && other.containsOption(normalizedOption);
+    }
+
+
+
+    @Override
+    public String firstNormalizedOption()
+    {
+      return normalizedOption;
+    }
+
+
+
+    @Override
+    public int hashCode()
+    {
+      return normalizedOption.hashCode();
+    }
+
+
+
+    @Override
+    public boolean hasOptions()
+    {
+      return true;
+    }
+
+
+
+    @Override
+    public boolean isSubTypeOf(Impl other)
+    {
+      // Other must have no options or the same option.
+      if (other == ZERO_OPTION_IMPL)
+      {
+        return true;
+      }
+      else
+      {
+        return equals(other);
+      }
+    }
+
+
+
+    @Override
+    public boolean isSuperTypeOf(Impl other)
+    {
+      // Other must have this option.
+      return other.containsOption(normalizedOption);
+    }
+
+
+
+    public Iterator<String> iterator()
+    {
+      return Iterators.singleton(option);
+    }
+
+
+
+    @Override
+    public int size()
+    {
+      return 1;
+    }
+
+  }
+
+
+
+  private static final class ZeroOptionImpl extends Impl
+  {
+    private ZeroOptionImpl()
+    {
+      // Nothing to do.
+    }
+
+
+
+    @Override
+    public int compareTo(Impl other)
+    {
+      // If other has options then this sorts before.
+      return this == other ? 0 : -1;
+    }
+
+
+
+    @Override
+    public boolean containsOption(String normalizedOption)
+    {
+      return false;
+    }
+
+
+
+    @Override
+    public boolean equals(Impl other)
+    {
+      return this == other;
+    }
+
+
+
+    @Override
+    public String firstNormalizedOption()
+    {
+      // No first option.
+      return null;
+    }
+
+
+
+    @Override
+    public int hashCode()
+    {
+      // Use attribute type hash code.
+      return 0;
+    }
+
+
+
+    @Override
+    public boolean hasOptions()
+    {
+      return false;
+    }
+
+
+
+    @Override
+    public boolean isSubTypeOf(Impl other)
+    {
+      // Can only be a sub-type if other has no options.
+      return this == other;
+    }
+
+
+
+    @Override
+    public boolean isSuperTypeOf(Impl other)
+    {
+      // Will always be a super-type.
+      return true;
+    }
+
+
+
+    public Iterator<String> iterator()
+    {
+      return Iterators.empty();
+    }
+
+
+
+    @Override
+    public int size()
+    {
+      return 0;
+    }
+
+  }
+
+
+
+  private static final ThreadLocal<WeakHashMap<Schema, Map<String, AttributeDescription>>> CACHE = new ThreadLocal<WeakHashMap<Schema, Map<String, AttributeDescription>>>()
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected WeakHashMap<Schema, Map<String, AttributeDescription>> initialValue()
+    {
+      return new WeakHashMap<Schema, Map<String, AttributeDescription>>();
+    }
+
+  };
+
+  // Object class attribute description.
+  private static final ZeroOptionImpl ZERO_OPTION_IMPL = new ZeroOptionImpl();
+
+  private static final AttributeDescription OBJECT_CLASS;
+  static
+  {
+    final AttributeType attributeType = Schema.getCoreSchema()
+        .getAttributeType("2.5.4.0");
+    OBJECT_CLASS = new AttributeDescription(attributeType
+        .getNameOrOID(), attributeType, ZERO_OPTION_IMPL);
+  }
+
+  // This is the size of the per-thread per-schema attribute description
+  // cache. We should be conservative here in case there are many
+  // threads.
+  private static final int ATTRIBUTE_DESCRIPTION_CACHE_SIZE = 512;
+
+
+
+  /**
+   * Creates an attribute description having the same attribute type and
+   * options as the provided attribute description and, in addition, the
+   * provided list of options.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param options
+   *          The attribute options.
+   * @return The new attribute description containing {@code options}.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code options} was
+   *           {@code null}.
+   */
+  public static AttributeDescription create(
+      AttributeDescription attributeDescription, String... options)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription, options);
+
+    // This should not be called very often, so don't optimize.
+    AttributeDescription newAttributeDescription = attributeDescription;
+    for (final String option : options)
+    {
+      newAttributeDescription = create(newAttributeDescription, option);
+    }
+    return newAttributeDescription;
+  }
+
+
+
+  /**
+   * Creates an attribute description having the same attribute type and
+   * options as the provided attribute description and, in addition, the
+   * provided new option.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param option
+   *          The attribute option.
+   * @return The new attribute description containing {@code option}.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code option} was
+   *           {@code null}.
+   */
+  public static AttributeDescription create(
+      AttributeDescription attributeDescription, String option)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription, option);
+
+    final String normalizedOption = toLowerCase(option);
+    if (attributeDescription.pimpl.containsOption(normalizedOption))
+    {
+      return attributeDescription;
+    }
+
+    final String oldAttributeDescription = attributeDescription.attributeDescription;
+    final StringBuilder builder = new StringBuilder(
+        oldAttributeDescription.length() + option.length() + 1);
+    builder.append(oldAttributeDescription);
+    builder.append(';');
+    builder.append(option);
+    final String newAttributeDescription = builder.toString();
+
+    final Impl impl = attributeDescription.pimpl;
+    if (impl instanceof ZeroOptionImpl)
+    {
+      return new AttributeDescription(newAttributeDescription,
+          attributeDescription.attributeType, new SingleOptionImpl(
+              option, normalizedOption));
+
+    }
+    else if (impl instanceof SingleOptionImpl)
+    {
+      final SingleOptionImpl simpl = (SingleOptionImpl) impl;
+
+      final String[] newOptions = new String[2];
+      newOptions[0] = simpl.option;
+      newOptions[1] = option;
+
+      final String[] newNormalizedOptions = new String[2];
+      if (normalizedOption.compareTo(simpl.normalizedOption) < 0)
+      {
+        newNormalizedOptions[0] = normalizedOption;
+        newNormalizedOptions[1] = simpl.normalizedOption;
+      }
+
+      return new AttributeDescription(newAttributeDescription,
+          attributeDescription.attributeType, new MultiOptionImpl(
+              newOptions, newNormalizedOptions));
+    }
+    else
+    {
+      final MultiOptionImpl mimpl = (MultiOptionImpl) impl;
+
+      final int sz1 = mimpl.options.length;
+      final String[] newOptions = new String[sz1 + 1];
+      for (int i = 0; i < sz1; i++)
+      {
+        newOptions[i] = mimpl.options[i];
+      }
+      newOptions[sz1] = option;
+
+      final int sz2 = mimpl.normalizedOptions.length;
+      final String[] newNormalizedOptions = new String[sz2 + 1];
+      boolean inserted = false;
+      for (int i = 0; i < sz2; i++)
+      {
+        if (!inserted)
+        {
+          final String s = mimpl.normalizedOptions[i];
+          if (normalizedOption.compareTo(s) < 0)
+          {
+            newNormalizedOptions[i] = normalizedOption;
+            newNormalizedOptions[i + 1] = s;
+            inserted = true;
+          }
+          else
+          {
+            newNormalizedOptions[i] = s;
+          }
+        }
+        else
+        {
+          newNormalizedOptions[i + 1] = mimpl.normalizedOptions[i];
+        }
+      }
+
+      if (!inserted)
+      {
+        newNormalizedOptions[sz2] = normalizedOption;
+      }
+
+      return new AttributeDescription(newAttributeDescription,
+          attributeDescription.attributeType, new MultiOptionImpl(
+              newOptions, newNormalizedOptions));
+    }
+  }
+
+
+
+  /**
+   * Creates an attribute description having the provided attribute type
+   * and no options.
+   *
+   * @param attributeType
+   *          The attribute type.
+   * @return The attribute description.
+   * @throws NullPointerException
+   *           If {@code attributeType} was {@code null}.
+   */
+  public static AttributeDescription create(AttributeType attributeType)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeType);
+
+    // Use object identity in case attribute type does not come from
+    // core schema.
+    if (attributeType == OBJECT_CLASS.getAttributeType())
+    {
+      return OBJECT_CLASS;
+    }
+    else
+    {
+      return new AttributeDescription(attributeType.getNameOrOID(),
+          attributeType, ZERO_OPTION_IMPL);
+    }
+  }
+
+
+
+  /**
+   * Creates an attribute description having the provided attribute type
+   * and single option.
+   *
+   * @param attributeType
+   *          The attribute type.
+   * @param option
+   *          The attribute option.
+   * @return The attribute description.
+   * @throws NullPointerException
+   *           If {@code attributeType} or {@code option} was {@code
+   *           null}.
+   */
+  public static AttributeDescription create(
+      AttributeType attributeType, String option)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeType, option);
+
+    final String oid = attributeType.getNameOrOID();
+    final StringBuilder builder = new StringBuilder(oid.length()
+        + option.length() + 1);
+    builder.append(oid);
+    builder.append(';');
+    builder.append(option);
+    final String attributeDescription = builder.toString();
+    final String normalizedOption = toLowerCase(option);
+
+    return new AttributeDescription(attributeDescription,
+        attributeType, new SingleOptionImpl(option, normalizedOption));
+  }
+
+
+
+  /**
+   * Creates an attribute description having the provided attribute type
+   * and options.
+   *
+   * @param attributeType
+   *          The attribute type.
+   * @param options
+   *          The attribute options.
+   * @return The attribute description.
+   * @throws NullPointerException
+   *           If {@code attributeType} or {@code options} was {@code
+   *           null}.
+   */
+  public static AttributeDescription create(
+      AttributeType attributeType, String... options)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeType, options);
+
+    switch (options.length)
+    {
+    case 0:
+      return create(attributeType);
+    case 1:
+      return create(attributeType, options[0]);
+    default:
+      final String[] optionsList = new String[options.length];
+      final String[] normalizedOptions = new String[options.length];
+
+      final String oid = attributeType.getNameOrOID();
+      final StringBuilder builder = new StringBuilder(oid.length()
+          + options[0].length() + options[1].length() + 2);
+      builder.append(oid);
+
+      int i = 0;
+      for (final String option : options)
+      {
+        builder.append(';');
+        builder.append(option);
+        optionsList[i] = option;
+        final String normalizedOption = toLowerCase(option);
+        normalizedOptions[i++] = normalizedOption;
+      }
+      Arrays.sort(normalizedOptions);
+
+      final String attributeDescription = builder.toString();
+      return new AttributeDescription(attributeDescription,
+          attributeType, new MultiOptionImpl(optionsList,
+              normalizedOptions));
+    }
+
+  }
+
+
+
+  /**
+   * Returns an attribute description representing the object class
+   * attribute type with no options.
+   *
+   * @return The object class attribute description.
+   */
+  public static AttributeDescription objectClass()
+  {
+    return OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Parses the provided LDAP string representation of an attribute
+   * description using the default schema.
+   *
+   * @param attributeDescription
+   *          The LDAP string representation of an attribute
+   *          description.
+   * @return The parsed attribute description.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} is not a valid LDAP
+   *           string representation of an attribute description.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  public static AttributeDescription valueOf(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return valueOf(attributeDescription, Schema.getDefaultSchema());
+  }
+
+
+
+  /**
+   * Parses the provided LDAP string representation of an attribute
+   * description using the provided schema.
+   *
+   * @param attributeDescription
+   *          The LDAP string representation of an attribute
+   *          description.
+   * @param schema
+   *          The schema to use when parsing the attribute description.
+   * @return The parsed attribute description.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} is not a valid LDAP
+   *           string representation of an attribute description.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code schema} was
+   *           {@code null}.
+   */
+  @SuppressWarnings("serial")
+  public static AttributeDescription valueOf(
+      String attributeDescription, Schema schema)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription, schema);
+
+    // First look up the attribute description in the cache.
+    final WeakHashMap<Schema, Map<String, AttributeDescription>> threadLocalMap = CACHE
+        .get();
+    Map<String, AttributeDescription> schemaLocalMap = threadLocalMap
+        .get(schema);
+
+    AttributeDescription ad = null;
+    if (schemaLocalMap == null)
+    {
+      schemaLocalMap = new LinkedHashMap<String, AttributeDescription>(
+          ATTRIBUTE_DESCRIPTION_CACHE_SIZE, 0.75f, true)
+      {
+        @Override
+        protected boolean removeEldestEntry(
+            Map.Entry<String, AttributeDescription> eldest)
+        {
+          return size() > ATTRIBUTE_DESCRIPTION_CACHE_SIZE;
+        }
+      };
+      threadLocalMap.put(schema, schemaLocalMap);
+    }
+    else
+    {
+      ad = schemaLocalMap.get(attributeDescription);
+    }
+
+    // Cache miss: decode and cache.
+    if (ad == null)
+    {
+      ad = valueOf0(attributeDescription, schema);
+      schemaLocalMap.put(attributeDescription, ad);
+    }
+
+    return ad;
+  }
+
+
+
+  private static int skipTrailingWhiteSpace(
+      String attributeDescription, int i, int length)
+      throws LocalizedIllegalArgumentException
+  {
+    char c;
+    while (i < length)
+    {
+      c = attributeDescription.charAt(i);
+      if (c != ' ')
+      {
+        final Message message = ERR_ATTRIBUTE_DESCRIPTION_INTERNAL_WHITESPACE
+            .get(attributeDescription);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+      i++;
+    }
+    return i;
+  }
+
+
+
+  // Uncached valueOf implementation.
+  private static AttributeDescription valueOf0(
+      String attributeDescription, Schema schema)
+      throws LocalizedIllegalArgumentException
+  {
+    int i = 0;
+    final int length = attributeDescription.length();
+    char c = 0;
+
+    // Skip leading white space.
+    while (i < length)
+    {
+      c = attributeDescription.charAt(i);
+      if (c != ' ')
+      {
+        break;
+      }
+      i++;
+    }
+
+    // If we're already at the end then the attribute description only
+    // contained whitespace.
+    if (i == length)
+    {
+      final Message message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY
+          .get(attributeDescription);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // Validate the first non-whitespace character.
+    ASCIICharProp cp = ASCIICharProp.valueOf(c);
+    if (cp == null)
+    {
+      final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+          .get(attributeDescription, c, i);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // Mark the attribute type start position.
+    final int attributeTypeStart = i;
+    if (cp.isLetter())
+    {
+      // Non-numeric OID: letter + zero or more keychars.
+      i++;
+      while (i < length)
+      {
+        c = attributeDescription.charAt(i);
+
+        if (c == ';' || c == ' ')
+        {
+          break;
+        }
+
+        cp = ASCIICharProp.valueOf(c);
+        if (!cp.isKeyChar())
+        {
+          final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+              .get(attributeDescription, c, i);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+        i++;
+      }
+
+      // (charAt(i) == ';' || c == ' ' || i == length)
+    }
+    else if (cp.isDigit())
+    {
+      // Numeric OID: decimal digit + zero or more dots or decimals.
+      i++;
+      while (i < length)
+      {
+        c = attributeDescription.charAt(i);
+        if (c == ';' || c == ' ')
+        {
+          break;
+        }
+
+        cp = ASCIICharProp.valueOf(c);
+        if (c != '.' && !cp.isDigit())
+        {
+          final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+              .get(attributeDescription, c, i);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+        i++;
+      }
+
+      // (charAt(i) == ';' || charAt(i) == ' ' || i == length)
+    }
+    else
+    {
+      final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+          .get(attributeDescription, c, i);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // Skip trailing white space.
+    final int attributeTypeEnd = i;
+    if (c == ' ')
+    {
+      i = skipTrailingWhiteSpace(attributeDescription, i + 1, length);
+    }
+
+    // Determine the portion of the string containing the attribute type
+    // name.
+    String oid;
+    if (attributeTypeStart == 0 && attributeTypeEnd == length)
+    {
+      oid = attributeDescription;
+    }
+    else
+    {
+      oid = attributeDescription.substring(attributeTypeStart,
+          attributeTypeEnd);
+    }
+
+    if (oid.length() == 0)
+    {
+      final Message message = ERR_ATTRIBUTE_DESCRIPTION_NO_TYPE
+          .get(attributeDescription);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // Get the attribute type from the schema.
+    AttributeType attributeType;
+    try
+    {
+      attributeType = schema.getAttributeType(oid);
+    }
+    catch (final UnknownSchemaElementException e)
+    {
+      final Message message = ERR_ATTRIBUTE_DESCRIPTION_TYPE_NOT_FOUND
+          .get(attributeDescription, e.getMessageObject());
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // If we're already at the end of the attribute description then it
+    // does not contain any options.
+    if (i == length)
+    {
+      // Use object identity in case attribute type does not come from
+      // core schema.
+      if (attributeType == OBJECT_CLASS.getAttributeType()
+          && attributeDescription.equals(OBJECT_CLASS.toString()))
+      {
+        return OBJECT_CLASS;
+      }
+      else
+      {
+        return new AttributeDescription(attributeDescription,
+            attributeType, ZERO_OPTION_IMPL);
+      }
+    }
+
+    // At this point 'i' must point at a semi-colon.
+    i++;
+    StringBuilder builder = null;
+    int optionStart = i;
+    while (i < length)
+    {
+      c = attributeDescription.charAt(i);
+      if (c == ' ' || c == ';')
+      {
+        break;
+      }
+
+      cp = ASCIICharProp.valueOf(c);
+      if (!cp.isKeyChar())
+      {
+        final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+            .get(attributeDescription, c, i);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      if (builder == null)
+      {
+        if (cp.isUpperCase())
+        {
+          // Need to normalize the option.
+          builder = new StringBuilder(length - optionStart);
+          builder.append(attributeDescription, optionStart, i);
+          builder.append(cp.toLowerCase());
+        }
+      }
+      else
+      {
+        builder.append(cp.toLowerCase());
+      }
+      i++;
+    }
+
+    String option = attributeDescription.substring(optionStart, i);
+    String normalizedOption;
+    if (builder != null)
+    {
+      normalizedOption = builder.toString();
+    }
+    else
+    {
+      normalizedOption = option;
+    }
+
+    if (option.length() == 0)
+    {
+      final Message message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY_OPTION
+          .get(attributeDescription);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // Skip trailing white space.
+    if (c == ' ')
+    {
+      i = skipTrailingWhiteSpace(attributeDescription, i + 1, length);
+    }
+
+    // If we're already at the end of the attribute description then it
+    // only contains a single option.
+    if (i == length)
+    {
+      return new AttributeDescription(attributeDescription,
+          attributeType, new SingleOptionImpl(option, normalizedOption));
+    }
+
+    // Multiple options need sorting and duplicates removed - we could
+    // optimize a bit further here for 2 option attribute descriptions.
+    final List<String> options = new LinkedList<String>();
+    options.add(option);
+
+    final SortedSet<String> normalizedOptions = new TreeSet<String>();
+    normalizedOptions.add(normalizedOption);
+
+    while (i < length)
+    {
+      // At this point 'i' must point at a semi-colon.
+      i++;
+      builder = null;
+      optionStart = i;
+      while (i < length)
+      {
+        c = attributeDescription.charAt(i);
+        if (c == ' ' || c == ';')
+        {
+          break;
+        }
+
+        cp = ASCIICharProp.valueOf(c);
+        if (!cp.isKeyChar())
+        {
+          final Message message = ERR_ATTRIBUTE_DESCRIPTION_ILLEGAL_CHARACTER
+              .get(attributeDescription, c, i);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+
+        if (builder == null)
+        {
+          if (cp.isUpperCase())
+          {
+            // Need to normalize the option.
+            builder = new StringBuilder(length - optionStart);
+            builder.append(attributeDescription, optionStart, i);
+            builder.append(cp.toLowerCase());
+          }
+        }
+        else
+        {
+          builder.append(cp.toLowerCase());
+        }
+        i++;
+      }
+
+      option = attributeDescription.substring(optionStart, i);
+      if (builder != null)
+      {
+        normalizedOption = builder.toString();
+      }
+      else
+      {
+        normalizedOption = option;
+      }
+
+      if (option.length() == 0)
+      {
+        final Message message = ERR_ATTRIBUTE_DESCRIPTION_EMPTY_OPTION
+            .get(attributeDescription);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip trailing white space.
+      if (c == ' ')
+      {
+        i = skipTrailingWhiteSpace(attributeDescription, i + 1, length);
+      }
+
+      options.add(option);
+      normalizedOptions.add(normalizedOption);
+    }
+
+    return new AttributeDescription(attributeDescription,
+        attributeType, new MultiOptionImpl(options
+            .toArray(new String[options.size()]), normalizedOptions
+            .toArray(new String[normalizedOptions.size()])));
+  }
+
+
+
+  private final String attributeDescription;
+
+  private final AttributeType attributeType;
+
+  private final Impl pimpl;
+
+
+
+  // Private constructor.
+  private AttributeDescription(String attributeDescription,
+      AttributeType attributeType, Impl pimpl)
+  {
+    this.attributeDescription = attributeDescription;
+    this.attributeType = attributeType;
+    this.pimpl = pimpl;
+  }
+
+
+
+  /**
+   * Compares this attribute description to the provided attribute
+   * description. The attribute types are compared first and then, if
+   * equal, the options are normalized, sorted, and compared.
+   *
+   * @param other
+   *          The attribute description to be compared.
+   * @return A negative integer, zero, or a positive integer as this
+   *         attribute description is less than, equal to, or greater
+   *         than the specified attribute description.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public int compareTo(AttributeDescription other)
+      throws NullPointerException
+  {
+    final int result = attributeType.compareTo(other.attributeType);
+    if (result != 0)
+    {
+      return result;
+    }
+    else
+    {
+      // Attribute type is the same, so compare options.
+      return pimpl.compareTo(other.pimpl);
+    }
+  }
+
+
+
+  /**
+   * Indicates whether or not this attribute description contains the
+   * provided option.
+   *
+   * @param option
+   *          The option for which to make the determination.
+   * @return {@code true} if this attribute description has the provided
+   *         option, or {@code false} if not.
+   * @throws NullPointerException
+   *           If {@code option} was {@code null}.
+   */
+  public boolean containsOption(String option)
+      throws NullPointerException
+  {
+    final String normalizedOption = toLowerCase(option);
+    return pimpl.containsOption(normalizedOption);
+  }
+
+
+
+  /**
+   * Indicates whether the provided object is an attribute description
+   * which is equal to this attribute description. It will be considered
+   * equal if the attribute type and normalized sorted list of options
+   * are identical.
+   *
+   * @param o
+   *          The object for which to make the determination.
+   * @return {@code true} if the provided object is an attribute
+   *         description that is equal to this attribute description, or
+   *         {@code false} if not.
+   */
+  @Override
+  public boolean equals(Object o)
+  {
+    if (this == o)
+    {
+      return true;
+    }
+
+    if (!(o instanceof AttributeDescription))
+    {
+      return false;
+    }
+
+    final AttributeDescription other = (AttributeDescription) o;
+    if (!attributeType.equals(other.attributeType))
+    {
+      return false;
+    }
+
+    // Attribute type is the same, compare options.
+    return pimpl.equals(other.pimpl);
+  }
+
+
+
+  /**
+   * Returns the attribute type associated with this attribute
+   * description.
+   *
+   * @return The attribute type associated with this attribute
+   *         description.
+   */
+  public AttributeType getAttributeType()
+  {
+    return attributeType;
+  }
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the options contained in
+   * this attribute description. Attempts to remove options using an
+   * iterator's {@code remove()} method are not permitted and will
+   * result in an {@code UnsupportedOperationException} being thrown.
+   *
+   * @return An {@code Iterable} containing the options.
+   */
+  public Iterable<String> getOptions()
+  {
+    return pimpl;
+  }
+
+
+
+  /**
+   * Returns the hash code for this attribute description. It will be
+   * calculated as the sum of the hash codes of the attribute type and
+   * normalized sorted list of options.
+   *
+   * @return The hash code for this attribute description.
+   */
+  @Override
+  public int hashCode()
+  {
+    // FIXME: should we cache this?
+    return attributeType.hashCode() * 31 + pimpl.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether or not this attribute description has any
+   * options.
+   *
+   * @return {@code true} if this attribute description has any options,
+   *         or {@code false} if not.
+   */
+  public boolean hasOptions()
+  {
+    return pimpl.hasOptions();
+  }
+
+
+
+  /**
+   * Indicates whether or not this attribute description is the {@code
+   * objectClass} attribute description with no options.
+   *
+   * @return {@code true} if this attribute description is the {@code
+   *         objectClass} attribute description with no options, or
+   *         {@code false} if not.
+   */
+  public boolean isObjectClass()
+  {
+    return attributeType.isObjectClass() && !hasOptions();
+  }
+
+
+
+  /**
+   * Indicates whether or not this attribute description is a sub-type
+   * of the provided attribute description as defined in RFC 4512
+   * section 2.5. Specifically, this method will return {@code true} if
+   * and only if the following conditions are both {@code true}:
+   * <ul>
+   * <li>This attribute description has an attribute type which is equal
+   * to, or is a sub-type of, the attribute type in the provided
+   * attribute description.
+   * <li>This attribute description contains all of the options
+   * contained in the provided attribute description.
+   * </ul>
+   * Note that this method will return {@code true} if this attribute
+   * description is equal to the provided attribute description.
+   *
+   * @param other
+   *          The attribute description for which to make the
+   *          determination.
+   * @return {@code true} if this attribute description is a sub-type of
+   *         the provided attribute description, or {@code false} if
+   *         not.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public boolean isSubTypeOf(AttributeDescription other)
+      throws NullPointerException
+  {
+    if (!attributeType.isSubTypeOf(other.attributeType))
+    {
+      return false;
+    }
+    else
+    {
+      return pimpl.isSubTypeOf(other.pimpl);
+    }
+  }
+
+
+
+  /**
+   * Indicates whether or not this attribute description is a super-type
+   * of the provided attribute description as defined in RFC 4512
+   * section 2.5. Specifically, this method will return {@code true} if
+   * and only if the following conditions are both {@code true}:
+   * <ul>
+   * <li>This attribute description has an attribute type which is equal
+   * to, or is a super-type of, the attribute type in the provided
+   * attribute description.
+   * <li>This attribute description contains a sub-set of the options
+   * contained in the provided attribute description.
+   * </ul>
+   * Note that this method will return {@code true} if this attribute
+   * description is equal to the provided attribute description.
+   *
+   * @param other
+   *          The attribute description for which to make the
+   *          determination.
+   * @return {@code true} if this attribute description is a super-type
+   *         of the provided attribute description, or {@code false} if
+   *         not.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public boolean isSuperTypeOf(AttributeDescription other)
+      throws NullPointerException
+  {
+    if (!other.attributeType.isSubTypeOf(attributeType))
+    {
+      return false;
+    }
+    else
+    {
+      return pimpl.isSuperTypeOf(other.pimpl);
+    }
+  }
+
+
+
+  /**
+   * Returns the string representation of this attribute description as
+   * defined in RFC4512 section 2.5.
+   *
+   * @return The string representation of this attribute description.
+   */
+  @Override
+  public String toString()
+  {
+    return attributeDescription;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java b/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java
new file mode 100644
index 0000000..1bb8ed5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/AuthenticatedConnectionFactory.java
@@ -0,0 +1,702 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.CompareResult;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An authenticated connection factory can be used to create
+ * pre-authenticated connections to a Directory Server.
+ * <p>
+ * The connections returned by an authenticated connection factory
+ * support all operations with the exception of Bind requests. Attempts
+ * to perform a Bind will result in an {@code
+ * UnsupportedOperationException}.
+ * <p>
+ * In addition, the returned connections support retrieval of the
+ * {@code BindResult} returned from the initial Bind request, or last
+ * rebind.
+ * <p>
+ * Support for connection re-authentication is provided through the
+ * {@link #setRebindAllowed} method which, if set to {@code true},
+ * causes subsequent connections created using the factory to support
+ * the {@code rebind} method.
+ * <p>
+ * If the Bind request fails for some reason (e.g. invalid credentials),
+ * then the connection attempt will fail and an {@code
+ * ErrorResultException} will be thrown.
+ */
+public final class AuthenticatedConnectionFactory
+    implements
+    ConnectionFactory<AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection>
+{
+  // We implement the factory using the pimpl idiom in order have
+  // cleaner Javadoc which does not expose implementation methods from
+  // AbstractConnectionFactory.
+
+  private static final class Impl
+      extends
+      AbstractConnectionFactory<AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection>
+      implements
+      ConnectionFactory<AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection>
+  {
+    private final BindRequest request;
+
+    private final ConnectionFactory<?> parentFactory;
+
+    private boolean allowRebinds = false;
+
+
+
+    private Impl(ConnectionFactory<?> factory, BindRequest request)
+        throws NullPointerException
+    {
+      Validator.ensureNotNull(factory, request);
+      this.parentFactory = factory;
+
+      // FIXME: should do a defensive copy.
+      this.request = request;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public <P> ConnectionFuture<AuthenticatedAsynchronousConnection> getAsynchronousConnection(
+        ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler,
+        P p)
+    {
+      ConnectionFutureImpl<P> future = new ConnectionFutureImpl<P>(
+          allowRebinds ? request : null, handler, p);
+      future.connectFuture = parentFactory.getAsynchronousConnection(
+          future, null);
+      return future;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public AuthenticatedConnection getConnection()
+        throws ErrorResultException
+    {
+      return new AuthenticatedConnection(
+          blockingGetAsynchronousConnection());
+    }
+
+  }
+
+
+
+  private final Impl impl;
+
+
+
+  /**
+   * An authenticated synchronous connection supports all operations
+   * except Bind operations.
+   */
+  public static final class AuthenticatedConnection extends
+      SynchronousConnection
+  {
+    private final AuthenticatedAsynchronousConnection connection;
+
+
+
+    private AuthenticatedConnection(
+        AuthenticatedAsynchronousConnection connection)
+    {
+      super(connection);
+      this.connection = connection;
+    }
+
+
+
+    /**
+     * Bind operations are not supported by pre-authenticated
+     * connections. This method will always throw {@code
+     * UnsupportedOperationException}.
+     */
+    public BindResult bind(BindRequest request)
+        throws UnsupportedOperationException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * Bind operations are not supported by pre-authenticated
+     * connections. This method will always throw {@code
+     * UnsupportedOperationException}.
+     */
+    public BindResult bind(String name, String password)
+        throws UnsupportedOperationException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * Re-authenticates to the Directory Server using the bind request
+     * associated with this connection. If re-authentication fails for
+     * some reason then this connection will be automatically closed.
+     *
+     * @return The result of the operation.
+     * @throws ErrorResultException
+     *           If the result code indicates that the request failed
+     *           for some reason.
+     * @throws InterruptedException
+     *           If the current thread was interrupted while waiting.
+     * @throws UnsupportedOperationException
+     *           If this connection does not support rebind operations.
+     * @throws IllegalStateException
+     *           If this connection has already been closed, i.e. if
+     *           {@code isClosed() == true}.
+     */
+    public BindResult rebind() throws ErrorResultException,
+        InterruptedException, UnsupportedOperationException,
+        IllegalStateException
+    {
+
+      if (connection.request == null)
+      {
+        throw new UnsupportedOperationException();
+      }
+      return super.bind(connection.request);
+    }
+
+
+
+    /**
+     * Returns an unmodifiable view of the Bind result which was
+     * returned from the server after authentication.
+     *
+     * @return The Bind result which was returned from the server after
+     *         authentication.
+     */
+    public BindResult getAuthenticatedBindResult()
+    {
+      return connection.getAuthenticatedBindResult();
+    }
+  }
+
+
+
+  /**
+   * An authenticated asynchronous connection supports all operations
+   * except Bind operations.
+   */
+  public static final class AuthenticatedAsynchronousConnection
+      implements AsynchronousConnection
+  {
+
+    private final BindRequest request;
+
+    private volatile BindResult result;
+
+    private final AsynchronousConnection connection;
+
+
+
+    private AuthenticatedAsynchronousConnection(
+        AsynchronousConnection connection, BindRequest request,
+        BindResult result)
+    {
+      this.connection = connection;
+      this.request = request;
+      this.result = result;
+    }
+
+
+
+    /**
+     * Returns an unmodifiable view of the Bind result which was
+     * returned from the server after authentication.
+     *
+     * @return The Bind result which was returned from the server after
+     *         authentication.
+     */
+    public BindResult getAuthenticatedBindResult()
+    {
+      return result;
+    }
+
+
+
+    public void abandon(AbandonRequest request)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      connection.abandon(request);
+    }
+
+
+
+    public <P> ResultFuture<Result> add(AddRequest request,
+        ResultHandler<Result, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      return connection.add(request, handler, p);
+    }
+
+
+
+    public void addConnectionEventListener(
+        ConnectionEventListener listener) throws IllegalStateException,
+        NullPointerException
+    {
+      connection.addConnectionEventListener(listener);
+    }
+
+
+
+    /**
+     * Bind operations are not supported by pre-authenticated
+     * connections. This method will always throw {@code
+     * UnsupportedOperationException}.
+     */
+    public <P> ResultFuture<BindResult> bind(BindRequest request,
+        ResultHandler<? super BindResult, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public void close()
+    {
+      connection.close();
+    }
+
+
+
+    public void close(UnbindRequest request)
+        throws NullPointerException
+    {
+      connection.close(request);
+    }
+
+
+
+    public <P> ResultFuture<CompareResult> compare(
+        CompareRequest request,
+        ResultHandler<? super CompareResult, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      return connection.compare(request, handler, p);
+    }
+
+
+
+    public <P> ResultFuture<Result> delete(DeleteRequest request,
+        ResultHandler<Result, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      return connection.delete(request, handler, p);
+    }
+
+
+
+    public <R extends Result, P> ResultFuture<R> extendedRequest(
+        ExtendedRequest<R> request,
+        ResultHandler<? super R, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      return connection.extendedRequest(request, handler, p);
+    }
+
+
+
+    public <P> ResultFuture<Result> modify(ModifyRequest request,
+        ResultHandler<Result, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      return connection.modify(request, handler, p);
+    }
+
+
+
+    public <P> ResultFuture<Result> modifyDN(ModifyDNRequest request,
+        ResultHandler<Result, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      return connection.modifyDN(request, handler, p);
+    }
+
+
+
+    /**
+     * Re-authenticates to the Directory Server using the bind request
+     * associated with this connection. If re-authentication fails for
+     * some reason then this connection will be automatically closed.
+     *
+     * @param <P>
+     *          The type of the additional parameter to the handler's
+     *          methods.
+     * @param handler
+     *          A result handler which can be used to asynchronously
+     *          process the operation result when it is received, may be
+     *          {@code null}.
+     * @param p
+     *          Optional additional handler parameter.
+     * @return A future representing the result of the operation.
+     * @throws UnsupportedOperationException
+     *           If this connection does not support rebind operations.
+     * @throws IllegalStateException
+     *           If this connection has already been closed, i.e. if
+     *           {@code isClosed() == true}.
+     */
+    public <P> ResultFuture<BindResult> rebind(
+        ResultHandler<? super BindResult, P> handler, P p)
+        throws UnsupportedOperationException, IllegalStateException
+    {
+      if (request == null)
+      {
+        throw new UnsupportedOperationException();
+      }
+
+      // Wrap the client handler so that we can update the connection
+      // state.
+      final ResultHandler<? super BindResult, P> clientHandler = handler;
+      final P clientParameter = p;
+
+      ResultHandler<BindResult, Void> handlerWrapper = new ResultHandler<BindResult, Void>()
+      {
+
+        public void handleErrorResult(Void p, ErrorResultException error)
+        {
+          // This connection is now unauthenticated so prevent
+          // further use.
+          connection.close();
+
+          if (clientHandler != null)
+          {
+            clientHandler.handleErrorResult(clientParameter, error);
+          }
+        }
+
+
+
+        public void handleResult(Void p, BindResult result)
+        {
+          // Save the result.
+          AuthenticatedAsynchronousConnection.this.result = result;
+
+          if (clientHandler != null)
+          {
+            clientHandler.handleResult(clientParameter, result);
+          }
+        }
+
+      };
+
+      return connection.bind(request, handlerWrapper, null);
+    }
+
+
+
+    public void removeConnectionEventListener(
+        ConnectionEventListener listener) throws NullPointerException
+    {
+      connection.removeConnectionEventListener(listener);
+    }
+
+
+
+    public <P> ResultFuture<Result> search(SearchRequest request,
+        ResultHandler<Result, P> resultHandler,
+        SearchResultHandler<P> searchResulthandler, P p)
+        throws UnsupportedOperationException, IllegalStateException,
+        NullPointerException
+    {
+      return connection.search(request, resultHandler,
+          searchResulthandler, p);
+    }
+
+  }
+
+
+
+  /**
+   * Creates a new authenticated connection factory which will obtain
+   * connections using the provided connection factory and immediately
+   * perform the provided Bind request.
+   *
+   * @param factory
+   *          The connection factory to use for connecting to the
+   *          Directory Server.
+   * @param request
+   *          The Bind request to use for authentication.
+   * @throws NullPointerException
+   *           If {@code factory} or {@code request} was {@code null}.
+   */
+  public AuthenticatedConnectionFactory(ConnectionFactory<?> factory,
+      BindRequest request) throws NullPointerException
+  {
+    impl = new Impl(factory, request);
+  }
+
+
+
+  private static final class ConnectionFutureImpl<P> implements
+      ConnectionFuture<AuthenticatedAsynchronousConnection>,
+      ConnectionResultHandler<AsynchronousConnection, Void>,
+      ResultHandler<BindResult, Void>
+  {
+    private volatile AuthenticatedAsynchronousConnection authenticatedConnection;
+
+    private volatile AsynchronousConnection connection;
+
+    private volatile ErrorResultException exception;
+
+    private volatile ConnectionFuture<?> connectFuture;
+
+    private volatile ResultFuture<BindResult> bindFuture;
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+
+    private final ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler;
+
+    private final P p;
+
+    private boolean cancelled;
+
+    private final BindRequest request;
+
+
+
+    private ConnectionFutureImpl(
+        BindRequest request,
+        ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler,
+        P p)
+    {
+      this.request = request;
+      this.handler = handler;
+      this.p = p;
+    }
+
+
+
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+      cancelled = connectFuture.cancel(mayInterruptIfRunning)
+          || bindFuture != null
+          && bindFuture.cancel(mayInterruptIfRunning);
+      if (cancelled)
+      {
+        latch.countDown();
+      }
+      return cancelled;
+    }
+
+
+
+    public AuthenticatedAsynchronousConnection get()
+        throws InterruptedException, ErrorResultException
+    {
+      latch.await();
+      if (cancelled)
+      {
+        throw new CancellationException();
+      }
+      if (exception != null)
+      {
+        throw exception;
+      }
+      return authenticatedConnection;
+    }
+
+
+
+    public AuthenticatedAsynchronousConnection get(long timeout,
+        TimeUnit unit) throws InterruptedException, TimeoutException,
+        ErrorResultException
+    {
+      latch.await(timeout, unit);
+      if (cancelled)
+      {
+        throw new CancellationException();
+      }
+      if (exception != null)
+      {
+        throw exception;
+      }
+      return authenticatedConnection;
+    }
+
+
+
+    public boolean isCancelled()
+    {
+      return cancelled;
+    }
+
+
+
+    public boolean isDone()
+    {
+      return latch.getCount() == 0;
+    }
+
+
+
+    public void handleConnection(Void v,
+        AsynchronousConnection connection)
+    {
+      this.connection = connection;
+      this.bindFuture = this.connection.bind(request, this, null);
+    }
+
+
+
+    public void handleConnectionError(Void v, ErrorResultException error)
+    {
+      exception = error;
+      latch.countDown();
+    }
+
+
+
+    public void handleResult(Void v, BindResult result)
+    {
+      // FIXME: should make the result unmodifiable.
+      authenticatedConnection = new AuthenticatedAsynchronousConnection(
+          connection, request, result);
+      latch.countDown();
+      if (handler != null)
+      {
+        handler.handleConnection(p, authenticatedConnection);
+      }
+    }
+
+
+
+    public void handleErrorResult(Void v, ErrorResultException error)
+    {
+      // Ensure that the connection is closed.
+      try
+      {
+        connection.close();
+      }
+      catch (Exception e)
+      {
+        // Ignore.
+      }
+
+      exception = error;
+      latch.countDown();
+      if (handler != null)
+      {
+        handler.handleConnectionError(p, exception);
+      }
+    }
+  }
+
+
+
+  /**
+   * Specifies whether or not rebind requests are to be supported by
+   * connections created by this authenticated connection factory.
+   * <p>
+   * Rebind requests are invoked using the connection's {@code rebind}
+   * method which will throw an {@code UnsupportedOperationException} if
+   * rebinds are not supported (the default).
+   *
+   * @param allowRebinds
+   *          {@code true} if the {@code rebind} operation is to be
+   *          supported, otherwise {@code false}.
+   * @return A reference to this connection factory.
+   */
+  public AuthenticatedConnectionFactory setRebindAllowed(
+      boolean allowRebinds)
+  {
+    impl.allowRebinds = allowRebinds;
+    return this;
+  }
+
+
+
+  /**
+   * Indicates whether or not rebind requests are to be supported by
+   * connections created by this authenticated connection factory.
+   * <p>
+   * Rebind requests are invoked using the connection's {@code rebind}
+   * method which will throw an {@code UnsupportedOperationException} if
+   * rebinds are not supported (the default).
+   *
+   * @return allowRebinds {@code true} if the {@code rebind} operation
+   *         is to be supported, otherwise {@code false}.
+   */
+  public boolean isRebindAllowed()
+  {
+    return impl.allowRebinds;
+  }
+
+
+
+  public <P> ConnectionFuture<AuthenticatedAsynchronousConnection> getAsynchronousConnection(
+      ConnectionResultHandler<? super AuthenticatedAsynchronousConnection, P> handler,
+      P p)
+  {
+    return impl.getAsynchronousConnection(handler, p);
+  }
+
+
+
+  public AuthenticatedConnection getConnection()
+      throws ErrorResultException
+  {
+    return impl.getConnection();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/Change.java b/sdk/src/org/opends/sdk/Change.java
new file mode 100644
index 0000000..04f7b4b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Change.java
@@ -0,0 +1,126 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A modification to be performed on an entry during a Modify operation.
+ * <p>
+ * TODO: other constructors.
+ */
+public final class Change
+{
+  private final ModificationType modificationType;
+
+  private final Attribute attribute;
+
+
+
+  /**
+   * Creates a new modification having the provided modification type
+   * and attribute values to be updated. Note that while the returned
+   * {@code Change} is immutable, the underlying attribute may not be.
+   * The following code ensures that the returned {@code Change} is
+   * fully immutable:
+   *
+   * <pre>
+   * Change change =
+   *     new Change(modificationType, Types.unmodifiableAttribute(attribute));
+   * </pre>
+   *
+   * @param modificationType
+   *          The type of change to be performed.
+   * @param attribute
+   *          The the attribute containing the values to be modified.
+   */
+  public Change(ModificationType modificationType, Attribute attribute)
+  {
+    Validator.ensureNotNull(modificationType, attribute);
+
+    this.modificationType = modificationType;
+    this.attribute = attribute;
+  }
+
+
+
+  /**
+   * Returns the type of change to be performed.
+   *
+   * @return The type of change to be performed.
+   */
+  public ModificationType getModificationType()
+  {
+    return modificationType;
+  }
+
+
+
+  /**
+   * Returns the attribute containing the values to be modified.
+   *
+   * @return The the attribute containing the values to be modified.
+   */
+  public Attribute getAttribute()
+  {
+    return attribute;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("Change(modificationType=");
+    builder.append(modificationType);
+    builder.append(", attributeDescription=");
+    builder.append(attribute.getAttributeDescriptionAsString());
+    builder.append(", attributeValues={");
+    boolean firstValue = true;
+    for (ByteString value : attribute)
+    {
+      if (!firstValue)
+      {
+        builder.append(", ");
+      }
+      builder.append(value);
+      firstValue = false;
+    }
+    builder.append("})");
+    return builder.toString();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ConditionResult.java b/sdk/src/org/opends/sdk/ConditionResult.java
new file mode 100644
index 0000000..f6a7ddd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConditionResult.java
@@ -0,0 +1,295 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+/**
+ * The result of a tri-state logical expression. Condition results are
+ * used to represent the result of a conditional evaluation that can
+ * yield three possible values: {@code FALSE} (i.e. "no"), {@code TRUE}
+ * (i.e. "yes"), or {@code UNDEFINED} (i.e. "maybe"). A result of
+ * {@code UNDEFINED} indicates that further investigation may be
+ * required.
+ */
+public enum ConditionResult
+{
+  /**
+   * Indicates that the condition evaluated to {@code false}.
+   */
+  FALSE("false"),
+
+  /**
+   * Indicates that the condition could not be evaluated and its result
+   * is undefined.
+   */
+  UNDEFINED("undefined"),
+
+  /**
+   * Indicates that the condition evaluated to {@code true}.
+   */
+  TRUE("true");
+
+  // Boolean -> ConditionResult map.
+  private static final boolean[] booleanMap = { false, false, true };
+
+  // AND truth table.
+  private static final ConditionResult[][] logicalAND =
+      { { FALSE, FALSE, FALSE }, { FALSE, UNDEFINED, UNDEFINED },
+          { FALSE, UNDEFINED, TRUE }, };
+
+  // NOT truth table.
+  private static final ConditionResult[] logicalNOT =
+      { TRUE, UNDEFINED, FALSE };
+
+  // OR truth table.
+  private static final ConditionResult[][] logicalOR =
+      { { FALSE, UNDEFINED, TRUE }, { UNDEFINED, UNDEFINED, TRUE },
+          { TRUE, TRUE, TRUE }, };
+
+
+
+  /**
+   * Returns the logical AND of zero condition results, which is always
+   * {@code TRUE}.
+   *
+   * @return The logical OR of zero condition results, which is always
+   *         {@code TRUE}.
+   */
+  public static ConditionResult and()
+  {
+    return TRUE;
+  }
+
+
+
+  /**
+   * Returns the logical AND of the provided condition result, which is
+   * always {@code r}.
+   *
+   * @param r
+   *          The condition result.
+   * @return The logical AND of the provided condition result, which is
+   *         always {@code r}.
+   */
+  public static ConditionResult and(ConditionResult r)
+  {
+    return r;
+  }
+
+
+
+  /**
+   * Returns the logical AND of the provided condition results, which is
+   * {@code TRUE} if all of the provided condition results are {@code
+   * TRUE}, {@code FALSE} if at least one of them is {@code FALSE}, and
+   * {@code UNDEFINED} otherwise. Note that {@code TRUE} is returned if
+   * the provided list of results is empty.
+   *
+   * @param results
+   *          The condition results to be compared.
+   * @return The logical AND of the provided condition results.
+   */
+  public static ConditionResult and(ConditionResult... results)
+  {
+    ConditionResult finalResult = TRUE;
+    for (ConditionResult result : results)
+    {
+      finalResult = and(finalResult, result);
+      if (finalResult == FALSE)
+        break;
+    }
+    return finalResult;
+  }
+
+
+
+  /**
+   * Returns the logical AND of the provided condition results, which is
+   * {@code TRUE} if both of the provided condition results are {@code
+   * TRUE}, {@code FALSE} if at least one of them is {@code FALSE} , and
+   * {@code UNDEFINED} otherwise.
+   *
+   * @param r1
+   *          The first condition result to be compared.
+   * @param r2
+   *          The second condition result to be compared.
+   * @return The logical AND of the provided condition results.
+   */
+  public static ConditionResult and(ConditionResult r1,
+      ConditionResult r2)
+  {
+    return logicalAND[r1.ordinal()][r2.ordinal()];
+  }
+
+
+
+  /**
+   * Returns the logical NOT of the provided condition result, which is
+   * {@code TRUE} if the provided condition result is {@code FALSE},
+   * {@code TRUE} if it is {@code FALSE}, and {@code UNDEFINED}
+   * otherwise.
+   *
+   * @param r
+   *          The condition result to invert.
+   * @return The logical NOT of the provided condition result.
+   */
+  public static ConditionResult not(ConditionResult r)
+  {
+    return logicalNOT[r.ordinal()];
+  }
+
+
+
+  /**
+   * Returns the logical OR of zero condition results, which is always
+   * {@code FALSE}.
+   *
+   * @return The logical OR of zero condition results, which is always
+   *         {@code FALSE}.
+   */
+  public static ConditionResult or()
+  {
+    return FALSE;
+  }
+
+
+
+  /**
+   * Returns the logical OR of the provided condition result, which is
+   * always {@code r}.
+   *
+   * @param r
+   *          The condition result.
+   * @return The logical OR of the provided condition result, which is
+   *         always {@code r}.
+   */
+  public static ConditionResult or(ConditionResult r)
+  {
+    return r;
+  }
+
+
+
+  /**
+   * Returns the logical OR of the provided condition results, which is
+   * {@code FALSE} if all of the provided condition results are {@code
+   * FALSE}, {@code TRUE} if at least one of them is {@code TRUE}, and
+   * {@code UNDEFINED} otherwise. Note that {@code FALSE} is returned if
+   * the provided list of results is empty.
+   *
+   * @param results
+   *          The condition results to be compared.
+   * @return The logical OR of the provided condition results.
+   */
+  public static ConditionResult or(ConditionResult... results)
+  {
+    ConditionResult finalResult = FALSE;
+    for (ConditionResult result : results)
+    {
+      finalResult = and(finalResult, result);
+      if (finalResult == TRUE)
+        break;
+    }
+    return finalResult;
+  }
+
+
+
+  /**
+   * Returns the logical OR of the provided condition results, which is
+   * {@code FALSE} if both of the provided condition results are {@code
+   * FALSE}, {@code TRUE} if at least one of them is {@code TRUE} , and
+   * {@code UNDEFINED} otherwise.
+   *
+   * @param r1
+   *          The first condition result to be compared.
+   * @param r2
+   *          The second condition result to be compared.
+   * @return The logical OR of the provided condition results.
+   */
+  public static ConditionResult or(ConditionResult r1,
+      ConditionResult r2)
+  {
+    return logicalOR[r1.ordinal()][r2.ordinal()];
+  }
+
+
+
+  /**
+   * Returns the condition result which is equivalent to the provided
+   * boolean value.
+   *
+   * @param b
+   *          The boolean value.
+   * @return {@code TRUE} if {@code b} was {@code true}, otherwise
+   *         {@code FALSE}.
+   */
+  public static ConditionResult valueOf(boolean b)
+  {
+    return b ? TRUE : FALSE;
+  }
+
+  // The human-readable name for this result.
+  private final String resultName;
+
+
+
+  // Prevent instantiation.
+  private ConditionResult(String resultName)
+  {
+    this.resultName = resultName;
+  }
+
+
+
+  /**
+   * Converts this condition result to a boolean value. {@code FALSE}
+   * and {@code UNDEFINED} are both converted to {@code false}, and
+   * {@code TRUE} is converted to {@code true}.
+   *
+   * @return The boolean equivalent of this condition result.
+   */
+  public boolean toBoolean()
+  {
+    return booleanMap[ordinal()];
+  }
+
+
+
+  /**
+   * Returns the string representation of this condition result.
+   *
+   * @return The string representation of his condition result.
+   */
+  @Override
+  public String toString()
+  {
+    return resultName;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/Connection.java b/sdk/src/org/opends/sdk/Connection.java
new file mode 100644
index 0000000..a4be674
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Connection.java
@@ -0,0 +1,1084 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.List;
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * A synchronous connection with a Directory Server over which read and
+ * update operations may be performed. See RFC 4511 for the LDAPv3
+ * protocol specification and more information about the types of
+ * operations defined in LDAP.
+ * <p>
+ * <h3>Operation processing</h3>
+ * <p>
+ * All operations are performed synchronously and return an appropriate
+ * {@link Result} representing the final status of the operation.
+ * Operation failures, for whatever reason, are signalled using an
+ * {@link ErrorResultException}.
+ * <p>
+ * <h3>Closing connections</h3>
+ * <p>
+ * Applications must ensure that a connection is closed by calling
+ * {@link #close()} even if a fatal error occurs on the connection. Once
+ * a connection has been closed by the client application, any attempts
+ * to continue to use the connection will result in an
+ * {@link IllegalStateException} being thrown. Note that, if a fatal
+ * error is encountered on the connection, then the application can
+ * continue to use the connection. In this case all requests subsequent
+ * to the failure will fail with an appropriate
+ * {@link ErrorResultException} when their result is retrieved.
+ * <p>
+ * <h3>Event notification</h3>
+ * <p>
+ * Applications can choose to be notified when a connection is closed by
+ * the application, receives an unsolicited notification, or experiences
+ * a fatal error by registering a {@link ConnectionEventListener} with
+ * the connection using the {@link #addConnectionEventListener} method.
+ * <p>
+ * <h3>TO DO</h3>
+ * <p>
+ * <ul>
+ * <li>do we need isClosed() and isValid()?
+ * <li>do we need connection event notification of client close? JDBC
+ * and JCA have this functionality in their pooled (managed) connection
+ * APIs. We need some form of event notification at the app level for
+ * unsolicited notifications.
+ * <li>method for performing update operation (e.g. LDIF change
+ * records).
+ * <li>should unsupported methods throw UnsupportedOperationException or
+ * throw an ErrorResultException using an UnwillingToPerform result code
+ * (or something similar)?
+ * <li>Handler version of search.
+ * <li>Finish off Search APIs to support blocking queues and
+ * collections.
+ * <li>searchSingleEntry Javadoc needs finishing. Code sample is
+ * incorrect. How about references? What exceptions are thrown?
+ * </ul>
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 -
+ *      Lightweight Directory Access Protocol (LDAP): The Protocol </a>
+ */
+public interface Connection extends Closeable
+{
+
+  /**
+   * Adds an entry to the Directory Server using the provided add
+   * request.
+   *
+   * @param request
+   *          The add request.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support add operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  Result add(AddRequest request) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Adds an entry to the Directory Server using the provided lines of
+   * LDIF.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * AddRequest request = new AddRequest(ldifLines);
+   * connection.add(request);
+   * </pre>
+   *
+   * @param ldifLines
+   *          Lines of LDIF containing the an LDIF add change record or
+   *          an LDIF entry record.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support add operations.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} was empty, or contained invalid
+   *           LDIF, or could not be decoded using the default schema.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null} .
+   */
+  Result add(String... ldifLines) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      LocalizedIllegalArgumentException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Adds the provided entry to the Directory Server.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * AddRequest request = new AddRequest(entry);
+   * connection.add(request);
+   * </pre>
+   *
+   * @param entry
+   *          The entry to be added.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support add operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null} .
+   */
+  Result add(Entry entry) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Authenticates to the Directory Server using the provided bind
+   * request.
+   *
+   * @param request
+   *          The bind request.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support bind operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  BindResult bind(BindRequest request) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Authenticates to the Directory Server using simple authentication
+   * and the provided user name and password.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * BindRequest request = new SimpleBindRequest(name, password);
+   * connection.bind(request);
+   * </pre>
+   *
+   * @param name
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as, which may be empty.
+   * @param password
+   *          The password of the Directory object that the client
+   *          wishes to bind as, which may be empty.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support bind operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code name} or {@code password} was {@code null}.
+   */
+  BindResult bind(String name, String password)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Releases any resources associated with this connection. For
+   * physical connections to a Directory Server this will mean that an
+   * unbind request is sent and the underlying socket is closed.
+   * <p>
+   * Other connection implementations may behave differently, and may
+   * choose not to send an unbind request if its use is inappropriate
+   * (for example a pooled connection will be released and returned to
+   * its connection pool without ever issuing an unbind request).
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * UnbindRequest request = new UnbindRequest();
+   * connection.close(request);
+   * </pre>
+   *
+   * Calling {@code close} on a connection that is already closed has no
+   * effect.
+   */
+  void close();
+
+
+
+  /**
+   * Releases any resources associated with this connection. For
+   * physical connections to a Directory Server this will mean that the
+   * provided unbind request is sent and the underlying socket is
+   * closed.
+   * <p>
+   * Other connection implementations may behave differently, and may
+   * choose to ignore the provided unbind request if its use is
+   * inappropriate (for example a pooled connection will be released and
+   * returned to its connection pool without ever issuing an unbind
+   * request).
+   * <p>
+   * Calling {@code close} on a connection that is already closed has no
+   * effect.
+   *
+   * @param request
+   *          The unbind request to use in the case where a physical
+   *          connection is closed.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  void close(UnbindRequest request) throws NullPointerException;
+
+
+
+  /**
+   * Compares an entry in the Directory Server using the provided
+   * compare request.
+   *
+   * @param request
+   *          The compare request.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support compare operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  CompareResult compare(CompareRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Compares the named entry in the Directory Server against the
+   * provided attribute value assertion.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * CompareRequest request = new CompareRequest(name, attributeDescription,
+   *     assertionValue);
+   * connection.compare(request);
+   * </pre>
+   *
+   * @param name
+   *          The distinguished name of the entry to be compared.
+   * @param attributeDescription
+   *          The name of the attribute to be compared.
+   * @param assertionValue
+   *          The assertion value to be compared.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} or {@code AttributeDescription} could not
+   *           be decoded using the default schema.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support compare operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code name}, {@code attributeDescription}, or {@code
+   *           assertionValue} was {@code null}.
+   */
+  CompareResult compare(String name, String attributeDescription,
+      String assertionValue) throws ErrorResultException,
+      InterruptedException, LocalizedIllegalArgumentException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Deletes an entry from the Directory Server using the provided
+   * delete request.
+   *
+   * @param request
+   *          The delete request.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support delete operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  Result delete(DeleteRequest request) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Deletes the named entry from the Directory Server.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * DeleteRequest request = new DeleteRequest(name);
+   * connection.delete(request);
+   * </pre>
+   *
+   * @param name
+   *          The distinguished name of the entry to be deleted.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support delete operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  Result delete(String name) throws ErrorResultException,
+      InterruptedException, LocalizedIllegalArgumentException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Requests that the Directory Server performs the provided extended
+   * request.
+   *
+   * @param <R>
+   *          The type of result returned by the extended request.
+   * @param request
+   *          The extended request.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support extended operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <R extends Result> R extendedRequest(ExtendedRequest<R> request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Requests that the Directory Server performs the provided extended
+   * request.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * GenericExtendedRequest request = new GenericExtendedRequest(
+   *     requestName, requestValue);
+   * connection.extendedRequest(request);
+   * </pre>
+   *
+   * @param requestName
+   *          The dotted-decimal representation of the unique OID
+   *          corresponding to the extended request.
+   * @param requestValue
+   *          The content of the extended request in a form defined by
+   *          the extended operation, or {@code null} if there is no
+   *          content.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support extended operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code requestName} was {@code null}.
+   */
+  GenericExtendedResult extendedRequest(String requestName,
+      ByteString requestValue) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  // /**
+  // * Indicates whether or not this connection has been explicitly
+  // closed
+  // * by calling {@code close}. This method will not return {@code
+  // true}
+  // * if a fatal error has occurred on the connection unless {@code
+  // * close} has been called.
+  // *
+  // * @return {@code true} if this connection has been explicitly
+  // closed
+  // * by calling {@code close}, or {@code false} otherwise.
+  // */
+  // boolean isClosed();
+  //
+  //
+  //
+  // /**
+  // * Indicates whether or not this connection is valid. A connection
+  // is
+  // * not valid if the method {@code close} has been called on it or if
+  // * certain fatal errors have occurred. This method is guaranteed to
+  // * return {@code false} only when it is called after the method
+  // * {@code close} has been called.
+  // * <p>
+  // * Implementations may choose to send a no-op request to the
+  // * underlying Directory Server in order to determine if the
+  // underlying
+  // * connection is still valid.
+  // *
+  // * @return {@code true} if this connection is valid, or {@code
+  // false}
+  // * otherwise.
+  // * @throws InterruptedException
+  // * If the current thread was interrupted while waiting.
+  // */
+  // boolean isValid() throws InterruptedException;
+  //
+  //
+  //
+  // /**
+  // * Indicates whether or not this connection is valid. A connection
+  // is
+  // * not valid if the method {@code close} has been called on it or if
+  // * certain fatal errors have occurred. This method is guaranteed to
+  // * return {@code false} only when it is called after the method
+  // * {@code close} has been called.
+  // * <p>
+  // * Implementations may choose to send a no-op request to the
+  // * underlying Directory Server in order to determine if the
+  // underlying
+  // * connection is still valid.
+  // *
+  // * @param timeout
+  // * The maximum time to wait.
+  // * @param unit
+  // * The time unit of the timeout argument.
+  // * @return {@code true} if this connection is valid, or {@code
+  // false}
+  // * otherwise.
+  // * @throws InterruptedException
+  // * If the current thread was interrupted while waiting.
+  // * @throws TimeoutException
+  // * If the wait timed out.
+  // */
+  // boolean isValid(long timeout, TimeUnit unit)
+  // throws InterruptedException, TimeoutException;
+
+  /**
+   * Modifies an entry in the Directory Server using the provided modify
+   * request.
+   *
+   * @param request
+   *          The modify request.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support modify operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  Result modify(ModifyRequest request) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Modifies an entry in the Directory Server using the provided lines
+   * of LDIF.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * ModifyRequest request = new ModifyRequest(name, ldifChanges);
+   * connection.modify(request);
+   * </pre>
+   *
+   * @param ldifLines
+   *          Lines of LDIF containing the a single LDIF modify change
+   *          record.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support modify operations.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} was empty, or contained invalid
+   *           LDIF, or could not be decoded using the default schema.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null} .
+   */
+  Result modify(String... ldifLines) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      LocalizedIllegalArgumentException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Renames an entry in the Directory Server using the provided modify
+   * DN request.
+   *
+   * @param request
+   *          The modify DN request.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support modify DN operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  Result modifyDN(ModifyDNRequest request) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Renames the named entry in the Directory Server using the provided
+   * new RDN.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * ModifyDNRequest request = new ModifyDNRequest(name, newRDN);
+   * connection.modifyDN(request);
+   * </pre>
+   *
+   * @param name
+   *          The distinguished name of the entry to be renamed.
+   * @param newRDN
+   *          The new RDN of the entry.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} or {@code newRDN} could not be decoded
+   *           using the default schema.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support modify DN operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code name} or {@code newRDN} was {@code null}.
+   */
+  Result modifyDN(String name, String newRDN)
+      throws ErrorResultException, LocalizedIllegalArgumentException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Searches the Directory Server using the provided search request.
+   * Any matching entries returned by the search as well as any search
+   * result references will be passed to the provided search result
+   * handler.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param request
+   *          The search request.
+   * @param handler
+   *          A search result handler which can be used to process the
+   *          search result entries and references as they are received,
+   *          may be {@code null}.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} was {@code null}.
+   */
+  <P> Result search(SearchRequest request,
+      SearchResultHandler<P> handler, P p) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Searches the Directory Server using the provided search request.
+   * Any matching entries returned by the search will be added to
+   * {@code entries}, even if the final search result indicates that the
+   * search failed. Search result references will be discarded.
+   * <p>
+   * <b>Warning:</b> Usage of this method is discouraged if the search
+   * request is expected to yield a large number of search results since
+   * the entire set of results will be stored in memory, potentially
+   * causing an {@code OutOfMemoryError}.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * connection.search(request, entries, null);
+   * </pre>
+   *
+   * @param request
+   *          The search request.
+   * @param entries
+   *          The collection to which matching entries should be added.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} or {@code entries} was {@code null}.
+   */
+  Result search(SearchRequest request,
+      Collection<? super SearchResultEntry> entries)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Searches the Directory Server using the provided search request.
+   * Any matching entries returned by the search will be added to
+   * {@code entries}, even if the final search result indicates that the
+   * search failed. Similarly, search result references returned by the
+   * search will be added to {@code references}.
+   * <p>
+   * <b>Warning:</b> Usage of this method is discouraged if the search
+   * request is expected to yield a large number of search results since
+   * the entire set of results will be stored in memory, potentially
+   * causing an {@code OutOfMemoryError}.
+   *
+   * @param request
+   *          The search request.
+   * @param entries
+   *          The collection to which matching entries should be added.
+   * @param references
+   *          The collection to which search result references should be
+   *          added, or {@code null} if references are to be discarded.
+   * @return The result of the operation.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If {@code request} or {@code entries} was {@code null}.
+   */
+  Result search(SearchRequest request,
+      Collection<? super SearchResultEntry> entries,
+      Collection<? super SearchResultReference> references)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Searches the Directory Server using the provided search parameters.
+   * Any matching entries returned by the search will be added to a
+   * {@code List} which is returned if the search succeeds. Search
+   * result references will be discarded.
+   * <p>
+   * <b>Warning:</b> Usage of this method is discouraged if the search
+   * request is expected to yield a large number of search results since
+   * the entire set of results will be stored in memory, potentially
+   * causing an {@code OutOfMemoryError}.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * SearchRequest request = new SearchRequest(baseDN, scope, filter,
+   *     attributeDescriptions);
+   * connection.search(request, new LinkedList&lt;SearchResultEntry&gt;());
+   * </pre>
+   *
+   * @param baseObject
+   *          The distinguished name of the base entry relative to which
+   *          the search is to be performed.
+   * @param scope
+   *          The scope of the search.
+   * @param filter
+   *          The filter that defines the conditions that must be
+   *          fulfilled in order for an entry to be returned.
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with each
+   *          entry.
+   * @return A list containing any matching entries returned by the
+   *         search.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code baseObject} could not be decoded using the
+   *           default schema or if {@code filter} is not a valid LDAP
+   *           string representation of a filter.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If the {@code baseObject}, {@code scope}, or {@code
+   *           filter} were {@code null}.
+   */
+  List<SearchResultEntry> search(String baseObject, SearchScope scope,
+      String filter, String... attributeDescriptions)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Searches the Directory Server for a single entry using the provided
+   * search request. If the search returns more than one entry then an
+   * {@code ErrorResultException} is thrown. If no entry is found then
+   * this method returns {@code null}.
+   *
+   * @param request
+   *          The search request.
+   * @return The single search result entry returned from the search.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If the {@code request} was {@code null}.
+   */
+  SearchResultEntry searchSingleEntry(SearchRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Searches the Directory Server for a single entry using the provided
+   * search parameters. If the search returns more than one entry then
+   * an {@code ErrorResultException} is thrown. If no entry is found
+   * then this method returns {@code null}.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * SearchRequest request = new SearchRequest(baseObject, scope, filter,
+   *     attributeDescriptions);
+   * connection.searchSingleEntry(request);
+   * </pre>
+   *
+   * @param baseObject
+   *          The distinguished name of the base entry relative to which
+   *          the search is to be performed.
+   * @param scope
+   *          The scope of the search.
+   * @param filter
+   *          The filter that defines the conditions that must be
+   *          fulfilled in order for an entry to be returned.
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with each
+   *          entry.
+   * @return The single search result entry returned from the search.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code baseObject} could not be decoded using the
+   *           default schema or if {@code filter} is not a valid LDAP
+   *           string representation of a filter.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If the {@code baseObject}, {@code scope}, or {@code
+   *           filter} were {@code null}.
+   */
+  SearchResultEntry searchSingleEntry(String baseObject,
+      SearchScope scope, String filter, String... attributeDescriptions)
+      throws ErrorResultException, InterruptedException,
+      LocalizedIllegalArgumentException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Reads the named entry from the Directory Server. If no entry is
+   * found then this method returns {@code null}.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * SearchRequest request = new SearchRequest(baseObject,
+   *     SearchScope.BASE_OBJECT, &quot;(objectClass=*)&quot;, attributeDescriptions);
+   * connection.searchSingleEntry(request);
+   * </pre>
+   *
+   * @param baseObject
+   *          The distinguished name of the entry to be read.
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with the entry.
+   * @return The single search result entry returned from the search.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code baseObject} could not be decoded using the
+   *           default schema.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If the {@code baseObject} was {@code null}.
+   */
+  SearchResultEntry readEntry(String baseObject,
+      String... attributeDescriptions) throws ErrorResultException,
+      InterruptedException, LocalizedIllegalArgumentException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException;
+
+
+
+  /**
+   * Reads the named entry from the Directory Server. If no entry is
+   * found then this method returns {@code null}.
+   * <p>
+   * This method is equivalent to the following code:
+   *
+   * <pre>
+   * SearchRequest request = new SearchRequest(baseObject,
+   *     SearchScope.BASE_OBJECT, &quot;(objectClass=*)&quot;, attributeDescriptions);
+   * connection.searchSingleEntry(request);
+   * </pre>
+   *
+   * @param baseObject
+   *          The distinguished name of the entry to be read.
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with the entry.
+   * @return The single search result entry returned from the search.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws UnsupportedOperationException
+   *           If this connection does not support search operations.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If the {@code baseObject} was {@code null}.
+   */
+  SearchResultEntry readEntry(DN baseObject,
+      String... attributeDescriptions) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Registers the provided connection event listener so that it will be
+   * notified when this connection is closed by the application,
+   * receives an unsolicited notification, or experiences a fatal error.
+   *
+   * @param listener
+   *          The listener which wants to be notified when events occur
+   *          on this connection.
+   * @throws IllegalStateException
+   *           If this connection has already been closed, i.e. if
+   *           {@code isClosed() == true}.
+   * @throws NullPointerException
+   *           If the {@code listener} was {@code null}.
+   */
+  void addConnectionEventListener(ConnectionEventListener listener)
+      throws IllegalStateException, NullPointerException;
+
+
+
+  /**
+   * Removes the provided connection event listener from this connection
+   * so that it will no longer be notified when this connection is
+   * closed by the application, receives an unsolicited notification, or
+   * experiences a fatal error.
+   *
+   * @param listener
+   *          The listener which no longer wants to be notified when
+   *          events occur on this connection.
+   * @throws NullPointerException
+   *           If the {@code listener} was {@code null}.
+   */
+  void removeConnectionEventListener(ConnectionEventListener listener)
+      throws NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionEventListener.java b/sdk/src/org/opends/sdk/ConnectionEventListener.java
new file mode 100644
index 0000000..d17475e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionEventListener.java
@@ -0,0 +1,109 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.EventListener;
+
+import org.opends.sdk.responses.GenericExtendedResult;
+
+
+
+/**
+ * An object that registers to be notified when a connection is closed
+ * by the application, receives an unsolicited notification, or
+ * experiences a fatal error.
+ * <p>
+ * TODO: isolate fatal connection errors as a sub-type of
+ * ErrorResultException.
+ * <p>
+ * TODO: do we need client initiated close notification as in JCA /
+ * JDBC? A simpler approach would be for the connection pool to wrap the
+ * underlying physical connection with its own. It can then intercept
+ * the close request from the client. This has the disadvantage in that
+ * we lose any specialized methods exposed by the underlying physical
+ * connection (i.e. if the physical connection extends Connection and
+ * provides additional methods) since the connection pool effectively
+ * hides them via its wrapper.
+ */
+public interface ConnectionEventListener extends EventListener
+{
+  // /**
+  // * Notifies this connection event listener that the application has
+  // * called {@link Connection#close} on the connection. The connection
+  // * event listener will be notified immediately after the application
+  // * calls the {@link Connection#close} method on the associated
+  // * connection.
+  // *
+  // * @param connection
+  // * The connection that has just been closed by the
+  // * application.
+  // */
+  // void connectionClosed(Connection connection);
+
+  /**
+   * Notifies this connection event listener that the connection has
+   * just received the provided unsolicited notification from the
+   * server.
+   * <p>
+   * <b>Note:</b> disconnect notifications are treated as fatal
+   * connection errors and are handled by the
+   * {@link #connectionErrorOccurred} method.
+   *
+   * @param notification
+   *          The unsolicited notification
+   */
+  void connectionReceivedUnsolicitedNotification(
+      GenericExtendedResult notification);
+
+
+
+  /**
+   * Notifies this connection event listener that a fatal error has
+   * occurred and the connection can no longer be used - the server has
+   * crashed, for example. The connection implementation makes this
+   * notification just before it throws the provided
+   * {@link ErrorResultException} to the application.
+   * <p>
+   * <b>Note:</b> disconnect notifications are treated as fatal
+   * connection errors and are handled by this method. In this case
+   * {@code isDisconnectNotification} will be {@code true} and {@code
+   * error} will contain the result code and any diagnostic information
+   * contained in the notification message.
+   *
+   * @param isDisconnectNotification
+   *          {@code true} if the error was triggered by a disconnect
+   *          notification sent by the server, otherwise {@code false}.
+   * @param error
+   *          The exception that is about to be thrown to the
+   *          application.
+   */
+  void connectionErrorOccurred(boolean isDisconnectNotification,
+      ErrorResultException error);
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionFactory.java b/sdk/src/org/opends/sdk/ConnectionFactory.java
new file mode 100644
index 0000000..8d8d998
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionFactory.java
@@ -0,0 +1,95 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+/**
+ * A connection factory provides an interface for obtaining a connection
+ * to a Directory Server. Connection factories can be used to wrap other
+ * connection factories in order to provide enhanced capabilities in a
+ * manner which is transparent to the application. For example:
+ * <ul>
+ * <li>Connection pooling
+ * <li>Load balancing
+ * <li>Keep alive
+ * <li>Transactional connections
+ * <li>Connections to LDIF files
+ * <li>Data transformations
+ * <li>Logging connections
+ * <li>Read-only connections
+ * <li>Pre-authenticated connections
+ * <li>Recording connections, with primitive roll-back functionality
+ * </ul>
+ * An application typically obtains a connection from a connection
+ * factory, performs one or more operations, and then closes the
+ * connection. Applications should aim to close connections as soon as
+ * possible in order to avoid resource contention.
+ *
+ * @param <C>
+ *          The type of asynchronous connection returned by this
+ *          connection factory.
+ */
+public interface ConnectionFactory<C extends AsynchronousConnection>
+{
+  /**
+   * Returns a connection to the Directory Server associated with this
+   * connection factory. The connection returned by this method can be
+   * used immediately.
+   *
+   * @return A connection to the Directory Server associated with this
+   *         connection factory.
+   * @throws ErrorResultException
+   *           If the connection request failed for some reason.
+   */
+  Connection getConnection() throws ErrorResultException;
+
+
+
+  /**
+   * Initiates an asynchronous connection request to the Directory
+   * Server associated with this connection factory. The returned
+   * {@code ConnectionFuture} can be used to retrieve the completed
+   * asynchronous connection. Alternatively, if a {@code
+   * ConnectionResultHandler} is provided, the handler will be notified
+   * when the connection is available and ready for use.
+   *
+   * @param <P>
+   *          The type of the additional parameter to the handler's
+   *          methods.
+   * @param handler
+   *          The completion handler, or {@code null} if no handler is
+   *          to be used.
+   * @param p
+   *          Optional additional handler parameter.
+   * @return A future which can be used to retrieve the asynchronous
+   *         connection.
+   */
+  <P> ConnectionFuture<? extends C> getAsynchronousConnection(
+      ConnectionResultHandler<? super C, P> handler, P p);
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionFuture.java b/sdk/src/org/opends/sdk/ConnectionFuture.java
new file mode 100644
index 0000000..517f68c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionFuture.java
@@ -0,0 +1,150 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+
+
+/**
+ * A handle which can be used to retrieve a requested {@code
+ * AsynchronousConnection}.
+ * <p>
+ * TODO: Do we want to throw an ErrorResultException? I think we do
+ * because exceptions are not limited to connection related errors. For
+ * example, a transacted connection would already have a physical
+ * connection; an error could occur when sending the start txn extended
+ * op.
+ *
+ * @param <C>
+ *          The type of asynchronous connection returned by this
+ *          connection future.
+ */
+public interface ConnectionFuture<C extends AsynchronousConnection>
+    extends Future<C>
+{
+  /**
+   * Attempts to cancel the request. This attempt will fail if the
+   * request has already completed or has already been cancelled. If
+   * successful, then cancellation results in an abandon or cancel
+   * request (if configured) being sent to the server.
+   * <p>
+   * After this method returns, subsequent calls to {@link #isDone} will
+   * always return {@code true}. Subsequent calls to
+   * {@link #isCancelled} will always return {@code true} if this method
+   * returned {@code true}.
+   *
+   * @param mayInterruptIfRunning
+   *          {@code true} if the thread executing executing the
+   *          response handler should be interrupted; otherwise,
+   *          in-progress response handlers are allowed to complete.
+   * @return {@code false} if the request could not be cancelled,
+   *         typically because it has already completed normally;
+   *         {@code true} otherwise.
+   */
+  boolean cancel(boolean mayInterruptIfRunning);
+
+
+
+  /**
+   * Waits if necessary for the connection request to complete, and then
+   * returns the asynchronous connection if the connection request
+   * succeeded. If the connection request failed (i.e. a non-successful
+   * result code was obtained) then a {@link ErrorResultException} is
+   * thrown.
+   *
+   * @return The asynchronous connection, but only if the the connection
+   *         request succeeded.
+   * @throws CancellationException
+   *           If the connection request was cancelled using a call to
+   *           {@link #cancel}.
+   * @throws ErrorResultException
+   *           If the connection request failed for some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   */
+  C get() throws InterruptedException, ErrorResultException;
+
+
+
+  /**
+   * Waits if necessary for at most the given time for the connection
+   * request to complete, and then returns the asynchronous connection
+   * if the connection request succeeded. If the connection request
+   * failed (i.e. a non-successful result code was obtained) then a
+   * {@link ErrorResultException} is thrown.
+   *
+   * @param timeout
+   *          The maximum time to wait.
+   * @param unit
+   *          The time unit of the timeout argument.
+   * @return The asynchronous connection, but only if the the connection
+   *         request succeeded.
+   * @throws CancellationException
+   *           If the connection request was cancelled using a call to
+   *           {@link #cancel}.
+   * @throws ErrorResultException
+   *           If the connection request failed for some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws TimeoutException
+   *           If the wait timed out.
+   */
+  C get(long timeout, TimeUnit unit) throws InterruptedException,
+      TimeoutException, ErrorResultException;
+
+
+
+  /**
+   * Returns {@code true} if the connection request was cancelled before
+   * it completed normally.
+   *
+   * @return {@code true} if the connection request was cancelled before
+   *         it completed normally, otherwise {@code false}.
+   */
+  boolean isCancelled();
+
+
+
+  /**
+   * Returns {@code true} if the connection request has completed.
+   * <p>
+   * Completion may be due to normal termination, an exception, or
+   * cancellation. In all of these cases, this method will return
+   * {@code true}.
+   *
+   * @return {@code true} if the connection request has completed,
+   *         otherwise {@code false}.
+   */
+  boolean isDone();
+}
diff --git a/sdk/src/org/opends/sdk/ConnectionResultHandler.java b/sdk/src/org/opends/sdk/ConnectionResultHandler.java
new file mode 100644
index 0000000..c4ee3d6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ConnectionResultHandler.java
@@ -0,0 +1,81 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+/**
+ * A completion handler which is notified when an asynchronous
+ * connection attempt has completed.
+ * <p>
+ * {@link ConnectionFactory} objects allow a connection result
+ * completion handler to be specified when attempting to connect to a
+ * Directory Server. The {@link #handleConnection} method is invoked
+ * when the operation completes successfully. The
+ * {@link #handleConnectionError} method is invoked if the operations
+ * fails.
+ * <p>
+ * Implementations of these methods should complete in a timely manner
+ * so as to avoid keeping the invoking thread from dispatching to other
+ * completion handlers.
+ *
+ * @param <C>
+ *          The type of asynchronous connection handled by this
+ *          connection result handler.
+ * @param <P>
+ *          The type of the additional parameter to this handler's
+ *          methods. Use {@link java.lang.Void} for visitors that do not
+ *          need an additional parameter.
+ */
+public interface ConnectionResultHandler<C extends AsynchronousConnection, P>
+{
+  /**
+   * Invoked when the asynchronous connection has completed
+   * successfully.
+   *
+   * @param p
+   *          A handler specified parameter.
+   * @param connection
+   *          The connection which can be used to interact with the
+   *          Directory Server.
+   */
+  void handleConnection(P p, C connection);
+
+
+
+  /**
+   * Invoked when the asynchronous connection attempt has failed.
+   *
+   * @param p
+   *          A handler specified parameter.
+   * @param error
+   *          The error result exception indicating why the asynchronous
+   *          connection attempt has failed.
+   */
+  void handleConnectionError(P p, ErrorResultException error);
+}
diff --git a/sdk/src/org/opends/sdk/DN.java b/sdk/src/org/opends/sdk/DN.java
new file mode 100644
index 0000000..40208c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/DN.java
@@ -0,0 +1,763 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.schema.UnknownSchemaElementException;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.SubstringReader;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A distinguished name (DN) as defined in RFC 4512 section 2.3 is the
+ * concatenation of its relative distinguished name (RDN) and its
+ * immediate superior's DN. A DN unambiguously refers to an entry in the
+ * Directory.
+ * <p>
+ * The following are examples of string representations of DNs:
+ * 
+ * <pre>
+ * UID=nobody@example.com,DC=example,DC=com CN=John
+ * Smith,OU=Sales,O=ACME Limited,L=Moab,ST=Utah,C=US
+ * </pre>
+ * 
+ * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.3">RFC
+ *      4512 - Lightweight Directory Access Protocol (LDAP): Directory
+ *      Information Models </a>
+ */
+public final class DN implements Iterable<RDN>, Comparable<DN>
+{
+  private static final DN ROOT_DN = new DN(null, null, "");
+
+  // This is the size of the per-thread per-schema DN cache. We should
+  // be conservative here in case there are many threads. We will only
+  // cache parent DNs, so there's no need for it to be big.
+  private static final int DN_CACHE_SIZE = 32;
+
+  private static final ThreadLocal<WeakHashMap<Schema, Map<String, DN>>> CACHE = new ThreadLocal<WeakHashMap<Schema, Map<String, DN>>>()
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected WeakHashMap<Schema, Map<String, DN>> initialValue()
+    {
+      return new WeakHashMap<Schema, Map<String, DN>>();
+    }
+
+  };
+
+
+
+  /**
+   * Returns the Root DN. The Root DN does not contain and RDN
+   * components and is superior to all other DNs.
+   * 
+   * @return The Root DN.
+   */
+  public static DN rootDN()
+  {
+    return ROOT_DN;
+  }
+
+
+
+  /**
+   * Parses the provided LDAP string representation of a DN using the
+   * default schema.
+   * 
+   * @param dn
+   *          The LDAP string representation of a DN.
+   * @return The parsed DN.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} is not a valid LDAP string representation
+   *           of a DN.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public static DN valueOf(String dn)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return valueOf(dn, Schema.getDefaultSchema());
+  }
+
+
+
+  /**
+   * Parses the provided LDAP string representation of a DN using the
+   * provided schema.
+   * 
+   * @param dn
+   *          The LDAP string representation of a DN.
+   * @param schema
+   *          The schema to use when parsing the DN.
+   * @return The parsed DN.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} is not a valid LDAP string representation
+   *           of a DN.
+   * @throws NullPointerException
+   *           If {@code dn} or {@code schema} was {@code null}.
+   */
+  public static DN valueOf(String dn, Schema schema)
+      throws LocalizedIllegalArgumentException
+  {
+    Validator.ensureNotNull(schema);
+    if (dn.length() == 0)
+    {
+      return ROOT_DN;
+    }
+
+    // First check if DN is already cached.
+    final Map<String, DN> cache = getCache(schema);
+    final DN cachedDN = cache.get(dn);
+    if (cachedDN != null)
+    {
+      return cachedDN;
+    }
+
+    // Not in cache so decode.
+    final SubstringReader reader = new SubstringReader(dn);
+    return decode(dn, reader, schema, cache);
+  }
+
+
+
+  // Decodes a DN using the provided reader and schema.
+  private static DN decode(String dnString, SubstringReader reader,
+      Schema schema, Map<String, DN> cache)
+      throws LocalizedIllegalArgumentException
+  {
+    reader.skipWhitespaces();
+    if (reader.remaining() == 0)
+    {
+      return ROOT_DN;
+    }
+
+    RDN rdn;
+    try
+    {
+      rdn = RDN.decode(null, reader, schema);
+    }
+    catch (final UnknownSchemaElementException e)
+    {
+      final Message message = ERR_DN_TYPE_NOT_FOUND.get(reader
+          .getString(), e.getMessageObject());
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    DN parent;
+    if (reader.remaining() > 0 && reader.read() == ',')
+    {
+      reader.mark();
+      final String parentString = reader.read(reader.remaining());
+
+      parent = cache.get(parentString);
+      if (parent == null)
+      {
+        reader.reset();
+        parent = decode(parentString, reader, schema, cache);
+
+        // Only cache parent DNs since leaf DNs are likely to make the
+        // cache to volatile.
+        cache.put(parentString, parent);
+      }
+    }
+    else
+    {
+      parent = ROOT_DN;
+    }
+
+    return new DN(rdn, parent, dnString);
+  }
+
+
+
+  @SuppressWarnings("serial")
+  private static Map<String, DN> getCache(Schema schema)
+  {
+    final WeakHashMap<Schema, Map<String, DN>> threadLocalMap = CACHE
+        .get();
+    Map<String, DN> schemaLocalMap = threadLocalMap.get(schema);
+
+    if (schemaLocalMap == null)
+    {
+      schemaLocalMap = new LinkedHashMap<String, DN>(DN_CACHE_SIZE,
+          0.75f, true)
+      {
+        @Override
+        protected boolean removeEldestEntry(Map.Entry<String, DN> e)
+        {
+          return size() > DN_CACHE_SIZE;
+        }
+      };
+      threadLocalMap.put(schema, schemaLocalMap);
+    }
+    return schemaLocalMap;
+  }
+
+
+
+  private final RDN rdn;
+
+  private final DN parent;
+
+  private final int size;
+
+  // We need to store the original string value if provided in order to
+  // preserve the original whitespace.
+  private String stringValue;
+
+  private String normalizedStringValue = null;
+
+
+
+  // Private constructor.
+  private DN(RDN rdn, DN parent, String stringValue)
+  {
+    this.rdn = rdn;
+    this.parent = parent;
+    this.stringValue = stringValue;
+    this.size = parent != null ? parent.size + 1 : 0;
+  }
+
+
+
+  /**
+   * Returns a DN which is subordinate to this DN and having the
+   * additional RDN components contained in the provided DN.
+   * 
+   * @param dn
+   *          The DN containing the RDN components to be added to this
+   *          DN.
+   * @return The subordinate DN.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public DN child(DN dn) throws NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+
+    if (dn.isRootDN())
+    {
+      return this;
+    }
+    else if (isRootDN())
+    {
+      return dn;
+    }
+    else
+    {
+      final RDN[] rdns = new RDN[dn.size()];
+      int i = rdns.length;
+      for (DN next = dn; next.rdn != null; next = next.parent)
+      {
+        rdns[--i] = next.rdn;
+      }
+      DN newDN = this;
+      for (i = 0; i < rdns.length; i++)
+      {
+        newDN = new DN(rdns[i], newDN, null);
+      }
+      return newDN;
+    }
+  }
+
+
+
+  /**
+   * Returns a DN which is an immediate child of this DN and having the
+   * specified RDN.
+   * 
+   * @param rdn
+   *          The RDN for the child DN.
+   * @return The child DN.
+   * @throws NullPointerException
+   *           If {@code rdn} was {@code null}.
+   */
+  public DN child(RDN rdn) throws NullPointerException
+  {
+    Validator.ensureNotNull(rdn);
+    return new DN(rdn, this, null);
+  }
+
+
+
+  /**
+   * Returns a DN which is subordinate to this DN and having the
+   * additional RDN components contained in the provided DN decoded
+   * using the default schema.
+   * 
+   * @param dn
+   *          The DN containing the RDN components to be added to this
+   *          DN.
+   * @return The subordinate DN.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} is not a valid LDAP string representation
+   *           of a DN.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public DN child(String dn) throws LocalizedIllegalArgumentException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    return child(valueOf(dn));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(DN dn)
+  {
+    final String s1 = toNormalizedString();
+    final String s2 = dn.toNormalizedString();
+    return s1.compareTo(s2);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object obj)
+  {
+    if (this == obj)
+    {
+      return true;
+    }
+    else if (obj instanceof DN)
+    {
+      final String s1 = toNormalizedString();
+      final String s2 = ((DN) obj).toNormalizedString();
+      return s1.equals(s2);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    final String s = toNormalizedString();
+    return s.hashCode();
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is an immediate child of the
+   * provided DN.
+   * 
+   * @param dn
+   *          The potential parent DN.
+   * @return {@code true} if this DN is the immediate child of the
+   *         provided DN, otherwise {@code false}.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isChildOf(DN dn) throws NullPointerException
+  {
+    // If this is the Root DN then parent will be null but this is ok.
+    return dn.equals(parent);
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is an immediate child of the
+   * provided DN decoded using the default schema.
+   * 
+   * @param dn
+   *          The potential parent DN.
+   * @return {@code true} if this DN is the immediate child of the
+   *         provided DN, otherwise {@code false}.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} is not a valid LDAP string representation
+   *           of a DN.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isChildOf(String dn)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    // If this is the Root DN then parent will be null but this is ok.
+    return isChildOf(valueOf(dn));
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is the immediate parent of the
+   * provided DN.
+   * 
+   * @param dn
+   *          The potential child DN.
+   * @return {@code true} if this DN is the immediate parent of the
+   *         provided DN, otherwise {@code false}.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isParentOf(DN dn) throws NullPointerException
+  {
+    // If dn is the Root DN then parent will be null but this is ok.
+    return equals(dn.parent);
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is the immediate parent of the
+   * provided DN.
+   * 
+   * @param dn
+   *          The potential child DN.
+   * @return {@code true} if this DN is the immediate parent of the
+   *         provided DN, otherwise {@code false}.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} is not a valid LDAP string representation
+   *           of a DN.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isParentOf(String dn)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    // If dn is the Root DN then parent will be null but this is ok.
+    return isParentOf(valueOf(dn));
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is the Root DN.
+   * 
+   * @return {@code true} if this DN is the Root DN, otherwise {@code
+   *         false}.
+   */
+  public boolean isRootDN()
+  {
+    return size == 0;
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is subordinate to or equal to the
+   * provided DN.
+   * 
+   * @param dn
+   *          The potential child DN.
+   * @return {@code true} if this DN is subordinate to or equal to the
+   *         provided DN, otherwise {@code false}.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isSubordinateOrEqualTo(DN dn)
+      throws NullPointerException
+  {
+    if (size < dn.size)
+    {
+      return false;
+    }
+    else if (size == dn.size)
+    {
+      return equals(dn);
+    }
+    else
+    {
+      // dn is a potential superior of this.
+      return parent(dn.size - size).equals(dn);
+    }
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is subordinate to or equal to the
+   * provided DN.
+   * 
+   * @param dn
+   *          The potential child DN.
+   * @return {@code true} if this DN is subordinate to or equal to the
+   *         provided DN, otherwise {@code false}.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} is not a valid LDAP string representation
+   *           of a DN.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isSubordinateOrEqualTo(String dn)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return isSubordinateOrEqualTo(valueOf(dn));
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is superior to or equal to the
+   * provided DN.
+   * 
+   * @param dn
+   *          The potential child DN.
+   * @return {@code true} if this DN is superior to or equal to the
+   *         provided DN, otherwise {@code false}.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isSuperiorOrEqualTo(DN dn) throws NullPointerException
+  {
+    if (size > dn.size)
+    {
+      return false;
+    }
+    else if (size == dn.size)
+    {
+      return equals(dn);
+    }
+    else
+    {
+      // dn is a potential subordinate of this.
+      return dn.parent(dn.size - size).equals(this);
+    }
+  }
+
+
+
+  /**
+   * Returns {@code true} if this DN is superior to or equal to the
+   * provided DN.
+   * 
+   * @param dn
+   *          The potential child DN.
+   * @return {@code true} if this DN is superior to or equal to the
+   *         provided DN, otherwise {@code false}.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} is not a valid LDAP string representation
+   *           of a DN.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  public boolean isSuperiorOrEqualTo(String dn)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return isSuperiorOrEqualTo(valueOf(dn));
+  }
+
+
+
+  /**
+   * Returns an iterator of the RDNs contained in this DN. The RDNs will
+   * be returned in the order starting with this DN's RDN, followed by
+   * the RDN of the parent DN, and so on.
+   * <p>
+   * Attempts to remove RDNs using an iterator's {@code remove()} method
+   * are not permitted and will result in an {@code
+   * UnsupportedOperationException} being thrown.
+   * 
+   * @return An iterator of the RDNs contained in this DN.
+   */
+  public Iterator<RDN> iterator()
+  {
+    return new Iterator<RDN>()
+    {
+      private DN dn = DN.this;
+
+
+
+      public boolean hasNext()
+      {
+        return dn.rdn != null;
+      }
+
+
+
+      public RDN next()
+      {
+        if (dn.rdn == null)
+        {
+          throw new NoSuchElementException();
+        }
+
+        final RDN rdn = dn.rdn;
+        dn = dn.parent;
+        return rdn;
+      }
+
+
+
+      public void remove()
+      {
+        throw new UnsupportedOperationException();
+      }
+    };
+  }
+
+
+
+  /**
+   * Returns the DN which is the immediate parent of this DN, or {@code
+   * null} if this DN is the Root DN.
+   * <p>
+   * This method is equivalent to:
+   * 
+   * <pre>
+   * parent(1);
+   * </pre>
+   * 
+   * @return The DN which is the immediate parent of this DN, or {@code
+   *         null} if this DN is the Root DN.
+   */
+  public DN parent()
+  {
+    return parent;
+  }
+
+
+
+  /**
+   * Returns the DN which is equal to this DN with the specified number
+   * of RDNs removed. Note that if {@code index} is zero then this DN
+   * will be returned (identity).
+   * 
+   * @param index
+   *          The number of RDNs to be removed.
+   * @return The DN which is equal to this DN with the specified number
+   *         of RDNs removed, or {@code null} if the parent of the Root
+   *         DN is reached.
+   * @throws IllegalArgumentException
+   *           If {@code index} is less than zero.
+   */
+  public DN parent(int index) throws IllegalArgumentException
+  {
+    // We allow size + 1 so that we can return null as the parent of the
+    // Root DN.
+    Validator.ensureTrue(index >= 0, "index less than zero");
+
+    DN parentDN = this;
+    for (int i = 0; parentDN != null && i < index; i++)
+    {
+      parentDN = parentDN.parent;
+    }
+    return parentDN;
+  }
+
+
+
+  /**
+   * Returns the RDN of this DN, or {@code null} if this DN is the Root
+   * DN.
+   * 
+   * @return The RDN of this DN, or {@code null} if this DN is the Root
+   *         DN.
+   */
+  public RDN rdn()
+  {
+    return rdn;
+  }
+
+
+
+  /**
+   * Returns the number of RDN components in this DN.
+   * 
+   * @return The number of RDN components in this DN.
+   */
+  public int size()
+  {
+    return size();
+  }
+
+
+
+  /**
+   * Returns the normalized string representation of this DN.
+   * 
+   * @return The normalized string representation of this DN.
+   */
+  public String toNormalizedString()
+  {
+    if (normalizedStringValue == null)
+    {
+      final StringBuilder builder = new StringBuilder();
+      if (!parent.isRootDN())
+      {
+        builder.append(parent.toNormalizedString());
+        builder.append(',');
+      }
+      rdn.toNormalizedString(builder);
+      normalizedStringValue = builder.toString();
+    }
+    return normalizedStringValue;
+  }
+
+
+
+  /**
+   * Returns the RFC 4514 string representation of this DN.
+   * 
+   * @return The RFC 4514 string representation of this DN.
+   * @see <a href="http://tools.ietf.org/html/rfc4514">RFC 4514 -
+   *      Lightweight Directory Access Protocol (LDAP): String
+   *      Representation of Distinguished Names </a>
+   */
+  public String toString()
+  {
+    // We don't care about potential race conditions here.
+    if (stringValue == null)
+    {
+      final StringBuilder builder = new StringBuilder();
+      rdn.toString(builder);
+      if (!parent.isRootDN())
+      {
+        builder.append(',');
+        builder.append(parent.toString());
+      }
+      stringValue = builder.toString();
+    }
+    return stringValue;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/DecodeException.java b/sdk/src/org/opends/sdk/DecodeException.java
new file mode 100644
index 0000000..fabaca2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/DecodeException.java
@@ -0,0 +1,154 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * Thrown when data from an input source cannot be decoded, perhaps due
+ * to the data being malformed in some way. By default decoding
+ * exceptions are fatal, indicating that the associated input source is
+ * no longer usable.
+ */
+@SuppressWarnings("serial")
+public final class DecodeException extends IOException implements
+    LocalizableException
+{
+  private final Message message;
+
+  private final boolean isFatal;
+
+
+
+  /**
+   * Creates a new fatal decode exception with the provided message. The
+   * associated input source can no longer be used.
+   *
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @return The new fatal decode exception.
+   */
+  public static DecodeException fatalError(Message message)
+  {
+    return new DecodeException(message, true, null);
+  }
+
+
+
+  /**
+   * Creates a new fatal decode exception with the provided message and
+   * root cause. The associated input source can no longer be used.
+   *
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @param cause
+   *          The underlying cause of this exception.
+   * @return The new fatal decode exception.
+   */
+  public static DecodeException fatalError(Message message,
+      Throwable cause)
+  {
+    return new DecodeException(message, true, cause);
+  }
+
+
+
+  /**
+   * Creates a new non-fatal decode exception with the provided message.
+   *
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @return The new non-fatal decode exception.
+   */
+  public static DecodeException error(Message message)
+  {
+    return new DecodeException(message, false, null);
+  }
+
+
+
+  /**
+   * Creates a new non-fatal decode exception with the provided message
+   * and root cause.
+   *
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @param cause
+   *          The underlying cause of this exception.
+   * @return The new non-fatal decode exception.
+   */
+  public static DecodeException error(Message message, Throwable cause)
+  {
+    return new DecodeException(message, false, cause);
+  }
+
+
+
+  // Construction is provided via factory methods.
+  private DecodeException(Message message, boolean isFatal,
+      Throwable cause)
+  {
+    super(message.toString(), cause);
+    this.message = message;
+    this.isFatal = isFatal;
+  }
+
+
+
+  /**
+   * Returns the message that explains the problem that occurred.
+   *
+   * @return Message of the problem
+   */
+  public Message getMessageObject()
+  {
+    return message;
+  }
+
+
+
+  /**
+   * Indicates whether or not the error was fatal and the associated
+   * input source can no longer be used.
+   *
+   * @return {@code true} if the error was fatal and the associated
+   *         input source can no longer be used, otherwise {@code false}
+   *         .
+   */
+  public boolean isFatal()
+  {
+    return isFatal;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java b/sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java
new file mode 100644
index 0000000..43cf1fb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/DereferenceAliasesPolicy.java
@@ -0,0 +1,223 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * A Search operation alias dereferencing policy as defined in RFC 4511
+ * section 4.5.1.3 is used to indicate whether or not alias entries (as
+ * defined in RFC 4512) are to be dereferenced during stages of a Search
+ * operation. The act of dereferencing an alias includes recursively
+ * dereferencing aliases that refer to aliases.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.5.1.3">RFC
+ *      4511 - Lightweight Directory Access Protocol (LDAP): The
+ *      Protocol </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4512">RFC 4512 -
+ *      Lightweight Directory Access Protocol (LDAP): Directory
+ *      Information Models </a>
+ */
+public final class DereferenceAliasesPolicy
+{
+  private static final DereferenceAliasesPolicy[] ELEMENTS = new DereferenceAliasesPolicy[4];
+
+  private static final List<DereferenceAliasesPolicy> IMMUTABLE_ELEMENTS = Collections
+      .unmodifiableList(Arrays.asList(ELEMENTS));
+
+  /**
+   * Do not dereference aliases in searching or in locating the base
+   * object of a Search operation.
+   */
+  public static final DereferenceAliasesPolicy NEVER = register(0,
+      "never");
+
+  /**
+   * While searching subordinates of the base object, dereference any
+   * alias within the scope of the Search operation. Dereferenced
+   * objects become the vertices of further search scopes where the
+   * Search operation is also applied. If the search scope is {@code
+   * WHOLE_SUBTREE}, the Search continues in the subtree(s) of any
+   * dereferenced object. If the search scope is {@code SINGLE_LEVEL},
+   * the search is applied to any dereferenced objects and is not
+   * applied to their subordinates.
+   */
+  public static final DereferenceAliasesPolicy IN_SEARCHING = register(
+      1, "search");
+
+  /**
+   * Dereference aliases in locating the base object of a Search
+   * operation, but not when searching subordinates of the base object.
+   */
+  public static final DereferenceAliasesPolicy FINDING_BASE = register(
+      2, "find");
+
+  /**
+   * Dereference aliases both in searching and in locating the base
+   * object of a Search operation.
+   */
+  public static final DereferenceAliasesPolicy ALWAYS = register(3,
+      "always");
+
+
+
+  /**
+   * Returns the alias dereferencing policy having the specified integer
+   * value as defined in RFC 4511 section 4.5.1.
+   *
+   * @param intValue
+   *          The integer value of the alias dereferencing policy.
+   * @return The dereference aliases policy, or {@code null} if there
+   *         was no alias dereferencing policy associated with {@code
+   *         intValue}.
+   */
+  public static DereferenceAliasesPolicy valueOf(int intValue)
+  {
+    if (intValue < 0 || intValue >= ELEMENTS.length)
+    {
+      return null;
+    }
+    return ELEMENTS[intValue];
+  }
+
+
+
+  /**
+   * Returns an unmodifiable list containing the set of available alias
+   * dereferencing policies indexed on their integer value as defined in
+   * RFC 4511 section 4.5.1.
+   *
+   * @return An unmodifiable list containing the set of available alias
+   *         dereferencing policies.
+   */
+  public static List<DereferenceAliasesPolicy> values()
+  {
+    return IMMUTABLE_ELEMENTS;
+  }
+
+
+
+  /**
+   * Creates and registers a new alias dereferencing policy with the
+   * application.
+   *
+   * @param intValue
+   *          The integer value of the alias dereferencing policy as
+   *          defined in RFC 4511 section 4.5.1.
+   * @param name
+   *          The name of the alias dereferencing policy.
+   * @return The new alias dereferencing policy.
+   */
+  private static DereferenceAliasesPolicy register(int intValue,
+      String name)
+  {
+    final DereferenceAliasesPolicy t = new DereferenceAliasesPolicy(
+        intValue, name);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  private final int intValue;
+
+  private final String name;
+
+
+
+  // Prevent direct instantiation.
+  private DereferenceAliasesPolicy(int intValue, String name)
+  {
+    this.intValue = intValue;
+    this.name = name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object obj)
+  {
+    if (this == obj)
+    {
+      return true;
+    }
+    else if (obj instanceof DereferenceAliasesPolicy)
+    {
+      return this.intValue == ((DereferenceAliasesPolicy) obj).intValue;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the integer value of this alias dereferencing policy as
+   * defined in RFC 4511 section 4.5.1.
+   *
+   * @return The integer value of this alias dereferencing policy.
+   */
+  public int intValue()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the string representation of this alias dereferencing
+   * policy.
+   *
+   * @return The string representation of this alias dereferencing
+   *         policy.
+   */
+  public String toString()
+  {
+    return name;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/Entry.java b/sdk/src/org/opends/sdk/Entry.java
new file mode 100644
index 0000000..ed49121
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Entry.java
@@ -0,0 +1,603 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * An entry, comprising of a distinguished name and zero or more
+ * attributes.
+ * <p>
+ * Some methods require a schema in order to decode their parameters
+ * (e.g. {@link #addAttribute(String, Object...)} and
+ * {@link #setName(String)}). In these cases the default schema is used
+ * unless an alternative schema is specified in the {@code Entry}
+ * constructor. The default schema is not used for any other purpose. In
+ * particular, an {@code Entry} will permit attributes to be added which
+ * have been decoded using a different schema.
+ * <p>
+ * Full LDAP modify semantics are provided via the {@link #addAttribute}, {@link #removeAttribute}, and {@link #replaceAttribute} methods.
+ * <p>
+ * Implementations should specify any constraints or special behavior.
+ * In particular, which methods are supported, and the order in which
+ * attributes are returned using the {@link #getAttributes()} method.
+ * <p>
+ * TODO: can we return collections/lists instead of iterables?
+ * <p>
+ * TODO: containsAttributeValue(String, Object)
+ */
+public interface Entry
+{
+
+  /**
+   * Adds all of the attribute values contained in {@code attribute} to
+   * this entry, merging with any existing attribute values (optional
+   * operation). If {@code attribute} is empty then this entry is left
+   * unchanged.
+   * <p>
+   * <b>NOTE:</b> This method implements LDAP Modify add semantics.
+   *
+   * @param attribute
+   *          The attribute values to be added to this entry, merging
+   *          with any existing attribute values.
+   * @param duplicateValues
+   *          A collection into which duplicate values will be added, or
+   *          {@code null} if duplicate values should not be saved.
+   * @return {@code true} if this entry changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes or their values
+   *           to be added.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  boolean addAttribute(Attribute attribute,
+      Collection<ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds all of the attribute values contained in {@code attribute} to
+   * this entry, merging with any existing attribute values (optional
+   * operation). If {@code attribute} is empty then this entry is left
+   * unchanged.
+   * <p>
+   * If {@code attribute} is an instance of {@code Attribute} then it
+   * will be added to this entry as if {@link #addAttribute} was called.
+   * <p>
+   * If {@code attribute} is not an instance of {@code Attribute} then
+   * its attribute description will be decoded using the schema
+   * associated with this entry, and any attribute values which are not
+   * instances of {@code ByteString} will be converted using the
+   * {@link ByteString#valueOf(Object)} method.
+   * <p>
+   * <b>NOTE:</b> This method implements LDAP Modify add semantics.
+   *
+   * @param attribute
+   *          The attribute values to be added to this entry merging
+   *          with any existing attribute values.
+   * @return {@code true} if this entry changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes or their values
+   *           to be added.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  boolean addAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds all of the attribute values contained in {@code values} to
+   * this entry, merging with any existing attribute values (optional
+   * operation). If {@code values} is {@code null} or empty then this
+   * entry is left unchanged.
+   * <p>
+   * The attribute description will be decoded using the schema
+   * associated with this entry.
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   * <p>
+   * <b>NOTE:</b> This method implements LDAP Modify add semantics.
+   *
+   * @param attributeDescription
+   *          The name of the attribute whose values are to be added.
+   * @param values
+   *          The attribute values to be added to this entry, merging
+   *          any existing attribute values.
+   * @return This entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the schema associated with this entry.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes or their values
+   *           to be added.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  Entry addAttribute(String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the attributes from this entry (optional operation).
+   *
+   * @return This entry.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes to be removed.
+   */
+  Entry clearAttributes() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Indicates whether or not this entry contains the named attribute.
+   *
+   * @param attributeDescription
+   *          The name of the attribute.
+   * @return {@code true} if this entry contains the named attribute,
+   *         otherwise {@code false}.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  boolean containsAttribute(AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  /**
+   * Indicates whether or not this entry contains the named attribute.
+   * <p>
+   * The attribute description will be decoded using the schema
+   * associated with this entry.
+   *
+   * @param attributeDescription
+   *          The name of the attribute.
+   * @return {@code true} if this entry contains the named attribute,
+   *         otherwise {@code false}.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the schema associated with this entry.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  boolean containsAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  /**
+   * Indicates whether or not this entry contains the provided object
+   * class.
+   *
+   * @param objectClass
+   *          The object class.
+   * @return {@code true} if this entry contains the object class,
+   *         otherwise {@code false}.
+   * @throws NullPointerException
+   *           If {@code objectClass} was {@code null}.
+   */
+  boolean containsObjectClass(ObjectClass objectClass)
+      throws NullPointerException;
+
+
+
+  /**
+   * Indicates whether or not this entry contains the named object
+   * class.
+   *
+   * @param objectClass
+   *          The name of the object class.
+   * @return {@code true} if this entry contains the object class,
+   *         otherwise {@code false}.
+   * @throws NullPointerException
+   *           If {@code objectClass} was {@code null}.
+   */
+  boolean containsObjectClass(String objectClass)
+      throws NullPointerException;
+
+
+
+  /**
+   * Returns {@code true} if {@code object} is an entry which is equal
+   * to this entry. Two entries are considered equal if their
+   * distinguished names are equal, they both have the same number of
+   * attributes, and every attribute contained in the first entry is
+   * also contained in the second entry.
+   *
+   * @param object
+   *          The object to be tested for equality with this entry.
+   * @return {@code true} if {@code object} is an entry which is equal
+   *         to this entry, or {@code false} if not.
+   */
+  boolean equals(Object object);
+
+
+
+  /**
+   * Returns an {@code Iterable} containing all the attributes in this
+   * entry having an attribute description which is a sub-type of the
+   * provided attribute description. The returned {@code Iterable} may
+   * be used to remove attributes if permitted by this entry.
+   *
+   * @param attributeDescription
+   *          The name of the attributes to be returned.
+   * @return An {@code Iterable} containing the matching attributes.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  Iterable<Attribute> findAttributes(
+      AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing all the attributes in this
+   * entry having an attribute description which is a sub-type of the
+   * provided attribute description. The returned {@code Iterable} may
+   * be used to remove attributes if permitted by this entry.
+   * <p>
+   * The attribute description will be decoded using the schema
+   * associated with this entry.
+   *
+   * @param attributeDescription
+   *          The name of the attributes to be returned.
+   * @return An {@code Iterable} containing the matching attributes.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the schema associated with this entry.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  Iterable<Attribute> findAttributes(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  /**
+   * Returns the named attribute contained in this entry, or {@code
+   * null} if it is not included with this entry.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be returned.
+   * @return The named attribute, or {@code null} if it is not included
+   *         with this entry.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  Attribute getAttribute(AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  /**
+   * Returns the named attribute contained in this entry, or {@code
+   * null} if it is not included with this entry.
+   * <p>
+   * The attribute description will be decoded using the schema
+   * associated with this entry.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be returned.
+   * @return The named attribute, or {@code null} if it is not included
+   *         with this entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the schema associated with this entry.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  Attribute getAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  /**
+   * Returns the number of attributes in this entry.
+   *
+   * @return The number of attributes.
+   */
+  int getAttributeCount();
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the attributes in this
+   * entry. The returned {@code Iterable} may be used to remove
+   * attributes if permitted by this entry.
+   *
+   * @return An {@code Iterable} containing the attributes.
+   */
+  Iterable<Attribute> getAttributes();
+
+
+
+  /**
+   * Returns the string representation of the distinguished name of this
+   * entry.
+   *
+   * @return The string representation of the distinguished name.
+   */
+  DN getName();
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the names of the object
+   * classes in this entry. The returned {@code Iterable} may be used to
+   * remove object classes if permitted by this entry.
+   *
+   * @return An {@code Iterable} containing the object classes.
+   */
+  Iterable<String> getObjectClasses();
+
+
+
+  /**
+   * Returns the hash code for this entry. It will be calculated as the
+   * sum of the hash codes of the distinguished name and all of the
+   * attributes.
+   *
+   * @return The hash code for this entry.
+   */
+  int hashCode();
+
+
+
+  /**
+   * 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.
+   * <p>
+   * <b>NOTE:</b> This method implements LDAP Modify delete semantics.
+   *
+   * @param attribute
+   *          The attribute values to be removed from this entry, which
+   *          may be empty if the entire attribute is to be removed.
+   * @param missingValues
+   *          A collection into which missing values will be added, or
+   *          {@code null} if missing values should not be saved.
+   * @return {@code true} if this entry changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes or their values
+   *           to be removed.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  boolean removeAttribute(Attribute attribute,
+      Collection<ByteString> missingValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes the named attribute from this entry if it is present
+   * (optional operation). If this attribute does not contain the
+   * attribute, the call leaves this entry unchanged and returns {@code
+   * false}.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be removed.
+   * @return {@code true} if this entry changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes to be removed.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  boolean removeAttribute(AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes the named attribute from this entry if it is present
+   * (optional operation). If this attribute does not contain the
+   * attribute, the call leaves this entry unchanged.
+   * <p>
+   * The attribute description will be decoded using the schema
+   * associated with this entry.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be removed.
+   * @return This entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the schema associated with this entry.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes to be removed.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  Entry removeAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all of the attribute values contained in {@code values}
+   * from the named attribute in this entry if it is present (optional
+   * operation). If {@code values} is {@code null} or empty then the
+   * entire attribute will be removed if it is present.
+   * <p>
+   * The attribute description will be decoded using the schema
+   * associated with this entry.
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   * <p>
+   * <b>NOTE:</b> This method implements LDAP Modify delete semantics.
+   *
+   * @param attributeDescription
+   *          The name of the attribute whose values are to be removed.
+   * @param values
+   *          The attribute values to be removed from this entry, which
+   *          may be {@code null} or empty if the entire attribute is to
+   *          be removed.
+   * @return This entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the schema associated with this entry.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes or their values
+   *           to be removed.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  Entry removeAttribute(String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds all of the attribute values contained in {@code attribute} to
+   * this entry, replacing any existing attribute values (optional
+   * operation). If {@code attribute} is empty then the entire attribute
+   * will be removed if it is present.
+   * <p>
+   * <b>NOTE:</b> This method implements LDAP Modify replace semantics.
+   *
+   * @param attribute
+   *          The attribute values to be added to this entry, replacing
+   *          any existing attribute values, and which may be empty if
+   *          the entire attribute is to be removed.
+   * @return {@code true} if this entry changed as a result of this
+   *         call.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes or their values
+   *           to be replaced.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  boolean replaceAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds all of the attribute values contained in {@code values} to
+   * this entry, replacing any existing attribute values (optional
+   * operation). If {@code values} is {@code null} or empty then the
+   * entire attribute will be removed if it is present.
+   * <p>
+   * The attribute description will be decoded using the schema
+   * associated with this entry.
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   * <p>
+   * <b>NOTE:</b> This method implements LDAP Modify replace semantics.
+   *
+   * @param attributeDescription
+   *          The name of the attribute whose values are to be replaced.
+   * @param values
+   *          The attribute values to be added to this entry, replacing
+   *          any existing attribute values, and which may be {@code
+   *          null} or empty if the entire attribute is to be removed.
+   * @return This entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the schema associated with this entry.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit attributes or their values
+   *           to be replaced.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  Entry replaceAttribute(String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of this entry (optional operation).
+   *
+   * @param dn
+   *          The string representation of the distinguished name.
+   * @return This entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the schema
+   *           associated with this entry.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit the distinguished name to
+   *           be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  Entry setName(String dn) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of this entry (optional operation).
+   *
+   * @param dn
+   *          The distinguished name.
+   * @return This entry.
+   * @throws UnsupportedOperationException
+   *           If this entry does not permit the distinguished name to
+   *           be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  Entry setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Returns a string representation of this entry.
+   *
+   * @return The string representation of this entry.
+   */
+  String toString();
+}
diff --git a/sdk/src/org/opends/sdk/ErrorResultException.java b/sdk/src/org/opends/sdk/ErrorResultException.java
new file mode 100644
index 0000000..5a4d7dc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ErrorResultException.java
@@ -0,0 +1,103 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.ExecutionException;
+
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * Thrown when the result code returned in a Result indicates that the
+ * Request was unsuccessful.
+ */
+@SuppressWarnings("serial")
+public class ErrorResultException extends ExecutionException
+{
+  private final Result result;
+
+
+
+  /**
+   * Wraps the provided result in an appropriate error result exception.
+   * The type of error result exception used depends on the underlying
+   * result code.
+   *
+   * @param result
+   *          The result whose result code indicates a failure.
+   * @return The error result exception wrapping the provided result.
+   * @throws IllegalArgumentException
+   *           If the provided result does not represent a failure.
+   * @throws NullPointerException
+   *           If {@code result} was {@code null}.
+   */
+  public static ErrorResultException wrap(Result result)
+      throws IllegalArgumentException, NullPointerException
+  {
+    if (!result.getResultCode().isExceptional())
+    {
+      throw new IllegalArgumentException(
+          "Attempted to wrap a successful result: " + result);
+    }
+
+    // TODO: choose type of exception based on result code (e.g.
+    // referral).
+    return new ErrorResultException(result);
+  }
+
+
+
+  /**
+   * Creates a new error result exception using the provided result.
+   *
+   * @param result
+   *          The error result.
+   */
+  ErrorResultException(Result result)
+  {
+    super(result.getResultCode() + ": " + result.getDiagnosticMessage());
+    this.result = result;
+  }
+
+
+
+  /**
+   * Returns the error result which caused this exception to be thrown.
+   * The type of result returned corresponds to the expected result type
+   * of the original request.
+   *
+   * @return The error result which caused this exception to be thrown.
+   */
+  public Result getResult()
+  {
+    return result;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ErrorResultIOException.java b/sdk/src/org/opends/sdk/ErrorResultIOException.java
new file mode 100644
index 0000000..a2db44a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ErrorResultIOException.java
@@ -0,0 +1,75 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An {@code ErrorResultIOException} adapts an {@code
+ * ErrorResultException} to an {@code IOException}.
+ */
+@SuppressWarnings("serial")
+public final class ErrorResultIOException extends IOException
+{
+  private final ErrorResultException cause;
+
+
+
+  /**
+   * Creates a new error result IO exception with the provided cause.
+   *
+   * @param cause
+   *          The cause which may be later retrieved by the
+   *          {@link #getCause} method.
+   * @throws NullPointerException
+   *           If {@code cause} was {@code null}.
+   */
+  public ErrorResultIOException(ErrorResultException cause)
+      throws NullPointerException
+  {
+    super(Validator.ensureNotNull(cause));
+
+    this.cause = cause;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ErrorResultException getCause()
+  {
+    return cause;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/Filter.java b/sdk/src/org/opends/sdk/Filter.java
new file mode 100644
index 0000000..6370007
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Filter.java
@@ -0,0 +1,1978 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+import static org.opends.sdk.util.StaticUtils.getBytes;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * A search filter as defined in RFC 4511. In addition this class also
+ * provides support for the absolute true and absolute false filters as
+ * defined in RFC 4526.
+ * <p>
+ * This class provides many factory methods for creating common types of
+ * filter. Applications interact with a filter using
+ * {@link FilterVisitor} which is applied to a filter using the
+ * {@link #accept(FilterVisitor, Object)} method.
+ * <p>
+ * The RFC 4515 string representation of a filter can be generated using
+ * the {@link #toString} methods and parsed using the
+ * {@link #valueOf(String)} factory method.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511">RFC 4511 -
+ *      Lightweight Directory Access Protocol (LDAP): The Protocol </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4515">RFC 4515 - String
+ *      Representation of Search Filters </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4526">RFC 4526 - Absolute
+ *      True and False Filters </a>
+ */
+public final class Filter
+{
+  private static final class AndImpl extends Impl
+  {
+    private final List<Filter> subFilters;
+
+
+
+    public AndImpl(List<Filter> subFilters)
+    {
+      this.subFilters = subFilters;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitAndFilter(p, subFilters);
+    }
+
+  }
+
+
+
+  private static final class ApproxMatchImpl extends Impl
+  {
+
+    private final ByteSequence assertionValue;
+
+    private final String attributeDescription;
+
+
+
+    public ApproxMatchImpl(String attributeDescription,
+        ByteSequence assertionValue)
+    {
+      this.attributeDescription = attributeDescription;
+      this.assertionValue = assertionValue;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitApproxMatchFilter(p, attributeDescription,
+          assertionValue);
+    }
+
+  }
+
+
+
+  private static final class EqualityMatchImpl extends Impl
+  {
+
+    private final ByteSequence assertionValue;
+
+    private final String attributeDescription;
+
+
+
+    public EqualityMatchImpl(String attributeDescription,
+        ByteSequence assertionValue)
+    {
+      this.attributeDescription = attributeDescription;
+      this.assertionValue = assertionValue;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitEqualityMatchFilter(p, attributeDescription,
+          assertionValue);
+    }
+
+  }
+
+
+
+  private static final class ExtensibleMatchImpl extends Impl
+  {
+    private final String attributeDescription;
+
+    private final boolean dnAttributes;
+
+    private final String matchingRule;
+
+    private final ByteSequence matchValue;
+
+
+
+    public ExtensibleMatchImpl(String matchingRule,
+        String attributeDescription, ByteSequence matchValue,
+        boolean dnAttributes)
+    {
+      this.matchingRule = matchingRule;
+      this.attributeDescription = attributeDescription;
+      this.matchValue = matchValue;
+      this.dnAttributes = dnAttributes;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitExtensibleMatchFilter(p, matchingRule,
+          attributeDescription, matchValue, dnAttributes);
+    }
+
+  }
+
+
+
+  private static final class GreaterOrEqualImpl extends Impl
+  {
+
+    private final ByteSequence assertionValue;
+
+    private final String attributeDescription;
+
+
+
+    public GreaterOrEqualImpl(String attributeDescription,
+        ByteSequence assertionValue)
+    {
+      this.attributeDescription = attributeDescription;
+      this.assertionValue = assertionValue;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitGreaterOrEqualFilter(p, attributeDescription,
+          assertionValue);
+    }
+
+  }
+
+
+
+  private static abstract class Impl
+  {
+    protected Impl()
+    {
+      // Nothing to do.
+    }
+
+
+
+    public abstract <R, P> R accept(FilterVisitor<R, P> v, P p);
+  }
+
+
+
+  private static final class LessOrEqualImpl extends Impl
+  {
+
+    private final ByteSequence assertionValue;
+
+    private final String attributeDescription;
+
+
+
+    public LessOrEqualImpl(String attributeDescription,
+        ByteSequence assertionValue)
+    {
+      this.attributeDescription = attributeDescription;
+      this.assertionValue = assertionValue;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitLessOrEqualFilter(p, attributeDescription,
+          assertionValue);
+    }
+
+  }
+
+
+
+  private static final class NotImpl extends Impl
+  {
+    private final Filter subFilter;
+
+
+
+    public NotImpl(Filter subFilter)
+    {
+      this.subFilter = subFilter;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitNotFilter(p, subFilter);
+    }
+
+  }
+
+
+
+  private static final class OrImpl extends Impl
+  {
+    private final List<Filter> subFilters;
+
+
+
+    public OrImpl(List<Filter> subFilters)
+    {
+      this.subFilters = subFilters;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitOrFilter(p, subFilters);
+    }
+
+  }
+
+
+
+  private static final class PresentImpl extends Impl
+  {
+
+    private final String attributeDescription;
+
+
+
+    public PresentImpl(String attributeDescription)
+    {
+      this.attributeDescription = attributeDescription;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitPresentFilter(p, attributeDescription);
+    }
+
+  }
+
+
+
+  private static final class SubstringsImpl extends Impl
+  {
+
+    private final List<ByteSequence> anyStrings;
+
+    private final String attributeDescription;
+
+    private final ByteSequence finalString;
+
+    private final ByteSequence initialString;
+
+
+
+    public SubstringsImpl(String attributeDescription,
+        ByteSequence initialString, List<ByteSequence> anyStrings,
+        ByteSequence finalString)
+    {
+      this.attributeDescription = attributeDescription;
+      this.initialString = initialString;
+      this.anyStrings = anyStrings;
+      this.finalString = finalString;
+
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitSubstringsFilter(p, attributeDescription,
+          initialString, anyStrings, finalString);
+    }
+
+  }
+
+
+
+  private static final class UnrecognizedImpl extends Impl
+  {
+
+    private final ByteSequence filterBytes;
+
+    private final byte filterTag;
+
+
+
+    public UnrecognizedImpl(byte filterTag, ByteSequence filterBytes)
+    {
+      this.filterTag = filterTag;
+      this.filterBytes = filterBytes;
+    }
+
+
+
+    @Override
+    public <R, P> R accept(FilterVisitor<R, P> v, P p)
+    {
+      return v.visitUnrecognizedFilter(p, filterTag, filterBytes);
+    }
+
+  }
+
+
+
+  // RFC 4526 - FALSE filter.
+  private static final Filter FALSE = new Filter(new OrImpl(Collections
+      .<Filter> emptyList()));
+
+  // Heavily used (objectClass=*) filter.
+  private static final Filter OBJECT_CLASS_PRESENT = new Filter(
+      new PresentImpl("objectClass"));
+
+  private static final FilterVisitor<StringBuilder, StringBuilder> TO_STRING_VISITOR = new FilterVisitor<StringBuilder, StringBuilder>()
+  {
+
+    public StringBuilder visitAndFilter(StringBuilder builder,
+        List<Filter> subFilters)
+    {
+      builder.append("(&");
+      for (Filter subFilter : subFilters)
+      {
+        subFilter.accept(this, builder);
+      }
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitApproxMatchFilter(StringBuilder builder,
+        String attributeDescription, ByteSequence assertionValue)
+    {
+      builder.append('(');
+      builder.append(attributeDescription);
+      builder.append("~=");
+      valueToFilterString(builder, assertionValue);
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitEqualityMatchFilter(
+        StringBuilder builder, String attributeDescription,
+        ByteSequence assertionValue)
+    {
+      builder.append('(');
+      builder.append(attributeDescription);
+      builder.append("=");
+      valueToFilterString(builder, assertionValue);
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitExtensibleMatchFilter(
+        StringBuilder builder, String matchingRule,
+        String attributeDescription, ByteSequence assertionValue,
+        boolean dnAttributes)
+    {
+      builder.append('(');
+
+      if (attributeDescription != null)
+      {
+        builder.append(attributeDescription);
+      }
+
+      if (dnAttributes)
+      {
+        builder.append(":dn");
+      }
+
+      if (matchingRule != null)
+      {
+        builder.append(':');
+        builder.append(matchingRule);
+      }
+
+      builder.append(":=");
+      valueToFilterString(builder, assertionValue);
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitGreaterOrEqualFilter(
+        StringBuilder builder, String attributeDescription,
+        ByteSequence assertionValue)
+    {
+      builder.append('(');
+      builder.append(attributeDescription);
+      builder.append(">=");
+      valueToFilterString(builder, assertionValue);
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitLessOrEqualFilter(StringBuilder builder,
+        String attributeDescription, ByteSequence assertionValue)
+    {
+      builder.append('(');
+      builder.append(attributeDescription);
+      builder.append("<=");
+      valueToFilterString(builder, assertionValue);
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitNotFilter(StringBuilder builder,
+        Filter subFilter)
+    {
+      builder.append("(|");
+      subFilter.accept(this, builder);
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitOrFilter(StringBuilder builder,
+        List<Filter> subFilters)
+    {
+      builder.append("(|");
+      for (Filter subFilter : subFilters)
+      {
+        subFilter.accept(this, builder);
+      }
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitPresentFilter(StringBuilder builder,
+        String attributeDescription)
+    {
+      builder.append('(');
+      builder.append(attributeDescription);
+      builder.append("=*)");
+      return builder;
+    }
+
+
+
+    public StringBuilder visitSubstringsFilter(StringBuilder builder,
+        String attributeDescription, ByteSequence initialSubstring,
+        List<ByteSequence> anySubstrings, ByteSequence finalSubstring)
+    {
+      builder.append('(');
+      builder.append(attributeDescription);
+      builder.append("=");
+      if (initialSubstring != null)
+      {
+        valueToFilterString(builder, initialSubstring);
+      }
+      for (ByteSequence anySubstring : anySubstrings)
+      {
+        builder.append('*');
+        valueToFilterString(builder, anySubstring);
+      }
+      builder.append('*');
+      if (finalSubstring != null)
+      {
+        valueToFilterString(builder, finalSubstring);
+      }
+      builder.append(')');
+      return builder;
+    }
+
+
+
+    public StringBuilder visitUnrecognizedFilter(StringBuilder builder,
+        byte filterTag, ByteSequence filterBytes)
+    {
+      // Fake up a representation.
+      builder.append('(');
+      builder.append(byteToHex(filterTag));
+      builder.append(':');
+      StaticUtils.toHex(filterBytes, builder);
+      builder.append(')');
+      return builder;
+    }
+  };
+
+  // RFC 4526 - TRUE filter.
+  private static final Filter TRUE = new Filter(new AndImpl(Collections
+      .<Filter> emptyList()));
+
+
+
+  /**
+   * Returns the {@code absolute false} filter as defined in RFC 4526
+   * which is comprised of an {@code or} filter containing zero
+   * components.
+   *
+   * @return The absolute false filter.
+   * @see <a href="http://tools.ietf.org/html/rfc4526">RFC 4526</a>
+   */
+  public static Filter getAbsoluteFalseFilter()
+  {
+    return FALSE;
+  }
+
+
+
+  /**
+   * Returns the {@code absolute true} filter as defined in RFC 4526
+   * which is comprised of an {@code and} filter containing zero
+   * components.
+   *
+   * @return The absolute true filter.
+   * @see <a href="http://tools.ietf.org/html/rfc4526">RFC 4526</a>
+   */
+  public static Filter getAbsoluteTrueFilter()
+  {
+    return TRUE;
+  }
+
+
+
+  /**
+   * Returns the {@code objectClass} presence filter {@code
+   * (objectClass=*)}.
+   * <p>
+   * A call to this method is equivalent to but more efficient than the
+   * following code:
+   *
+   * <pre>
+   * Filter.present(&quot;objectClass&quot;);
+   * </pre>
+   *
+   * @return The {@code objectClass} presence filter {@code
+   *         (objectClass=*)}.
+   */
+  public static Filter getObjectClassPresentFilter()
+  {
+    return OBJECT_CLASS_PRESENT;
+  }
+
+
+
+  /**
+   * Creates a new {@code and} filter using the provided list of
+   * sub-filters.
+   * <p>
+   * Creating a new {@code and} filter with a {@code null} or empty list
+   * of sub-filters is equivalent to calling
+   * {@link #getAbsoluteTrueFilter()}.
+   *
+   * @param subFilters
+   *          The list of sub-filters, may be empty or {@code null}.
+   * @return The newly created {@code and} filter.
+   */
+  public static Filter newAndFilter(Collection<Filter> subFilters)
+  {
+    if (subFilters == null || subFilters.isEmpty())
+    {
+      // RFC 4526 - TRUE filter.
+      return getAbsoluteTrueFilter();
+    }
+    else if (subFilters.size() == 1)
+    {
+      Filter subFilter = subFilters.iterator().next();
+      Validator.ensureNotNull(subFilter);
+      return new Filter(new AndImpl(Collections
+          .singletonList(subFilter)));
+    }
+    else
+    {
+      List<Filter> subFiltersList = new ArrayList<Filter>(subFilters
+          .size());
+      for (Filter subFilter : subFilters)
+      {
+        Validator.ensureNotNull(subFilter);
+        subFiltersList.add(subFilter);
+      }
+      return new Filter(new AndImpl(Collections
+          .unmodifiableList(subFiltersList)));
+    }
+  }
+
+
+
+  /**
+   * Creates a new {@code and} filter using the provided list of
+   * sub-filters.
+   * <p>
+   * Creating a new {@code and} filter with a {@code null} or empty list
+   * of sub-filters is equivalent to calling
+   * {@link #getAbsoluteTrueFilter()}.
+   *
+   * @param subFilters
+   *          The list of sub-filters, may be empty or {@code null}.
+   * @return The newly created {@code and} filter.
+   */
+  public static Filter newAndFilter(Filter... subFilters)
+  {
+    if ((subFilters == null) || (subFilters.length == 0))
+    {
+      // RFC 4526 - TRUE filter.
+      return getAbsoluteTrueFilter();
+    }
+    else if (subFilters.length == 1)
+    {
+      Validator.ensureNotNull(subFilters[0]);
+      return new Filter(new AndImpl(Collections
+          .singletonList(subFilters[0])));
+    }
+    else
+    {
+      List<Filter> subFiltersList = new ArrayList<Filter>(
+          subFilters.length);
+      for (Filter subFilter : subFilters)
+      {
+        Validator.ensureNotNull(subFilter);
+        subFiltersList.add(subFilter);
+      }
+      return new Filter(new AndImpl(Collections
+          .unmodifiableList(subFiltersList)));
+    }
+  }
+
+
+
+  /**
+   * Creates a new {@code approximate match} filter using the provided
+   * attribute description and assertion value.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return The newly created {@code approximate match} filter.
+   */
+  public static Filter newApproxMatchFilter(
+      String attributeDescription, ByteSequence assertionValue)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    Validator.ensureNotNull(assertionValue);
+    return new Filter(new ApproxMatchImpl(attributeDescription,
+        assertionValue));
+  }
+
+
+
+  /**
+   * Creates a new {@code equality match} filter using the provided
+   * attribute description and assertion value.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return The newly created {@code equality match} filter.
+   */
+  public static Filter newEqualityMatchFilter(
+      String attributeDescription, ByteSequence assertionValue)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    Validator.ensureNotNull(assertionValue);
+    return new Filter(new EqualityMatchImpl(attributeDescription,
+        assertionValue));
+  }
+
+
+
+  /**
+   * Creates a new {@code extensible match} filter.
+   *
+   * @param matchingRule
+   *          The matching rule name, may be {@code null} if {@code
+   *          attributeDescription} is specified.
+   * @param attributeDescription
+   *          The attribute description, may be {@code null} if {@code
+   *          matchingRule} is specified.
+   * @param assertionValue
+   *          The assertion value.
+   * @param dnAttributes
+   *          Indicates whether DN matching should be performed.
+   * @return The newly created {@code extensible match} filter.
+   */
+  public static Filter newExtensibleMatchFilter(String matchingRule,
+      String attributeDescription, ByteSequence assertionValue,
+      boolean dnAttributes)
+  {
+    Validator.ensureTrue((matchingRule != null)
+        || (attributeDescription != null), "matchingRule and/or "
+        + "attributeDescription must not be null");
+    Validator.ensureNotNull(assertionValue);
+    return new Filter(new ExtensibleMatchImpl(matchingRule,
+        attributeDescription, assertionValue, dnAttributes));
+  }
+
+
+
+  /**
+   * Creates a new {@code greater or equal} filter using the provided
+   * attribute description and assertion value.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return The newly created {@code greater or equal} filter.
+   */
+  public static Filter newGreaterOrEqualFilter(
+      String attributeDescription, ByteSequence assertionValue)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    Validator.ensureNotNull(assertionValue);
+    return new Filter(new GreaterOrEqualImpl(attributeDescription,
+        assertionValue));
+  }
+
+
+
+  /**
+   * Creates a new {@code less or equal} filter using the provided
+   * attribute description and assertion value.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return The newly created {@code less or equal} filter.
+   */
+  public static Filter newLessOrEqualFilter(
+      String attributeDescription, ByteSequence assertionValue)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    Validator.ensureNotNull(assertionValue);
+    return new Filter(new LessOrEqualImpl(attributeDescription,
+        assertionValue));
+  }
+
+
+
+  /**
+   * Creates a new {@code not} filter using the provided sub-filter.
+   *
+   * @param subFilter
+   *          The sub-filter.
+   * @return The newly created {@code not} filter.
+   */
+  public static Filter newNotFilter(Filter subFilter)
+  {
+    Validator.ensureNotNull(subFilter);
+    return new Filter(new NotImpl(subFilter));
+  }
+
+
+
+  /**
+   * Creates a new {@code or} filter using the provided list of
+   * sub-filters.
+   * <p>
+   * Creating a new {@code or} filter with a {@code null} or empty list
+   * of sub-filters is equivalent to calling
+   * {@link #getAbsoluteFalseFilter()}.
+   *
+   * @param subFilters
+   *          The list of sub-filters, may be empty or {@code null}.
+   * @return The newly created {@code or} filter.
+   */
+  public static Filter newOrFilter(Collection<Filter> subFilters)
+  {
+    if (subFilters == null || subFilters.isEmpty())
+    {
+      // RFC 4526 - FALSE filter.
+      return getAbsoluteFalseFilter();
+    }
+    else if (subFilters.size() == 1)
+    {
+      Filter subFilter = subFilters.iterator().next();
+      Validator.ensureNotNull(subFilter);
+      return new Filter(
+          new OrImpl(Collections.singletonList(subFilter)));
+    }
+    else
+    {
+      List<Filter> subFiltersList = new ArrayList<Filter>(subFilters
+          .size());
+      for (Filter subFilter : subFilters)
+      {
+        Validator.ensureNotNull(subFilter);
+        subFiltersList.add(subFilter);
+      }
+      return new Filter(new OrImpl(Collections
+          .unmodifiableList(subFiltersList)));
+    }
+  }
+
+
+
+  /**
+   * Creates a new {@code or} filter using the provided list of
+   * sub-filters.
+   * <p>
+   * Creating a new {@code or} filter with a {@code null} or empty list
+   * of sub-filters is equivalent to calling
+   * {@link #getAbsoluteFalseFilter()}.
+   *
+   * @param subFilters
+   *          The list of sub-filters, may be empty or {@code null}.
+   * @return The newly created {@code or} filter.
+   */
+  public static Filter newOrFilter(Filter... subFilters)
+  {
+    if ((subFilters == null) || (subFilters.length == 0))
+    {
+      // RFC 4526 - FALSE filter.
+      return getAbsoluteFalseFilter();
+    }
+    else if (subFilters.length == 1)
+    {
+      Validator.ensureNotNull(subFilters[0]);
+      return new Filter(new OrImpl(Collections
+          .singletonList(subFilters[0])));
+    }
+    else
+    {
+      List<Filter> subFiltersList = new ArrayList<Filter>(
+          subFilters.length);
+      for (Filter subFilter : subFilters)
+      {
+        Validator.ensureNotNull(subFilter);
+        subFiltersList.add(subFilter);
+      }
+      return new Filter(new OrImpl(Collections
+          .unmodifiableList(subFiltersList)));
+    }
+  }
+
+
+
+  /**
+   * Creates a new {@code present} filter using the provided attribute
+   * description.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @return The newly created {@code present} filter.
+   */
+  public static Filter newPresentFilter(String attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    if (toLowerCase(attributeDescription).equals("objectclass"))
+    {
+      return OBJECT_CLASS_PRESENT;
+    }
+    return new Filter(new PresentImpl(attributeDescription));
+  }
+
+
+
+  /**
+   * Creates a new {@code substrings} filter using the provided
+   * attribute description, {@code initial}, {@code final}, and {@code
+   * any} sub-strings.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param initialSubstring
+   *          The initial sub-string, may be {@code null} if either
+   *          {@code finalSubstring} or {@code anySubstrings} are
+   *          specified.
+   * @param anySubstrings
+   *          The final sub-string, may be {@code null} or empty if
+   *          either {@code finalSubstring} or {@code initialSubstring}
+   *          are specified.
+   * @param finalSubstring
+   *          The final sub-string, may be {@code null}, may be {@code
+   *          null} if either {@code initialSubstring} or {@code
+   *          anySubstrings} are specified.
+   * @return The newly created {@code substrings} filter.
+   */
+  public static Filter newSubstringsFilter(String attributeDescription,
+      ByteSequence initialSubstring, ByteSequence[] anySubstrings,
+      ByteSequence finalSubstring)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    Validator.ensureTrue((initialSubstring != null)
+        || (finalSubstring != null)
+        || ((anySubstrings != null) && (anySubstrings.length > 0)),
+        "at least one substring (initial, any or final)"
+            + " must be specified");
+
+    List<ByteSequence> anySubstringList;
+    if ((anySubstrings == null) || (anySubstrings.length == 0))
+    {
+      anySubstringList = Collections.emptyList();
+    }
+    else if (anySubstrings.length == 1)
+    {
+      Validator.ensureNotNull(anySubstrings[0]);
+      anySubstringList = Collections.singletonList(anySubstrings[0]);
+    }
+    else
+    {
+      anySubstringList = new ArrayList<ByteSequence>(
+          anySubstrings.length);
+      for (ByteSequence anySubstring : anySubstrings)
+      {
+        Validator.ensureNotNull(anySubstring);
+
+        anySubstringList.add(anySubstring);
+      }
+      anySubstringList = Collections.unmodifiableList(anySubstringList);
+    }
+
+    return new Filter(new SubstringsImpl(attributeDescription,
+        initialSubstring, anySubstringList, finalSubstring));
+  }
+
+
+
+  /**
+   * Creates a new {@code substrings} filter using the provided
+   * attribute description, {@code initial}, {@code final}, and {@code
+   * any} sub-strings.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param initialSubstring
+   *          The initial sub-string, may be {@code null} if either
+   *          {@code finalSubstring} or {@code anySubstrings} are
+   *          specified.
+   * @param anySubstrings
+   *          The final sub-string, may be {@code null} or empty if
+   *          either {@code finalSubstring} or {@code initialSubstring}
+   *          are specified.
+   * @param finalSubstring
+   *          The final sub-string, may be {@code null}, may be {@code
+   *          null} if either {@code initialSubstring} or {@code
+   *          anySubstrings} are specified.
+   * @return The newly created {@code substrings} filter.
+   */
+  public static Filter newSubstringsFilter(String attributeDescription,
+      ByteSequence initialSubstring,
+      Collection<ByteSequence> anySubstrings,
+      ByteSequence finalSubstring)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    Validator.ensureTrue((initialSubstring != null)
+        || (finalSubstring != null)
+        || ((anySubstrings != null) && (anySubstrings.size() > 0)),
+        "at least one substring (initial, any or final)"
+            + " must be specified");
+
+    List<ByteSequence> anySubstringList;
+    if ((anySubstrings == null) || (anySubstrings.size() == 0))
+    {
+      anySubstringList = Collections.emptyList();
+    }
+    else if (anySubstrings.size() == 1)
+    {
+      ByteSequence anySubstring = anySubstrings.iterator().next();
+      Validator.ensureNotNull(anySubstring);
+      anySubstringList = Collections.singletonList(anySubstring);
+    }
+    else
+    {
+      anySubstringList = new ArrayList<ByteSequence>(anySubstrings
+          .size());
+      for (ByteSequence anySubstring : anySubstrings)
+      {
+        Validator.ensureNotNull(anySubstring);
+
+        anySubstringList.add(anySubstring);
+      }
+      anySubstringList = Collections.unmodifiableList(anySubstringList);
+    }
+
+    return new Filter(new SubstringsImpl(attributeDescription,
+        initialSubstring, anySubstringList, finalSubstring));
+  }
+
+
+
+  /**
+   * Creates a new {@code unrecognized} filter using the provided ASN1
+   * filter tag and content. This type of filter should be used for
+   * filters which are not part of the standard filter definition.
+   *
+   * @param filterTag
+   *          The ASN.1 tag.
+   * @param filterBytes
+   *          The filter content.
+   * @return The newly created {@code unrecognized} filter.
+   */
+  public static Filter newUnrecognizedFilter(byte filterTag,
+      ByteSequence filterBytes)
+  {
+    Validator.ensureNotNull(filterBytes);
+    return new Filter(new UnrecognizedImpl(filterTag, filterBytes));
+  }
+
+
+
+  /**
+   * Parses the provided LDAP string representation of a filter as a
+   * {@code Filter}.
+   *
+   * @param string
+   *          The LDAP string representation of a filter.
+   * @return The parsed {@code Filter}.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code string} is not a valid LDAP string
+   *           representation of a filter.
+   */
+  public static Filter valueOf(String string)
+      throws LocalizedIllegalArgumentException
+  {
+    Validator.ensureNotNull(string);
+
+    // If the filter is enclosed in a pair of single quotes it
+    // is invalid (issue #1024).
+    if ((string.length() > 1) && string.startsWith("'")
+        && string.endsWith("'"))
+    {
+      Message message = ERR_LDAP_FILTER_ENCLOSED_IN_APOSTROPHES
+          .get(string);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    if (string.startsWith("("))
+    {
+      if (string.endsWith(")"))
+      {
+        return valueOf0(string, 1, string.length() - 1);
+      }
+      else
+      {
+        Message message = ERR_LDAP_FILTER_MISMATCHED_PARENTHESES.get(
+            string, 1, string.length());
+        throw new LocalizedIllegalArgumentException(message);
+      }
+    }
+    else
+    {
+      // We tolerate the top level filter component not being surrounded
+      // by parentheses.
+      return valueOf0(string, 0, string.length());
+    }
+  }
+
+
+
+  private static Filter valueOf0(String string,
+      int beginIndex /* inclusive */, int endIndex /* exclusive */)
+      throws LocalizedIllegalArgumentException
+  {
+    if (beginIndex >= endIndex)
+    {
+      Message message = ERR_LDAP_FILTER_STRING_NULL.get();
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    int index = beginIndex;
+    char c = string.charAt(index);
+
+    if (c == '&')
+    {
+      List<Filter> subFilters = valueOfFilterList(string, index + 1,
+          endIndex);
+      if (subFilters.isEmpty())
+      {
+        return getAbsoluteTrueFilter();
+      }
+      else
+      {
+        return new Filter(new AndImpl(subFilters));
+      }
+    }
+    else if (c == '|')
+    {
+      List<Filter> subFilters = valueOfFilterList(string, index + 1,
+          endIndex);
+      if (subFilters.isEmpty())
+      {
+        return getAbsoluteFalseFilter();
+      }
+      else
+      {
+        return new Filter(new OrImpl(subFilters));
+      }
+    }
+    else if (c == '!')
+    {
+      if ((string.charAt(index + 1) != '(')
+          || (string.charAt(endIndex - 1) != ')'))
+      {
+        Message message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES
+            .get(string, index, endIndex - 1);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      Filter subFilter = valueOf0(string, index + 2, endIndex - 1);
+      return new Filter(new NotImpl(subFilter));
+    }
+    else
+    {
+      // It must be a simple filter. It must have an equal sign at some
+      // point, so find it.
+      int equalPos = -1;
+      for (int i = index; i < endIndex; i++)
+      {
+        if (string.charAt(i) == '=')
+        {
+          equalPos = i;
+          break;
+        }
+      }
+
+      // Look at the character immediately before the equal sign,
+      // because it may help determine the filter type.
+      String attributeDescription;
+      ByteSequence assertionValue;
+
+      switch (string.charAt(equalPos - 1))
+      {
+      case '~':
+        attributeDescription = valueOfAttributeDescription(string,
+            index, equalPos - 1);
+        assertionValue = valueOfAssertionValue(string, equalPos + 1,
+            endIndex);
+        return new Filter(new ApproxMatchImpl(attributeDescription,
+            assertionValue));
+      case '>':
+        attributeDescription = valueOfAttributeDescription(string,
+            index, equalPos - 1);
+        assertionValue = valueOfAssertionValue(string, equalPos + 1,
+            endIndex);
+        return new Filter(new GreaterOrEqualImpl(attributeDescription,
+            assertionValue));
+      case '<':
+        attributeDescription = valueOfAttributeDescription(string,
+            index, equalPos - 1);
+        assertionValue = valueOfAssertionValue(string, equalPos + 1,
+            endIndex);
+        return new Filter(new LessOrEqualImpl(attributeDescription,
+            assertionValue));
+      case ':':
+        return valueOfExtensibleFilter(string, index, equalPos,
+            endIndex);
+      default:
+        attributeDescription = valueOfAttributeDescription(string,
+            index, equalPos);
+        return valueOfGenericFilter(string, attributeDescription,
+            equalPos + 1, endIndex);
+      }
+    }
+  }
+
+
+
+  private static ByteSequence valueOfAssertionValue(String string,
+      int startIndex, int endIndex)
+      throws LocalizedIllegalArgumentException
+  {
+    boolean hasEscape = false;
+    byte[] valueBytes = getBytes(string.substring(startIndex, endIndex));
+    for (byte valueByte : valueBytes)
+    {
+      if (valueByte == 0x5C) // The backslash character
+      {
+        hasEscape = true;
+        break;
+      }
+    }
+
+    if (hasEscape)
+    {
+      ByteStringBuilder valueBuffer = new ByteStringBuilder(
+          valueBytes.length);
+      for (int i = 0; i < valueBytes.length; i++)
+      {
+        if (valueBytes[i] == 0x5C) // The backslash character
+        {
+          // The next two bytes must be the hex characters that comprise
+          // the binary value.
+          if ((i + 2) >= valueBytes.length)
+          {
+            Message message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
+                string, startIndex + i + 1);
+            throw new LocalizedIllegalArgumentException(message);
+          }
+
+          byte byteValue = 0;
+          switch (valueBytes[++i])
+          {
+          case 0x30: // '0'
+            break;
+          case 0x31: // '1'
+            byteValue = (byte) 0x10;
+            break;
+          case 0x32: // '2'
+            byteValue = (byte) 0x20;
+            break;
+          case 0x33: // '3'
+            byteValue = (byte) 0x30;
+            break;
+          case 0x34: // '4'
+            byteValue = (byte) 0x40;
+            break;
+          case 0x35: // '5'
+            byteValue = (byte) 0x50;
+            break;
+          case 0x36: // '6'
+            byteValue = (byte) 0x60;
+            break;
+          case 0x37: // '7'
+            byteValue = (byte) 0x70;
+            break;
+          case 0x38: // '8'
+            byteValue = (byte) 0x80;
+            break;
+          case 0x39: // '9'
+            byteValue = (byte) 0x90;
+            break;
+          case 0x41: // 'A'
+          case 0x61: // 'a'
+            byteValue = (byte) 0xA0;
+            break;
+          case 0x42: // 'B'
+          case 0x62: // 'b'
+            byteValue = (byte) 0xB0;
+            break;
+          case 0x43: // 'C'
+          case 0x63: // 'c'
+            byteValue = (byte) 0xC0;
+            break;
+          case 0x44: // 'D'
+          case 0x64: // 'd'
+            byteValue = (byte) 0xD0;
+            break;
+          case 0x45: // 'E'
+          case 0x65: // 'e'
+            byteValue = (byte) 0xE0;
+            break;
+          case 0x46: // 'F'
+          case 0x66: // 'f'
+            byteValue = (byte) 0xF0;
+            break;
+          default:
+            Message message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
+                string, startIndex + i + 1);
+            throw new LocalizedIllegalArgumentException(message);
+          }
+
+          switch (valueBytes[++i])
+          {
+          case 0x30: // '0'
+            break;
+          case 0x31: // '1'
+            byteValue |= (byte) 0x01;
+            break;
+          case 0x32: // '2'
+            byteValue |= (byte) 0x02;
+            break;
+          case 0x33: // '3'
+            byteValue |= (byte) 0x03;
+            break;
+          case 0x34: // '4'
+            byteValue |= (byte) 0x04;
+            break;
+          case 0x35: // '5'
+            byteValue |= (byte) 0x05;
+            break;
+          case 0x36: // '6'
+            byteValue |= (byte) 0x06;
+            break;
+          case 0x37: // '7'
+            byteValue |= (byte) 0x07;
+            break;
+          case 0x38: // '8'
+            byteValue |= (byte) 0x08;
+            break;
+          case 0x39: // '9'
+            byteValue |= (byte) 0x09;
+            break;
+          case 0x41: // 'A'
+          case 0x61: // 'a'
+            byteValue |= (byte) 0x0A;
+            break;
+          case 0x42: // 'B'
+          case 0x62: // 'b'
+            byteValue |= (byte) 0x0B;
+            break;
+          case 0x43: // 'C'
+          case 0x63: // 'c'
+            byteValue |= (byte) 0x0C;
+            break;
+          case 0x44: // 'D'
+          case 0x64: // 'd'
+            byteValue |= (byte) 0x0D;
+            break;
+          case 0x45: // 'E'
+          case 0x65: // 'e'
+            byteValue |= (byte) 0x0E;
+            break;
+          case 0x46: // 'F'
+          case 0x66: // 'f'
+            byteValue |= (byte) 0x0F;
+            break;
+          default:
+            Message message = ERR_LDAP_FILTER_INVALID_ESCAPED_BYTE.get(
+                string, startIndex + i + 1);
+            throw new LocalizedIllegalArgumentException(message);
+          }
+
+          valueBuffer.append(byteValue);
+        }
+        else
+        {
+          valueBuffer.append(valueBytes[i]);
+        }
+      }
+
+      return valueBuffer.toByteString();
+    }
+    else
+    {
+      return ByteString.wrap(valueBytes);
+    }
+  }
+
+
+
+  private static String valueOfAttributeDescription(String string,
+      int startIndex, int endIndex)
+      throws LocalizedIllegalArgumentException
+  {
+    // The part of the filter string before the equal sign should be the
+    // attribute type. Make sure that the characters it contains are
+    // acceptable for attribute types, including those allowed by
+    // attribute name exceptions (ASCII letters and digits, the dash,
+    // and the underscore). We also need to allow attribute options,
+    // which includes the semicolon and the equal sign.
+    String attrType = string.substring(startIndex, endIndex);
+    for (int i = 0; i < attrType.length(); i++)
+    {
+      switch (attrType.charAt(i))
+      {
+      case '-':
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+      case ';':
+      case '=':
+      case 'A':
+      case 'B':
+      case 'C':
+      case 'D':
+      case 'E':
+      case 'F':
+      case 'G':
+      case 'H':
+      case 'I':
+      case 'J':
+      case 'K':
+      case 'L':
+      case 'M':
+      case 'N':
+      case 'O':
+      case 'P':
+      case 'Q':
+      case 'R':
+      case 'S':
+      case 'T':
+      case 'U':
+      case 'V':
+      case 'W':
+      case 'X':
+      case 'Y':
+      case 'Z':
+      case '_':
+      case 'a':
+      case 'b':
+      case 'c':
+      case 'd':
+      case 'e':
+      case 'f':
+      case 'g':
+      case 'h':
+      case 'i':
+      case 'j':
+      case 'k':
+      case 'l':
+      case 'm':
+      case 'n':
+      case 'o':
+      case 'p':
+      case 'q':
+      case 'r':
+      case 's':
+      case 't':
+      case 'u':
+      case 'v':
+      case 'w':
+      case 'x':
+      case 'y':
+      case 'z':
+        // These are all OK.
+        break;
+
+      case '.':
+      case '/':
+      case ':':
+      case '<':
+      case '>':
+      case '?':
+      case '@':
+      case '[':
+      case '\\':
+      case ']':
+      case '^':
+      case '`':
+        // These are not allowed, but they are explicitly called out
+        // because they are included in the range of values between '-'
+        // and 'z', and making sure all possible characters are included
+        // can help make the switch statement more efficient. We'll fall
+        // through to the default clause to reject them.
+      default:
+        Message message = ERR_LDAP_FILTER_INVALID_CHAR_IN_ATTR_TYPE
+            .get(attrType, String.valueOf(attrType.charAt(i)), i);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+    }
+
+    return attrType;
+  }
+
+
+
+  private static Filter valueOfExtensibleFilter(String string,
+      int startIndex, int equalIndex, int endIndex)
+      throws LocalizedIllegalArgumentException
+  {
+    String attributeDescription = null;
+    boolean dnAttributes = false;
+    String matchingRule = null;
+
+    // Look at the first character. If it is a colon, then it must be
+    // followed by either the string "dn" or the matching rule ID. If it
+    // is not, then must be the attribute type.
+    String lowerLeftStr = toLowerCase(string.substring(startIndex,
+        equalIndex));
+    if (string.charAt(startIndex) == ':')
+    {
+      // See if it starts with ":dn". Otherwise, it much be the matching
+      // rule ID.
+      if (lowerLeftStr.startsWith(":dn:"))
+      {
+        dnAttributes = true;
+
+        if ((startIndex + 4) < (equalIndex - 1))
+        {
+          matchingRule = string.substring(startIndex + 4,
+              equalIndex - 1);
+        }
+      }
+      else
+      {
+        matchingRule = string.substring(startIndex + 1, equalIndex - 1);
+      }
+    }
+    else
+    {
+      int colonPos = string.indexOf(':', startIndex);
+      if (colonPos < 0)
+      {
+        Message message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_COLON
+            .get(string, startIndex);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      attributeDescription = string.substring(startIndex, colonPos);
+
+      // If there is anything left, then it should be ":dn" and/or ":"
+      // followed by the matching rule ID.
+      if (colonPos < (equalIndex - 1))
+      {
+        if (lowerLeftStr.startsWith(":dn:", colonPos - startIndex))
+        {
+          dnAttributes = true;
+
+          if ((colonPos + 4) < (equalIndex - 1))
+          {
+            matchingRule = string.substring(colonPos + 4,
+                equalIndex - 1);
+          }
+        }
+        else
+        {
+          matchingRule = string.substring(colonPos + 1, equalIndex - 1);
+        }
+      }
+    }
+
+    // Parse out the attribute value.
+    ByteSequence matchValue = valueOfAssertionValue(string,
+        equalIndex + 1, endIndex);
+
+    // Make sure that the filter has at least one of an attribute
+    // description and/or a matching rule ID.
+    if ((attributeDescription == null) && (matchingRule == null))
+    {
+      Message message = ERR_LDAP_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR
+          .get(string, startIndex);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    return new Filter(new ExtensibleMatchImpl(matchingRule,
+        attributeDescription, matchValue, dnAttributes));
+  }
+
+
+
+  private static List<Filter> valueOfFilterList(String string,
+      int startIndex, int endIndex)
+      throws LocalizedIllegalArgumentException
+  {
+    // If the end index is equal to the start index, then there are no
+    // components.
+    if (startIndex >= endIndex)
+    {
+      return Collections.emptyList();
+    }
+
+    // At least one sub-filter.
+    Filter firstFilter = null;
+    List<Filter> subFilters = null;
+
+    // The first and last characters must be parentheses. If not, then
+    // that's an error.
+    if ((string.charAt(startIndex) != '(')
+        || (string.charAt(endIndex - 1) != ')'))
+    {
+      Message message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES
+          .get(string, startIndex, endIndex);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // Iterate through the characters in the value. Whenever an open
+    // parenthesis is found, locate the corresponding close parenthesis
+    // by counting the number of intermediate open/close parentheses.
+    int pendingOpens = 0;
+    int openIndex = -1;
+    for (int i = startIndex; i < endIndex; i++)
+    {
+      char c = string.charAt(i);
+      if (c == '(')
+      {
+        if (openIndex < 0)
+        {
+          openIndex = i;
+        }
+        pendingOpens++;
+      }
+      else if (c == ')')
+      {
+        pendingOpens--;
+        if (pendingOpens == 0)
+        {
+          Filter subFilter = valueOf0(string, openIndex + 1, i);
+          if (subFilters != null)
+          {
+            subFilters.add(subFilter);
+          }
+          else if (firstFilter != null)
+          {
+            subFilters = new LinkedList<Filter>();
+            subFilters.add(firstFilter);
+            subFilters.add(subFilter);
+            firstFilter = null;
+          }
+          else
+          {
+            firstFilter = subFilter;
+          }
+          openIndex = -1;
+        }
+        else if (pendingOpens < 0)
+        {
+          Message message = ERR_LDAP_FILTER_NO_CORRESPONDING_OPEN_PARENTHESIS
+              .get(string, i);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+      else if (pendingOpens <= 0)
+      {
+        Message message = ERR_LDAP_FILTER_COMPOUND_MISSING_PARENTHESES
+            .get(string, startIndex, endIndex);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+    }
+
+    // At this point, we have parsed the entire set of filter
+    // components. The list of open parenthesis positions must be empty.
+    if (pendingOpens != 0)
+    {
+      Message message = ERR_LDAP_FILTER_NO_CORRESPONDING_CLOSE_PARENTHESIS
+          .get(string, openIndex);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    if (subFilters != null)
+    {
+      return Collections.unmodifiableList(subFilters);
+    }
+    else
+    {
+      return Collections.singletonList(firstFilter);
+    }
+  }
+
+
+
+  private static Filter valueOfGenericFilter(String string,
+      String attributeDescription, int startIndex, int endIndex)
+      throws LocalizedIllegalArgumentException
+  {
+    if (startIndex >= endIndex)
+    {
+      // Equality filter with empty assertion value.
+      return new Filter(new EqualityMatchImpl(attributeDescription,
+          ByteString.empty()));
+    }
+    else if ((endIndex - startIndex == 1)
+        && (string.charAt(startIndex) == '*'))
+    {
+      // Single asterisk is a present filter.
+      return newPresentFilter(attributeDescription);
+    }
+    else
+    {
+      // Either an equality or substring filter.
+      ByteSequence assertionValue = valueOfAssertionValue(string,
+          startIndex, endIndex);
+
+      ByteSequence initialString = null;
+      ByteSequence finalString = null;
+      LinkedList<ByteSequence> anyStrings = null;
+
+      int lastAsteriskIndex = -1;
+      int length = assertionValue.length();
+      for (int i = 0; i < length; i++)
+      {
+        if (assertionValue.byteAt(i) == '*')
+        {
+          if (lastAsteriskIndex == -1)
+          {
+            if (i > 0)
+            {
+              // Got an initial substring.
+              initialString = assertionValue.subSequence(0, i);
+            }
+            lastAsteriskIndex = i;
+          }
+          else
+          {
+            // Got an any substring.
+            if (anyStrings == null)
+            {
+              anyStrings = new LinkedList<ByteSequence>();
+            }
+
+            int s = lastAsteriskIndex + 1;
+            if (s == i)
+            {
+              // A zero length substring.
+              Message message = ERR_LDAP_FILTER_BAD_SUBSTRING.get(
+                  string, string.subSequence(startIndex, endIndex));
+              throw new LocalizedIllegalArgumentException(message);
+            }
+
+            anyStrings.add(assertionValue.subSequence(s, i));
+            lastAsteriskIndex = i;
+          }
+        }
+      }
+
+      if (lastAsteriskIndex >= 0 && lastAsteriskIndex < length - 1)
+      {
+        // Got a final substring.
+        finalString = assertionValue.subSequence(lastAsteriskIndex + 1,
+            length);
+      }
+
+      if ((initialString == null) && (anyStrings == null)
+          && (finalString == null))
+      {
+        return new Filter(new EqualityMatchImpl(attributeDescription,
+            assertionValue));
+      }
+      else
+      {
+        List<ByteSequence> tmp;
+
+        if (anyStrings == null)
+        {
+          tmp = Collections.emptyList();
+        }
+        else if (anyStrings.size() == 1)
+        {
+          tmp = Collections.singletonList(anyStrings.getFirst());
+        }
+        else
+        {
+          tmp = Collections.unmodifiableList(anyStrings);
+        }
+
+        return new Filter(new SubstringsImpl(attributeDescription,
+            initialString, tmp, finalString));
+      }
+    }
+  }
+
+
+
+  /**
+   * Appends a properly-cleaned version of the provided value to the
+   * given builder so that it can be safely used in string
+   * representations of this search filter. The formatting changes that
+   * may be performed will be in compliance with the specification in
+   * RFC 2254.
+   *
+   * @param builder
+   *          The builder to which the "safe" version of the value will
+   *          be appended.
+   * @param value
+   *          The value to be appended to the builder.
+   */
+  private static void valueToFilterString(StringBuilder builder,
+      ByteSequence value)
+  {
+    // Get the binary representation of the value and iterate through
+    // it to see if there are any unsafe characters. If there are,
+    // then escape them and replace them with a two-digit hex
+    // equivalent.
+    builder.ensureCapacity(builder.length() + value.length());
+    for (int i = 0; i < value.length(); i++)
+    {
+      // TODO: this is a bit overkill - it will escape all non-ascii
+      // chars!
+      byte b = value.byteAt(i);
+      if (((b & 0x7F) != b) || // Not 7-bit clean
+          (b <= 0x1F) || // Below the printable character range
+          (b == 0x28) || // Open parenthesis
+          (b == 0x29) || // Close parenthesis
+          (b == 0x2A) || // Asterisk
+          (b == 0x5C) || // Backslash
+          (b == 0x7F)) // Delete character
+      {
+        builder.append('\\');
+        builder.append(byteToHex(b));
+      }
+      else
+      {
+        builder.append((char) b);
+      }
+    }
+  }
+
+
+
+  private final Impl pimpl;
+
+
+
+  private Filter(Impl pimpl)
+  {
+    this.pimpl = pimpl;
+  }
+
+
+
+  /**
+   * Applies a {@code FilterVisitor} to this {@code Filter}.
+   *
+   * @param <R>
+   *          The return type of the visitor's methods.
+   * @param <P>
+   *          The type of the additional parameters to the visitor's
+   *          methods.
+   * @param v
+   *          The filter visitor.
+   * @param p
+   *          Optional additional visitor parameter.
+   * @return A result as specified by the visitor.
+   */
+  public <R, P> R accept(FilterVisitor<R, P> v, P p)
+  {
+    return pimpl.accept(v, p);
+  }
+
+
+
+  /**
+   * Returns a {@code Matcher} which can be used to compare this {@code
+   * Filter} against entries using the provided {@code Schema}.
+   *
+   * @param schema
+   *          The schema which the {@code Matcher} should use for
+   *          comparisons.
+   * @return The {@code Matcher}.
+   */
+  public Matcher matcher(Schema schema)
+  {
+    return new Matcher(this, schema);
+  }
+
+
+
+  /**
+   * Returns a {@code Matcher} which can be used to compare this {@code
+   * Filter} against entries using the default schema.
+   *
+   * @return The {@code Matcher}.
+   */
+  public Matcher matcher()
+  {
+    return new Matcher(this, Schema.getDefaultSchema());
+  }
+
+
+
+  /**
+   * Indicates whether this {@code Filter} matches the provided {@code
+   * Entry} using the schema associated with the entry.
+   * <p>
+   * Calling this method is equivalent to the following:
+   *
+   * <pre>
+   * boolean b = matcher(entry.getSchema()).matches(entry);
+   * </pre>
+   *
+   * @param entry
+   *          The entry to be matched.
+   * @return {@code true} if this {@code Filter} matches the provided
+   *         {@code Entry}.
+   */
+  public ConditionResult matches(Entry entry)
+  {
+    return matcher(Schema.getDefaultSchema()).matches(entry);
+  }
+
+
+
+  /**
+   * Returns a {@code String} whose contents is the LDAP string
+   * representation of this {@code Filter}.
+   *
+   * @return The LDAP string representation of this {@code Filter}.
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    return toString(builder).toString();
+  }
+
+
+
+  /**
+   * Appends the LDAP string representation of this {@code Filter} to
+   * the provided {@code StringBuilder}.
+   *
+   * @param builder
+   *          The {@code StringBuilder} to which the LDAP string
+   *          representation of this {@code Filter} should be appended.
+   * @return The updated {@code StringBuilder}.
+   */
+  public StringBuilder toString(StringBuilder builder)
+  {
+    return pimpl.accept(TO_STRING_VISITOR, builder);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/FilterVisitor.java b/sdk/src/org/opends/sdk/FilterVisitor.java
new file mode 100644
index 0000000..a7d180e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/FilterVisitor.java
@@ -0,0 +1,238 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.List;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+/**
+ * A visitor of {@code Filter}s, in the style of the visitor design
+ * pattern.
+ * <p>
+ * Classes implementing this interface can query filters in a type-safe
+ * manner. When a visitor is passed to a filter's accept method, the
+ * corresponding visit method most applicable to that filter is invoked.
+ *
+ * @param <R>
+ *          The return type of this visitor's methods. Use
+ *          {@link java.lang.Void} for visitors that do not need to
+ *          return results.
+ * @param <P>
+ *          The type of the additional parameter to this visitor's
+ *          methods. Use {@link java.lang.Void} for visitors that do not
+ *          need an additional parameter.
+ */
+public interface FilterVisitor<R, P>
+{
+
+  /**
+   * Visits an {@code and} filter.
+   * <p>
+   * <b>Implementation note</b>: for the purposes of matching an empty
+   * sub-filter list should always evaluate to {@code true} as per RFC
+   * 4526.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param subFilters
+   *          The unmodifiable list of sub-filters.
+   * @return Returns a visitor specified result.
+   */
+  R visitAndFilter(P p, List<Filter> subFilters);
+
+
+
+  /**
+   * Visits an {@code approximate match} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return Returns a visitor specified result.
+   */
+  R visitApproxMatchFilter(P p, String attributeDescription,
+      ByteSequence assertionValue);
+
+
+
+  /**
+   * Visits an {@code equality match} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return Returns a visitor specified result.
+   */
+  R visitEqualityMatchFilter(P p, String attributeDescription,
+      ByteSequence assertionValue);
+
+
+
+  /**
+   * Visits an {@code extensible} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param matchingRule
+   *          The matching rule name, may be {@code null} if {@code
+   *          attributeDescription} is specified.
+   * @param attributeDescription
+   *          The attribute description, may be {@code null} if {@code
+   *          matchingRule} is specified.
+   * @param assertionValue
+   *          The assertion value.
+   * @param dnAttributes
+   *          Indicates whether DN matching should be performed.
+   * @return Returns a visitor specified result.
+   */
+  R visitExtensibleMatchFilter(P p, String matchingRule,
+      String attributeDescription, ByteSequence assertionValue,
+      boolean dnAttributes);
+
+
+
+  /**
+   * Visits a {@code greater or equal} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return Returns a visitor specified result.
+   */
+  R visitGreaterOrEqualFilter(P p, String attributeDescription,
+      ByteSequence assertionValue);
+
+
+
+  /**
+   * Visits a {@code less or equal} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param attributeDescription
+   *          The attribute description.
+   * @param assertionValue
+   *          The assertion value.
+   * @return Returns a visitor specified result.
+   */
+  R visitLessOrEqualFilter(P p, String attributeDescription,
+      ByteSequence assertionValue);
+
+
+
+  /**
+   * Visits a {@code not} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param subFilter
+   *          The sub-filter.
+   * @return Returns a visitor specified result.
+   */
+  R visitNotFilter(P p, Filter subFilter);
+
+
+
+  /**
+   * Visits an {@code or} filter.
+   * <p>
+   * <b>Implementation note</b>: for the purposes of matching an empty
+   * sub-filter list should always evaluate to {@code false} as per RFC
+   * 4526.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param subFilters
+   *          The unmodifiable list of sub-filters.
+   * @return Returns a visitor specified result.
+   */
+  R visitOrFilter(P p, List<Filter> subFilters);
+
+
+
+  /**
+   * Visits a {@code present} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param attributeDescription
+   *          The attribute description.
+   * @return Returns a visitor specified result.
+   */
+  R visitPresentFilter(P p, String attributeDescription);
+
+
+
+  /**
+   * Visits a {@code substrings} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param attributeDescription
+   *          The attribute description.
+   * @param initialSubstring
+   *          The initial sub-string, may be {@code null}.
+   * @param anySubstrings
+   *          The unmodifiable list of any sub-strings, may be empty.
+   * @param finalSubstring
+   *          The final sub-string, may be {@code null}.
+   * @return Returns a visitor specified result.
+   */
+  R visitSubstringsFilter(P p, String attributeDescription,
+      ByteSequence initialSubstring, List<ByteSequence> anySubstrings,
+      ByteSequence finalSubstring);
+
+
+
+  /**
+   * Visits an {@code unrecognized} filter.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param filterTag
+   *          The ASN.1 tag.
+   * @param filterBytes
+   *          The filter content.
+   * @return Returns a visitor specified result.
+   */
+  R visitUnrecognizedFilter(P p, byte filterTag, ByteSequence filterBytes);
+
+}
\ No newline at end of file
diff --git a/sdk/src/org/opends/sdk/LinkedAttribute.java b/sdk/src/org/opends/sdk/LinkedAttribute.java
new file mode 100644
index 0000000..1b52b5b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/LinkedAttribute.java
@@ -0,0 +1,1063 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.*;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An implementation of the {@code Attribute} interface with predictable
+ * iteration order.
+ * <p>
+ * Internally, attribute values are stored in a linked list and it's
+ * this list which defines the iteration ordering, which is the order in
+ * which elements were inserted into the set (insertion-order). This
+ * ordering is particularly useful in LDAP where clients generally
+ * appreciate having things returned in the same order they were
+ * presented.
+ * <p>
+ * All operations are supported by this implementation.
+ */
+public final class LinkedAttribute extends AbstractAttribute
+{
+  private static abstract class Impl
+  {
+
+    abstract boolean add(LinkedAttribute attribute, ByteString value);
+
+
+
+    boolean addAll(LinkedAttribute attribute,
+        Collection<? extends ByteString> values,
+        Collection<? super ByteString> duplicateValues)
+        throws NullPointerException
+    {
+      // TODO: could optimize if values is a BasicAttribute.
+      ensureCapacity(attribute, values.size());
+      boolean modified = false;
+      for (ByteString value : values)
+      {
+        if (add(attribute, value))
+        {
+          modified = true;
+        }
+        else if (duplicateValues != null)
+        {
+          duplicateValues.add(value);
+        }
+      }
+      resize(attribute);
+      return modified;
+    }
+
+
+
+    abstract void clear(LinkedAttribute attribute);
+
+
+
+    abstract boolean contains(LinkedAttribute attribute,
+        ByteString value);
+
+
+
+    boolean containsAll(LinkedAttribute attribute, Collection<?> values)
+    {
+      // TODO: could optimize if objects is a BasicAttribute.
+      for (Object value : values)
+      {
+        if (!contains(attribute, ByteString.valueOf(value)))
+        {
+          return false;
+        }
+      }
+      return true;
+    }
+
+
+
+    abstract void ensureCapacity(LinkedAttribute attribute, int size);
+
+
+
+    abstract ByteString firstValue(LinkedAttribute attribute)
+        throws NoSuchElementException;
+
+
+
+    abstract Iterator<ByteString> iterator(LinkedAttribute attribute);
+
+
+
+    abstract boolean remove(LinkedAttribute attribute, ByteString value);
+
+
+
+    <T> boolean removeAll(LinkedAttribute attribute,
+        Collection<T> values, Collection<? super T> missingValues)
+    {
+      // TODO: could optimize if objects is a BasicAttribute.
+      boolean modified = false;
+      for (T value : values)
+      {
+        if (remove(attribute, ByteString.valueOf(value)))
+        {
+          modified = true;
+        }
+        else if (missingValues != null)
+        {
+          missingValues.add(value);
+        }
+      }
+      return modified;
+    }
+
+
+
+    abstract void resize(LinkedAttribute attribute);
+
+
+
+    abstract <T> boolean retainAll(LinkedAttribute attribute,
+        Collection<T> values, Collection<? super T> missingValues);
+
+
+
+    abstract int size(LinkedAttribute attribute);
+  }
+
+
+
+  private static final class MultiValueImpl extends Impl
+  {
+
+    boolean add(LinkedAttribute attribute, ByteString value)
+    {
+      ByteString normalizedValue = normalizeValue(attribute, value);
+      if (attribute.multipleValues.put(normalizedValue, value) == null)
+      {
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+
+    void clear(LinkedAttribute attribute)
+    {
+      attribute.multipleValues = null;
+      attribute.pimpl = ZERO_VALUE_IMPL;
+    }
+
+
+
+    boolean contains(LinkedAttribute attribute, ByteString value)
+    {
+      return attribute.multipleValues.containsKey(normalizeValue(
+          attribute, value));
+    }
+
+
+
+    void ensureCapacity(LinkedAttribute attribute, int size)
+    {
+      // Nothing to do.
+    }
+
+
+
+    ByteString firstValue(LinkedAttribute attribute)
+        throws NoSuchElementException
+    {
+      return attribute.multipleValues.values().iterator().next();
+    }
+
+
+
+    Iterator<ByteString> iterator(final LinkedAttribute attribute)
+    {
+      return new Iterator<ByteString>()
+      {
+        private Impl expectedImpl = MULTI_VALUE_IMPL;
+
+        private Iterator<ByteString> iterator = attribute.multipleValues
+            .values().iterator();
+
+
+
+        public boolean hasNext()
+        {
+          return iterator.hasNext();
+        }
+
+
+
+        public ByteString next()
+        {
+          if (attribute.pimpl != expectedImpl)
+          {
+            throw new ConcurrentModificationException();
+          }
+          else
+          {
+            return iterator.next();
+          }
+        }
+
+
+
+        public void remove()
+        {
+          if (attribute.pimpl != expectedImpl)
+          {
+            throw new ConcurrentModificationException();
+          }
+          else
+          {
+            iterator.remove();
+
+            // Resize if we have removed the second to last value.
+            if (attribute.multipleValues != null
+                && attribute.multipleValues.size() == 1)
+            {
+              resize(attribute);
+              iterator = attribute.pimpl.iterator(attribute);
+            }
+
+            // Always update since we may change to single or zero value
+            // impl.
+            expectedImpl = attribute.pimpl;
+          }
+        }
+
+      };
+    }
+
+
+
+    boolean remove(LinkedAttribute attribute, ByteString value)
+    {
+      ByteString normalizedValue = normalizeValue(attribute, value);
+      if (attribute.multipleValues.remove(normalizedValue) != null)
+      {
+        resize(attribute);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+
+    void resize(LinkedAttribute attribute)
+    {
+      // May need to resize if initial size estimate was wrong (e.g. all
+      // values in added collection were the same).
+      switch (attribute.multipleValues.size())
+      {
+      case 0:
+        attribute.multipleValues = null;
+        attribute.pimpl = ZERO_VALUE_IMPL;
+        break;
+      case 1:
+        Map.Entry<ByteString, ByteString> e = attribute.multipleValues
+            .entrySet().iterator().next();
+        attribute.singleValue = e.getValue();
+        attribute.normalizedSingleValue = e.getKey();
+        attribute.multipleValues = null;
+        attribute.pimpl = SINGLE_VALUE_IMPL;
+        break;
+      default:
+        // Nothing to do.
+        break;
+      }
+    }
+
+
+
+    <T> boolean retainAll(LinkedAttribute attribute,
+        Collection<T> values, Collection<? super T> missingValues)
+    {
+      // TODO: could optimize if objects is a BasicAttribute.
+      if (values.isEmpty())
+      {
+        clear(attribute);
+        return true;
+      }
+
+      Map<ByteString, T> valuesToRetain = new HashMap<ByteString, T>(
+          values.size());
+      for (T value : values)
+      {
+        valuesToRetain.put(normalizeValue(attribute, ByteString
+            .valueOf(value)), value);
+      }
+
+      boolean modified = false;
+      Iterator<ByteString> iterator = attribute.multipleValues.keySet()
+          .iterator();
+      while (iterator.hasNext())
+      {
+        ByteString normalizedValue = iterator.next();
+        if (valuesToRetain.remove(normalizedValue) == null)
+        {
+          modified = true;
+          iterator.remove();
+        }
+      }
+
+      if (missingValues != null)
+      {
+        missingValues.addAll(valuesToRetain.values());
+      }
+
+      resize(attribute);
+
+      return modified;
+    }
+
+
+
+    int size(LinkedAttribute attribute)
+    {
+      return attribute.multipleValues.size();
+    }
+  }
+
+
+
+  private static final class SingleValueImpl extends Impl
+  {
+
+    boolean add(LinkedAttribute attribute, ByteString value)
+    {
+      ByteString normalizedValue = normalizeValue(attribute, value);
+      if (attribute.normalizedSingleValue().equals(normalizedValue))
+      {
+        return false;
+      }
+
+      attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+          2);
+      attribute.multipleValues.put(attribute.normalizedSingleValue,
+          attribute.singleValue);
+      attribute.multipleValues.put(normalizedValue, value);
+      attribute.singleValue = null;
+      attribute.normalizedSingleValue = null;
+      attribute.pimpl = MULTI_VALUE_IMPL;
+
+      return true;
+    }
+
+
+
+    void clear(LinkedAttribute attribute)
+    {
+      attribute.singleValue = null;
+      attribute.normalizedSingleValue = null;
+      attribute.pimpl = ZERO_VALUE_IMPL;
+    }
+
+
+
+    boolean contains(LinkedAttribute attribute, ByteString value)
+    {
+      ByteString normalizedValue = normalizeValue(attribute, value);
+      return attribute.normalizedSingleValue().equals(normalizedValue);
+    }
+
+
+
+    void ensureCapacity(LinkedAttribute attribute, int size)
+    {
+      if (size == 0)
+      {
+        return;
+      }
+
+      attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+          1 + size);
+      attribute.multipleValues.put(attribute.normalizedSingleValue,
+          attribute.singleValue);
+      attribute.singleValue = null;
+      attribute.normalizedSingleValue = null;
+      attribute.pimpl = MULTI_VALUE_IMPL;
+    }
+
+
+
+    ByteString firstValue(LinkedAttribute attribute)
+        throws NoSuchElementException
+    {
+      if (attribute.singleValue != null)
+      {
+        return attribute.singleValue;
+      }
+      else
+      {
+        throw new NoSuchElementException();
+      }
+    }
+
+
+
+    Iterator<ByteString> iterator(final LinkedAttribute attribute)
+    {
+      return new Iterator<ByteString>()
+      {
+        private Impl expectedImpl = SINGLE_VALUE_IMPL;
+
+        private boolean hasNext = true;
+
+
+
+        public boolean hasNext()
+        {
+          return hasNext;
+        }
+
+
+
+        public ByteString next()
+        {
+          if (attribute.pimpl != expectedImpl)
+          {
+            throw new ConcurrentModificationException();
+          }
+          else if (hasNext)
+          {
+            hasNext = false;
+            return attribute.singleValue;
+          }
+          else
+          {
+            throw new NoSuchElementException();
+          }
+        }
+
+
+
+        public void remove()
+        {
+          if (attribute.pimpl != expectedImpl)
+          {
+            throw new ConcurrentModificationException();
+          }
+          else if (hasNext || attribute.singleValue == null)
+          {
+            throw new IllegalStateException();
+          }
+          else
+          {
+            clear(attribute);
+            expectedImpl = attribute.pimpl;
+          }
+        }
+
+      };
+    }
+
+
+
+    boolean remove(LinkedAttribute attribute, ByteString value)
+    {
+      if (contains(attribute, value))
+      {
+        clear(attribute);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+
+    void resize(LinkedAttribute attribute)
+    {
+      // Nothing to do.
+    }
+
+
+
+    <T> boolean retainAll(LinkedAttribute attribute,
+        Collection<T> values, Collection<? super T> missingValues)
+    {
+      // TODO: could optimize if objects is a BasicAttribute.
+      if (values.isEmpty())
+      {
+        clear(attribute);
+        return true;
+      }
+
+      ByteString normalizedSingleValue = attribute
+          .normalizedSingleValue();
+      boolean retained = false;
+      for (T value : values)
+      {
+        ByteString normalizedValue = normalizeValue(attribute,
+            ByteString.valueOf(value));
+        if (normalizedSingleValue.equals(normalizedValue))
+        {
+          if (missingValues == null)
+          {
+            // We can stop now.
+            return false;
+          }
+          retained = true;
+        }
+        else if (missingValues != null)
+        {
+          missingValues.add(value);
+        }
+      }
+
+      if (!retained)
+      {
+        clear(attribute);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+
+    int size(LinkedAttribute attribute)
+    {
+      return 1;
+    }
+  }
+
+
+
+  private static final class ZeroValueImpl extends Impl
+  {
+
+    boolean add(LinkedAttribute attribute, ByteString value)
+    {
+      attribute.singleValue = value;
+      attribute.pimpl = SINGLE_VALUE_IMPL;
+      return true;
+    }
+
+
+
+    void clear(LinkedAttribute attribute)
+    {
+      // Nothing to do.
+    }
+
+
+
+    boolean contains(LinkedAttribute attribute, ByteString value)
+    {
+      return false;
+    }
+
+
+
+    boolean containsAll(LinkedAttribute attribute, Collection<?> values)
+    {
+      return values.isEmpty();
+    }
+
+
+
+    void ensureCapacity(LinkedAttribute attribute, int size)
+    {
+      if (size < 2)
+      {
+        return;
+      }
+
+      attribute.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+          size);
+      attribute.pimpl = MULTI_VALUE_IMPL;
+    }
+
+
+
+    ByteString firstValue(LinkedAttribute attribute)
+        throws NoSuchElementException
+    {
+      throw new NoSuchElementException();
+    }
+
+
+
+    Iterator<ByteString> iterator(final LinkedAttribute attribute)
+    {
+      return new Iterator<ByteString>()
+      {
+        public boolean hasNext()
+        {
+          return false;
+        }
+
+
+
+        public ByteString next()
+        {
+          if (attribute.pimpl != ZERO_VALUE_IMPL)
+          {
+            throw new ConcurrentModificationException();
+          }
+          else
+          {
+            throw new NoSuchElementException();
+          }
+        }
+
+
+
+        public void remove()
+        {
+          if (attribute.pimpl != ZERO_VALUE_IMPL)
+          {
+            throw new ConcurrentModificationException();
+          }
+          else
+          {
+            throw new IllegalStateException();
+          }
+        }
+
+      };
+    }
+
+
+
+    boolean remove(LinkedAttribute attribute, ByteString value)
+    {
+      return false;
+    }
+
+
+
+    void resize(LinkedAttribute attribute)
+    {
+      // Nothing to do.
+    }
+
+
+
+    <T> boolean retainAll(LinkedAttribute attribute,
+        Collection<T> values, Collection<? super T> missingValues)
+    {
+      if (missingValues != null)
+      {
+        missingValues.addAll(values);
+      }
+      return false;
+    }
+
+
+
+    int size(LinkedAttribute attribute)
+    {
+      return 0;
+    }
+
+  }
+
+
+
+  private static final MultiValueImpl MULTI_VALUE_IMPL = new MultiValueImpl();
+
+  private static final SingleValueImpl SINGLE_VALUE_IMPL = new SingleValueImpl();
+
+  private static final ZeroValueImpl ZERO_VALUE_IMPL = new ZeroValueImpl();
+
+  private final AttributeDescription attributeDescription;
+
+  private Map<ByteString, ByteString> multipleValues = null;
+
+  private ByteString normalizedSingleValue = null;
+
+  private Impl pimpl = ZERO_VALUE_IMPL;
+
+  private ByteString singleValue = null;
+
+
+
+  /**
+   * Creates a new attribute having the same attribute description and
+   * attribute values as {@code attribute}.
+   *
+   * @param attribute
+   *          The attribute to be copied.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  public LinkedAttribute(Attribute attribute)
+      throws NullPointerException
+  {
+    this.attributeDescription = attribute.getAttributeDescription();
+
+    if (attribute instanceof LinkedAttribute)
+    {
+      LinkedAttribute other = (LinkedAttribute) attribute;
+      this.pimpl = other.pimpl;
+      this.singleValue = other.singleValue;
+      this.normalizedSingleValue = other.normalizedSingleValue;
+      if (other.multipleValues != null)
+      {
+        this.multipleValues = new LinkedHashMap<ByteString, ByteString>(
+            other.multipleValues);
+      }
+    }
+    else
+    {
+      addAll(attribute);
+    }
+  }
+
+
+
+  /**
+   * Creates a new attribute having the specified attribute description
+   * and no attribute values.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  public LinkedAttribute(AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+    this.attributeDescription = attributeDescription;
+  }
+
+
+
+  /**
+   * Creates a new attribute having the specified attribute description
+   * and no attribute values. The attribute description will be decoded
+   * using the default schema.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the default schema.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  public LinkedAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    this(AttributeDescription.valueOf(attributeDescription));
+  }
+
+
+
+  /**
+   * Creates a new attribute having the specified attribute description
+   * and single attribute value.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param value
+   *          The single attribute value.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code value} was
+   *           {@code null}.
+   */
+  public LinkedAttribute(AttributeDescription attributeDescription,
+      ByteString value) throws NullPointerException
+  {
+    this(attributeDescription);
+    add(value);
+  }
+
+
+
+  /**
+   * Creates a new attribute having the specified attribute description
+   * and attribute values.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param values
+   *          The attribute values.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code values} was
+   *           {@code null}.
+   */
+  public LinkedAttribute(AttributeDescription attributeDescription,
+      ByteString... values) throws NullPointerException
+  {
+    this(attributeDescription);
+    addAll(Arrays.asList(values));
+  }
+
+
+
+  /**
+   * Creates a new attribute having the specified attribute description
+   * and attribute values.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param values
+   *          The attribute values.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code values} was
+   *           {@code null}.
+   */
+  public LinkedAttribute(AttributeDescription attributeDescription,
+      Collection<ByteString> values) throws NullPointerException
+  {
+    this(attributeDescription);
+    addAll(values);
+  }
+
+
+
+  /**
+   * Creates a new attribute having the specified attribute description
+   * and single attribute value. The attribute description will be
+   * decoded using the default schema.
+   * <p>
+   * If {@code value} is not an instance of {@code ByteString} then it
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param value
+   *          The single attribute value.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the default schema.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code value} was
+   *           {@code null}.
+   */
+  public LinkedAttribute(String attributeDescription, Object value)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    this(attributeDescription);
+    add(ByteString.valueOf(value));
+  }
+
+
+
+  /**
+   * Creates a new attribute having the specified attribute description
+   * and attribute values. The attribute description will be decoded
+   * using the default schema.
+   * <p>
+   * Any attribute values which are not instances of {@code ByteString}
+   * will be converted using the {@link ByteString#valueOf(Object)}
+   * method.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @param values
+   *          The attribute values.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the default schema.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} or {@code values} was
+   *           {@code null}.
+   */
+  public LinkedAttribute(String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    this(attributeDescription);
+    for (Object value : values)
+    {
+      add(ByteString.valueOf(value));
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean add(ByteString value) throws NullPointerException
+  {
+    Validator.ensureNotNull(value);
+    return pimpl.add(this, value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAll(Collection<? extends ByteString> values,
+      Collection<? super ByteString> duplicateValues)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(values);
+    return pimpl.addAll(this, values, duplicateValues);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void clear()
+  {
+    pimpl.clear(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAll(Collection<?> values)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(values);
+    return pimpl.containsAll(this, values);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString firstValue() throws NoSuchElementException
+  {
+    return pimpl.firstValue(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AttributeDescription getAttributeDescription()
+  {
+    return attributeDescription;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterator<ByteString> iterator()
+  {
+    return pimpl.iterator(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <T> boolean removeAll(Collection<T> values,
+      Collection<? super T> missingValues) throws NullPointerException
+  {
+    Validator.ensureNotNull(values);
+    return pimpl.removeAll(this, values, missingValues);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <T> boolean retainAll(Collection<T> values,
+      Collection<? super T> missingValues) throws NullPointerException
+  {
+    Validator.ensureNotNull(values);
+    return pimpl.retainAll(this, values, missingValues);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int size()
+  {
+    return pimpl.size(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean contains(Object value) throws NullPointerException
+  {
+    Validator.ensureNotNull(value);
+    return pimpl.contains(this, ByteString.valueOf(value));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean remove(Object value) throws NullPointerException
+  {
+    Validator.ensureNotNull(value);
+    return pimpl.remove(this, ByteString.valueOf(value));
+  }
+
+
+
+  // Lazily computes the normalized single value.
+  private ByteString normalizedSingleValue()
+  {
+    if (normalizedSingleValue == null)
+    {
+      normalizedSingleValue = normalizeValue(this, singleValue);
+    }
+    return normalizedSingleValue;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/Matcher.java b/sdk/src/org/opends/sdk/Matcher.java
new file mode 100644
index 0000000..7e7aace
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Matcher.java
@@ -0,0 +1,871 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.sdk.util.StaticUtils.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+
+import org.opends.sdk.schema.MatchingRule;
+import org.opends.sdk.schema.MatchingRuleUse;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.schema.UnknownSchemaElementException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+
+
+/**
+ * An interface for determining whether entries match a {@code Filter}.
+ */
+public final class Matcher
+{
+  private static class AndMatcherImpl extends MatcherImpl
+  {
+    private final List<MatcherImpl> subMatchers;
+
+
+
+    private AndMatcherImpl(List<MatcherImpl> subMatchers)
+    {
+      this.subMatchers = subMatchers;
+    }
+
+
+
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      ConditionResult r = ConditionResult.TRUE;
+      for (final MatcherImpl m : subMatchers)
+      {
+        final ConditionResult p = m.matches(entry);
+        if (p == ConditionResult.FALSE)
+        {
+          return p;
+        }
+        r = ConditionResult.and(r, p);
+      }
+      return r;
+    }
+  }
+
+
+
+  private static class AssertionMatcherImpl extends MatcherImpl
+  {
+    private final Assertion assertion;
+    private final AttributeDescription attributeDescription;
+    private final boolean dnAttributes;
+    private final MatchingRule rule;
+    private final MatchingRuleUse ruleUse;
+
+
+
+    private AssertionMatcherImpl(
+        AttributeDescription attributeDescription, MatchingRule rule,
+        MatchingRuleUse ruleUse, Assertion assertion,
+        boolean dnAttributes)
+    {
+      this.attributeDescription = attributeDescription;
+      this.rule = rule;
+      this.ruleUse = ruleUse;
+      this.assertion = assertion;
+      this.dnAttributes = dnAttributes;
+    }
+
+
+
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      ConditionResult r = ConditionResult.FALSE;
+      if (attributeDescription != null)
+      {
+        // If the matchingRule field is absent, the type field will be
+        // present and the default equality matching rule is used,
+        // and an equality match is performed for that type.
+
+        // If the type field is present and the matchingRule is present,
+        // the matchValue is compared against the specified attribute
+        // type and its subtypes.
+        final ConditionResult p = Matcher.matches(
+            entry.getAttribute(attributeDescription), rule, assertion);
+        if (p == ConditionResult.TRUE)
+        {
+          return p;
+        }
+        r = ConditionResult.or(r, p);
+      }
+      else
+      {
+        // If the type field is absent and the matchingRule is present,
+        // the matchValue is compared against all attributes in an entry
+        // that support that matchingRule.
+        for (final Attribute a : entry.getAttributes())
+        {
+          if (ruleUse.hasAttribute(a.getAttributeDescription()
+              .getAttributeType()))
+          {
+            final ConditionResult p = Matcher.matches(a, rule, assertion);
+            if (p == ConditionResult.TRUE)
+            {
+              return p;
+            }
+            r = ConditionResult.or(r, p);
+          }
+        }
+      }
+
+      if (dnAttributes)
+      {
+        // If the dnAttributes field is set to TRUE, the match is
+        // additionally applied against all the AttributeValueAssertions
+        // in an entry's distinguished name, and it evaluates to TRUE if
+        // there is at least one attribute or subtype in the
+        // distinguished name for which the filter item evaluates to
+        // TRUE.
+        final DN dn = entry.getName();
+        for (final RDN rdn : dn)
+        {
+          for (final RDN.AVA ava : rdn)
+          {
+            if (ruleUse.hasAttribute(ava.getAttributeType()))
+            {
+              final ConditionResult p =
+                  Matcher.matches(ava.getAttributeValue(), rule, assertion);
+              if (p == ConditionResult.TRUE)
+              {
+                return p;
+              }
+              r = ConditionResult.or(r, p);
+            }
+          }
+        }
+      }
+      return r;
+    }
+  }
+
+
+
+  private static class FalseMatcherImpl extends MatcherImpl
+  {
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      return ConditionResult.FALSE;
+    }
+  }
+
+
+
+  private static abstract class MatcherImpl
+  {
+    public abstract ConditionResult matches(Entry entry);
+  }
+
+
+
+  private static class NotMatcherImpl extends MatcherImpl
+  {
+    private final MatcherImpl subFilter;
+
+
+
+    private NotMatcherImpl(MatcherImpl subFilter)
+    {
+      this.subFilter = subFilter;
+    }
+
+
+
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      return ConditionResult.not(subFilter.matches(entry));
+    }
+  }
+
+
+
+  private static class OrMatcherImpl extends MatcherImpl
+  {
+    private final List<MatcherImpl> subMatchers;
+
+
+
+    private OrMatcherImpl(List<MatcherImpl> subMatchers)
+    {
+      this.subMatchers = subMatchers;
+    }
+
+
+
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      ConditionResult r = ConditionResult.FALSE;
+      for (final MatcherImpl m : subMatchers)
+      {
+        final ConditionResult p = m.matches(entry);
+        if (p == ConditionResult.TRUE)
+        {
+          return p;
+        }
+        r = ConditionResult.or(r, p);
+      }
+      return r;
+    }
+  }
+
+
+
+  private static class PresentMatcherImpl extends MatcherImpl
+  {
+    private final AttributeDescription attribute;
+
+
+
+    private PresentMatcherImpl(AttributeDescription attribute)
+    {
+      this.attribute = attribute;
+    }
+
+
+
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      return entry.getAttribute(attribute) == null ? ConditionResult.FALSE
+                                                   : ConditionResult.TRUE;
+    }
+  }
+
+
+
+  private static class TrueMatcherImpl extends MatcherImpl
+  {
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      return ConditionResult.TRUE;
+    }
+  }
+
+
+
+  private static class UndefinedMatcherImpl extends MatcherImpl
+  {
+    @Override
+    public ConditionResult matches(Entry entry)
+    {
+      return ConditionResult.UNDEFINED;
+    }
+  }
+
+
+
+  /**
+   * A visitor which is used to transform a filter into a matcher.
+   */
+  private static final class Visitor implements
+                                     FilterVisitor<MatcherImpl, Schema>
+  {
+    public MatcherImpl visitAndFilter(Schema schema,
+                                      List<Filter> subFilters)
+    {
+      if (subFilters.isEmpty())
+      {
+        if(DEBUG_LOG.isLoggable(Level.FINER))
+        {
+          DEBUG_LOG.finer("Empty add filter component. " +
+                          "Will always return TRUE");
+        }
+        return TRUE;
+      }
+
+      final List<MatcherImpl> subMatchers =
+          new ArrayList<MatcherImpl>(subFilters.size());
+      for (final Filter f : subFilters)
+      {
+        subMatchers.add(f.accept(this, schema));
+      }
+      return new AndMatcherImpl(subMatchers);
+    }
+
+
+
+    public MatcherImpl visitApproxMatchFilter(Schema schema,
+                                              String attributeDescription, ByteSequence assertionValue)
+    {
+      AttributeDescription ad;
+      MatchingRule rule;
+      Assertion assertion;
+
+      try
+      {
+        ad = AttributeDescription.valueOf(attributeDescription, schema);
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "Attribute description " + attributeDescription  +
+              " is not recognized: " + e.toString());
+        }
+        return UNDEFINED;
+      }
+
+      if ((rule = ad.getAttributeType().getApproximateMatchingRule()) == null)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The attribute type " + attributeDescription +
+              " does not define an approximate matching rule");
+        }
+        return UNDEFINED;
+      }
+
+      try
+      {
+        assertion = rule.getAssertion(assertionValue);
+      }
+      catch (final DecodeException de)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The assertion value " + assertionValue + " is invalid: " +
+              de.toString());
+        }
+        return UNDEFINED;
+      }
+      return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+    }
+
+
+
+    public MatcherImpl visitEqualityMatchFilter(Schema schema,
+                                                String attributeDescription, ByteSequence assertionValue)
+    {
+      AttributeDescription ad;
+      MatchingRule rule;
+      Assertion assertion;
+
+      try
+      {
+        ad = AttributeDescription.valueOf(attributeDescription, schema);
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "Attribute description " + attributeDescription  +
+              " is not recognized: " + e.toString());
+        }
+        return UNDEFINED;
+      }
+
+      if ((rule = ad.getAttributeType().getEqualityMatchingRule()) == null)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The attribute type " + attributeDescription +
+              " does not define an equality matching rule");
+        }
+        return UNDEFINED;
+      }
+
+      try
+      {
+        assertion = rule.getAssertion(assertionValue);
+      }
+      catch (final DecodeException de)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The assertion value " + assertionValue + " is invalid: " +
+              de.toString());
+        }
+        return UNDEFINED;
+      }
+      return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+    }
+
+
+
+    public MatcherImpl visitExtensibleMatchFilter(Schema schema,
+                                                  String matchingRule,
+                                                  String attributeDescription,
+                                                  ByteSequence assertionValue,
+                                                  boolean dnAttributes)
+    {
+      AttributeDescription ad = null;
+      MatchingRule rule = null;
+      MatchingRuleUse ruleUse = null;
+      Assertion assertion;
+
+      if (matchingRule != null)
+      {
+        try
+        {
+          rule = schema.getMatchingRule(matchingRule);
+        }
+        catch(final UnknownSchemaElementException e)
+        {
+          if(DEBUG_LOG.isLoggable(Level.WARNING))
+          {
+            DEBUG_LOG.warning(
+                "Matching rule " + matchingRule  + " is not recognized: " +
+                e.toString());
+          }
+          return UNDEFINED;
+        }
+      }
+
+      if (attributeDescription != null)
+      {
+        try
+        {
+          ad =
+              AttributeDescription
+                  .valueOf(attributeDescription, schema);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          if(DEBUG_LOG.isLoggable(Level.WARNING))
+          {
+            DEBUG_LOG.warning(
+                "Attribute description " + attributeDescription  +
+                " is not recognized: " + e.toString());
+          }
+          return UNDEFINED;
+        }
+
+        if (rule == null)
+        {
+          if ((rule = ad.getAttributeType().getEqualityMatchingRule()) == null)
+          {
+            if(DEBUG_LOG.isLoggable(Level.WARNING))
+            {
+              DEBUG_LOG.warning(
+                  "The attribute type " + attributeDescription +
+                  " does not define an equality matching rule");
+            }
+            return UNDEFINED;
+          }
+        }
+        else
+        {
+          try
+          {
+            ruleUse = schema.getMatchingRuleUse(rule);
+          }
+          catch(final UnknownSchemaElementException e)
+          {
+            if(DEBUG_LOG.isLoggable(Level.WARNING))
+            {
+              DEBUG_LOG.warning("No matching rule use is defined for " +
+                                "matching rule " + matchingRule);
+              return UNDEFINED;
+            }
+          }
+          if(!ruleUse.hasAttribute(ad.getAttributeType()))
+          {
+            if(DEBUG_LOG.isLoggable(Level.WARNING))
+            {
+              DEBUG_LOG.warning("The matching rule " + matchingRule +
+                                " is not valid for attribute type " +
+                                attributeDescription);
+            }
+            return UNDEFINED;
+          }
+        }
+      }
+      else
+      {
+        try
+        {
+          ruleUse = schema.getMatchingRuleUse(rule);
+        }
+        catch(final UnknownSchemaElementException e)
+        {
+          if(DEBUG_LOG.isLoggable(Level.WARNING))
+          {
+            DEBUG_LOG.warning("No matching rule use is defined for " +
+                              "matching rule " + matchingRule);
+          }
+          return UNDEFINED;
+        }
+      }
+
+      try
+      {
+        assertion = rule.getAssertion(assertionValue);
+      }
+      catch (final DecodeException de)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The assertion value " + assertionValue + " is invalid: " +
+              de.toString());
+        }
+        return UNDEFINED;
+      }
+      return new AssertionMatcherImpl(ad, rule, ruleUse, assertion,
+                                      dnAttributes);
+    }
+
+
+
+    public MatcherImpl visitGreaterOrEqualFilter(Schema schema,
+                                                 String attributeDescription,
+                                                 ByteSequence assertionValue)
+    {
+      AttributeDescription ad;
+      MatchingRule rule;
+      Assertion assertion;
+
+      try
+      {
+        ad = AttributeDescription.valueOf(attributeDescription, schema);
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "Attribute description " + attributeDescription  +
+              " is not recognized: " + e.toString());
+        }
+        return UNDEFINED;
+      }
+
+      if ((rule = ad.getAttributeType().getOrderingMatchingRule()) == null)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The attribute type " + attributeDescription +
+              " does not define an ordering matching rule");
+        }
+        return UNDEFINED;
+      }
+
+      try
+      {
+        assertion = rule.getGreaterOrEqualAssertion(assertionValue);
+      }
+      catch (final DecodeException de)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The assertion value " + assertionValue + " is invalid: " +
+              de.toString());
+        }
+        return UNDEFINED;
+      }
+      return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+    }
+
+
+
+    public MatcherImpl visitLessOrEqualFilter(Schema schema,
+                                              String attributeDescription,
+                                              ByteSequence assertionValue)
+    {
+      AttributeDescription ad;
+      MatchingRule rule;
+      Assertion assertion;
+
+      try
+      {
+        ad = AttributeDescription.valueOf(attributeDescription, schema);
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "Attribute description " + attributeDescription  +
+              " is not recognized: " + e.toString());
+        }
+        return UNDEFINED;
+      }
+
+      if ((rule = ad.getAttributeType().getOrderingMatchingRule()) == null)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The attribute type " + attributeDescription +
+              " does not define an ordering matching rule");
+        }
+        return UNDEFINED;
+      }
+
+      try
+      {
+        assertion = rule.getLessOrEqualAssertion(assertionValue);
+      }
+      catch (final DecodeException de)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The assertion value " + assertionValue + " is invalid: " +
+              de.toString());
+        }
+        return UNDEFINED;
+      }
+      return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+    }
+
+
+
+    public MatcherImpl visitNotFilter(Schema schema, Filter subFilter)
+    {
+      final MatcherImpl subMatcher = subFilter.accept(this, schema);
+      return new NotMatcherImpl(subMatcher);
+    }
+
+
+
+    public MatcherImpl visitOrFilter(Schema schema,
+                                     List<Filter> subFilters)
+    {
+      if (subFilters.isEmpty())
+      {
+        if(DEBUG_LOG.isLoggable(Level.FINER))
+        {
+          DEBUG_LOG.finer("Empty or filter component. " +
+                          "Will always return FALSE");
+        }
+        return FALSE;
+      }
+
+      final List<MatcherImpl> subMatchers =
+          new ArrayList<MatcherImpl>(subFilters.size());
+      for (final Filter f : subFilters)
+      {
+        subMatchers.add(f.accept(this, schema));
+      }
+      return new OrMatcherImpl(subMatchers);
+    }
+
+
+
+    public MatcherImpl visitPresentFilter(Schema schema,
+                                          String attributeDescription)
+    {
+      AttributeDescription ad;
+      try
+      {
+        ad = AttributeDescription.valueOf(attributeDescription, schema);
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "Attribute description " + attributeDescription  +
+              " is not recognized: " + e.toString());
+        }
+        return UNDEFINED;
+      }
+
+      return new PresentMatcherImpl(ad);
+    }
+
+
+
+    public MatcherImpl visitSubstringsFilter(Schema schema,
+                                             String attributeDescription,
+                                             ByteSequence initialSubstring,
+                                             List<ByteSequence> anySubstrings,
+                                             ByteSequence finalSubstring)
+    {
+      AttributeDescription ad;
+      MatchingRule rule;
+      Assertion assertion;
+
+      try
+      {
+        ad = AttributeDescription.valueOf(attributeDescription, schema);
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "Attribute description " + attributeDescription  +
+              " is not recognized: " + e.toString());
+        }
+        return UNDEFINED;
+      }
+
+      if ((rule = ad.getAttributeType().getSubstringMatchingRule()) == null)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The attribute type " + attributeDescription +
+              " does not define an substring matching rule");
+        }
+        return UNDEFINED;
+      }
+
+      try
+      {
+        assertion =
+            rule.getAssertion(initialSubstring, anySubstrings,
+                              finalSubstring);
+      }
+      catch (final DecodeException de)
+      {
+        if(DEBUG_LOG.isLoggable(Level.WARNING))
+        {
+          DEBUG_LOG.warning(
+              "The substring assertion values contain an invalid value: " +
+              de.toString());
+        }
+        return UNDEFINED;
+      }
+      return new AssertionMatcherImpl(ad, rule, null, assertion, false);
+    }
+
+
+
+    public MatcherImpl visitUnrecognizedFilter(Schema schema,
+                                               byte filterTag,
+                                               ByteSequence filterBytes)
+    {
+      if(DEBUG_LOG.isLoggable(Level.WARNING))
+      {
+        DEBUG_LOG.warning("The type of filtering requested with tag " +
+                          StaticUtils.byteToHex(filterTag) +
+                          " is not implemented");
+      }
+      return UNDEFINED;
+    }
+  }
+
+  private static final MatcherImpl FALSE = new FalseMatcherImpl();
+
+  private static final MatcherImpl TRUE = new TrueMatcherImpl();
+
+  private static final MatcherImpl UNDEFINED =
+      new UndefinedMatcherImpl();
+
+  private static final FilterVisitor<MatcherImpl, Schema> VISITOR =
+      new Visitor();
+
+
+
+  private static ConditionResult matches(Attribute a,
+                                         MatchingRule rule, Assertion assertion)
+  {
+
+    ConditionResult r = ConditionResult.FALSE;
+    if (a != null)
+    {
+      for (final ByteString v : a)
+      {
+        switch (matches(v, rule, assertion))
+        {
+          case TRUE:
+            return ConditionResult.TRUE;
+          case UNDEFINED:
+            r = ConditionResult.UNDEFINED;
+        }
+      }
+    }
+    return r;
+  }
+
+
+
+  private static ConditionResult matches(ByteString v,
+                                         MatchingRule rule, Assertion assertion)
+  {
+    try
+    {
+      final ByteString normalizedValue =
+          rule.normalizeAttributeValue(v);
+      return assertion.matches(normalizedValue);
+    }
+    catch (final DecodeException de)
+    {
+      if(DEBUG_LOG.isLoggable(Level.WARNING))
+      {
+        DEBUG_LOG.warning("The attribute value " + v.toString() + " is " +
+                         "invalid for matching rule " + rule.getNameOrOID() +
+                         ". Possible schema error? : " + de.toString());
+      }
+      return ConditionResult.UNDEFINED;
+    }
+  }
+
+  private final MatcherImpl impl;
+
+
+
+  Matcher(Filter filter, Schema schema)
+  {
+    this.impl = filter.accept(VISITOR, schema);
+  }
+
+
+
+  /**
+   * Indicates whether this filter {@code Matcher} matches the provided
+   * {@code Entry}.
+   *
+   * @param entry
+   *          The entry to be matched.
+   * @return {@code true} if this filter {@code Matcher} matches the
+   *         provided {@code Entry}.
+   */
+  public ConditionResult matches(Entry entry)
+  {
+    return impl.matches(entry);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ModificationType.java b/sdk/src/org/opends/sdk/ModificationType.java
new file mode 100644
index 0000000..020fb17
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ModificationType.java
@@ -0,0 +1,212 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * A Modify operation change type as defined in RFC 4511 section 4.6 is
+ * used to specify the type of modification being performed on an
+ * attribute.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.6">RFC
+ *      4511 - Lightweight Directory Access Protocol (LDAP): The
+ *      Protocol </a>
+ * @see <a href="http://tools.ietf.org/html/rfc4525">RFC 4525 -
+ *      Lightweight Directory Access Protocol (LDAP) Modify-Increment
+ *      Extension </a>
+ */
+public final class ModificationType
+{
+  private static final ModificationType[] ELEMENTS = new ModificationType[4];
+
+  private static final List<ModificationType> IMMUTABLE_ELEMENTS = Collections
+      .unmodifiableList(Arrays.asList(ELEMENTS));
+
+  /**
+   * Add the values listed in the modification to the attribute,
+   * creating the attribute if necessary.
+   */
+  public static final ModificationType ADD = register(0, "add");
+
+  /**
+   * Delete the values listed in the modification from the attribute. If
+   * no values are listed, or if all current values of the attribute are
+   * listed, the entire attribute is removed.
+   */
+  public static final ModificationType DELETE = register(1, "delete");
+
+  /**
+   * Replace all existing values of the attribute with the new values
+   * listed in the modification, creating the attribute if it did not
+   * already exist. A replace with no listed values will delete the
+   * entire attribute if it exists, and it is ignored if the attribute
+   * does not exist.
+   */
+  public static final ModificationType REPLACE = register(2, "replace");
+
+  /**
+   * Increment all existing values of the attribute by the amount
+   * specified in the modification value.
+   */
+  public static final ModificationType INCREMENT = register(3,
+      "increment");
+
+
+
+  /**
+   * Creates and registers a new modification change type with the
+   * application.
+   *
+   * @param intValue
+   *          The integer value of the modification change type as
+   *          defined in RFC 4511 section 4.6.
+   * @param name
+   *          The name of the modification change type.
+   * @return The new modification change type.
+   */
+  private static ModificationType register(int intValue, String name)
+  {
+    ModificationType t = new ModificationType(intValue, name);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  /**
+   * Returns the modification change type having the specified integer
+   * value as defined in RFC 4511 section 4.6.
+   *
+   * @param intValue
+   *          The integer value of the modification change type.
+   * @return The modification change type, or {@code null} if there was
+   *         no modification change type associated with {@code
+   *         intValue}.
+   */
+  public static ModificationType valueOf(int intValue)
+  {
+    if (intValue < 0 || intValue >= ELEMENTS.length)
+    {
+      return null;
+    }
+    return ELEMENTS[intValue];
+  }
+
+
+
+  /**
+   * Returns an unmodifiable list containing the set of available
+   * modification change types indexed on their integer value as defined
+   * in RFC 4511 section 4.6.
+   *
+   * @return An unmodifiable list containing the set of available
+   *         modification change types.
+   */
+  public static List<ModificationType> values()
+  {
+    return IMMUTABLE_ELEMENTS;
+  }
+
+
+
+  private final int intValue;
+
+  private final String name;
+
+
+
+  // Prevent direct instantiation.
+  private ModificationType(int intValue, String name)
+  {
+    this.intValue = intValue;
+    this.name = name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object obj)
+  {
+    if (this == obj)
+    {
+      return true;
+    }
+    else if (obj instanceof ModificationType)
+    {
+      return this.intValue == ((ModificationType) obj).intValue;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the integer value of this modification change type as
+   * defined in RFC 4511 section 4.6.
+   *
+   * @return The integer value of this modification change type.
+   */
+  public int intValue()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the string representation of this modification change type.
+   *
+   * @return The string representation of this modification change type.
+   */
+  public String toString()
+  {
+    return name;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/RDN.java b/sdk/src/org/opends/sdk/RDN.java
new file mode 100644
index 0000000..2831f9e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/RDN.java
@@ -0,0 +1,899 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.util.StaticUtils.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.schema.*;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * A relative distinguished name (RDN) as defined in RFC 4512 section
+ * 2.3 is the name of an entry relative to its immediate superior. An
+ * RDN is composed of an unordered set of one or more attribute value
+ * assertions (AVA) consisting of an attribute description with zero
+ * options and an attribute value. These AVAs are chosen to match
+ * attribute values (each a distinguished value) of the entry.
+ * <p>
+ * An entry's relative distinguished name must be unique among all
+ * immediate subordinates of the entry's immediate superior (i.e. all
+ * siblings).
+ * <p>
+ * The following are examples of string representations of RDNs:
+ *
+ * <pre>
+ * uid=12345
+ * ou=Engineering
+ * cn=Kurt Zeilenga+L=Redwood Shores
+ * </pre>
+ *
+ * The last is an example of a multi-valued RDN; that is, an RDN
+ * composed of multiple AVAs.
+ * <p>
+ * TODO: need more constructors.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4512#section-2.3">RFC
+ *      4512 - Lightweight Directory Access Protocol (LDAP): Directory
+ *      Information Models </a>
+ */
+public final class RDN implements Iterable<RDN.AVA>, Comparable<RDN>
+{
+  /**
+   * An attribute value assertion (AVA) as defined in RFC 4512 section
+   * 2.3 consists of an attribute description with zero options and an
+   * attribute value.
+   */
+  public static final class AVA implements Comparable<AVA>
+  {
+    private final AttributeType attributeType;
+
+    private final ByteString attributeValue;
+
+
+
+    /**
+     * Creates a new attribute value assertion (AVA) using the provided
+     * attribute type and value.
+     *
+     * @param attributeType
+     *          The attribute type.
+     * @param attributeValue
+     *          The attribute value.
+     * @throws NullPointerException
+     *           If {@code attributeType} or {@code attributeValue} was
+     *           {@code null}.
+     */
+    public AVA(AttributeType attributeType, ByteString attributeValue)
+        throws NullPointerException
+    {
+      Validator.ensureNotNull(attributeType, attributeValue);
+
+      this.attributeType = attributeType;
+      this.attributeValue = attributeValue;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(AVA ava)
+    {
+      int result = attributeType.compareTo(ava.attributeType);
+
+      if (result == 0)
+      {
+        final ByteString nv1 = getNormalizeValue();
+        final ByteString nv2 = ava.getNormalizeValue();
+        result = nv1.compareTo(nv2);
+      }
+
+      return result;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object obj)
+    {
+      if (this == obj)
+      {
+        return true;
+      }
+      else if (obj instanceof AVA)
+      {
+        return compareTo((AVA) obj) == 0;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+
+    /**
+     * Returns the attribute type associated with this AVA.
+     *
+     * @return The attribute type associated with this AVA.
+     */
+    public AttributeType getAttributeType()
+    {
+      return attributeType;
+    }
+
+
+
+    /**
+     * Returns the attribute value associated with this AVA.
+     *
+     * @return The attribute value associated with this AVA.
+     */
+    public ByteString getAttributeValue()
+    {
+      return attributeValue;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+      return attributeType.hashCode() * 31
+          + getNormalizeValue().hashCode();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+      final StringBuilder builder = new StringBuilder();
+      return toString(builder).toString();
+    }
+
+
+
+    private ByteString getNormalizeValue()
+    {
+      final MatchingRule matchingRule = attributeType
+          .getEqualityMatchingRule();
+      if (matchingRule != null)
+      {
+        try
+        {
+          return matchingRule.normalizeAttributeValue(attributeValue);
+        }
+        catch (final DecodeException de)
+        {
+          // Ignore - we'll drop back to the user provided value.
+        }
+      }
+      return attributeValue;
+    }
+
+
+
+    private StringBuilder toNormalizedString(StringBuilder builder)
+    {
+      return toString(builder, true);
+    }
+
+
+
+    private StringBuilder toString(StringBuilder builder)
+    {
+      return toString(builder, false);
+    }
+
+
+
+    private StringBuilder toString(StringBuilder builder,
+        boolean normalize)
+    {
+      final ByteString value = normalize ? getNormalizeValue()
+          : attributeValue;
+
+      if (!attributeType.getNames().iterator().hasNext())
+      {
+        builder.append(attributeType.getOID());
+        builder.append("=#");
+        StaticUtils.toHex(value, builder);
+      }
+      else
+      {
+        final String name = attributeType.getNameOrOID();
+        if (normalize)
+        {
+          // Normalizing.
+          StaticUtils.toLowerCase(name, builder);
+        }
+        else
+        {
+          builder.append(name);
+        }
+
+        builder.append("=");
+
+        final Syntax syntax = attributeType.getSyntax();
+        if (!syntax.isHumanReadable())
+        {
+          builder.append("#");
+          StaticUtils.toHex(value, builder);
+        }
+        else
+        {
+          final String str = value.toString();
+          char c;
+          for (int si = 0; si < str.length(); si++)
+          {
+            c = str.charAt(si);
+            if (c == ' ' || c == '#' || c == '"' || c == '+'
+                || c == ',' || c == ';' || c == '<' || c == '='
+                || c == '>' || c == '\\' || c == '\u0000')
+            {
+              builder.append('\\');
+            }
+            builder.append(c);
+          }
+        }
+      }
+      return builder;
+    }
+  }
+
+
+
+  private static final char[] SPECIAL_CHARS = new char[] { '\"', '+',
+      ',', ';', '<', '>', ' ', '#', '=', '\\' };
+
+  private static final char[] DELIMITER_CHARS = new char[] { '+', ',',
+      ';' };
+
+  private static final char[] DQUOTE_CHAR = new char[] { '\"' };
+
+  private static final Comparator<AVA> ATV_COMPARATOR = new Comparator<AVA>()
+  {
+    public int compare(AVA o1, AVA o2)
+    {
+      return o1.getAttributeType().compareTo(o2.getAttributeType());
+    }
+  };
+
+
+
+  /**
+   * Parses the provided LDAP string representation of an RDN using the
+   * default schema.
+   *
+   * @param rdn
+   *          The LDAP string representation of a RDN.
+   * @return The parsed RDN.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code rdn} is not a valid LDAP string representation
+   *           of a RDN.
+   * @throws NullPointerException
+   *           If {@code rdn} was {@code null}.
+   */
+  public static RDN valueOf(String rdn)
+      throws LocalizedIllegalArgumentException
+  {
+    return valueOf(rdn, Schema.getDefaultSchema());
+  }
+
+
+
+  /**
+   * Parses the provided LDAP string representation of a RDN using the
+   * provided schema.
+   *
+   * @param rdn
+   *          The LDAP string representation of a RDN.
+   * @param schema
+   *          The schema to use when parsing the RDN.
+   * @return The parsed RDN.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code rdn} is not a valid LDAP string representation
+   *           of a RDN.
+   * @throws NullPointerException
+   *           If {@code rdn} or {@code schema} was {@code null}.
+   */
+  public static RDN valueOf(String rdn, Schema schema)
+      throws LocalizedIllegalArgumentException
+  {
+    final SubstringReader reader = new SubstringReader(rdn);
+    try
+    {
+      return decode(rdn, reader, schema);
+    }
+    catch (final UnknownSchemaElementException e)
+    {
+      final Message message = ERR_RDN_TYPE_NOT_FOUND.get(rdn, e
+          .getMessageObject());
+      throw new LocalizedIllegalArgumentException(message);
+    }
+  }
+
+
+
+  private static AVA readAttributeTypeAndValue(SubstringReader reader,
+      Schema schema) throws LocalizedIllegalArgumentException,
+      UnknownSchemaElementException
+  {
+    // Skip over any spaces at the beginning.
+    reader.skipWhitespaces();
+
+    final AttributeType attribute = readDNAttributeName(reader, schema);
+
+    // Make sure that we're not at the end of the DN string because
+    // that would be invalid.
+    if (reader.remaining() == 0)
+    {
+      final Message message = ERR_ATTR_SYNTAX_DN_END_WITH_ATTR_NAME
+          .get(reader.getString(), attribute.getNameOrOID());
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // The next character must be an equal sign. If it is not, then
+    // that's an error.
+    char c;
+    if ((c = reader.read()) != '=')
+    {
+      final Message message = ERR_ATTR_SYNTAX_DN_NO_EQUAL.get(reader
+          .getString(), attribute.getNameOrOID(), c);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    // Skip over any spaces after the equal sign.
+    reader.skipWhitespaces();
+
+    // Parse the value for this RDN component.
+    final ByteString value = readDNAttributeValue(reader);
+
+    return new AVA(attribute, value);
+  }
+
+
+
+  private static AttributeType readDNAttributeName(
+      SubstringReader reader, Schema schema)
+      throws LocalizedIllegalArgumentException,
+      UnknownSchemaElementException
+  {
+    int length = 1;
+    reader.mark();
+
+    // The next character must be either numeric (for an OID) or
+    // alphabetic (for
+    // an attribute description).
+    char c = reader.read();
+    if (isDigit(c))
+    {
+      boolean lastWasPeriod = false;
+      do
+      {
+        if (c == '.')
+        {
+          if (lastWasPeriod)
+          {
+            final Message message = ERR_ATTR_SYNTAX_OID_CONSECUTIVE_PERIODS
+                .get(reader.getString(), reader.pos() - 1);
+            throw new LocalizedIllegalArgumentException(message);
+          }
+          else
+          {
+            lastWasPeriod = true;
+          }
+        }
+        else if (!isDigit(c))
+        {
+          // This must have been an illegal character.
+          final Message message = ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER
+              .get(reader.getString(), reader.pos() - 1);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+        else
+        {
+          lastWasPeriod = false;
+        }
+        length++;
+      } while ((c = reader.read()) != '=');
+    }
+    if (isAlpha(c))
+    {
+      // This must be an attribute description. In this case, we will
+      // only
+      // accept alphabetic characters, numeric digits, and the hyphen.
+      while ((c = reader.read()) != '=')
+      {
+        if (length == 0 && !isAlpha(c))
+        {
+          // This is an illegal character.
+          final Message message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR
+              .get(reader.getString(), c, reader.pos() - 1);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+
+        if (!isAlpha(c) && !isDigit(c) && c != '-')
+        {
+          // This is an illegal character.
+          final Message message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR
+              .get(reader.getString(), c, reader.pos() - 1);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+
+        length++;
+      }
+    }
+    else
+    {
+      final Message message = ERR_ATTR_SYNTAX_DN_ATTR_ILLEGAL_CHAR.get(
+          reader.getString(), c, reader.pos() - 1);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    reader.reset();
+
+    // Return the position of the first non-space character after the
+    // token.
+
+    return schema.getAttributeType(reader.read(length));
+  }
+
+
+
+  private static ByteString readDNAttributeValue(SubstringReader reader)
+      throws LocalizedIllegalArgumentException
+  {
+    // All leading spaces have already been stripped so we can start
+    // reading the value. However, it may be empty so check for that.
+    if (reader.remaining() == 0)
+    {
+      return ByteString.empty();
+    }
+
+    reader.mark();
+
+    // Look at the first character. If it is an octothorpe (#), then
+    // that means that the value should be a hex string.
+    char c = reader.read();
+    int length = 0;
+    if (c == '#')
+    {
+      // The first two characters must be hex characters.
+      reader.mark();
+      if (reader.remaining() < 2)
+      {
+        final Message message = ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT
+            .get(reader.getString());
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      for (int i = 0; i < 2; i++)
+      {
+        c = reader.read();
+        if (isHexDigit(c))
+        {
+          length++;
+        }
+        else
+        {
+          final Message message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
+              .get(reader.getString(), c);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      // The rest of the value must be a multiple of two hex
+      // characters. The end of the value may be designated by the
+      // end of the DN, a comma or semicolon, or a space.
+      while (reader.remaining() > 0)
+      {
+        c = reader.read();
+        if (isHexDigit(c))
+        {
+          length++;
+
+          if (reader.remaining() > 0)
+          {
+            c = reader.read();
+            if (isHexDigit(c))
+            {
+              length++;
+            }
+            else
+            {
+              final Message message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
+                  .get(reader.getString(), c);
+              throw new LocalizedIllegalArgumentException(message);
+            }
+          }
+          else
+          {
+            final Message message = ERR_ATTR_SYNTAX_DN_HEX_VALUE_TOO_SHORT
+                .get(reader.getString());
+            throw new LocalizedIllegalArgumentException(message);
+          }
+        }
+        else if ((c == ' ') || (c == ',') || (c == ';'))
+        {
+          // This denotes the end of the value.
+          break;
+        }
+        else
+        {
+          final Message message = ERR_ATTR_SYNTAX_DN_INVALID_HEX_DIGIT
+              .get(reader.getString(), c);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      // At this point, we should have a valid hex string. Convert it
+      // to a byte array and set that as the value of the provided
+      // octet string.
+      try
+      {
+        reader.reset();
+        return ByteString
+            .wrap(hexStringToByteArray(reader.read(length)));
+      }
+      catch (final Exception e)
+      {
+        final Message message = ERR_ATTR_SYNTAX_DN_ATTR_VALUE_DECODE_FAILURE
+            .get(reader.getString(), String.valueOf(e));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+    }
+
+    // If the first character is a quotation mark, then the value
+    // should continue until the corresponding closing quotation mark.
+    else if (c == '"')
+    {
+      try
+      {
+        return StaticUtils.evaluateEscapes(reader, DQUOTE_CHAR, false);
+      }
+      catch (final DecodeException e)
+      {
+        throw new LocalizedIllegalArgumentException(e
+            .getMessageObject());
+      }
+    }
+
+    // Otherwise, use general parsing to find the end of the value.
+    else
+    {
+      reader.reset();
+      ByteString bytes;
+      try
+      {
+        bytes = StaticUtils.evaluateEscapes(reader, SPECIAL_CHARS,
+            DELIMITER_CHARS, true);
+      }
+      catch (final DecodeException e)
+      {
+        throw new LocalizedIllegalArgumentException(e
+            .getMessageObject());
+      }
+      if (bytes.length() == 0)
+      {
+        // We don't allow an empty attribute value.
+        final Message message = ERR_ATTR_SYNTAX_DN_INVALID_REQUIRES_ESCAPE_CHAR
+            .get(reader.getString(), reader.pos());
+        throw new LocalizedIllegalArgumentException(message);
+      }
+      return bytes;
+    }
+  }
+
+
+
+  // FIXME: ensure that the decoded RDN does not contain multiple AVAs
+  // with the same type.
+  static RDN decode(String rdnString, SubstringReader reader,
+      Schema schema) throws LocalizedIllegalArgumentException,
+      UnknownSchemaElementException
+  {
+    final AVA firstAVA = readAttributeTypeAndValue(reader, schema);
+
+    // Skip over any spaces that might be after the attribute value.
+    reader.skipWhitespaces();
+
+    reader.mark();
+    if (reader.remaining() > 0 && reader.read() == '+')
+    {
+      final List<AVA> avas = new ArrayList<AVA>();
+      avas.add(firstAVA);
+
+      do
+      {
+        avas.add(readAttributeTypeAndValue(reader, schema));
+
+        // Skip over any spaces that might be after the attribute value.
+        reader.skipWhitespaces();
+
+        reader.mark();
+      } while (reader.read() == '+');
+
+      reader.reset();
+      return new RDN(avas.toArray(new AVA[avas.size()]), rdnString);
+    }
+    else
+    {
+      reader.reset();
+      return new RDN(new AVA[] { firstAVA }, rdnString);
+    }
+  }
+
+
+
+  // In original order.
+  private final AVA[] avas;
+
+  // We need to store the original string value if provided in order to
+  // preserve the original whitespace.
+  private String stringValue;
+
+
+
+  private RDN(AVA[] avas, String stringValue)
+  {
+    this.avas = avas;
+    this.stringValue = stringValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(RDN rdn)
+  {
+    final int sz1 = avas.length;
+    final int sz2 = rdn.avas.length;
+
+    if (sz1 != sz2)
+    {
+      return sz1 - sz2;
+    }
+
+    if (sz1 == 1)
+    {
+      return avas[0].compareTo(rdn.avas[0]);
+    }
+
+    // Need to sort the AVAs before comparing.
+    final AVA[] a1 = new AVA[sz1];
+    System.arraycopy(avas, 0, a1, 0, sz1);
+    Arrays.sort(a1, ATV_COMPARATOR);
+
+    final AVA[] a2 = new AVA[sz1];
+    System.arraycopy(rdn.avas, 0, a2, 0, sz1);
+    Arrays.sort(a2, ATV_COMPARATOR);
+
+    for (int i = 0; i < sz1; i++)
+    {
+      final int result = a1[i].compareTo(a2[i]);
+      if (result != 0)
+      {
+        return result;
+      }
+    }
+
+    return 0;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object obj)
+  {
+    if (this == obj)
+    {
+      return true;
+    }
+    else if (obj instanceof RDN)
+    {
+      return compareTo((RDN) obj) == 0;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Returns the attribute value contained in this RDN which is
+   * associated with the provided attribute type, or {@code null} if
+   * this RDN does not include such an attribute value.
+   *
+   * @param attributeType
+   *          The attribute type.
+   * @return The attribute value.
+   */
+  public ByteString getAttributeValue(AttributeType attributeType)
+  {
+    for (final AVA ava : avas)
+    {
+      if (ava.getAttributeType().equals(attributeType))
+      {
+        return ava.getAttributeValue();
+      }
+    }
+    return null;
+  }
+
+
+
+  /**
+   * Returns the first AVA contained in this RDN.
+   *
+   * @return The first AVA contained in this RDN.
+   */
+  public AVA getFirstAVA()
+  {
+    return avas[0];
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    // Avoid an algorithm that requires the AVAs to be sorted.
+    int hash = 0;
+    for (int i = 0; i < avas.length; i++)
+    {
+      hash += avas[i].hashCode();
+    }
+    return hash;
+  }
+
+
+
+  /**
+   * Returns {@code true} if this RDN contains more than one AVA.
+   *
+   * @return {@code true} if this RDN contains more than one AVA,
+   *         otherwise {@code false}.
+   */
+  public boolean isMultiValued()
+  {
+    return avas.length > 1;
+  }
+
+
+
+  /**
+   * Returns an iterator of the AVAs contained in this RDN. The AVAs
+   * will be returned in the user provided order.
+   * <p>
+   * Attempts to remove AVAs using an iterator's {@code remove()} method
+   * are not permitted and will result in an {@code
+   * UnsupportedOperationException} being thrown.
+   *
+   * @return An iterator of the AVAs contained in this RDN.
+   */
+  public Iterator<AVA> iterator()
+  {
+    return Iterators.arrayIterator(avas);
+  }
+
+
+
+  /**
+   * Returns the number of AVAs in this RDN.
+   *
+   * @return The number of AVAs in this RDN.
+   */
+  public int size()
+  {
+    return avas.length;
+  }
+
+
+
+  /**
+   * Returns the RFC 4514 string representation of this RDN.
+   *
+   * @return The RFC 4514 string representation of this RDN.
+   * @see <a href="http://tools.ietf.org/html/rfc4514">RFC 4514 -
+   *      Lightweight Directory Access Protocol (LDAP): String
+   *      Representation of Distinguished Names </a>
+   */
+  public String toString()
+  {
+    // We don't care about potential race conditions here.
+    if (stringValue == null)
+    {
+      final StringBuilder builder = new StringBuilder();
+      avas[0].toString(builder);
+      for (int i = 1; i < avas.length; i++)
+      {
+        builder.append(',');
+        avas[i].toString(builder);
+      }
+      stringValue = builder.toString();
+    }
+    return stringValue;
+  }
+
+
+
+  StringBuilder toNormalizedString(StringBuilder builder)
+  {
+    final int sz = avas.length;
+    if (sz == 1)
+    {
+      return avas[0].toNormalizedString(builder);
+    }
+    else
+    {
+      // Need to sort the AVAs before comparing.
+      final AVA[] a = new AVA[sz];
+      System.arraycopy(avas, 0, a, 0, sz);
+      Arrays.sort(a, ATV_COMPARATOR);
+
+      a[0].toString(builder);
+      for (int i = 1; i < sz; i++)
+      {
+        builder.append(',');
+        a[i].toNormalizedString(builder);
+      }
+
+      return builder;
+    }
+  }
+
+
+
+  StringBuilder toString(StringBuilder builder)
+  {
+    return builder.append(toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ResultCode.java b/sdk/src/org/opends/sdk/ResultCode.java
new file mode 100644
index 0000000..c8d7cb0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ResultCode.java
@@ -0,0 +1,764 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import static org.opends.messages.CoreMessages.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * An operation result code as defined in RFC 4511 section 4.1.9 is used
+ * to indicate the final status of an operation. If a server detects
+ * multiple errors for an operation, only one result code is returned.
+ * The server should return the result code that best indicates the
+ * nature of the error encountered. Servers may return substituted
+ * result codes to prevent unauthorized disclosures.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.1.9">RFC
+ *      4511 - Lightweight Directory Access Protocol (LDAP): The
+ *      Protocol </a>
+ */
+public final class ResultCode
+{
+  private static final ResultCode[] ELEMENTS = new ResultCode[16655];
+
+  private static final List<ResultCode> IMMUTABLE_ELEMENTS = Collections
+      .unmodifiableList(Arrays.asList(ELEMENTS));
+
+  /**
+   * The result code that indicates that the operation completed
+   * successfully.
+   */
+  public static final ResultCode SUCCESS = registerSuccessResultCode(0,
+      INFO_RESULT_SUCCESS.get());
+
+  /**
+   * The result code that indicates that an internal error prevented the
+   * operation from being processed properly.
+   */
+  public static final ResultCode OPERATIONS_ERROR = registerErrorResultCode(
+      1, INFO_RESULT_OPERATIONS_ERROR.get());
+
+  /**
+   * The result code that indicates that the client sent a malformed or
+   * illegal request to the server.
+   */
+  public static final ResultCode PROTOCOL_ERROR = registerErrorResultCode(
+      2, INFO_RESULT_PROTOCOL_ERROR.get());
+
+  /**
+   * The result code that indicates that a time limit was exceeded while
+   * attempting to process the request.
+   */
+  public static final ResultCode TIME_LIMIT_EXCEEDED = registerErrorResultCode(
+      3, INFO_RESULT_TIME_LIMIT_EXCEEDED.get());
+
+  /**
+   * The result code that indicates that a size limit was exceeded while
+   * attempting to process the request.
+   */
+  public static final ResultCode SIZE_LIMIT_EXCEEDED = registerErrorResultCode(
+      4, INFO_RESULT_SIZE_LIMIT_EXCEEDED.get());
+
+  /**
+   * The result code that indicates that the attribute value assertion
+   * included in a compare request did not match the targeted entry.
+   */
+  public static final ResultCode COMPARE_FALSE = registerSuccessResultCode(
+      5, INFO_RESULT_COMPARE_FALSE.get());
+
+  /**
+   * The result code that indicates that the attribute value assertion
+   * included in a compare request did match the targeted entry.
+   */
+  public static final ResultCode COMPARE_TRUE = registerSuccessResultCode(
+      6, INFO_RESULT_COMPARE_TRUE.get());
+
+  /**
+   * The result code that indicates that the requested authentication
+   * attempt failed because it referenced an invalid SASL mechanism.
+   */
+  public static final ResultCode AUTH_METHOD_NOT_SUPPORTED = registerErrorResultCode(
+      7, INFO_RESULT_AUTH_METHOD_NOT_SUPPORTED.get());
+
+  /**
+   * The result code that indicates that the requested operation could
+   * not be processed because it requires that the client has completed
+   * a strong form of authentication.
+   */
+  public static final ResultCode STRONG_AUTH_REQUIRED = registerErrorResultCode(
+      8, INFO_RESULT_STRONG_AUTH_REQUIRED.get());
+
+  /**
+   * The result code that indicates that a referral was encountered.
+   * <p>
+   * Strictly speaking this result code should not be exceptional since
+   * it is considered as a "success" response. However, referrals should
+   * occur rarely in practice and, when they do occur, should not be
+   * ignored since the application may believe that a request has
+   * succeeded when, in fact, nothing was done.
+   */
+  public static final ResultCode REFERRAL = registerErrorResultCode(10,
+      INFO_RESULT_REFERRAL.get());
+
+  /**
+   * The result code that indicates that processing on the requested
+   * operation could not continue because an administrative limit was
+   * exceeded.
+   */
+  public static final ResultCode ADMIN_LIMIT_EXCEEDED = registerErrorResultCode(
+      11, INFO_RESULT_ADMIN_LIMIT_EXCEEDED.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it included a critical extension that is unsupported or
+   * inappropriate for that request.
+   */
+  public static final ResultCode UNAVAILABLE_CRITICAL_EXTENSION = registerErrorResultCode(
+      12, INFO_RESULT_UNAVAILABLE_CRITICAL_EXTENSION.get());
+
+  /**
+   * The result code that indicates that the requested operation could
+   * not be processed because it requires confidentiality for the
+   * communication between the client and the server.
+   */
+  public static final ResultCode CONFIDENTIALITY_REQUIRED = registerErrorResultCode(
+      13, INFO_RESULT_CONFIDENTIALITY_REQUIRED.get());
+
+  /**
+   * The result code that should be used for intermediate responses in
+   * multi-stage SASL bind operations.
+   */
+  public static final ResultCode SASL_BIND_IN_PROGRESS = registerSuccessResultCode(
+      14, INFO_RESULT_SASL_BIND_IN_PROGRESS.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it targeted an attribute or attribute value that did not
+   * exist in the specified entry.
+   */
+  public static final ResultCode NO_SUCH_ATTRIBUTE = registerErrorResultCode(
+      16, INFO_RESULT_NO_SUCH_ATTRIBUTE.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it referenced an attribute that is not defined in the
+   * server schema.
+   */
+  public static final ResultCode UNDEFINED_ATTRIBUTE_TYPE = registerErrorResultCode(
+      17, INFO_RESULT_UNDEFINED_ATTRIBUTE_TYPE.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it attempted to perform an inappropriate type of matching
+   * against an attribute.
+   */
+  public static final ResultCode INAPPROPRIATE_MATCHING = registerErrorResultCode(
+      18, INFO_RESULT_INAPPROPRIATE_MATCHING.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it would have violated some constraint defined in the
+   * server.
+   */
+  public static final ResultCode CONSTRAINT_VIOLATION = registerErrorResultCode(
+      19, INFO_RESULT_CONSTRAINT_VIOLATION.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it would have resulted in a conflict with an existing
+   * attribute or attribute value in the target entry.
+   */
+  public static final ResultCode ATTRIBUTE_OR_VALUE_EXISTS = registerErrorResultCode(
+      20, INFO_RESULT_ATTRIBUTE_OR_VALUE_EXISTS.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it violated the syntax for a specified attribute.
+   */
+  public static final ResultCode INVALID_ATTRIBUTE_SYNTAX = registerErrorResultCode(
+      21, INFO_RESULT_INVALID_ATTRIBUTE_SYNTAX.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it referenced an entry that does not exist.
+   */
+  public static final ResultCode NO_SUCH_OBJECT = registerErrorResultCode(
+      32, INFO_RESULT_NO_SUCH_OBJECT.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it attempted to perform an illegal operation on an alias.
+   */
+  public static final ResultCode ALIAS_PROBLEM = registerErrorResultCode(
+      33, INFO_RESULT_ALIAS_PROBLEM.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it would have resulted in an entry with an invalid or
+   * malformed DN.
+   */
+  public static final ResultCode INVALID_DN_SYNTAX = registerErrorResultCode(
+      34, INFO_RESULT_INVALID_DN_SYNTAX.get());
+
+  /**
+   * The result code that indicates that a problem was encountered while
+   * attempting to dereference an alias for a search operation.
+   */
+  public static final ResultCode ALIAS_DEREFERENCING_PROBLEM = registerErrorResultCode(
+      36, INFO_RESULT_ALIAS_DEREFERENCING_PROBLEM.get());
+
+  /**
+   * The result code that indicates that an authentication attempt
+   * failed because the requested type of authentication was not
+   * appropriate for the targeted entry.
+   */
+  public static final ResultCode INAPPROPRIATE_AUTHENTICATION = registerErrorResultCode(
+      48, INFO_RESULT_INAPPROPRIATE_AUTHENTICATION.get());
+
+  /**
+   * The result code that indicates that an authentication attempt
+   * failed because the user did not provide a valid set of credentials.
+   */
+  public static final ResultCode INVALID_CREDENTIALS = registerErrorResultCode(
+      49, INFO_RESULT_INVALID_CREDENTIALS.get());
+
+  /**
+   * The result code that indicates that the client does not have
+   * sufficient permission to perform the requested operation.
+   */
+  public static final ResultCode INSUFFICIENT_ACCESS_RIGHTS = registerErrorResultCode(
+      50, INFO_RESULT_INSUFFICIENT_ACCESS_RIGHTS.get());
+
+  /**
+   * The result code that indicates that the server is too busy to
+   * process the requested operation.
+   */
+  public static final ResultCode BUSY = registerErrorResultCode(51,
+      INFO_RESULT_BUSY.get());
+
+  /**
+   * The result code that indicates that either the entire server or one
+   * or more required resources were not available for use in processing
+   * the request.
+   */
+  public static final ResultCode UNAVAILABLE = registerErrorResultCode(
+      52, INFO_RESULT_UNAVAILABLE.get());
+
+  /**
+   * The result code that indicates that the server is unwilling to
+   * perform the requested operation.
+   */
+  public static final ResultCode UNWILLING_TO_PERFORM = registerErrorResultCode(
+      53, INFO_RESULT_UNWILLING_TO_PERFORM.get());
+
+  /**
+   * The result code that indicates that a referral or chaining loop was
+   * detected while processing the request.
+   */
+  public static final ResultCode LOOP_DETECT = registerErrorResultCode(
+      54, INFO_RESULT_LOOP_DETECT.get());
+
+  /**
+   * The result code that indicates that a search request included a VLV
+   * request control without a server-side sort control.
+   */
+  public static final ResultCode SORT_CONTROL_MISSING = registerErrorResultCode(
+      60, INFO_RESULT_SORT_CONTROL_MISSING.get());
+
+  /**
+   * The result code that indicates that a search request included a VLV
+   * request control with an invalid offset.
+   */
+  public static final ResultCode OFFSET_RANGE_ERROR = registerErrorResultCode(
+      61, INFO_RESULT_OFFSET_RANGE_ERROR.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it would have violated the server's naming configuration.
+   */
+  public static final ResultCode NAMING_VIOLATION = registerErrorResultCode(
+      64, INFO_RESULT_NAMING_VIOLATION.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it would have resulted in an entry that violated the server
+   * schema.
+   */
+  public static final ResultCode OBJECTCLASS_VIOLATION = registerErrorResultCode(
+      65, INFO_RESULT_OBJECTCLASS_VIOLATION.get());
+
+  /**
+   * The result code that indicates that the requested operation is not
+   * allowed for non-leaf entries.
+   */
+  public static final ResultCode NOT_ALLOWED_ON_NONLEAF = registerErrorResultCode(
+      66, INFO_RESULT_NOT_ALLOWED_ON_NONLEAF.get());
+
+  /**
+   * The result code that indicates that the requested operation is not
+   * allowed on an RDN attribute.
+   */
+  public static final ResultCode NOT_ALLOWED_ON_RDN = registerErrorResultCode(
+      67, INFO_RESULT_NOT_ALLOWED_ON_RDN.get());
+
+  /**
+   * The result code that indicates that the requested operation failed
+   * because it would have resulted in an entry that conflicts with an
+   * entry that already exists.
+   */
+  public static final ResultCode ENTRY_ALREADY_EXISTS = registerErrorResultCode(
+      68, INFO_RESULT_ENTRY_ALREADY_EXISTS.get());
+
+  /**
+   * The result code that indicates that the operation could not be
+   * processed because it would have modified the objectclasses
+   * associated with an entry in an illegal manner.
+   */
+  public static final ResultCode OBJECTCLASS_MODS_PROHIBITED = registerErrorResultCode(
+      69, INFO_RESULT_OBJECTCLASS_MODS_PROHIBITED.get());
+
+  /**
+   * The result code that indicates that the operation could not be
+   * processed because it would impact multiple DSAs or other
+   * repositories.
+   */
+  public static final ResultCode AFFECTS_MULTIPLE_DSAS = registerErrorResultCode(
+      71, INFO_RESULT_AFFECTS_MULTIPLE_DSAS.get());
+
+  /**
+   * The result code that indicates that the operation could not be
+   * processed because there was an error while processing the virtual
+   * list view control.
+   */
+  public static final ResultCode VIRTUAL_LIST_VIEW_ERROR = registerErrorResultCode(
+      76, INFO_RESULT_VIRTUAL_LIST_VIEW_ERROR.get());
+
+  /**
+   * The result code that should be used if no other result code is
+   * appropriate.
+   */
+  public static final ResultCode OTHER = registerErrorResultCode(80,
+      INFO_RESULT_OTHER.get());
+
+  /**
+   * The client-side result code that indicates that a
+   * previously-established connection to the server was lost. This is
+   * for client-side use only and should never be transferred over
+   * protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_SERVER_DOWN = registerErrorResultCode(
+      81, INFO_RESULT_CLIENT_SIDE_SERVER_DOWN.get());
+
+  /**
+   * The client-side result code that indicates that a local error
+   * occurred that had nothing to do with interaction with the server.
+   * This is for client-side use only and should never be transferred
+   * over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_LOCAL_ERROR = registerErrorResultCode(
+      82, INFO_RESULT_CLIENT_SIDE_LOCAL_ERROR.get());
+
+  /**
+   * The client-side result code that indicates that an error occurred
+   * while encoding a request to send to the server. This is for
+   * client-side use only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_ENCODING_ERROR = registerErrorResultCode(
+      83, INFO_RESULT_CLIENT_SIDE_ENCODING_ERROR.get());
+
+  /**
+   * The client-side result code that indicates that an error occurred
+   * while decoding a response from the server. This is for client-side
+   * use only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_DECODING_ERROR = registerErrorResultCode(
+      84, INFO_RESULT_CLIENT_SIDE_DECODING_ERROR.get());
+
+  /**
+   * The client-side result code that indicates that the client did not
+   * receive an expected response in a timely manner. This is for
+   * client-side use only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_TIMEOUT = registerErrorResultCode(
+      85, INFO_RESULT_CLIENT_SIDE_TIMEOUT.get());
+
+  /**
+   * The client-side result code that indicates that the user requested
+   * an unknown or unsupported authentication mechanism. This is for
+   * client-side use only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_AUTH_UNKNOWN = registerErrorResultCode(
+      86, INFO_RESULT_CLIENT_SIDE_AUTH_UNKNOWN.get());
+
+  /**
+   * The client-side result code that indicates that the filter provided
+   * by the user was malformed and could not be parsed. This is for
+   * client-side use only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_FILTER_ERROR = registerErrorResultCode(
+      87, INFO_RESULT_CLIENT_SIDE_FILTER_ERROR.get());
+
+  /**
+   * The client-side result code that indicates that the user cancelled
+   * an operation. This is for client-side use only and should never be
+   * transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_USER_CANCELLED = registerErrorResultCode(
+      88, INFO_RESULT_CLIENT_SIDE_USER_CANCELLED.get());
+
+  /**
+   * The client-side result code that indicates that there was a problem
+   * with one or more of the parameters provided by the user. This is
+   * for client-side use only and should never be transferred over
+   * protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_PARAM_ERROR = registerErrorResultCode(
+      89, INFO_RESULT_CLIENT_SIDE_PARAM_ERROR.get());
+
+  /**
+   * The client-side result code that indicates that the client
+   * application was not able to allocate enough memory for the
+   * requested operation. This is for client-side use only and should
+   * never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_NO_MEMORY = registerErrorResultCode(
+      90, INFO_RESULT_CLIENT_SIDE_NO_MEMORY.get());
+
+  /**
+   * The client-side result code that indicates that the client was not
+   * able to establish a connection to the server. This is for
+   * client-side use only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_CONNECT_ERROR = registerErrorResultCode(
+      91, INFO_RESULT_CLIENT_SIDE_CONNECT_ERROR.get());
+
+  /**
+   * The client-side result code that indicates that the user requested
+   * an operation that is not supported. This is for client-side use
+   * only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_NOT_SUPPORTED = registerErrorResultCode(
+      92, INFO_RESULT_CLIENT_SIDE_NOT_SUPPORTED.get());
+
+  /**
+   * The client-side result code that indicates that the client expected
+   * a control to be present in the response from the server but it was
+   * not included. This is for client-side use only and should never be
+   * transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_CONTROL_NOT_FOUND = registerErrorResultCode(
+      93, INFO_RESULT_CLIENT_SIDE_CONTROL_NOT_FOUND.get());
+
+  /**
+   * The client-side result code that indicates that the server did not
+   * return any results for a search operation that was expected to
+   * match at least one entry. This is for client-side use only and
+   * should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_NO_RESULTS_RETURNED = registerErrorResultCode(
+      94, INFO_RESULT_CLIENT_SIDE_NO_RESULTS_RETURNED.get());
+
+  /**
+   * The client-side result code that indicates that the server has
+   * returned more matching entries for a search operation than have
+   * been processed so far. This is for client-side use only and should
+   * never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_MORE_RESULTS_TO_RETURN = registerErrorResultCode(
+      95, INFO_RESULT_CLIENT_SIDE_MORE_RESULTS_TO_RETURN.get());
+
+  /**
+   * The client-side result code that indicates that the client detected
+   * a referral loop caused by servers referencing each other in a
+   * circular manner. This is for client-side use only and should never
+   * be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_CLIENT_LOOP = registerErrorResultCode(
+      96, INFO_RESULT_CLIENT_SIDE_CLIENT_LOOP.get());
+
+  /**
+   * The client-side result code that indicates that the client reached
+   * the maximum number of hops allowed when attempting to follow a
+   * referral (i.e., following one referral resulted in another referral
+   * which resulted in another referral and so on). This is for
+   * client-side use only and should never be transferred over protocol.
+   */
+  public static final ResultCode CLIENT_SIDE_REFERRAL_LIMIT_EXCEEDED = registerErrorResultCode(
+      97, INFO_RESULT_CLIENT_SIDE_REFERRAL_LIMIT_EXCEEDED.get());
+
+  /**
+   * The result code that indicates that a cancel request was
+   * successful, or that the specified operation was canceled.
+   */
+  public static final ResultCode CANCELED = registerErrorResultCode(
+      118, INFO_RESULT_CANCELED.get());
+
+  /**
+   * The result code that indicates that a cancel request was
+   * unsuccessful because the targeted operation did not exist or had
+   * already completed.
+   */
+  public static final ResultCode NO_SUCH_OPERATION = registerErrorResultCode(
+      119, INFO_RESULT_NO_SUCH_OPERATION.get());
+
+  /**
+   * The result code that indicates that a cancel request was
+   * unsuccessful because processing on the targeted operation had
+   * already reached a point at which it could not be canceled.
+   */
+  public static final ResultCode TOO_LATE = registerErrorResultCode(
+      120, INFO_RESULT_TOO_LATE.get());
+
+  /**
+   * The result code that indicates that a cancel request was
+   * unsuccessful because the targeted operation was one that could not
+   * be canceled.
+   */
+  public static final ResultCode CANNOT_CANCEL = registerErrorResultCode(
+      121, INFO_RESULT_CANNOT_CANCEL.get());
+
+  /**
+   * The result code that indicates that the filter contained in an
+   * assertion control failed to match the target entry.
+   */
+  public static final ResultCode ASSERTION_FAILED = registerErrorResultCode(
+      122, INFO_RESULT_ASSERTION_FAILED.get());
+
+  /**
+   * The result code that should be used if the server will not allow
+   * the client to use the requested authorization.
+   */
+  public static final ResultCode AUTHORIZATION_DENIED = registerErrorResultCode(
+      123, INFO_RESULT_AUTHORIZATION_DENIED.get());
+
+  /**
+   * The result code that should be used if the server did not actually
+   * complete processing on the associated operation because the request
+   * included the LDAP No-Op control.
+   */
+  public static final ResultCode NO_OPERATION = registerErrorResultCode(
+      16654, INFO_RESULT_NO_OPERATION.get());
+
+
+
+  /**
+   * Creates and registers a new error result code with the application.
+   *
+   * @param intValue
+   *          The integer value of the error result code as defined in
+   *          RFC 4511 section 4.1.9.
+   * @param name
+   *          The name of the error result code.
+   * @return The new error result code.
+   */
+  private static ResultCode registerErrorResultCode(int intValue,
+      Message name)
+  {
+    ResultCode t = new ResultCode(intValue, name, true);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  /**
+   * Creates and registers a new success result code with the
+   * application.
+   *
+   * @param intValue
+   *          The integer value of the success result code as defined in
+   *          RFC 4511 section 4.1.9.
+   * @param name
+   *          The name of the success result code.
+   * @return The new success result code.
+   */
+  private static ResultCode registerSuccessResultCode(int intValue,
+      Message name)
+  {
+    ResultCode t = new ResultCode(intValue, name, false);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  /**
+   * Returns the result code having the specified integer value as
+   * defined in RFC 4511 section 4.1.9. If there is no result code
+   * associated with the specified integer value then a temporary result
+   * code is automatically created in order to handle cases where
+   * unexpected result codes are returned from the server.
+   *
+   * @param intValue
+   *          The integer value of the result code.
+   * @return The result code.
+   */
+  public static ResultCode valueOf(int intValue)
+  {
+    ResultCode resultCode = null;
+
+    if (intValue >= 0 || intValue < ELEMENTS.length)
+    {
+      resultCode = ELEMENTS[intValue];
+    }
+
+    if (resultCode == null)
+    {
+      resultCode = new ResultCode(intValue, Message.raw("undefined("
+          + intValue + ")"), true);
+    }
+
+    return resultCode;
+  }
+
+
+
+  /**
+   * Returns an unmodifiable list containing the set of available result
+   * codes indexed on their integer value as defined in RFC 4511 section
+   * 4.1.9.
+   *
+   * @return An unmodifiable list containing the set of available result
+   *         codes.
+   */
+  public static List<ResultCode> values()
+  {
+    return IMMUTABLE_ELEMENTS;
+  }
+
+
+
+  private final int intValue;
+
+  private final Message name;
+
+  private final boolean exceptional;
+
+
+
+  // Prevent direct instantiation.
+  private ResultCode(int intValue, Message name, boolean exceptional)
+  {
+    this.intValue = intValue;
+    this.name = name;
+    this.exceptional = exceptional;
+  }
+
+
+
+  /**
+   * Indicates whether or not this result code represents an error
+   * result. In order to make it easier for application to detect
+   * referrals, the {@code REFERRAL} result code is treated as an error
+   * result (the LDAP RFCs treat referrals as a success response).
+   *
+   * @return {@code true} if this result code represents an error
+   *         result, otherwise {@code false}.
+   */
+  public boolean isExceptional()
+  {
+    return exceptional;
+  }
+
+
+
+  /**
+   * Returns the short human-readable name of this result code.
+   *
+   * @return The short human-readable name of this result code.
+   */
+  public Message getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object obj)
+  {
+    if (this == obj)
+    {
+      return true;
+    }
+    else if (obj instanceof ResultCode)
+    {
+      return this.intValue == ((ResultCode) obj).intValue;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the integer value of this result code.
+   *
+   * @return The integer value of this result code.
+   */
+  public int intValue()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the string representation of this result code.
+   *
+   * @return The string representation of this result code.
+   */
+  public String toString()
+  {
+    return name.toString();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ResultFuture.java b/sdk/src/org/opends/sdk/ResultFuture.java
new file mode 100644
index 0000000..63a8a09
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ResultFuture.java
@@ -0,0 +1,153 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * A handle which can be used to retrieve the Result of an asynchronous
+ * Request.
+ * 
+ * @param <S>
+ *          The type of result returned by this future.
+ */
+public interface ResultFuture<S extends Result> extends Future<S>
+{
+  /**
+   * Attempts to cancel the request. This attempt will fail if the
+   * request has already completed or has already been cancelled. If
+   * successful, then cancellation results in an abandon or cancel
+   * request (if configured) being sent to the server.
+   * <p>
+   * After this method returns, subsequent calls to {@link #isDone} will
+   * always return {@code true}. Subsequent calls to
+   * {@link #isCancelled} will always return {@code true} if this method
+   * returned {@code true}.
+   * 
+   * @param mayInterruptIfRunning
+   *          {@code true} if the thread executing executing the
+   *          response handler should be interrupted; otherwise,
+   *          in-progress response handlers are allowed to complete.
+   * @return {@code false} if the request could not be cancelled,
+   *         typically because it has already completed normally;
+   *         {@code true} otherwise.
+   */
+  boolean cancel(boolean mayInterruptIfRunning);
+
+
+
+  /**
+   * Waits if necessary for the request to complete, and then returns
+   * the result if the request succeeded. If the request failed (i.e. a
+   * non-successful result code was obtained) then the result is thrown
+   * as an {@link ErrorResultException}.
+   * 
+   * @return The result, but only if the result code indicates that the
+   *         request succeeded.
+   * @throws CancellationException
+   *           If the request was cancelled using a call to
+   *           {@link #cancel}.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   */
+  S get() throws InterruptedException, ErrorResultException;
+
+
+
+  /**
+   * Waits if necessary for at most the given time for the request to
+   * complete, and then returns the result if the request succeeded. If
+   * the request failed (i.e. a non-successful result code was obtained)
+   * then the result is thrown as an {@link ErrorResultException}.
+   * 
+   * @param timeout
+   *          The maximum time to wait.
+   * @param unit
+   *          The time unit of the timeout argument.
+   * @return The result, but only if the result code indicates that the
+   *         request succeeded.
+   * @throws CancellationException
+   *           If the request was cancelled using a call to
+   *           {@link #cancel}.
+   * @throws ErrorResultException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws TimeoutException
+   *           If the wait timed out.
+   */
+  S get(long timeout, TimeUnit unit) throws InterruptedException,
+      TimeoutException, ErrorResultException;
+
+
+
+  /**
+   * Returns the message ID of the request.
+   * 
+   * @return The message ID.
+   */
+  int getMessageID();
+
+
+
+  /**
+   * Returns {@code true} if the request was cancelled before it
+   * completed normally.
+   * 
+   * @return {@code true} if the request was cancelled before it
+   *         completed normally, otherwise {@code false}.
+   */
+  boolean isCancelled();
+
+
+
+  /**
+   * Returns {@code true} if the request has completed.
+   * <p>
+   * Completion may be due to normal termination, an exception, or
+   * cancellation. In all of these cases, this method will return
+   * {@code true}.
+   * 
+   * @return {@code true} if the request has completed, otherwise
+   *         {@code false}.
+   */
+  boolean isDone();
+}
diff --git a/sdk/src/org/opends/sdk/ResultHandler.java b/sdk/src/org/opends/sdk/ResultHandler.java
new file mode 100644
index 0000000..8433352
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ResultHandler.java
@@ -0,0 +1,81 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * A completion handler for consuming the result of an asynchronous
+ * operation.
+ * <p>
+ * {@link Connection} objects allow a result completion handler to be
+ * specified when sending operation requests to a Directory Server. The
+ * {@link #handleResult} method is invoked when the operation completes
+ * successfully. The {@link #handleErrorResult} method is invoked if the
+ * operation fails.
+ * <p>
+ * Implementations of these methods should complete in a timely manner
+ * so as to avoid keeping the invoking thread from dispatching to other
+ * completion handlers.
+ * 
+ * @param <S>
+ *          The type of result handled by this result handler.
+ * @param <P>
+ *          The type of the additional parameter to this handler's
+ *          methods. Use {@link java.lang.Void} for visitors that do not
+ *          need an additional parameter.
+ */
+public interface ResultHandler<S extends Result, P>
+{
+  /**
+   * Invoked when the asynchronous operation has failed.
+   * 
+   * @param p
+   *          A handler specified parameter.
+   * @param error
+   *          The error result exception indicating why the asynchronous
+   *          operation has failed.
+   */
+  void handleErrorResult(P p, ErrorResultException error);
+
+
+
+  /**
+   * Invoked when the asynchronous operation has completed successfully.
+   * 
+   * @param p
+   *          A handler specified parameter.
+   * @param result
+   *          The result of the asynchronous operation.
+   */
+  void handleResult(P p, S result);
+}
diff --git a/sdk/src/org/opends/sdk/RootDSE.java b/sdk/src/org/opends/sdk/RootDSE.java
new file mode 100644
index 0000000..697d3d6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/RootDSE.java
@@ -0,0 +1,463 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.SchemaNotFoundException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Functions;
+import org.opends.sdk.util.Iterables;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Root DSE Entry.
+ */
+public class RootDSE extends AbstractEntry
+{
+  private static final AttributeDescription ATTR_ALT_SERVER = AttributeDescription
+      .valueOf("altServer");
+
+  private static final AttributeDescription ATTR_NAMING_CONTEXTS = AttributeDescription
+      .valueOf("namingContexts");
+
+  private static final AttributeDescription ATTR_SUPPORTED_CONTROL = AttributeDescription
+      .valueOf("supportedControl");
+
+  private static final AttributeDescription ATTR_SUPPORTED_EXTENSION = AttributeDescription
+      .valueOf("supportedExtension");
+
+  private static final AttributeDescription ATTR_SUPPORTED_FEATURE = AttributeDescription
+      .valueOf("supportedFeatures");
+
+  private static final AttributeDescription ATTR_SUPPORTED_LDAP_VERSION = AttributeDescription
+      .valueOf("supportedLDAPVersion");
+
+  private static final AttributeDescription ATTR_SUPPORTED_SASL_MECHANISMS = AttributeDescription
+      .valueOf("supportedSASLMechanisms");
+
+  private static final AttributeDescription ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES = AttributeDescription
+      .valueOf("supportedAuthPasswordSchemes");
+
+  private static final AttributeDescription ATTR_VENDOR_NAME = AttributeDescription
+      .valueOf("vendorName");
+
+  private static final AttributeDescription ATTR_VENDOR_VERSION = AttributeDescription
+      .valueOf("vendorVersion");
+
+  private static String[] ROOTDSE_ATTRS = new String[] {
+      ATTR_ALT_SERVER.toString(), ATTR_NAMING_CONTEXTS.toString(),
+      ATTR_SUPPORTED_CONTROL.toString(),
+      ATTR_SUPPORTED_EXTENSION.toString(),
+      ATTR_SUPPORTED_FEATURE.toString(),
+      ATTR_SUPPORTED_LDAP_VERSION.toString(),
+      ATTR_SUPPORTED_SASL_MECHANISMS.toString(),
+      ATTR_VENDOR_NAME.toString(), ATTR_VENDOR_VERSION.toString(),
+      ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES.toString(), "*" };
+
+  private final Entry entry;
+
+  private final Iterable<String> altServers;
+
+  private final Iterable<DN> namingContexts;
+
+  private final Iterable<String> supportedControls;
+
+  private final Iterable<String> supportedExtensions;
+
+  private final Iterable<String> supportedFeatures;
+
+  private final Iterable<Integer> supportedLDAPVerions;
+
+  private final Iterable<String> supportedSASLMechanisms;
+
+  private final Iterable<String> supportedAuthPasswordSchemes;
+
+  private final String vendorName;
+
+  private final String vendorVersion;
+
+
+
+  private RootDSE(Entry entry) throws IllegalArgumentException
+  {
+    this.entry = Types.unmodifiableEntry(entry);
+
+    Attribute attr = getAttribute(ATTR_ALT_SERVER);
+    if (attr == null)
+    {
+      altServers = Collections.emptyList();
+    }
+    else
+    {
+      altServers = Iterables.unmodifiable(Iterables.transform(attr,
+          Functions.valueToString()));
+    }
+
+    attr = getAttribute(ATTR_NAMING_CONTEXTS);
+    if (attr == null)
+    {
+      namingContexts = Collections.emptyList();
+    }
+    else
+    {
+      namingContexts = Iterables.unmodifiable(Iterables.transform(attr,
+          Functions.valueToDN()));
+    }
+
+    attr = getAttribute(ATTR_SUPPORTED_CONTROL);
+    if (attr == null)
+    {
+      supportedControls = Collections.emptyList();
+    }
+    else
+    {
+      supportedControls = Iterables.unmodifiable(Iterables.transform(
+          attr, Functions.valueToString()));
+    }
+
+    attr = getAttribute(ATTR_SUPPORTED_EXTENSION);
+    if (attr == null)
+    {
+      supportedExtensions = Collections.emptyList();
+    }
+    else
+    {
+      supportedExtensions = Iterables.unmodifiable(Iterables.transform(
+          attr, Functions.valueToString()));
+    }
+
+    attr = getAttribute(ATTR_SUPPORTED_FEATURE);
+    if (attr == null)
+    {
+      supportedFeatures = Collections.emptyList();
+    }
+    else
+    {
+      supportedFeatures = Iterables.unmodifiable(Iterables.transform(
+          attr, Functions.valueToString()));
+    }
+
+    attr = getAttribute(ATTR_SUPPORTED_LDAP_VERSION);
+    if (attr == null)
+    {
+      supportedLDAPVerions = Collections.emptyList();
+    }
+    else
+    {
+      supportedLDAPVerions = Iterables.unmodifiable(Iterables
+          .transform(attr, Functions.valueToInteger()));
+    }
+
+    attr = getAttribute(ATTR_SUPPORTED_SASL_MECHANISMS);
+    if (attr == null)
+    {
+      supportedSASLMechanisms = Collections.emptyList();
+    }
+    else
+    {
+      supportedSASLMechanisms = Iterables.unmodifiable(Iterables
+          .transform(attr, Functions.valueToString()));
+    }
+
+    attr = getAttribute(ATTR_SUPPORTED_AUTH_PASSWORD_SCHEMES);
+    if (attr == null)
+    {
+      supportedAuthPasswordSchemes = Collections.emptyList();
+    }
+    else
+    {
+      supportedAuthPasswordSchemes = Iterables.unmodifiable(Iterables
+          .transform(attr, Functions.valueToString()));
+    }
+
+    attr = getAttribute(ATTR_VENDOR_NAME);
+    vendorName = attr == null ? "" : attr.firstValueAsString();
+
+    attr = getAttribute(ATTR_VENDOR_VERSION);
+    vendorVersion = attr == null ? "" : attr.firstValueAsString();
+  }
+
+
+
+  public static RootDSE getRootDSE(Connection connection)
+      throws ErrorResultException, InterruptedException,
+      DecodeException, SchemaNotFoundException
+  {
+    SearchResultEntry result = connection.readEntry(DN.rootDN(),
+        ROOTDSE_ATTRS);
+    return new RootDSE(result);
+  }
+
+
+
+  public Iterable<String> getAltServers()
+  {
+    return altServers;
+  }
+
+
+
+  public Iterable<DN> getNamingContexts()
+  {
+    return namingContexts;
+  }
+
+
+
+  public Iterable<String> getSupportedControls()
+  {
+    return supportedControls;
+  }
+
+
+
+  public boolean supportsControl(String oid)
+  {
+    Validator.ensureNotNull(oid);
+    for (String supported : supportedControls)
+    {
+      if (supported.equals(oid))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  public Iterable<String> getSupportedExtendedOperations()
+  {
+    return supportedExtensions;
+  }
+
+
+
+  public boolean supportsExtendedOperation(String oid)
+  {
+    Validator.ensureNotNull(oid);
+    for (String supported : supportedExtensions)
+    {
+      if (supported.equals(oid))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  public Iterable<String> getSupportedFeatures()
+  {
+    return supportedFeatures;
+  }
+
+
+
+  public boolean supportsFeature(String oid)
+  {
+    Validator.ensureNotNull(oid);
+    for (String supported : supportedFeatures)
+    {
+      if (supported.equals(oid))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  public Iterable<Integer> getSupportedLDAPVersions()
+  {
+    return supportedLDAPVerions;
+  }
+
+
+
+  public boolean supportsLDAPVersion(int version)
+  {
+    for (int supported : supportedLDAPVerions)
+    {
+      if (supported == version)
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  public Iterable<String> getSupportedSASLMechanismNames()
+  {
+    return supportedSASLMechanisms;
+  }
+
+
+
+  public boolean supportsSASLMechanism(String name)
+  {
+    Validator.ensureNotNull(name);
+    for (String supported : supportedSASLMechanisms)
+    {
+      if (supported.equals(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  public Iterable<String> getSupportedAuthPasswordSchemes()
+  {
+    return supportedSASLMechanisms;
+  }
+
+
+
+  public boolean supportsAuthPasswordScheme(String name)
+  {
+    Validator.ensureNotNull(name);
+    for (String supported : supportedAuthPasswordSchemes)
+    {
+      if (supported.equals(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  public String getVendorName()
+  {
+    return vendorName;
+  }
+
+
+
+  public String getVendorVersion()
+  {
+    return vendorVersion;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAttribute(Attribute attribute,
+      Collection<ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public Entry clearAttributes() throws UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public boolean containsAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+
+    return entry.containsAttribute(attributeDescription);
+  }
+
+
+
+  public Attribute getAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+
+    return entry.getAttribute(attributeDescription);
+  }
+
+
+
+  public int getAttributeCount()
+  {
+    return entry.getAttributeCount();
+  }
+
+
+
+  public Iterable<Attribute> getAttributes()
+  {
+    return entry.getAttributes();
+  }
+
+
+
+  public DN getName()
+  {
+    return DN.rootDN();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(Attribute attribute,
+      Collection<ByteString> missingValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public Entry setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/SearchResultHandler.java b/sdk/src/org/opends/sdk/SearchResultHandler.java
new file mode 100644
index 0000000..653e8c6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SearchResultHandler.java
@@ -0,0 +1,82 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.responses.SearchResultReference;
+
+
+
+/**
+ * A completion handler for consuming the results of an asynchronous
+ * Search operation.
+ * <p>
+ * {@link Connection} objects allow a search result completion handler
+ * to be specified when sending Search operation requests to a Directory
+ * Server. The {@link #handleEntry} method is invoked each time a Search
+ * Result Entry is returned from the Directory Server. The
+ * {@link #handleReference} method is invoked for each Search Result
+ * Reference returned from the Directory Server.
+ * <p>
+ * Implementations of these methods should complete in a timely manner
+ * so as to avoid keeping the invoking thread from dispatching to other
+ * completion handlers.
+ * 
+ * @param <P>
+ *          The type of the additional parameter to this handler's
+ *          methods. Use {@link java.lang.Void} for visitors that do not
+ *          need an additional parameter.
+ */
+public interface SearchResultHandler<P>
+{
+  /**
+   * Invoked each time a search result entry is returned from an
+   * asynchronous search operation.
+   * 
+   * @param p
+   *          A handler specified parameter.
+   * @param entry
+   *          The search result entry.
+   */
+  void handleEntry(P p, SearchResultEntry entry);
+
+
+
+  /**
+   * Invoked each time a search result reference is returned from an
+   * asynchronous search operation.
+   * 
+   * @param p
+   *          A handler specified parameter.
+   * @param reference
+   *          The search result reference.
+   */
+  void handleReference(P p, SearchResultReference reference);
+}
diff --git a/sdk/src/org/opends/sdk/SearchScope.java b/sdk/src/org/opends/sdk/SearchScope.java
new file mode 100644
index 0000000..7f02192
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SearchScope.java
@@ -0,0 +1,207 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * A Search operation search scope as defined in RFC 4511 section
+ * 4.5.1.2 is used to specify the scope of a Search operation.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc4511#section-4.5.1.2">RFC
+ *      4511 - Lightweight Directory Access Protocol (LDAP): The
+ *      Protocol </a>
+ * @see <a
+ *      href="http://tools.ietf.org/html/draft-sermersheim-ldap-subordinate-scope">
+ *      draft-sermersheim-ldap-subordinate-scope - Subordinate Subtree
+ *      Search Scope for LDAP </a>
+ */
+public final class SearchScope
+{
+  private static final SearchScope[] ELEMENTS = new SearchScope[4];
+
+  private static final List<SearchScope> IMMUTABLE_ELEMENTS = Collections
+      .unmodifiableList(Arrays.asList(ELEMENTS));
+
+  /**
+   * The scope is constrained to the search base entry.
+   */
+  public static final SearchScope BASE_OBJECT = register(0, "base");
+
+  /**
+   * The scope is constrained to the immediate subordinates of the
+   * search base entry.
+   */
+  public static final SearchScope SINGLE_LEVEL = register(1, "one");
+
+  /**
+   * The scope is constrained to the search base entry and to all its
+   * subordinates.
+   */
+  public static final SearchScope WHOLE_SUBTREE = register(2, "sub");
+
+  /**
+   * The scope is constrained to all the subordinates of the search base
+   * entry, but does not include the search base entry itself (as
+   * wholeSubtree does).
+   */
+  public static final SearchScope SUBORDINATES = register(3,
+      "subordinates");
+
+
+
+  /**
+   * Creates and registers a new search scope with the application.
+   *
+   * @param intValue
+   *          The integer value of the search scope as defined in RFC
+   *          4511 section 4.5.1.2.
+   * @param name
+   *          The name of the search scope as defined in RFC 4516.
+   * @return The new search scope.
+   */
+  private static SearchScope register(int intValue, String name)
+  {
+    SearchScope t = new SearchScope(intValue, name);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  /**
+   * Returns the search scope having the specified integer value as
+   * defined in RFC 4511 section 4.5.1.2.
+   *
+   * @param intValue
+   *          The integer value of the search scope.
+   * @return The search scope, or {@code null} if there was no search
+   *         scope associated with {@code intValue}.
+   */
+  public static SearchScope valueOf(int intValue)
+  {
+    if (intValue < 0 || intValue >= ELEMENTS.length)
+    {
+      return null;
+    }
+    return ELEMENTS[intValue];
+  }
+
+
+
+  /**
+   * Returns an unmodifiable list containing the set of available search
+   * scopes indexed on their integer value as defined in RFC 4511
+   * section 4.5.1.2.
+   *
+   * @return An unmodifiable list containing the set of available search
+   *         scopes.
+   */
+  public static List<SearchScope> values()
+  {
+    return IMMUTABLE_ELEMENTS;
+  }
+
+
+
+  private final int intValue;
+
+  private final String name;
+
+
+
+  // Prevent direct instantiation.
+  private SearchScope(int intValue, String name)
+  {
+    this.intValue = intValue;
+    this.name = name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(Object obj)
+  {
+    if (this == obj)
+    {
+      return true;
+    }
+    else if (obj instanceof SearchScope)
+    {
+      return this.intValue == ((SearchScope) obj).intValue;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int hashCode()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the integer value of this search scope as defined in RFC
+   * 4511 section 4.5.1.2.
+   *
+   * @return The integer value of this search scope.
+   */
+  public int intValue()
+  {
+    return intValue;
+  }
+
+
+
+  /**
+   * Returns the string representation of this search scope as defined
+   * in RFC 4516.
+   *
+   * @return The string representation of this search scope.
+   */
+  public String toString()
+  {
+    return name;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/SortedEntry.java b/sdk/src/org/opends/sdk/SortedEntry.java
new file mode 100644
index 0000000..5f48d0f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SortedEntry.java
@@ -0,0 +1,295 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An implementation of the {@code Entry} interface which uses a {@code
+ * SortedMap} for storing attributes. Attributes are returned in
+ * ascending order of attribute description, with {@code objectClass}
+ * first, then all user attributes, and finally any operational
+ * attributes. All operations are supported by this implementation.
+ */
+public final class SortedEntry extends AbstractEntry
+{
+  private final SortedMap<AttributeDescription, Attribute> attributes = new TreeMap<AttributeDescription, Attribute>();
+
+  private DN name;
+
+
+
+  /**
+   * Creates an empty sorted entry and an empty (root) distinguished
+   * name.
+   */
+  public SortedEntry()
+  {
+    this(DN.rootDN());
+  }
+
+
+
+  /**
+   * Creates an empty sorted entry using the provided distinguished
+   * name.
+   *
+   * @param name
+   *          The distinguished name of this entry.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public SortedEntry(DN name) throws NullPointerException
+  {
+    Validator.ensureNotNull(name);
+    this.name = name;
+  }
+
+
+
+  /**
+   * Creates an empty sorted entry using the provided distinguished name
+   * decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of this entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public SortedEntry(String name)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    this(DN.valueOf(name));
+  }
+
+
+
+  /**
+   * Creates a sorted entry having the same distinguished name,
+   * attributes, and object classes of the provided entry.
+   *
+   * @param entry
+   *          The entry to be copied.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null}.
+   */
+  public SortedEntry(Entry entry)
+  {
+    Validator.ensureNotNull(entry);
+
+    this.name = entry.getName();
+    for (Attribute attribute : entry.getAttributes())
+    {
+      addAttribute(attribute);
+    }
+  }
+
+
+
+  /**
+   * Creates a new sorted entry using the provided lines of LDIF decoded
+   * using the default schema.
+   *
+   * @param ldifLines
+   *          Lines of LDIF containing the an LDIF add change record or
+   *          an LDIF entry record.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} was empty, or contained invalid
+   *           LDIF, or could not be decoded using the default schema.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null} .
+   */
+  public SortedEntry(String... ldifLines)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    this(Requests.newAddRequest(ldifLines));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAttribute(Attribute attribute,
+      Collection<ByteString> duplicateValues)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attribute);
+
+    if (!attribute.isEmpty())
+    {
+      AttributeDescription attributeDescription = attribute
+          .getAttributeDescription();
+      Attribute oldAttribute = attributes.get(attributeDescription);
+      if (oldAttribute != null)
+      {
+        return oldAttribute.addAll(attribute, duplicateValues);
+      }
+      else
+      {
+        attributes.put(attributeDescription, attribute);
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry clearAttributes()
+  {
+    attributes.clear();
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+
+    return attributes.containsKey(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Attribute getAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+
+    return attributes.get(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getAttributeCount()
+  {
+    return attributes.size();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> getAttributes()
+  {
+    return attributes.values();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(Attribute attribute,
+      Collection<ByteString> missingValues) throws NullPointerException
+  {
+    Validator.ensureNotNull(attribute);
+
+    AttributeDescription attributeDescription = attribute
+        .getAttributeDescription();
+
+    if (attribute.isEmpty())
+    {
+      return attributes.remove(attributeDescription) != null;
+    }
+    else
+    {
+      Attribute oldAttribute = attributes.get(attributeDescription);
+      if (oldAttribute != null)
+      {
+        boolean modified = oldAttribute.removeAll(attribute,
+            missingValues);
+        if (oldAttribute.isEmpty())
+        {
+          attributes.remove(attributeDescription);
+          return true;
+        }
+        return modified;
+      }
+      else
+      {
+        return false;
+      }
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry setName(DN dn) throws NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = dn;
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/SynchronousConnection.java b/sdk/src/org/opends/sdk/SynchronousConnection.java
new file mode 100644
index 0000000..b648053
--- /dev/null
+++ b/sdk/src/org/opends/sdk/SynchronousConnection.java
@@ -0,0 +1,261 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.CompareResult;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A {@code SynchronousConnection} adapts an {@code
+ * AsynchronousConnection} into a synchronous {@code Connection}.
+ */
+public class SynchronousConnection extends AbstractConnection
+{
+  private final AsynchronousConnection connection;
+
+
+
+  /**
+   * Creates a new abstract connection which will route all synchronous
+   * requests to the provided asynchronous connection.
+   *
+   * @param connection
+   *          The asynchronous connection to be used.
+   * @throws NullPointerException
+   *           If {@code connection} was {@code null}.
+   */
+  public SynchronousConnection(AsynchronousConnection connection)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(connection);
+    this.connection = connection;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Result add(AddRequest request) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    ResultFuture<Result> future = connection.add(request, null, null);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+
+
+  public void addConnectionEventListener(
+      ConnectionEventListener listener) throws IllegalStateException,
+      NullPointerException
+  {
+    connection.addConnectionEventListener(listener);
+  }
+
+
+
+  public BindResult bind(BindRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    ResultFuture<BindResult> future = connection.bind(request, null,
+        null);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+
+
+  public void close()
+  {
+    connection.close();
+  }
+
+
+
+  public void close(UnbindRequest request) throws NullPointerException
+  {
+    connection.close(request);
+  }
+
+
+
+  public CompareResult compare(CompareRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    ResultFuture<CompareResult> future = connection.compare(request,
+        null, null);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+
+
+  public Result delete(DeleteRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    ResultFuture<Result> future = connection
+        .delete(request, null, null);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+
+
+  public <R extends Result> R extendedRequest(ExtendedRequest<R> request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    ResultFuture<R> future = connection.extendedRequest(request, null,
+        null);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+
+
+  public Result modify(ModifyRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    ResultFuture<Result> future = connection
+        .modify(request, null, null);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+
+
+  public Result modifyDN(ModifyDNRequest request)
+      throws ErrorResultException, InterruptedException,
+      UnsupportedOperationException, IllegalStateException,
+      NullPointerException
+  {
+    ResultFuture<Result> future = connection.modifyDN(request, null,
+        null);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+
+
+  public void removeConnectionEventListener(
+      ConnectionEventListener listener) throws NullPointerException
+  {
+    connection.removeConnectionEventListener(listener);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> Result search(SearchRequest request,
+      SearchResultHandler<P> handler, P p) throws ErrorResultException,
+      InterruptedException, UnsupportedOperationException,
+      IllegalStateException, NullPointerException
+  {
+    ResultFuture<Result> future = connection.search(request, null,
+        handler, p);
+    try
+    {
+      return future.get();
+    }
+    finally
+    {
+      // Cancel the request if it hasn't completed.
+      future.cancel(false);
+    }
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/Types.java b/sdk/src/org/opends/sdk/Types.java
new file mode 100644
index 0000000..b040dcf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/Types.java
@@ -0,0 +1,985 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk;
+
+
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * This class contains methods for creating and manipulating attributes,
+ * entries, and other types of object.
+ */
+public final class Types
+{
+
+  /**
+   * Empty attribute.
+   */
+  private static final class EmptyAttribute extends AbstractAttribute
+  {
+
+    private final AttributeDescription attributeDescription;
+
+
+
+    private EmptyAttribute(AttributeDescription attributeDescription)
+    {
+      this.attributeDescription = attributeDescription;
+    }
+
+
+
+    public boolean add(ByteString value)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public void clear() throws UnsupportedOperationException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public AttributeDescription getAttributeDescription()
+    {
+      return attributeDescription;
+    }
+
+
+
+    public boolean isEmpty()
+    {
+      return true;
+    }
+
+
+
+    public Iterator<ByteString> iterator()
+    {
+      return Iterators.empty();
+    }
+
+
+
+    public int size()
+    {
+      return 0;
+    }
+
+
+
+    public boolean contains(Object value) throws NullPointerException
+    {
+      return false;
+    }
+
+
+
+    public boolean remove(Object value)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+  }
+
+
+
+  /**
+   * Renamed attribute.
+   */
+  private static final class RenamedAttribute implements Attribute
+  {
+
+    private final Attribute attribute;
+
+    private final AttributeDescription attributeDescription;
+
+
+
+    private RenamedAttribute(Attribute attribute,
+        AttributeDescription attributeDescription)
+    {
+      this.attribute = attribute;
+      this.attributeDescription = attributeDescription;
+    }
+
+
+
+    public boolean add(ByteString value)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.add(value);
+    }
+
+
+
+    public boolean add(Object firstValue, Object... remainingValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.add(firstValue, remainingValues);
+    }
+
+
+
+    public boolean addAll(Collection<? extends ByteString> values)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.addAll(values);
+    }
+
+
+
+    public boolean addAll(Collection<? extends ByteString> values,
+        Collection<? super ByteString> duplicateValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.addAll(values, duplicateValues);
+    }
+
+
+
+    public void clear() throws UnsupportedOperationException
+    {
+      attribute.clear();
+    }
+
+
+
+    public boolean contains(Object value) throws NullPointerException
+    {
+      return attribute.contains(value);
+    }
+
+
+
+    public boolean containsAll(Collection<?> values)
+        throws NullPointerException
+    {
+      return attribute.containsAll(values);
+    }
+
+
+
+    public boolean equals(Object object)
+    {
+      return AbstractAttribute.equals(this, object);
+    }
+
+
+
+    public ByteString firstValue() throws NoSuchElementException
+    {
+      return attribute.firstValue();
+    }
+
+
+
+    public <T> T firstValueAsObject(
+        Function<? super ByteString, T, Void> type)
+        throws NoSuchElementException
+    {
+      return attribute.firstValueAsObject(type);
+    }
+
+
+
+    public <T, P> T firstValueAsObject(
+        Function<? super ByteString, T, P> type, P p)
+        throws NoSuchElementException
+    {
+      return attribute.firstValueAsObject(type, p);
+    }
+
+
+
+    public String firstValueAsString() throws NoSuchElementException
+    {
+      return attribute.firstValueAsString();
+    }
+
+
+
+    public AttributeDescription getAttributeDescription()
+    {
+      return attributeDescription;
+    }
+
+
+
+    public String getAttributeDescriptionAsString()
+    {
+      return attributeDescription.toString();
+    }
+
+
+
+    public int hashCode()
+    {
+      return AbstractAttribute.hashCode(this);
+    }
+
+
+
+    public boolean isEmpty()
+    {
+      return attribute.isEmpty();
+    }
+
+
+
+    public Iterator<ByteString> iterator()
+    {
+      return attribute.iterator();
+    }
+
+
+
+    public boolean remove(Object value)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.remove(value);
+    }
+
+
+
+    public boolean removeAll(Collection<?> values)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.removeAll(values);
+    }
+
+
+
+    public <T> boolean removeAll(Collection<T> values,
+        Collection<? super T> missingValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.removeAll(values, missingValues);
+    }
+
+
+
+    public boolean retainAll(Collection<?> values)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.retainAll(values);
+    }
+
+
+
+    public <T> boolean retainAll(Collection<T> values,
+        Collection<? super T> missingValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      return attribute.retainAll(values, missingValues);
+    }
+
+
+
+    public int size()
+    {
+      return attribute.size();
+    }
+
+
+
+    public ByteString[] toArray()
+    {
+      return attribute.toArray();
+    }
+
+
+
+    public <T> T[] toArray(T[] array) throws ArrayStoreException,
+        NullPointerException
+    {
+      return attribute.toArray(array);
+    }
+
+
+
+    public String toString()
+    {
+      return AbstractAttribute.toString(this);
+    }
+
+  }
+
+
+
+  /**
+   * Unmodifiable attribute.
+   */
+  private static final class UnmodifiableAttribute implements Attribute
+  {
+
+    private final Attribute attribute;
+
+
+
+    private UnmodifiableAttribute(Attribute attribute)
+    {
+      this.attribute = attribute;
+    }
+
+
+
+    public boolean add(ByteString value)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean add(Object firstValue, Object... remainingValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean addAll(Collection<? extends ByteString> values)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean addAll(Collection<? extends ByteString> values,
+        Collection<? super ByteString> duplicateValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public void clear() throws UnsupportedOperationException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean contains(Object value) throws NullPointerException
+    {
+      return attribute.contains(value);
+    }
+
+
+
+    public boolean containsAll(Collection<?> values)
+        throws NullPointerException
+    {
+      return attribute.containsAll(values);
+    }
+
+
+
+    public boolean equals(Object object)
+    {
+      return (object == this || attribute.equals(object));
+    }
+
+
+
+    public ByteString firstValue() throws NoSuchElementException
+    {
+      return attribute.firstValue();
+    }
+
+
+
+    public <T> T firstValueAsObject(
+        Function<? super ByteString, T, Void> type)
+        throws NoSuchElementException
+    {
+      return attribute.firstValueAsObject(type);
+    }
+
+
+
+    public <T, P> T firstValueAsObject(
+        Function<? super ByteString, T, P> type, P p)
+        throws NoSuchElementException
+    {
+      return attribute.firstValueAsObject(type, p);
+    }
+
+
+
+    public String firstValueAsString() throws NoSuchElementException
+    {
+      return attribute.firstValueAsString();
+    }
+
+
+
+    public AttributeDescription getAttributeDescription()
+    {
+      return attribute.getAttributeDescription();
+    }
+
+
+
+    public String getAttributeDescriptionAsString()
+    {
+      return attribute.getAttributeDescriptionAsString();
+    }
+
+
+
+    public int hashCode()
+    {
+      return attribute.hashCode();
+    }
+
+
+
+    public boolean isEmpty()
+    {
+      return attribute.isEmpty();
+    }
+
+
+
+    public Iterator<ByteString> iterator()
+    {
+      return Iterators.unmodifiable(attribute.iterator());
+    }
+
+
+
+    public boolean remove(Object value)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean removeAll(Collection<?> values)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public <T> boolean removeAll(Collection<T> values,
+        Collection<? super T> missingValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean retainAll(Collection<?> values)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public <T> boolean retainAll(Collection<T> values,
+        Collection<? super T> missingValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public int size()
+    {
+      return attribute.size();
+    }
+
+
+
+    public ByteString[] toArray()
+    {
+      return attribute.toArray();
+    }
+
+
+
+    public <T> T[] toArray(T[] array) throws ArrayStoreException,
+        NullPointerException
+    {
+      return attribute.toArray(array);
+    }
+
+
+
+    public String toString()
+    {
+      return attribute.toString();
+    }
+
+  }
+
+
+
+  private static final class UnmodifiableEntry implements Entry
+  {
+    private final Entry entry;
+
+
+
+    private UnmodifiableEntry(Entry entry)
+    {
+      this.entry = entry;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addAttribute(Attribute attribute,
+        Collection<ByteString> duplicateValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean addAttribute(Attribute attribute)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Entry addAttribute(String attributeDescription,
+        Object... values) throws LocalizedIllegalArgumentException,
+        UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public Entry clearAttributes() throws UnsupportedOperationException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean containsAttribute(
+        AttributeDescription attributeDescription)
+    {
+      return entry.containsAttribute(attributeDescription);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean containsAttribute(String attributeDescription)
+        throws LocalizedIllegalArgumentException, NullPointerException
+    {
+      return entry.containsAttribute(attributeDescription);
+    }
+
+
+
+    public boolean containsObjectClass(ObjectClass objectClass)
+    {
+      return entry.containsObjectClass(objectClass);
+    }
+
+
+
+    public boolean containsObjectClass(String objectClass)
+    {
+      return entry.containsObjectClass(objectClass);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(Object object)
+    {
+      return (object == this || entry.equals(object));
+    }
+
+
+
+    public Iterable<Attribute> findAttributes(
+        AttributeDescription attributeDescription)
+    {
+      return Iterables.unmodifiable(Iterables.transform(entry
+          .findAttributes(attributeDescription),
+          UNMODIFIABLE_ATTRIBUTE_FUNCTION));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterable<Attribute> findAttributes(
+        String attributeDescription)
+        throws LocalizedIllegalArgumentException, NullPointerException
+    {
+      return Iterables.unmodifiable(Iterables.transform(entry
+          .findAttributes(attributeDescription),
+          UNMODIFIABLE_ATTRIBUTE_FUNCTION));
+    }
+
+
+
+    public Attribute getAttribute(
+        AttributeDescription attributeDescription)
+    {
+      Attribute attribute = entry.getAttribute(attributeDescription);
+      if (attribute != null)
+      {
+        return unmodifiableAttribute(attribute);
+      }
+      else
+      {
+        return null;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Attribute getAttribute(String attributeDescription)
+        throws LocalizedIllegalArgumentException, NullPointerException
+    {
+      Attribute attribute = entry.getAttribute(attributeDescription);
+      if (attribute != null)
+      {
+        return unmodifiableAttribute(attribute);
+      }
+      else
+      {
+        return null;
+      }
+    }
+
+
+
+    public int getAttributeCount()
+    {
+      return entry.getAttributeCount();
+    }
+
+
+
+    public Iterable<Attribute> getAttributes()
+    {
+      return Iterables.unmodifiable(Iterables.transform(entry
+          .getAttributes(), UNMODIFIABLE_ATTRIBUTE_FUNCTION));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public DN getName()
+    {
+      return entry.getName();
+    }
+
+
+
+    public Iterable<String> getObjectClasses()
+    {
+      return Iterables.unmodifiable(entry.getObjectClasses());
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int hashCode()
+    {
+      return entry.hashCode();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean removeAttribute(Attribute attribute,
+        Collection<ByteString> missingValues)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public boolean removeAttribute(
+        AttributeDescription attributeDescription)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Entry removeAttribute(String attributeDescription)
+        throws LocalizedIllegalArgumentException,
+        UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Entry removeAttribute(String attributeDescription,
+        Object... values) throws LocalizedIllegalArgumentException,
+        UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean replaceAttribute(Attribute attribute)
+        throws UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Entry replaceAttribute(String attributeDescription,
+        Object... values) throws LocalizedIllegalArgumentException,
+        UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Entry setName(String dn)
+        throws LocalizedIllegalArgumentException,
+        UnsupportedOperationException, NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    public Entry setName(DN dn) throws UnsupportedOperationException,
+        NullPointerException
+    {
+      throw new UnsupportedOperationException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String toString()
+    {
+      return entry.toString();
+    }
+
+  }
+
+
+
+  private static final Function<Attribute, Attribute, Void> UNMODIFIABLE_ATTRIBUTE_FUNCTION = new Function<Attribute, Attribute, Void>()
+  {
+
+    public Attribute apply(Attribute value, Void p)
+    {
+      return unmodifiableAttribute(value);
+    }
+
+  };
+
+
+
+  /**
+   * Returns a read-only empty attribute having the specified attribute
+   * description.
+   *
+   * @param attributeDescription
+   *          The attribute description.
+   * @return The empty attribute.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  public static final Attribute emptyAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    return new EmptyAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * Returns a view of {@code attribute} having a different attribute
+   * description. All operations on the returned attribute
+   * "pass-through" to the underlying attribute.
+   *
+   * @param attribute
+   *          The attribute to be renamed.
+   * @param attributeDescription
+   *          The new attribute description for {@code attribute}, which
+   *          must be compatible with {@code attribute}'s attribute
+   *          description.
+   * @return A renamed view of {@code attribute}.
+   * @throws IllegalArgumentException
+   *           If {@code attributeDescription} does not have the same
+   *           attribute type as {@code attribute}'s attribute
+   *           description.
+   * @throws NullPointerException
+   *           If {@code attribute} or {@code attributeDescription} was
+   *           {@code null}.
+   */
+  public static final Attribute renameAttribute(Attribute attribute,
+      AttributeDescription attributeDescription)
+      throws IllegalArgumentException, NullPointerException
+  {
+    AttributeType oldType = attribute.getAttributeDescription()
+        .getAttributeType();
+    AttributeType newType = attributeDescription.getAttributeType();
+
+    // We could relax a bit by ensuring that they are both compatible
+    // (e.g. one sub-type of another, or same equality matching rule,
+    // etc).
+    Validator.ensureTrue(oldType.equals(newType),
+        "Old and new attribute type are not the same");
+
+    return new RenamedAttribute(attribute, attributeDescription);
+  }
+
+
+
+  /**
+   * Returns a read-only view of {@code attribute}. Query operations on
+   * the returned attribute "read-through" to the underlying attribute,
+   * and attempts to modify the returned attribute either directly or
+   * indirectly via an iterator result in an {@code
+   * UnsupportedOperationException}.
+   *
+   * @param attribute
+   *          The attribute for which a read-only view is to be
+   *          returned.
+   * @return A read-only view of {@code attribute}.
+   * @throws NullPointerException
+   *           If {@code attribute} was {@code null}.
+   */
+  public static final Attribute unmodifiableAttribute(
+      Attribute attribute) throws NullPointerException
+  {
+    return new UnmodifiableAttribute(attribute);
+  }
+
+
+
+  /**
+   * Returns a read-only view of {@code entry} and its attributes. Query
+   * operations on the returned entry and its attributes"read-through"
+   * to the underlying entry or attribute, and attempts to modify the
+   * returned entry and its attributes either directly or indirectly via
+   * an iterator result in an {@code UnsupportedOperationException}.
+   *
+   * @param entry
+   *          The entry for which a read-only view is to be returned.
+   * @return A read-only view of {@code entry}.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null}.
+   */
+  public static final Entry unmodifiableEntry(Entry entry)
+      throws NullPointerException
+  {
+    return new UnmodifiableEntry(entry);
+  }
+
+
+
+  // Prevent instantiation.
+  private Types()
+  {
+    // Nothing to do.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1.java b/sdk/src/org/opends/sdk/asn1/ASN1.java
new file mode 100644
index 0000000..c263d88
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1.java
@@ -0,0 +1,221 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.opends.sdk.util.*;
+
+
+/**
+ * This class contains various static factory methods for creating ASN.1
+ * readers and writers.
+ * 
+ * @see ASN1Reader
+ * @see ASN1Writer
+ */
+public final class ASN1
+{
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided byte array and
+   * having an unlimited maximum BER element size.
+   * 
+   * @param array
+   *          The byte array to use.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(byte[] array)
+  {
+    return getReader(array, 0);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided byte array and
+   * having a user defined maximum BER element size.
+   * 
+   * @param array
+   *          The byte array to use.
+   * @param maxElementSize
+   *          The maximum BER element size, or {@code 0} to indicate
+   *          that there is no limit.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(byte[] array, int maxElementSize)
+  {
+    return getReader(ByteString.wrap(array), maxElementSize);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided byte sequence
+   * and having an unlimited maximum BER element size.
+   * 
+   * @param sequence
+   *          The byte sequence to use.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(ByteSequence sequence)
+  {
+    return getReader(sequence, 0);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided byte sequence
+   * and having a user defined maximum BER element size.
+   * 
+   * @param sequence
+   *          The byte sequence to use.
+   * @param maxElementSize
+   *          The maximum BER element size, or {@code 0} to indicate
+   *          that there is no limit.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(ByteSequence sequence,
+      int maxElementSize)
+  {
+    return new ASN1ByteSequenceReader(sequence.asReader(),
+        maxElementSize);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided byte sequence
+   * reader and having an unlimited maximum BER element size.
+   * 
+   * @param reader
+   *          The byte sequence reader to use.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(ByteSequenceReader reader)
+  {
+    return getReader(reader, 0);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided byte sequence
+   * reader and having a user defined maximum BER element size.
+   * 
+   * @param reader
+   *          The byte sequence reader to use.
+   * @param maxElementSize
+   *          The maximum BER element size, or {@code 0} to indicate
+   *          that there is no limit.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(ByteSequenceReader reader,
+      int maxElementSize)
+  {
+    return new ASN1ByteSequenceReader(reader, maxElementSize);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided input stream
+   * and having an unlimited maximum BER element size.
+   * 
+   * @param stream
+   *          The input stream to use.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(InputStream stream)
+  {
+    return getReader(stream, 0);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 reader whose source is the provided input stream
+   * and having a user defined maximum BER element size.
+   * 
+   * @param stream
+   *          The input stream to use.
+   * @param maxElementSize
+   *          The maximum BER element size, or {@code 0} to indicate
+   *          that there is no limit.
+   * @return The new ASN.1 reader.
+   */
+  public static ASN1Reader getReader(InputStream stream,
+      int maxElementSize)
+  {
+    return new ASN1InputStreamReader(stream, maxElementSize);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 writer whose destination is the provided byte
+   * string builder.
+   * 
+   * @param builder
+   *          The byte string builder to use.
+   * @return The new ASN.1 writer.
+   */
+  public static ASN1Writer getWriter(ByteStringBuilder builder)
+  {
+    ByteSequenceOutputStream outputStream =
+        new ByteSequenceOutputStream(builder);
+    return getWriter(outputStream);
+  }
+
+
+
+  /**
+   * Returns an ASN.1 writer whose destination is the provided output
+   * stream.
+   * 
+   * @param stream
+   *          The output stream to use.
+   * @return The new ASN.1 writer.
+   */
+  public static ASN1Writer getWriter(OutputStream stream)
+  {
+    return new ASN1OutputStreamWriter(stream);
+  }
+
+
+
+  // Prevent instantiation.
+  private ASN1()
+  {
+    // Nothing to do.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java b/sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java
new file mode 100644
index 0000000..8e3ec17
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1ByteSequenceReader.java
@@ -0,0 +1,563 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_TYPE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequenceReader;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * An ASN.1 reader that reads from a {@link ByteSequenceReader}.
+ */
+final class ASN1ByteSequenceReader extends AbstractASN1Reader implements
+    ASN1Reader
+{
+
+  private int state = ELEMENT_READ_STATE_NEED_TYPE;
+
+  private byte peekType = 0;
+
+  private int peekLength = -1;
+
+  private final int maxElementSize;
+
+  private ByteSequenceReader reader;
+
+  private final LinkedList<ByteSequenceReader> readerStack;
+
+
+
+  /**
+   * Creates a new ASN1 reader whose source is the provided byte
+   * sequence reader and having a user defined maximum BER element size.
+   *
+   * @param reader
+   *          The byte sequence reader to be read.
+   * @param maxElementSize
+   *          The maximum BER element size, or <code>0</code> to
+   *          indicate that there is no limit.
+   */
+  ASN1ByteSequenceReader(ByteSequenceReader reader, int maxElementSize)
+  {
+    this.reader = reader;
+    this.readerStack = new LinkedList<ByteSequenceReader>();
+    this.maxElementSize = maxElementSize;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException
+  {
+    readerStack.clear();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean elementAvailable() throws IOException
+  {
+    if ((state == ELEMENT_READ_STATE_NEED_TYPE)
+        && !needTypeState(false))
+    {
+      return false;
+    }
+    if ((state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+        && !needFirstLengthByteState(false))
+    {
+      return false;
+    }
+
+    return peekLength <= reader.remaining();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean hasNextElement() throws IOException
+  {
+    return (state != ELEMENT_READ_STATE_NEED_TYPE)
+        || needTypeState(false);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int peekLength() throws IOException
+  {
+    peekType();
+
+    if (state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+    {
+      needFirstLengthByteState(true);
+    }
+
+    return peekLength;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte peekType() throws IOException
+  {
+    if (state == ELEMENT_READ_STATE_NEED_TYPE)
+    {
+      // Read just the type.
+      if (reader.remaining() <= 0)
+      {
+        Message message = ERR_ASN1_TRUCATED_TYPE_BYTE.get();
+        throw DecodeException.fatalError(message);
+      }
+      int type = reader.get();
+
+      peekType = (byte) type;
+      state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+    }
+
+    return peekType;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean readBoolean() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength != 1)
+    {
+      Message message = ERR_ASN1_BOOLEAN_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    if (reader.remaining() < peekLength)
+    {
+      Message message = ERR_ASN1_BOOLEAN_TRUNCATED_VALUE
+          .get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+    int readByte = reader.get();
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return readByte != 0x00;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readEndSequence() throws IOException,
+      IllegalStateException
+  {
+    if (readerStack.isEmpty())
+    {
+      Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
+      throw new IllegalStateException(message.toString());
+    }
+
+    if ((reader.remaining() > 0)
+        && StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+    {
+      StaticUtils.DEBUG_LOG.fine("Ignoring " + reader.remaining()
+          + " unused trailing bytes in " + "ASN.1 SEQUENCE");
+    }
+
+    reader = readerStack.removeFirst();
+
+    // Reset the state
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readEndSet() throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    readEndSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int readEnumerated() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if ((peekLength < 1) || (peekLength > 4))
+    {
+      Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    // From an implementation point of view, an enumerated value is
+    // equivalent to an integer.
+    return (int) readInteger();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public long readInteger() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if ((peekLength < 1) || (peekLength > 8))
+    {
+      Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    if (reader.remaining() < peekLength)
+    {
+      Message message = ERR_ASN1_INTEGER_TRUNCATED_VALUE
+          .get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+    if (peekLength > 4)
+    {
+      long longValue = 0;
+      for (int i = 0; i < peekLength; i++)
+      {
+        int readByte = reader.get();
+        if ((i == 0) && (readByte < 0))
+        {
+          longValue = 0xFFFFFFFFFFFFFFFFL;
+        }
+        longValue = (longValue << 8) | (readByte & 0xFF);
+      }
+
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return longValue;
+    }
+    else
+    {
+      int intValue = 0;
+      for (int i = 0; i < peekLength; i++)
+      {
+        int readByte = reader.get();
+        if ((i == 0) && (readByte < 0))
+        {
+          intValue = 0xFFFFFFFF;
+        }
+        intValue = (intValue << 8) | (readByte & 0xFF);
+      }
+
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return intValue;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readNull() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    // Make sure that the decoded length is exactly zero byte.
+    if (peekLength != 0)
+    {
+      Message message = ERR_ASN1_NULL_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString readOctetString() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (reader.remaining() < peekLength)
+    {
+      Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+          .get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return reader.getByteString(peekLength);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteStringBuilder readOctetString(ByteStringBuilder builder)
+      throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    // Copy the value.
+    if (reader.remaining() < peekLength)
+    {
+      Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+          .get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+    builder.append(reader, peekLength);
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return builder;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String readOctetStringAsString() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (reader.remaining() < peekLength)
+    {
+      Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+          .get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return reader.getString(peekLength);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSequence() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (reader.remaining() < peekLength)
+    {
+      Message message = ERR_ASN1_SEQUENCE_SET_TRUNCATED_VALUE
+          .get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    ByteSequenceReader subByteString = reader.getByteSequence(
+        peekLength).asReader();
+    readerStack.addFirst(reader);
+    reader = subByteString;
+
+    // Reset the state
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSet() throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    readStartSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Reader skipElement() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (reader.remaining() < peekLength)
+    {
+      Message message = ERR_ASN1_SKIP_TRUNCATED_VALUE.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    reader.skip(peekLength);
+    return this;
+  }
+
+
+
+  /**
+   * Internal helper method reading the first length bytes and
+   * transition to the next state if successful.
+   *
+   * @param throwEofException
+   *          <code>true</code> to throw an exception when the end of
+   *          the sequence is encountered.
+   * @return <code>true</code> if the length bytes was successfully read
+   * @throws IOException
+   *           If an error occurs while trying to decode an ASN1
+   *           element.
+   */
+  private boolean needFirstLengthByteState(boolean throwEofException)
+      throws IOException
+  {
+    if (reader.remaining() <= 0)
+    {
+      if (throwEofException)
+      {
+        Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTE.get();
+        throw DecodeException.fatalError(message);
+      }
+      return false;
+    }
+    int readByte = reader.get();
+    peekLength = (readByte & 0x7F);
+    if (peekLength != readByte)
+    {
+      int lengthBytesNeeded = peekLength;
+      if (lengthBytesNeeded > 4)
+      {
+        Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES
+            .get(lengthBytesNeeded);
+        throw DecodeException.fatalError(message);
+      }
+
+      peekLength = 0x00;
+      if (reader.remaining() < lengthBytesNeeded)
+      {
+        if (throwEofException)
+        {
+          Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTES
+              .get(lengthBytesNeeded);
+          throw DecodeException.fatalError(message);
+        }
+        return false;
+      }
+
+      while (lengthBytesNeeded > 0)
+      {
+        readByte = reader.get();
+        peekLength = (peekLength << 8) | (readByte & 0xFF);
+        lengthBytesNeeded--;
+      }
+    }
+
+    // Make sure that the element is not larger than the maximum allowed
+    // message size.
+    if ((maxElementSize > 0) && (peekLength > maxElementSize))
+    {
+      Message message = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED
+          .get(peekLength, maxElementSize);
+      throw DecodeException.fatalError(message);
+    }
+    state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+    return true;
+  }
+
+
+
+  /**
+   * Internal helper method reading the ASN.1 type byte and transition
+   * to the next state if successful.
+   *
+   * @param throwEofException
+   *          <code>true</code> to throw an exception when the end of
+   *          the sequence is encountered.
+   * @return <code>true</code> if the type byte was successfully read
+   * @throws IOException
+   *           If an error occurs while trying to decode an ASN1
+   *           element.
+   */
+  private boolean needTypeState(boolean throwEofException)
+      throws IOException
+  {
+    // Read just the type.
+    if (reader.remaining() <= 0)
+    {
+      if (throwEofException)
+      {
+        Message message = ERR_ASN1_TRUCATED_TYPE_BYTE.get();
+        throw DecodeException.fatalError(message);
+      }
+      return false;
+    }
+    int type = reader.get();
+
+    peekType = (byte) type;
+    state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1Constants.java b/sdk/src/org/opends/sdk/asn1/ASN1Constants.java
new file mode 100644
index 0000000..63d37fd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1Constants.java
@@ -0,0 +1,167 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+/**
+ * This class defines a number of constants that may be used when
+ * interacting with ASN.1 elements.
+ */
+public final class ASN1Constants
+{
+
+  // Prevent instantiation.
+  private ASN1Constants()
+  {
+    // Nothing to do.
+  }
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be the BER type for a new element.
+   */
+  static final int ELEMENT_READ_STATE_NEED_TYPE = 0;
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be the first byte for the element length.
+   */
+  static final int ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE = 1;
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be additional bytes of a multi-byte length.
+   */
+  static final int ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES = 2;
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be applied to the value of the element.
+   */
+  static final int ELEMENT_READ_STATE_NEED_VALUE_BYTES = 3;
+
+  /**
+   * The BER type that is assigned to the universal Boolean element.
+   */
+  public static final byte UNIVERSAL_BOOLEAN_TYPE = 0x01;
+
+  /**
+   * The BER type that is assigned to the universal integer type.
+   */
+  public static final byte UNIVERSAL_INTEGER_TYPE = 0x02;
+
+  /**
+   * The BER type that is assigned to the universal octet string type.
+   */
+  public static final byte UNIVERSAL_OCTET_STRING_TYPE = 0x04;
+
+  /**
+   * The BER type that is assigned to the universal null type.
+   */
+  public static final byte UNIVERSAL_NULL_TYPE = 0x05;
+
+  /**
+   * The BER type that is assigned to the universal enumerated type.
+   */
+  public static final byte UNIVERSAL_ENUMERATED_TYPE = 0x0A;
+
+  /**
+   * The BER type that is assigned to the universal sequence type.
+   */
+  public static final byte UNIVERSAL_SEQUENCE_TYPE = 0x30;
+
+  /**
+   * The BER type that is assigned to the universal set type.
+   */
+  public static final byte UNIVERSAL_SET_TYPE = 0x31;
+
+  /**
+   * The byte array that will be used for ASN.1 elements with no value.
+   */
+  static final byte[] NO_VALUE = new byte[0];
+
+  /**
+   * The bitmask that can be ANDed with the BER type to zero out all
+   * bits except those used in the class.
+   */
+  static final byte TYPE_MASK_ALL_BUT_CLASS = (byte) 0xC0;
+
+  /**
+   * The bitmask that can be ANDed with the BER type to determine if the
+   * element is in the universal class.
+   */
+  static final byte TYPE_MASK_UNIVERSAL = 0x00;
+
+  /**
+   * The bitmask that can be ANDed with the BER type to determine if the
+   * element is in the application-specific class.
+   */
+  static final byte TYPE_MASK_APPLICATION = 0x40;
+
+  /**
+   * The bitmask that can be ANDed with the BER type to determine if the
+   * element is in the context-specific class.
+   */
+  static final byte TYPE_MASK_CONTEXT = (byte) 0x80;
+
+  /**
+   * The bitmask that can be ANDed with the BER type to determine if the
+   * element is in the private class.
+   */
+  static final byte TYPE_MASK_PRIVATE = (byte) 0xC0;
+
+  /**
+   * The bitmask that can be ANDed with the BER type to zero out all
+   * bits except the primitive/constructed bit.
+   */
+  static final byte TYPE_MASK_ALL_BUT_PC = (byte) 0x20;
+
+  /**
+   * The bitmask that can be ANDed with the BER type to determine if the
+   * element is a primitive.
+   */
+  static final byte TYPE_MASK_PRIMITIVE = 0x00;
+
+  /**
+   * The bitmask that can be ANDed with the BER type to determine if the
+   * element is constructed.
+   */
+  static final byte TYPE_MASK_CONSTRUCTED = 0x20;
+
+  /**
+   * The byte array containing the pre-encoded ASN.1 encoding for a
+   * boolean value of "false".
+   */
+  public static final byte BOOLEAN_VALUE_FALSE = 0x00;
+
+  /**
+   * The byte array containing the pre-encoded ASN.1 encoding for a
+   * boolean value of "false".
+   */
+  public static final byte BOOLEAN_VALUE_TRUE = (byte) 0xFF;
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java b/sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java
new file mode 100644
index 0000000..1b67947
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1InputStreamReader.java
@@ -0,0 +1,789 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_TYPE;
+import static org.opends.sdk.asn1.ASN1Constants.ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.LinkedList;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.SizeLimitInputStream;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * An ASN1Reader that reads from an input stream.
+ */
+final class ASN1InputStreamReader extends AbstractASN1Reader implements
+    ASN1Reader
+{
+  private int state = ELEMENT_READ_STATE_NEED_TYPE;
+
+  private byte peekType = 0;
+
+  private int peekLength = -1;
+
+  private int lengthBytesNeeded = 0;
+
+  private final int maxElementSize;
+
+  private InputStream in;
+
+  private final LinkedList<InputStream> streamStack;
+
+  private byte[] buffer;
+
+
+
+  /**
+   * Creates a new ASN1 reader whose source is the provided input stream
+   * and having a user defined maximum BER element size.
+   *
+   * @param stream
+   *          The input stream to be read.
+   * @param maxElementSize
+   *          The maximum BER element size, or <code>0</code> to
+   *          indicate that there is no limit.
+   */
+  ASN1InputStreamReader(InputStream stream, int maxElementSize)
+  {
+    this.in = stream;
+    this.streamStack = new LinkedList<InputStream>();
+    this.buffer = new byte[512];
+    this.maxElementSize = maxElementSize;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException
+  {
+    // Calling close of SizeLimitInputStream should close the parent
+    // stream.
+    in.close();
+    streamStack.clear();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean elementAvailable() throws IOException
+  {
+    if ((state == ELEMENT_READ_STATE_NEED_TYPE)
+        && !needTypeState(false, false))
+    {
+      return false;
+    }
+    if ((state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+        && !needFirstLengthByteState(false, false))
+    {
+      return false;
+    }
+    if ((state == ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES)
+        && !needAdditionalLengthBytesState(false, false))
+    {
+      return false;
+    }
+
+    return peekLength <= in.available();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean hasNextElement() throws IOException
+  {
+    if (!streamStack.isEmpty())
+    {
+      // We are reading a sub sequence. Return true as long as we
+      // haven't exhausted the size limit for the sub sequence sub input
+      // stream.
+      SizeLimitInputStream subSq = (SizeLimitInputStream) in;
+      return (subSq.getSizeLimit() - subSq.getBytesRead() > 0);
+    }
+
+    return (state != ELEMENT_READ_STATE_NEED_TYPE)
+        || needTypeState(true, false);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int peekLength() throws IOException
+  {
+    peekType();
+
+    switch (state)
+    {
+    case ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE:
+      needFirstLengthByteState(true, true);
+      break;
+
+    case ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES:
+      needAdditionalLengthBytesState(true, true);
+    }
+
+    return peekLength;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte peekType() throws IOException
+  {
+    if (state == ELEMENT_READ_STATE_NEED_TYPE)
+    {
+      needTypeState(true, true);
+    }
+
+    return peekType;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean readBoolean() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength != 1)
+    {
+      Message message = ERR_ASN1_BOOLEAN_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    int readByte = in.read();
+    if (readByte == -1)
+    {
+      Message message = ERR_ASN1_BOOLEAN_TRUNCATED_VALUE
+          .get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)",
+          peekType, peekLength, String.valueOf(readByte != 0x00)));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return readByte != 0x00;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readEndSequence() throws IOException,
+      IllegalStateException
+  {
+    if (streamStack.isEmpty())
+    {
+      Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
+      throw new IllegalStateException(message.toString());
+    }
+
+    // Ignore all unused trailing components.
+    SizeLimitInputStream subSq = (SizeLimitInputStream) in;
+    if (subSq.getSizeLimit() - subSq.getBytesRead() > 0)
+    {
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+      {
+        StaticUtils.DEBUG_LOG.fine(String.format(
+            "Ignoring %d unused trailing bytes in ASN.1 SEQUENCE",
+            subSq.getSizeLimit() - subSq.getBytesRead()));
+      }
+
+      subSq.skip(subSq.getSizeLimit() - subSq.getBytesRead());
+
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String
+          .format("READ ASN.1 END SEQUENCE"));
+    }
+
+    in = streamStack.removeFirst();
+
+    // Reset the state
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readEndSet() throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    readEndSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int readEnumerated() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if ((peekLength < 1) || (peekLength > 4))
+    {
+      Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    // From an implementation point of view, an enumerated value is
+    // equivalent to an integer.
+    return (int) readInteger();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public long readInteger() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if ((peekLength < 1) || (peekLength > 8))
+    {
+      Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    if (peekLength > 4)
+    {
+      long longValue = 0;
+      for (int i = 0; i < peekLength; i++)
+      {
+        int readByte = in.read();
+        if (readByte == -1)
+        {
+          Message message = ERR_ASN1_INTEGER_TRUNCATED_VALUE
+              .get(peekLength);
+          throw DecodeException.fatalError(message);
+        }
+        if ((i == 0) && (((byte) readByte) < 0))
+        {
+          longValue = 0xFFFFFFFFFFFFFFFFL;
+        }
+        longValue = (longValue << 8) | (readByte & 0xFF);
+      }
+
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return longValue;
+    }
+    else
+    {
+      int intValue = 0;
+      for (int i = 0; i < peekLength; i++)
+      {
+        int readByte = in.read();
+        if (readByte == -1)
+        {
+          Message message = ERR_ASN1_INTEGER_TRUNCATED_VALUE
+              .get(peekLength);
+          throw DecodeException.fatalError(message);
+        }
+        if ((i == 0) && (((byte) readByte) < 0))
+        {
+          intValue = 0xFFFFFFFF;
+        }
+        intValue = (intValue << 8) | (readByte & 0xFF);
+      }
+
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "READ ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            peekType, peekLength, intValue));
+      }
+
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return intValue;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readNull() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    // Make sure that the decoded length is exactly zero byte.
+    if (peekLength != 0)
+    {
+      Message message = ERR_ASN1_NULL_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String
+          .format("READ ASN.1 NULL(type=0x%x, length=%d)", peekType,
+              peekLength));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString readOctetString() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength == 0)
+    {
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return ByteString.empty();
+    }
+
+    // Copy the value and construct the element to return.
+    byte[] value = new byte[peekLength];
+    int bytesNeeded = peekLength;
+    int bytesRead;
+    while (bytesNeeded > 0)
+    {
+      bytesRead = in.read(value, peekLength - bytesNeeded, bytesNeeded);
+      if (bytesRead < 0)
+      {
+        Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+            .get(peekLength);
+        throw DecodeException.fatalError(message);
+      }
+
+      bytesNeeded -= bytesRead;
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+          peekLength));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return ByteString.wrap(value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteStringBuilder readOctetString(ByteStringBuilder builder)
+      throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength == 0)
+    {
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return builder;
+    }
+
+    // Copy the value and construct the element to return.
+    int bytesNeeded = peekLength;
+    int bytesRead;
+    while (bytesNeeded > 0)
+    {
+      bytesRead = builder.append(in, bytesNeeded);
+      if (bytesRead < 0)
+      {
+        Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+            .get(peekLength);
+        throw DecodeException.fatalError(message);
+      }
+      bytesNeeded -= bytesRead;
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+          peekLength));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return builder;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String readOctetStringAsString() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength == 0)
+    {
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return "";
+    }
+
+    // Resize the temp buffer if needed
+    if (peekLength > buffer.length)
+    {
+      buffer = new byte[peekLength];
+    }
+
+    int bytesNeeded = peekLength;
+    int bytesRead;
+    while (bytesNeeded > 0)
+    {
+      bytesRead = in
+          .read(buffer, peekLength - bytesNeeded, bytesNeeded);
+      if (bytesRead < 0)
+      {
+        Message message = ERR_ASN1_OCTET_STRING_TRUNCATED_VALUE
+            .get(peekLength);
+        throw DecodeException.fatalError(message);
+      }
+      bytesNeeded -= bytesRead;
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+
+    String str;
+    try
+    {
+      str = new String(buffer, 0, peekLength, "UTF-8");
+    }
+    catch (Exception e)
+    {
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+      {
+        StaticUtils.DEBUG_LOG
+            .warning("Unable to decode ASN.1 OCTETSTRING "
+                + "bytes as UTF-8 string: " + e.toString());
+      }
+
+      str = new String(buffer, 0, peekLength);
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)",
+          peekType, peekLength, str));
+    }
+
+    return str;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSequence() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    SizeLimitInputStream subStream = new SizeLimitInputStream(in,
+        peekLength);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 START SEQUENCE(type=0x%x, length=%d)", peekType,
+          peekLength));
+    }
+
+    streamStack.addFirst(in);
+    in = subStream;
+
+    // Reset the state
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSet() throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    readStartSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Reader skipElement() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    long bytesSkipped = in.skip(peekLength);
+    if (bytesSkipped != peekLength)
+    {
+      Message message = ERR_ASN1_SKIP_TRUNCATED_VALUE.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return this;
+  }
+
+
+
+  /**
+   * Internal helper method reading the additional ASN.1 length bytes
+   * and transition to the next state if successful.
+   *
+   * @param isBlocking
+   *          <code>true</code> to block if the type byte is not
+   *          available or <code>false</code> to check for availability
+   *          first.
+   * @param throwEofException
+   *          <code>true</code> to throw an exception when an EOF is
+   *          encountered or <code>false</code> to return false.
+   * @return <code>true</code> if the length bytes was successfully
+   *         read.
+   * @throws IOException
+   *           If an error occurs while reading from the stream.
+   */
+  private boolean needAdditionalLengthBytesState(boolean isBlocking,
+      boolean throwEofException) throws IOException
+  {
+    if (!isBlocking && (in.available() < lengthBytesNeeded))
+    {
+      return false;
+    }
+
+    int readByte;
+    while (lengthBytesNeeded > 0)
+    {
+      readByte = in.read();
+      if (readByte == -1)
+      {
+        state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+        if (throwEofException)
+        {
+          Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTES
+              .get(lengthBytesNeeded);
+          throw DecodeException.fatalError(message);
+        }
+        return false;
+      }
+      peekLength = (peekLength << 8) | (readByte & 0xFF);
+      lengthBytesNeeded--;
+    }
+
+    // Make sure that the element is not larger than the maximum allowed
+    // message size.
+    if ((maxElementSize > 0) && (peekLength > maxElementSize))
+    {
+      Message message = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED
+          .get(peekLength, maxElementSize);
+      throw DecodeException.fatalError(message);
+    }
+    state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+    return true;
+  }
+
+
+
+  /**
+   * Internal helper method reading the first length bytes and
+   * transition to the next state if successful.
+   *
+   * @param isBlocking
+   *          <code>true</code> to block if the type byte is not
+   *          available or <code>false</code> to check for availability
+   *          first.
+   * @param throwEofException
+   *          <code>true</code> to throw an exception when an EOF is
+   *          encountered or <code>false</code> to return false.
+   * @return <code>true</code> if the length bytes was successfully read
+   * @throws IOException
+   *           If an error occurs while reading from the stream.
+   */
+  private boolean needFirstLengthByteState(boolean isBlocking,
+      boolean throwEofException) throws IOException
+  {
+    if (!isBlocking && (in.available() <= 0))
+    {
+      return false;
+    }
+
+    int readByte = in.read();
+    if (readByte == -1)
+    {
+      if (throwEofException)
+      {
+        Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTE.get();
+        throw DecodeException.fatalError(message);
+      }
+      return false;
+    }
+    peekLength = (readByte & 0x7F);
+    if (peekLength != readByte)
+    {
+      lengthBytesNeeded = peekLength;
+      if (lengthBytesNeeded > 4)
+      {
+        Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES
+            .get(lengthBytesNeeded);
+        throw DecodeException.fatalError(message);
+      }
+      peekLength = 0x00;
+
+      if (!isBlocking && (in.available() < lengthBytesNeeded))
+      {
+        state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+        return false;
+      }
+
+      while (lengthBytesNeeded > 0)
+      {
+        readByte = in.read();
+        if (readByte == -1)
+        {
+          state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+          if (throwEofException)
+          {
+            Message message = ERR_ASN1_TRUNCATED_LENGTH_BYTES
+                .get(lengthBytesNeeded);
+            throw DecodeException.fatalError(message);
+          }
+          return false;
+        }
+        peekLength = (peekLength << 8) | (readByte & 0xFF);
+        lengthBytesNeeded--;
+      }
+    }
+
+    // Make sure that the element is not larger than the maximum allowed
+    // message size.
+    if ((maxElementSize > 0) && (peekLength > maxElementSize))
+    {
+      Message message = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED
+          .get(peekLength, maxElementSize);
+      throw DecodeException.fatalError(message);
+    }
+    state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+    return true;
+  }
+
+
+
+  /**
+   * Internal helper method reading the ASN.1 type byte and transition
+   * to the next state if successful.
+   *
+   * @param isBlocking
+   *          <code>true</code> to block if the type byte is not
+   *          available or <code>false</code> to check for availability
+   *          first.
+   * @param throwEofException
+   *          <code>true</code> to throw an exception when an EOF is
+   *          encountered or <code>false</code> to return false.
+   * @return <code>true</code> if the type byte was successfully read
+   * @throws IOException
+   *           If an error occurs while reading from the stream.
+   */
+  private boolean needTypeState(boolean isBlocking,
+      boolean throwEofException) throws IOException
+  {
+    // Read just the type.
+    if (!isBlocking && (in.available() <= 0))
+    {
+      return false;
+    }
+
+    int type = in.read();
+    if (type == -1)
+    {
+      if (throwEofException)
+      {
+        Message message = ERR_ASN1_TRUCATED_TYPE_BYTE.get();
+        throw DecodeException.fatalError(message);
+      }
+      return false;
+    }
+
+    peekType = (byte) type;
+    state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java b/sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java
new file mode 100644
index 0000000..e3ca2e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1OutputStreamWriter.java
@@ -0,0 +1,560 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_FALSE;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_TRUE;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteSequenceOutputStream;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+/**
+ * An ASN1Writer implementation that outputs to an outputstream.
+ */
+final class ASN1OutputStreamWriter extends AbstractASN1Writer implements
+    ASN1Writer
+{
+  private final OutputStream rootStream;
+  private OutputStream out;
+  private final ArrayList<ByteSequenceOutputStream> streamStack;
+  private int stackDepth;
+
+
+
+  /**
+   * Creates a new ASN.1 output stream reader.
+   *
+   * @param stream
+   *          The underlying output stream.
+   */
+  ASN1OutputStreamWriter(OutputStream stream)
+  {
+    this.out = stream;
+    this.rootStream = stream;
+    this.streamStack = new ArrayList<ByteSequenceOutputStream>();
+    this.stackDepth = -1;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException
+  {
+    while (stackDepth >= 0)
+    {
+      writeEndSequence();
+    }
+    rootStream.flush();
+
+    streamStack.clear();
+    rootStream.close();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void flush() throws IOException
+  {
+    rootStream.flush();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeBoolean(byte type, boolean booleanValue)
+      throws IOException
+  {
+    out.write(type);
+    writeLength(1);
+    out.write(booleanValue ? BOOLEAN_VALUE_TRUE : BOOLEAN_VALUE_FALSE);
+
+    if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type,
+          1, String.valueOf(booleanValue)));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeEndSequence() throws IOException, IllegalStateException
+  {
+    if (stackDepth < 0)
+    {
+      Message message = ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED.get();
+      throw new IllegalStateException(message.toString());
+    }
+
+    ByteSequenceOutputStream childStream = streamStack.get(stackDepth);
+
+    // Decrement the stack depth and get the parent stream
+    --stackDepth;
+
+    OutputStream parentStream =
+        stackDepth < 0 ? rootStream : streamStack.get(stackDepth);
+
+    // Switch to parent stream and reset the sub-stream
+    out = parentStream;
+
+    // Write the length and contents of the sub-stream
+    writeLength(childStream.length());
+    childStream.writeTo(parentStream);
+
+    if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 END SEQUENCE(length=%d)", childStream.length()));
+    }
+
+    childStream.reset();
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeEndSet() throws IOException
+  {
+    return writeEndSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeEnumerated(byte type, int intValue)
+      throws IOException
+  {
+    return writeInteger(type, intValue);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeInteger(byte type, int intValue)
+      throws IOException
+  {
+    out.write(type);
+    if (((intValue < 0) && ((intValue & 0xFFFFFF80) == 0xFFFFFF80))
+        || ((intValue & 0x0000007F) == intValue))
+    {
+      writeLength(1);
+      out.write((byte) (intValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 1, intValue));
+      }
+    }
+    else if (((intValue < 0) && ((intValue & 0xFFFF8000) == 0xFFFF8000))
+        || ((intValue & 0x00007FFF) == intValue))
+    {
+      writeLength(2);
+      out.write((byte) ((intValue >> 8) & 0xFF));
+      out.write((byte) (intValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 2, intValue));
+      }
+    }
+    else if (((intValue < 0) && ((intValue & 0xFF800000) == 0xFF800000))
+        || ((intValue & 0x007FFFFF) == intValue))
+    {
+      writeLength(3);
+      out.write((byte) ((intValue >> 16) & 0xFF));
+      out.write((byte) ((intValue >> 8) & 0xFF));
+      out.write((byte) (intValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 3, intValue));
+      }
+    }
+    else
+    {
+      writeLength(4);
+      out.write((byte) ((intValue >> 24) & 0xFF));
+      out.write((byte) ((intValue >> 16) & 0xFF));
+      out.write((byte) ((intValue >> 8) & 0xFF));
+      out.write((byte) (intValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 4, intValue));
+      }
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeInteger(byte type, long longValue)
+      throws IOException
+  {
+    out.write(type);
+    if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFFFF80L) == 0xFFFFFFFFFFFFFF80L))
+        || ((longValue & 0x000000000000007FL) == longValue))
+    {
+      writeLength(1);
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 1, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L))
+        || ((longValue & 0x0000000000007FFFL) == longValue))
+    {
+      writeLength(2);
+      out.write((byte) ((longValue >> 8) & 0xFF));
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 2, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L))
+        || ((longValue & 0x00000000007FFFFFL) == longValue))
+    {
+      writeLength(3);
+      out.write((byte) ((longValue >> 16) & 0xFF));
+      out.write((byte) ((longValue >> 8) & 0xFF));
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 3, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L))
+        || ((longValue & 0x000000007FFFFFFFL) == longValue))
+    {
+      writeLength(4);
+      out.write((byte) ((longValue >> 24) & 0xFF));
+      out.write((byte) ((longValue >> 16) & 0xFF));
+      out.write((byte) ((longValue >> 8) & 0xFF));
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 4, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L))
+        || ((longValue & 0x0000007FFFFFFFFFL) == longValue))
+    {
+      writeLength(5);
+      out.write((byte) ((longValue >> 32) & 0xFF));
+      out.write((byte) ((longValue >> 24) & 0xFF));
+      out.write((byte) ((longValue >> 16) & 0xFF));
+      out.write((byte) ((longValue >> 8) & 0xFF));
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 5, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L))
+        || ((longValue & 0x00007FFFFFFFFFFFL) == longValue))
+    {
+      writeLength(6);
+      out.write((byte) ((longValue >> 40) & 0xFF));
+      out.write((byte) ((longValue >> 32) & 0xFF));
+      out.write((byte) ((longValue >> 24) & 0xFF));
+      out.write((byte) ((longValue >> 16) & 0xFF));
+      out.write((byte) ((longValue >> 8) & 0xFF));
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 6, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFF80000000000000L) == 0xFF80000000000000L))
+        || ((longValue & 0x007FFFFFFFFFFFFFL) == longValue))
+    {
+      writeLength(7);
+      out.write((byte) ((longValue >> 48) & 0xFF));
+      out.write((byte) ((longValue >> 40) & 0xFF));
+      out.write((byte) ((longValue >> 32) & 0xFF));
+      out.write((byte) ((longValue >> 24) & 0xFF));
+      out.write((byte) ((longValue >> 16) & 0xFF));
+      out.write((byte) ((longValue >> 8) & 0xFF));
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 7, longValue));
+      }
+    }
+    else
+    {
+      writeLength(8);
+      out.write((byte) ((longValue >> 56) & 0xFF));
+      out.write((byte) ((longValue >> 48) & 0xFF));
+      out.write((byte) ((longValue >> 40) & 0xFF));
+      out.write((byte) ((longValue >> 32) & 0xFF));
+      out.write((byte) ((longValue >> 24) & 0xFF));
+      out.write((byte) ((longValue >> 16) & 0xFF));
+      out.write((byte) ((longValue >> 8) & 0xFF));
+      out.write((byte) (longValue & 0xFF));
+      if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+                type, 8, longValue));
+      }
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeNull(byte type) throws IOException
+  {
+    out.write(type);
+    writeLength(0);
+
+    if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(byte type, byte[] value,
+      int offset, int length) throws IOException
+  {
+    out.write(type);
+    writeLength(length);
+    out.write(value, offset, length);
+
+    if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)",
+              type, length));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(byte type, ByteSequence value)
+      throws IOException
+  {
+    out.write(type);
+    writeLength(value.length());
+    value.copyTo(out);
+
+    if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value
+              .length()));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(byte type, String value)
+      throws IOException
+  {
+    out.write(type);
+
+    if (value == null)
+    {
+      writeLength(0);
+      return this;
+    }
+
+    byte[] bytes = StaticUtils.getBytes(value);
+    writeLength(bytes.length);
+    out.write(bytes);
+
+    if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, "
+              + "value=%s)", type, bytes.length, value));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeStartSequence(byte type) throws IOException
+  {
+    // Write the type in current stream switch to next sub-stream
+    out.write(type);
+
+    // Increment the stack depth and get the sub-stream from the stack
+    ++stackDepth;
+
+    // Make sure we have a cached sub-stream at this depth
+    if (stackDepth >= streamStack.size())
+    {
+      ByteSequenceOutputStream subStream =
+          new ByteSequenceOutputStream(new ByteStringBuilder());
+      streamStack.add(subStream);
+      out = subStream;
+    }
+    else
+    {
+      out = streamStack.get(stackDepth);
+    }
+
+    if(StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 START SEQUENCE(type=0x%x)", type));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeStartSet(byte type) throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    return writeStartSequence(type);
+  }
+
+
+
+  /**
+   * Writes the provided value for use as the length of an ASN.1
+   * element.
+   *
+   * @param length
+   *          The length to encode for use in an ASN.1 element.
+   * @throws IOException
+   *           if an error occurs while writing.
+   */
+  private void writeLength(int length) throws IOException
+  {
+    if (length < 128)
+    {
+      out.write((byte) length);
+    }
+    else if ((length & 0x000000FF) == length)
+    {
+      out.write((byte) 0x81);
+      out.write((byte) (length & 0xFF));
+    }
+    else if ((length & 0x0000FFFF) == length)
+    {
+      out.write((byte) 0x82);
+      out.write((byte) ((length >> 8) & 0xFF));
+      out.write((byte) (length & 0xFF));
+    }
+    else if ((length & 0x00FFFFFF) == length)
+    {
+      out.write((byte) 0x83);
+      out.write((byte) ((length >> 16) & 0xFF));
+      out.write((byte) ((length >> 8) & 0xFF));
+      out.write((byte) (length & 0xFF));
+    }
+    else
+    {
+      out.write((byte) 0x84);
+      out.write((byte) ((length >> 24) & 0xFF));
+      out.write((byte) ((length >> 16) & 0xFF));
+      out.write((byte) ((length >> 8) & 0xFF));
+      out.write((byte) (length & 0xFF));
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1Reader.java b/sdk/src/org/opends/sdk/asn1/ASN1Reader.java
new file mode 100644
index 0000000..424da2a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1Reader.java
@@ -0,0 +1,448 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * An interface for decoding ASN.1 elements from a data source.
+ * <p>
+ * Methods for creating {@link ASN1Reader}s are provided in the
+ * {@link ASN1} class.
+ */
+public interface ASN1Reader extends Closeable
+{
+
+  /**
+   * Closes this ASN.1 reader.
+   *
+   * @throws IOException
+   *           If an error occurs while closing.
+   */
+  void close() throws IOException;
+
+
+
+  /**
+   * Indicates whether or not the next element can be read without
+   * blocking.
+   *
+   * @return {@code true} if a complete element is available or {@code
+   *         false} otherwise.
+   * @throws DecodeException
+   *           If the available data was not valid ASN.1.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  boolean elementAvailable() throws DecodeException, IOException;
+
+
+
+  /**
+   * Indicates whether or not the current stream, sequence, or set
+   * contains another element. Note that this method may return {@code
+   * true} even if a previous call to {@link #elementAvailable} returned
+   * {@code false}, indicating that the current set or sequence contains
+   * another element but an attempt to read that element may block. This
+   * method will block if there is not enough data available to make the
+   * determination (typically only the next element's type is required).
+   *
+   * @return {@code true} if the current stream, sequence, or set
+   *         contains another element, or {@code false} if the end of
+   *         the stream, sequence, or set has been reached.
+   * @throws DecodeException
+   *           If the available data was not valid ASN.1.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  boolean hasNextElement() throws DecodeException, IOException;
+
+
+
+  /**
+   * Returns the data length of the next element without actually
+   * reading it.
+   *
+   * @return The data length of the next element, or {@code -1} if the
+   *         end of the stream, sequence, or set has been reached.
+   * @throws DecodeException
+   *           If the available data was not valid ASN.1.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  int peekLength() throws DecodeException, IOException;
+
+
+
+  /**
+   * Returns the type of the next element without actually reading it.
+   *
+   * @return The type of the next element, or {@code -1} if the end of
+   *         the stream, sequence, or set has been reached.
+   * @throws DecodeException
+   *           If the available data was not valid ASN.1.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  byte peekType() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as a boolean having the Universal Boolean
+   * ASN.1 type tag.
+   *
+   * @return The decoded boolean value.
+   * @throws DecodeException
+   *           If the element cannot be decoded as a boolean.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  boolean readBoolean() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as a boolean having the provided type tag.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @return The decoded boolean value.
+   * @throws DecodeException
+   *           If the element cannot be decoded as a boolean.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  boolean readBoolean(byte type) throws DecodeException, IOException;
+
+
+
+  /**
+   * Finishes reading a sequence and discards any unread elements.
+   *
+   * @throws DecodeException
+   *           If an error occurs while advancing to the end of the
+   *           sequence.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   * @throws IllegalStateException
+   *           If there is no sequence being read.
+   */
+  void readEndSequence() throws DecodeException, IOException,
+      IllegalStateException;
+
+
+
+  /**
+   * Finishes reading a set and discards any unread elements.
+   *
+   * @throws DecodeException
+   *           If an error occurs while advancing to the end of the set.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   * @throws IllegalStateException
+   *           If there is no set being read.
+   */
+  void readEndSet() throws DecodeException, IOException,
+      IllegalStateException;
+
+
+
+  /**
+   * Reads the next element as an enumerated having the Universal
+   * Enumerated ASN.1 type tag.
+   *
+   * @return The decoded enumerated value.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an enumerated value.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  int readEnumerated() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as an enumerated having the provided type
+   * tag.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @return The decoded enumerated value.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an enumerated value.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  int readEnumerated(byte type) throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as an integer having the Universal Integer
+   * ASN.1 type tag.
+   *
+   * @return The decoded integer value.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an integer.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  long readInteger() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as an integer having the provided type tag.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @return The decoded integer value.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an integer.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  long readInteger(byte type) throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as a null element having the Universal Null
+   * ASN.1 type tag.
+   *
+   * @throws DecodeException
+   *           If the element cannot be decoded as a null element.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  void readNull() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as a null element having the provided type
+   * tag.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @throws DecodeException
+   *           If the element cannot be decoded as a null element.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  void readNull(byte type) throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as an octet string having the Universal
+   * Octet String ASN.1 type tag.
+   *
+   * @return The decoded octet string represented using a
+   *         {@link ByteString}.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an octet string.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  ByteString readOctetString() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as an octet string having the provided type
+   * tag.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @return The decoded octet string represented using a
+   *         {@link ByteString}.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an octet string.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  ByteString readOctetString(byte type) throws DecodeException,
+      IOException;
+
+
+
+  /**
+   * Reads the next element as an octet string having the provided type
+   * tag and appends it to the provided {@link ByteStringBuilder}.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @param builder
+   *          The {@link ByteStringBuilder} to append the octet string
+   *          to.
+   * @return A reference to {@code builder}.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an octet string.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  ByteStringBuilder readOctetString(byte type, ByteStringBuilder builder)
+      throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as an octet string having the Universal
+   * Octet String ASN.1 type tag and appends it to the provided
+   * {@link ByteStringBuilder}.
+   *
+   * @param builder
+   *          The {@link ByteStringBuilder} to append the octet string
+   *          to.
+   * @return A reference to {@code builder}.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an octet string.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  ByteStringBuilder readOctetString(ByteStringBuilder builder)
+      throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as an octet string having the Universal
+   * Octet String ASN.1 type tag and decodes the value as a UTF-8
+   * encoded string.
+   *
+   * @return The decoded octet string as a UTF-8 encoded string.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an octet string.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  String readOctetStringAsString() throws DecodeException,
+      IOException;
+
+
+
+  /**
+   * Reads the next element as an octet string having the provided type
+   * tag and decodes the value as a UTF-8 encoded string.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @return The decoded octet string as a UTF-8 encoded string.
+   * @throws DecodeException
+   *           If the element cannot be decoded as an octet string.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  String readOctetStringAsString(byte type) throws DecodeException,
+      IOException;
+
+
+
+  /**
+   * Reads the next element as a sequence having the Universal Sequence
+   * ASN.1 type tag. All further reads will read the elements in the
+   * sequence until {@link #readEndSequence()} is called.
+   *
+   * @throws DecodeException
+   *           If the element cannot be decoded as a sequence.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  void readStartSequence() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as a sequence having the provided type tag.
+   * All further reads will read the elements in the sequence until
+   * {@link #readEndSequence()} is called.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @throws DecodeException
+   *           If the element cannot be decoded as a sequence.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  void readStartSequence(byte type) throws DecodeException,
+      IOException;
+
+
+
+  /**
+   * Reads the next element as a set having the Universal Set ASN.1 type
+   * tag. All further reads will read the elements in the set until
+   * {@link #readEndSet()} is called.
+   *
+   * @throws DecodeException
+   *           If the element cannot be decoded as a set.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  void readStartSet() throws DecodeException, IOException;
+
+
+
+  /**
+   * Reads the next element as a set having the provided type tag. All
+   * further reads will read the elements in the set until
+   * {@link #readEndSet()} is called.
+   *
+   * @param type
+   *          The expected type tag of the element.
+   * @throws DecodeException
+   *           If the element cannot be decoded as a set.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  void readStartSet(byte type) throws DecodeException, IOException;
+
+
+
+  /**
+   * Skips the next element without decoding it.
+   *
+   * @return A reference to this ASN.1 reader.
+   * @throws DecodeException
+   *           If the next element could not be skipped.
+   * @throws IOException
+   *           If an unexpected IO error occurred.
+   */
+  ASN1Reader skipElement() throws DecodeException, IOException;
+}
diff --git a/sdk/src/org/opends/sdk/asn1/ASN1Writer.java b/sdk/src/org/opends/sdk/asn1/ASN1Writer.java
new file mode 100644
index 0000000..dae71e0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/ASN1Writer.java
@@ -0,0 +1,401 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * An interface for encoding ASN.1 elements to a data source.
+ * <p>
+ * Methods for creating {@link ASN1Writer}s are provided in the
+ * {@link ASN1} class.
+ */
+public interface ASN1Writer extends Closeable, Flushable
+{
+
+  /**
+   * Closes this ASN.1 writer, flushing it first. Closing a previously
+   * closed ASN.1 writer has no effect. Any unfinished sequences and/or
+   * sets will be ended.
+   *
+   * @throws IOException
+   *           If an error occurs while closing.
+   */
+  void close() throws IOException;
+
+
+
+  /**
+   * Flushes this ASN.1 writer so that any buffered elements are written
+   * immediately to their intended destination. Then, if that
+   * destination is another byte stream, flush it. Thus one {@code
+   * flush()} invocation will flush all the buffers in a chain of
+   * streams.
+   * <p>
+   * If the intended destination of this stream is an abstraction
+   * provided by the underlying operating system, for example a file,
+   * then flushing the stream guarantees only that bytes previously
+   * written to the stream are passed to the operating system for
+   * writing; it does not guarantee that they are actually written to a
+   * physical device such as a disk drive.
+   *
+   * @throws IOException
+   *           If an error occurs while flushing.
+   */
+  void flush() throws IOException;
+
+
+
+  /**
+   * Writes a boolean element using the Universal Boolean ASN.1 type
+   * tag.
+   *
+   * @param value
+   *          The boolean value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeBoolean(boolean value) throws IOException;
+
+
+
+  /**
+   * Writes a boolean element using the provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @param value
+   *          The boolean value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeBoolean(byte type, boolean value) throws IOException;
+
+
+
+  /**
+   * Finishes writing a sequence element.
+   *
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   * @throws IllegalStateException
+   *           If there is no sequence being written.
+   */
+  ASN1Writer writeEndSequence() throws IOException,
+      IllegalStateException;
+
+
+
+  /**
+   * Finishes writing a set element.
+   *
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   * @throws IllegalStateException
+   *           If there is no set being written, IllegalStateException.
+   */
+  ASN1Writer writeEndSet() throws IOException;
+
+
+
+  /**
+   * Writes an enumerated element using the provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @param value
+   *          The enumerated value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeEnumerated(byte type, int value) throws IOException;
+
+
+
+  /**
+   * Writes an enumerated element using the Universal Enumerated ASN.1
+   * type tag.
+   *
+   * @param value
+   *          The enumerated value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeEnumerated(int value) throws IOException;
+
+
+
+  /**
+   * Writes an integer element using the provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @param value
+   *          The integer value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeInteger(byte type, int value) throws IOException;
+
+
+
+  /**
+   * Writes an integer element using the provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @param value
+   *          The integer value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeInteger(byte type, long value) throws IOException;
+
+
+
+  /**
+   * Writes an integer element using the Universal Integer ASN.1 type
+   * tag.
+   *
+   * @param value
+   *          The integer value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeInteger(int value) throws IOException;
+
+
+
+  /**
+   * Writes an integer element using the Universal Integer ASN.1 type
+   * tag.
+   *
+   * @param value
+   *          The integer value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeInteger(long value) throws IOException;
+
+
+
+  /**
+   * Writes a null element using the Universal Null ASN.1 type tag.
+   *
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeNull() throws IOException;
+
+
+
+  /**
+   * Writes a null element using the provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeNull(byte type) throws IOException;
+
+
+
+  /**
+   * Writes an octet string element using the provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @param value
+   *          The byte array containing the octet string data.
+   * @param offset
+   *          The offset in the byte array.
+   * @param length
+   *          The number of bytes to write.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeOctetString(byte type, byte[] value, int offset,
+      int length) throws IOException;
+
+
+
+  /**
+   * Writes an octet string element using the provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @param value
+   *          The octet string value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeOctetString(byte type, ByteSequence value)
+      throws IOException;
+
+
+
+  /**
+   * Writes a string as a UTF-8 encoded octet string element using the
+   * provided type tag.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @param value
+   *          The string to be written as a UTF-8 encoded octet string.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeOctetString(byte type, String value)
+      throws IOException;
+
+
+
+  /**
+   * Writes an octet string element using the Universal Octet String
+   * ASN.1 type tag.
+   *
+   * @param value
+   *          The byte array containing the octet string data.
+   * @param offset
+   *          The offset in the byte array.
+   * @param length
+   *          The number of bytes to write.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeOctetString(byte[] value, int offset, int length)
+      throws IOException;
+
+
+
+  /**
+   * Writes an octet string element using the Universal Octet String
+   * ASN.1 type tag.
+   *
+   * @param value
+   *          The octet string value.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeOctetString(ByteSequence value) throws IOException;
+
+
+
+  /**
+   * Writes a string as a UTF-8 encoded octet string element using the
+   * Universal Octet String ASN.1 type tag.
+   *
+   * @param value
+   *          The string to be written as a UTF-8 encoded octet string.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeOctetString(String value) throws IOException;
+
+
+
+  /**
+   * Writes a sequence element using the Universal Sequence ASN.1 type
+   * tag. All further writes will append elements to the sequence until
+   * {@link #writeEndSequence} is called.
+   *
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeStartSequence() throws IOException;
+
+
+
+  /**
+   * Writes a sequence element using the provided type tag. All further
+   * writes will append elements to the sequence until
+   * {@link #writeEndSequence} is called.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeStartSequence(byte type) throws IOException;
+
+
+
+  /**
+   * Writes a set element using the Universal Set ASN.1 type tag. All
+   * further writes will append elements to the set until
+   * {@link #writeEndSet} is called.
+   *
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeStartSet() throws IOException;
+
+
+
+  /**
+   * Writes a set element using the provided type tag. All further
+   * writes will append elements to the set until {@link #writeEndSet}
+   * is called.
+   *
+   * @param type
+   *          The type tag of the element.
+   * @return A reference to this ASN.1 writer.
+   * @throws IOException
+   *           If an error occurs while writing the element.
+   */
+  ASN1Writer writeStartSet(byte type) throws IOException;
+}
diff --git a/sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java b/sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java
new file mode 100644
index 0000000..131c9ab
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/AbstractASN1Reader.java
@@ -0,0 +1,210 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ASN1_UNEXPECTED_TAG;
+import static org.opends.sdk.asn1.ASN1Constants.*;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * An abstract {@code ASN1Reader} which can be used as the basis for
+ * implementing new ASN1 reader implementations.
+ */
+public abstract class AbstractASN1Reader implements ASN1Reader
+{
+  /**
+   * Creates a new abstract ASN.1 reader.
+   */
+  protected AbstractASN1Reader()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean readBoolean(byte type) throws IOException
+  {
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_BOOLEAN_TYPE;
+    }
+    checkType(type);
+    return readBoolean();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int readEnumerated(byte type) throws IOException
+  {
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_ENUMERATED_TYPE;
+    }
+    checkType(type);
+    return readEnumerated();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public long readInteger(byte type) throws IOException
+  {
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_INTEGER_TYPE;
+    }
+    checkType(type);
+    return readInteger();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readNull(byte type) throws IOException
+  {
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_NULL_TYPE;
+    }
+    checkType(type);
+    readNull();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString readOctetString(byte type) throws IOException
+  {
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_OCTET_STRING_TYPE;
+    }
+    checkType(type);
+    return readOctetString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteStringBuilder readOctetString(byte type,
+      ByteStringBuilder builder) throws IOException
+  {
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_OCTET_STRING_TYPE;
+    }
+    checkType(type);
+    readOctetString(builder);
+    return builder;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String readOctetStringAsString(byte type) throws IOException
+  {
+    // We could cache the UTF-8 CharSet if performance proves to be an
+    // issue.
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_OCTET_STRING_TYPE;
+    }
+    checkType(type);
+    return readOctetStringAsString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSequence(byte type) throws IOException
+  {
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_SEQUENCE_TYPE;
+    }
+    checkType(type);
+    readStartSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSet(byte type) throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    if (type == 0x00)
+    {
+      type = UNIVERSAL_SET_TYPE;
+    }
+    checkType(type);
+    readStartSet();
+  }
+
+
+
+  private void checkType(byte expectedType) throws IOException
+  {
+    if (peekType() != expectedType)
+    {
+      Message message = ERR_ASN1_UNEXPECTED_TAG.get(expectedType,
+          peekType());
+      throw DecodeException.fatalError(message);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java b/sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java
new file mode 100644
index 0000000..662043c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/asn1/AbstractASN1Writer.java
@@ -0,0 +1,157 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.asn1;
+
+
+
+import static org.opends.sdk.asn1.ASN1Constants.*;
+
+import java.io.IOException;
+
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * An abstract {@code ASN1Writer} which can be used as the basis for
+ * implementing new ASN1 writer implementations.
+ */
+public abstract class AbstractASN1Writer implements ASN1Writer
+{
+
+  /**
+   * Creates a new abstract ASN.1 writer.
+   */
+  protected AbstractASN1Writer()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeBoolean(boolean value) throws IOException
+  {
+    return writeBoolean(UNIVERSAL_BOOLEAN_TYPE, value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeEnumerated(int value) throws IOException
+  {
+    return writeEnumerated(UNIVERSAL_ENUMERATED_TYPE, value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeInteger(int value) throws IOException
+  {
+    return writeInteger(UNIVERSAL_INTEGER_TYPE, value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeInteger(long value) throws IOException
+  {
+    return writeInteger(UNIVERSAL_INTEGER_TYPE, value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeNull() throws IOException
+  {
+    return writeNull(UNIVERSAL_NULL_TYPE);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(byte[] value, int offset,
+      int length) throws IOException
+  {
+    return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value, offset,
+        length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(ByteSequence value)
+      throws IOException
+  {
+    return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(String value) throws IOException
+  {
+    return writeOctetString(UNIVERSAL_OCTET_STRING_TYPE, value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeStartSequence() throws IOException
+  {
+    return writeStartSequence(UNIVERSAL_SEQUENCE_TYPE);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeStartSet() throws IOException
+  {
+    return writeStartSet(UNIVERSAL_SET_TYPE);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java b/sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java
new file mode 100644
index 0000000..5b6bb74
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/AccountUsabilityControl.java
@@ -0,0 +1,691 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLEREQ_CONTROL_HAS_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLERES_DECODE_ERROR;
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLERES_NO_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ACCTUSABLERES_UNKNOWN_VALUE_ELEMENT_TYPE;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the Sun-defined account usable control.
+ */
+public class AccountUsabilityControl
+{
+  /**
+   * The OID for the account usable request and response controls.
+   */
+  public static final String OID_ACCOUNT_USABLE_CONTROL = "1.3.6.1.4.1.42.2.27.9.5.8";
+
+
+
+  /**
+   * This class implements the Sun-defined account usable request
+   * control. The OID for this control is 1.3.6.1.4.1.42.2.27.9.5.8, and
+   * it does not have a value.
+   */
+  public static class Request extends Control
+  {
+    public Request()
+    {
+      super(OID_ACCOUNT_USABLE_CONTROL, false);
+    }
+
+
+
+    public Request(boolean isCritical)
+    {
+      super(OID_ACCOUNT_USABLE_CONTROL, isCritical);
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      return null;
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return false;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("AccountUsableRequestControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * This class implements the account usable response control. This is
+   * a Sun-defined control with OID 1.3.6.1.4.1.42.2.27.9.5.8. The value
+   * of this control is composed according to the following BNF: <BR>
+   * 
+   * <PRE>
+   * ACCOUNT_USABLE_RESPONSE ::= CHOICE {
+   *      is_available           [0] INTEGER, -- Seconds before expiration --
+   *      is_not_available       [1] MORE_INFO }
+   * MORE_INFO ::= SEQUENCE {
+   *      inactive               [0] BOOLEAN DEFAULT FALSE,
+   *      reset                  [1] BOOLEAN DEFAULT FALSE,
+   *      expired                [2] BOOLEAN DEFAULT_FALSE,
+   *      remaining_grace        [3] INTEGER OPTIONAL,
+   *      seconds_before_unlock  [4] INTEGER OPTIONAL }
+   * </PRE>
+   */
+  public static class Response extends Control
+  {
+    // Indicates whether the user's account is usable.
+    private final boolean isUsable;
+
+    // Indicates whether the user's password is expired.
+    private final boolean isExpired;
+
+    // Indicates whether the user's account is inactive.
+    private final boolean isInactive;
+
+    // Indicates whether the user's account is currently locked.
+    private final boolean isLocked;
+
+    // Indicates whether the user's password has been reset and must be
+    // changed
+    // before anything else can be done.
+    private final boolean isReset;
+
+    // The number of remaining grace logins, if available.
+    private final int remainingGraceLogins;
+
+    // The length of time in seconds before the user's password expires,
+    // if
+    // available.
+    private final int secondsBeforeExpiration;
+
+    // The length of time before the user's account is unlocked, if
+    // available.
+    private final int secondsBeforeUnlock;
+
+
+
+    /**
+     * Creates a new account usability response control that may be used
+     * to indicate that the account is not available and provide
+     * information about the underlying reason. It will use the default
+     * OID and criticality.
+     * 
+     * @param isCritical
+     *          Indicates whether this control should be considered
+     *          critical in processing the request.
+     * @param isInactive
+     *          Indicates whether the user's account has been
+     *          inactivated by an administrator.
+     * @param isReset
+     *          Indicates whether the user's password has been reset by
+     *          an administrator.
+     * @param isExpired
+     *          Indicates whether the user's password is expired.
+     * @param remainingGraceLogins
+     *          The number of grace logins remaining. A value of zero
+     *          indicates that there are none remaining. A value of -1
+     *          indicates that grace login functionality is not enabled.
+     * @param isLocked
+     *          Indicates whether the user's account is currently locked
+     *          out.
+     * @param secondsBeforeUnlock
+     *          The length of time in seconds until the account is
+     *          unlocked. A value of -1 indicates that the account will
+     *          not be automatically unlocked and must be reset by an
+     *          administrator.
+     */
+    public Response(boolean isCritical, boolean isInactive,
+        boolean isReset, boolean isExpired, int remainingGraceLogins,
+        boolean isLocked, int secondsBeforeUnlock)
+    {
+      super(OID_ACCOUNT_USABLE_CONTROL, isCritical);
+
+      this.isInactive = isInactive;
+      this.isReset = isReset;
+      this.isExpired = isExpired;
+      this.remainingGraceLogins = remainingGraceLogins;
+      this.isLocked = isLocked;
+      this.secondsBeforeUnlock = secondsBeforeUnlock;
+
+      isUsable = false;
+      secondsBeforeExpiration = -1;
+    }
+
+
+
+    /**
+     * Creates a new account usability response control that may be used
+     * to indicate that the account is not available and provide
+     * information about the underlying reason. It will use the default
+     * OID and criticality.
+     * 
+     * @param isInactive
+     *          Indicates whether the user's account has been
+     *          inactivated by an administrator.
+     * @param isReset
+     *          Indicates whether the user's password has been reset by
+     *          an administrator.
+     * @param isExpired
+     *          Indicates whether the user's password is expired.
+     * @param remainingGraceLogins
+     *          The number of grace logins remaining. A value of zero
+     *          indicates that there are none remaining. A value of -1
+     *          indicates that grace login functionality is not enabled.
+     * @param isLocked
+     *          Indicates whether the user's account is currently locked
+     *          out.
+     * @param secondsBeforeUnlock
+     *          The length of time in seconds until the account is
+     *          unlocked. A value of -1 indicates that the account will
+     *          not be automatically unlocked and must be reset by an
+     *          administrator.
+     */
+    public Response(boolean isInactive, boolean isReset,
+        boolean isExpired, int remainingGraceLogins, boolean isLocked,
+        int secondsBeforeUnlock)
+    {
+      this(false, isInactive, isReset, isExpired, remainingGraceLogins,
+          isLocked, secondsBeforeUnlock);
+    }
+
+
+
+    /**
+     * Creates a new account usability response control that may be used
+     * to indicate that the account is available and provide the number
+     * of seconds until expiration. It will use the default OID and
+     * criticality.
+     * 
+     * @param isCritical
+     *          Indicates whether this control should be considered
+     *          critical in processing the request.
+     * @param secondsBeforeExpiration
+     *          The length of time in seconds until the user's password
+     *          expires, or -1 if the user's password will not expire or
+     *          the expiration time is unknown.
+     */
+    public Response(boolean isCritical, int secondsBeforeExpiration)
+    {
+      super(OID_ACCOUNT_USABLE_CONTROL, isCritical);
+
+      this.secondsBeforeExpiration = secondsBeforeExpiration;
+
+      isUsable = true;
+      isInactive = false;
+      isReset = false;
+      isExpired = false;
+      remainingGraceLogins = -1;
+      isLocked = false;
+      secondsBeforeUnlock = 0;
+    }
+
+
+
+    /**
+     * Creates a new account usability response control that may be used
+     * to indicate that the account is available and provide the number
+     * of seconds until expiration. It will use the default OID and
+     * criticality.
+     * 
+     * @param secondsBeforeExpiration
+     *          The length of time in seconds until the user's password
+     *          expires, or -1 if the user's password will not expire or
+     *          the expiration time is unknown.
+     */
+    public Response(int secondsBeforeExpiration)
+    {
+      this(false, secondsBeforeExpiration);
+    }
+
+
+
+    /**
+     * Retrieves the number of remaining grace logins for the user. This
+     * value is unreliable if the user's password is not expired.
+     * 
+     * @return The number of remaining grace logins for the user, or -1
+     *         if the grace logins feature is not enabled for the user.
+     */
+    public int getRemainingGraceLogins()
+    {
+      return remainingGraceLogins;
+    }
+
+
+
+    /**
+     * Retrieves the length of time in seconds before the user's
+     * password expires. This value is unreliable if the account is not
+     * available.
+     * 
+     * @return The length of time in seconds before the user's password
+     *         expires, or -1 if it is unknown or password expiration is
+     *         not enabled for the user.
+     */
+    public int getSecondsBeforeExpiration()
+    {
+      return secondsBeforeExpiration;
+    }
+
+
+
+    /**
+     * Retrieves the length of time in seconds before the user's account
+     * is automatically unlocked. This value is unreliable is the user's
+     * account is not locked.
+     * 
+     * @return The length of time in seconds before the user's account
+     *         is automatically unlocked, or -1 if it requires
+     *         administrative action to unlock the account.
+     */
+    public int getSecondsBeforeUnlock()
+    {
+      return secondsBeforeUnlock;
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writeValue(writer);
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    /**
+     * Indicates whether the user's password is expired.
+     * 
+     * @return <CODE>true</CODE> if the user's password is expired, or
+     *         <CODE>false</CODE> if not.
+     */
+    public boolean isExpired()
+    {
+      return isExpired;
+    }
+
+
+
+    /**
+     * Indicates whether the user's account has been inactivated by an
+     * administrator.
+     * 
+     * @return <CODE>true</CODE> if the user's account has been
+     *         inactivated by an administrator, or <CODE>false</CODE> if
+     *         not.
+     */
+    public boolean isInactive()
+    {
+      return isInactive;
+    }
+
+
+
+    /**
+     * Indicates whether the user's account is locked for some reason.
+     * 
+     * @return <CODE>true</CODE> if the user's account is locked, or
+     *         <CODE>false</CODE> if it is not.
+     */
+    public boolean isLocked()
+    {
+      return isLocked;
+    }
+
+
+
+    /**
+     * Indicates whether the user's password has been administratively
+     * reset and the user must change that password before any other
+     * operations will be allowed.
+     * 
+     * @return <CODE>true</CODE> if the user's password has been
+     *         administratively reset, or <CODE>false</CODE> if not.
+     */
+    public boolean isReset()
+    {
+      return isReset;
+    }
+
+
+
+    /**
+     * Indicates whether the associated user account is available for
+     * use.
+     * 
+     * @return <CODE>true</CODE> if the associated user account is
+     *         available, or <CODE>false</CODE> if not.
+     */
+    public boolean isUsable()
+    {
+      return isUsable;
+    }
+
+
+
+    /**
+     * Appends a string representation of this password policy response
+     * control to the provided buffer.
+     * 
+     * @param buffer
+     *          The buffer to which the information should be appended.
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("AccountUsableResponseControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", isUsable=");
+      buffer.append(isUsable);
+      if (isUsable)
+      {
+        buffer.append(",secondsBeforeExpiration=");
+        buffer.append(secondsBeforeExpiration);
+      }
+      else
+      {
+        buffer.append(",isInactive=");
+        buffer.append(isInactive);
+        buffer.append(",isReset=");
+        buffer.append(isReset);
+        buffer.append(",isExpired=");
+        buffer.append(isExpired);
+        buffer.append(",remainingGraceLogins=");
+        buffer.append(remainingGraceLogins);
+        buffer.append(",isLocked=");
+        buffer.append(isLocked);
+        buffer.append(",secondsBeforeUnlock=");
+        buffer.append(secondsBeforeUnlock);
+      }
+
+      buffer.append(")");
+    }
+
+
+
+    /**
+     * Writes this control's value to an ASN.1 writer.
+     * 
+     * @param writer
+     *          The ASN.1 output stream to write to.
+     * @throws IOException
+     *           If a problem occurs while writing to the stream.
+     */
+    public void writeValue(ASN1Writer writer) throws IOException
+    {
+      if (secondsBeforeExpiration < 0)
+      {
+        writer.writeInteger(TYPE_SECONDS_BEFORE_EXPIRATION,
+            secondsBeforeExpiration);
+      }
+      else
+      {
+        writer.writeStartSequence(TYPE_MORE_INFO);
+        if (isInactive)
+        {
+          writer.writeBoolean(TYPE_INACTIVE, true);
+        }
+
+        if (isReset)
+        {
+          writer.writeBoolean(TYPE_RESET, true);
+        }
+
+        if (isExpired)
+        {
+          writer.writeBoolean(TYPE_EXPIRED, true);
+
+          if (remainingGraceLogins >= 0)
+          {
+            writer.writeInteger(TYPE_REMAINING_GRACE_LOGINS,
+                remainingGraceLogins);
+          }
+        }
+
+        if (isLocked)
+        {
+          writer.writeInteger(TYPE_SECONDS_BEFORE_UNLOCK,
+              secondsBeforeUnlock);
+        }
+        writer.writeEndSequence();
+      }
+    }
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private static final class RequestDecoder implements
+      ControlDecoder<Request>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Request decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value != null)
+      {
+        Message message = ERR_ACCTUSABLEREQ_CONTROL_HAS_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      return new Request(isCritical);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_ACCOUNT_USABLE_CONTROL;
+    }
+
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class ResponseDecoder implements
+      ControlDecoder<Response>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Response decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        // The response control must always have a value.
+        Message message = ERR_ACCTUSABLERES_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      try
+      {
+        ASN1Reader reader = ASN1.getReader(value);
+        switch (reader.peekType())
+        {
+        case TYPE_SECONDS_BEFORE_EXPIRATION:
+          int secondsBeforeExpiration = (int) reader.readInteger();
+          return new Response(isCritical, secondsBeforeExpiration);
+        case TYPE_MORE_INFO:
+          boolean isInactive = false;
+          boolean isReset = false;
+          boolean isExpired = false;
+          boolean isLocked = false;
+          int remainingGraceLogins = -1;
+          int secondsBeforeUnlock = 0;
+
+          reader.readStartSequence();
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_INACTIVE))
+          {
+            isInactive = reader.readBoolean();
+          }
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_RESET))
+          {
+            isReset = reader.readBoolean();
+          }
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_EXPIRED))
+          {
+            isExpired = reader.readBoolean();
+          }
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_REMAINING_GRACE_LOGINS))
+          {
+            remainingGraceLogins = (int) reader.readInteger();
+          }
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_SECONDS_BEFORE_UNLOCK))
+          {
+            isLocked = true;
+            secondsBeforeUnlock = (int) reader.readInteger();
+          }
+          reader.readEndSequence();
+
+          return new Response(isCritical, isInactive, isReset,
+              isExpired, remainingGraceLogins, isLocked,
+              secondsBeforeUnlock);
+
+        default:
+          Message message = ERR_ACCTUSABLERES_UNKNOWN_VALUE_ELEMENT_TYPE
+              .get(byteToHex(reader.peekType()));
+          throw DecodeException.error(message);
+        }
+      }
+      catch (IOException e)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "AccountUsabilityControl.ResponseDecoder", "decode", e);
+
+        Message message = ERR_ACCTUSABLERES_DECODE_ERROR
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message);
+      }
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_ACCOUNT_USABLE_CONTROL;
+    }
+
+  }
+
+
+
+  /**
+   * The BER type to use for the seconds before expiration when the
+   * account is available.
+   */
+  private static final byte TYPE_SECONDS_BEFORE_EXPIRATION = (byte) 0x80;
+
+  /**
+   * The BER type to use for the MORE_INFO sequence when the account is
+   * not available.
+   */
+  private static final byte TYPE_MORE_INFO = (byte) 0xA1;
+
+  /**
+   * The BER type to use for the MORE_INFO element that indicates that
+   * the account has been inactivated.
+   */
+  private static final byte TYPE_INACTIVE = (byte) 0x80;
+
+  /**
+   * The BER type to use for the MORE_INFO element that indicates that
+   * the password has been administratively reset.
+   */
+  private static final byte TYPE_RESET = (byte) 0x81;
+
+  /**
+   * The BER type to use for the MORE_INFO element that indicates that
+   * the user's password is expired.
+   */
+  private static final byte TYPE_EXPIRED = (byte) 0x82;
+
+  /**
+   * The BER type to use for the MORE_INFO element that provides the
+   * number of remaining grace logins.
+   */
+  private static final byte TYPE_REMAINING_GRACE_LOGINS = (byte) 0x83;
+
+  /**
+   * The BER type to use for the MORE_INFO element that indicates that
+   * the password has been administratively reset.
+   */
+  private static final byte TYPE_SECONDS_BEFORE_UNLOCK = (byte) 0x84;
+
+  /**
+   * The Control Decoder that can be used to decode the request control.
+   */
+  public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+  /**
+   * The Control Decoder that can be used to decode the response
+   * control.
+   */
+  public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/AssertionControl.java b/sdk/src/org/opends/sdk/controls/AssertionControl.java
new file mode 100644
index 0000000..76e8dee
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/AssertionControl.java
@@ -0,0 +1,201 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_LDAPASSERT_INVALID_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_LDAPASSERT_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.Filter;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Assertion control.
+ */
+public class AssertionControl extends Control
+{
+  /**
+   * The IANA-assigned OID for the LDAP assertion control.
+   */
+  public static final String OID_LDAP_ASSERTION = "1.3.6.1.1.12";
+
+
+
+  /**
+   * Decodes a assertion control from a byte string.
+   */
+  private final static class Decoder implements
+      ControlDecoder<AssertionControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public AssertionControl decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_LDAPASSERT_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      Filter filter;
+      try
+      {
+        filter = LDAPUtils.decodeFilter(reader);
+      }
+      catch (IOException e)
+      {
+        throw DecodeException.error(
+            ERR_LDAPASSERT_INVALID_CONTROL_VALUE
+                .get(getExceptionMessage(e)), e);
+      }
+
+      return new AssertionControl(isCritical, filter);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getOID()
+    {
+      return OID_LDAP_ASSERTION;
+    }
+
+  }
+
+
+
+  /**
+   * A control decoder which can be used to decode assertion controls.
+   */
+  public static final ControlDecoder<AssertionControl> DECODER = new Decoder();
+
+  // The assertion filter.
+  private final Filter filter;
+
+
+
+  /**
+   * Creates a new assertion using the default OID and the provided
+   * criticality and assertion filter.
+   * 
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical to the operation processing.
+   * @param filter
+   *          The assertion filter.
+   */
+  public AssertionControl(boolean isCritical, Filter filter)
+  {
+    super(OID_LDAP_ASSERTION, isCritical);
+
+    Validator.ensureNotNull(filter);
+    this.filter = filter;
+  }
+
+
+
+  /**
+   * Returns the assertion filter.
+   * 
+   * @return The assertion filter.
+   */
+  public Filter getFilter()
+  {
+    return filter;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ByteString getValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+    try
+    {
+      LDAPUtils.encodeFilter(writer, filter);
+      return buffer.toByteString();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("AssertionControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", filter=\"");
+    filter.toString(buffer);
+    buffer.append("\")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java b/sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java
new file mode 100644
index 0000000..ddd3260
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/AuthorizationIdentityControl.java
@@ -0,0 +1,295 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_AUTHZIDREQ_CONTROL_HAS_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_AUTHZIDRESP_NO_CONTROL_VALUE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the authorization identity control as defined
+ * in RFC 3829.
+ */
+public class AuthorizationIdentityControl
+{
+  /**
+   * The OID for the authorization identity request control.
+   */
+  public static final String OID_AUTHZID_REQUEST = "2.16.840.1.113730.3.4.16";
+
+  /**
+   * The OID for the authorization identity response control.
+   */
+  public static final String OID_AUTHZID_RESPONSE = "2.16.840.1.113730.3.4.15";
+
+
+
+  /**
+   * This class implements the authorization identity request control as
+   * defined in RFC 3829.
+   */
+  public static class Request extends Control
+  {
+    public Request()
+    {
+      super(OID_AUTHZID_RESPONSE, false);
+    }
+
+
+
+    public Request(boolean isCritical)
+    {
+      super(OID_AUTHZID_RESPONSE, isCritical);
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      return null;
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return false;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("AuthorizationIdentityRequestControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(")");
+    }
+  }
+
+
+
+  public static class Response extends Control
+  {
+    // The authorization ID for this control.
+    private String authorizationID;
+
+
+
+    /**
+     * Creates a new authorization identity response control with the
+     * provided information.
+     * 
+     * @param isCritical
+     *          Indicates whether this control should be considered
+     *          critical in processing the request.
+     * @param authorizationDN
+     *          The authorization DN for this control.
+     */
+    public Response(boolean isCritical, DN authorizationDN)
+    {
+      super(OID_AUTHZID_REQUEST, isCritical);
+
+      Validator.ensureNotNull(authorizationDN);
+      if (authorizationDN == null)
+      {
+        this.authorizationID = "dn:";
+      }
+      else
+      {
+        this.authorizationID = "dn:" + authorizationDN.toString();
+      }
+    }
+
+
+
+    /**
+     * Creates a new authorization identity response control with the
+     * provided information.
+     * 
+     * @param isCritical
+     *          Indicates whether this control should be considered
+     *          critical in processing the request.
+     * @param authorizationID
+     *          The authorization ID for this control.
+     */
+    public Response(boolean isCritical, String authorizationID)
+    {
+      super(OID_AUTHZID_RESPONSE, isCritical);
+
+      Validator.ensureNotNull(authorizationID);
+      this.authorizationID = authorizationID;
+    }
+
+
+
+    /**
+     * Creates a new authorization identity response control with the
+     * provided information.
+     * 
+     * @param authorizationDN
+     *          The authorization DN for this control.
+     */
+    public Response(DN authorizationDN)
+    {
+      this(false, authorizationDN);
+    }
+
+
+
+    /**
+     * Creates a new authorization identity response control with the
+     * provided information.
+     * 
+     * @param authorizationID
+     *          The authorization ID for this control.
+     */
+    public Response(String authorizationID)
+    {
+      this(false, authorizationID);
+    }
+
+
+
+    /**
+     * Retrieves the authorization ID for this authorization identity
+     * response control.
+     * 
+     * @return The authorization ID for this authorization identity
+     *         response control.
+     */
+    public String getAuthorizationID()
+    {
+      return authorizationID;
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      return ByteString.valueOf(authorizationID);
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    /**
+     * Appends a string representation of this authorization identity
+     * response control to the provided buffer.
+     * 
+     * @param buffer
+     *          The buffer to which the information should be appended.
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("AuthorizationIdentityResponseControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", authzID=\"");
+      buffer.append(authorizationID);
+      buffer.append("\")");
+    }
+
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private static final class RequestDecoder implements
+      ControlDecoder<Request>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Request decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value != null)
+      {
+        Message message = ERR_AUTHZIDREQ_CONTROL_HAS_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      return new Request(isCritical);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_AUTHZID_REQUEST;
+    }
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class ResponseDecoder implements
+      ControlDecoder<Response>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Response decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_AUTHZIDRESP_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      String authID = value.toString();
+      return new Response(isCritical, authID);
+
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_AUTHZID_RESPONSE;
+    }
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode the request control.
+   */
+  public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+  /**
+   * The Control Decoder that can be used to decode the response
+   * control.
+   */
+  public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/Control.java b/sdk/src/org/opends/sdk/controls/Control.java
new file mode 100644
index 0000000..5f9af74
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/Control.java
@@ -0,0 +1,80 @@
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 29, 2009 Time:
+ * 10:59:19 AM To change this template use File | Settings | File
+ * Templates.
+ */
+public abstract class Control
+{
+  // The criticality for this control.
+  protected final boolean isCritical;
+
+  // The OID for this control.
+  protected final String oid;
+
+
+
+  public Control(String oid, boolean isCritical)
+  {
+    this.isCritical = isCritical;
+    this.oid = oid;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this control.
+   * 
+   * @return The OID for this control.
+   */
+  public String getOID()
+  {
+    return oid;
+  }
+
+
+
+  public abstract ByteString getValue();
+
+
+
+  public abstract boolean hasValue();
+
+
+
+  /**
+   * Indicates whether this control should be considered critical in
+   * processing the request.
+   * 
+   * @return <CODE>true</CODE> if this code should be considered
+   *         critical, or <CODE>false</CODE> if not.
+   */
+  public boolean isCritical()
+  {
+    return isCritical;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder buffer = new StringBuilder();
+    toString(buffer);
+    return buffer.toString();
+  }
+
+
+
+  public abstract void toString(StringBuilder buffer);
+}
diff --git a/sdk/src/org/opends/sdk/controls/ControlDecoder.java b/sdk/src/org/opends/sdk/controls/ControlDecoder.java
new file mode 100644
index 0000000..f853361
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ControlDecoder.java
@@ -0,0 +1,72 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An interface for decoding controls.
+ *
+ * @param <T>
+ *          The type of control decoded by this decoder.
+ */
+public interface ControlDecoder<T extends Control>
+{
+
+  /**
+   * Decodes the provided control.
+   *
+   * @param isCritical
+   *          Indicates whether the control should be considered
+   *          critical.
+   * @param value
+   *          The value for the control.
+   * @param schema
+   *          The schema which should be used when decoding the control,
+   *          if required.
+   * @return The decoded control.
+   * @throws DecodeException
+   *           If the control could not be decoded.
+   */
+  T decode(boolean isCritical, ByteString value, Schema schema)
+      throws DecodeException;
+
+
+
+  /**
+   * Gets the OID of the control decoded by this decoded.
+   *
+   * @return The OID of the control decoded by this decoded.
+   */
+  String getOID();
+}
diff --git a/sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java b/sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java
new file mode 100644
index 0000000..90b9c02
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/EntryChangeNotificationControl.java
@@ -0,0 +1,396 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ECN_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_ILLEGAL_PREVIOUS_DN;
+import static org.opends.messages.ProtocolMessages.ERR_ECN_NO_CONTROL_VALUE;
+import static org.opends.sdk.asn1.ASN1Constants.UNIVERSAL_INTEGER_TYPE;
+import static org.opends.sdk.asn1.ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the entry change notification control defined
+ * in draft-ietf-ldapext-psearch. It may be included in entries returned
+ * in response to a persistent search operation.
+ */
+public class EntryChangeNotificationControl extends Control
+{
+  /**
+   * The OID for the entry change notification control.
+   */
+  public static final String OID_ENTRY_CHANGE_NOTIFICATION = "2.16.840.1.113730.3.4.7";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class Decoder implements
+      ControlDecoder<EntryChangeNotificationControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public EntryChangeNotificationControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_ECN_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      String previousDN = null;
+      long changeNumber = -1;
+      PersistentSearchChangeType changeType;
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+        changeType = PersistentSearchChangeType.valueOf(reader
+            .readEnumerated());
+
+        if (reader.hasNextElement()
+            && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+        {
+          if (changeType != PersistentSearchChangeType.MODIFY_DN)
+          {
+            Message message = ERR_ECN_ILLEGAL_PREVIOUS_DN.get(String
+                .valueOf(changeType));
+            throw DecodeException.error(message);
+          }
+
+          previousDN = reader.readOctetStringAsString();
+        }
+        if (reader.hasNextElement()
+            && (reader.peekType() == UNIVERSAL_INTEGER_TYPE))
+        {
+          changeNumber = reader.readInteger();
+        }
+      }
+      catch (IOException e)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "EntryChangeNotificationControl.Decoder", "decode", e);
+
+        Message message = ERR_ECN_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+
+      return new EntryChangeNotificationControl(isCritical, changeType,
+          previousDN, changeNumber);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_ENTRY_CHANGE_NOTIFICATION;
+    }
+
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<EntryChangeNotificationControl> DECODER = new Decoder();
+
+  // The previous DN for this change notification control.
+  private String previousDN;
+
+  // The change number for this change notification control.
+  private long changeNumber;
+
+  // The change type for this change notification control.
+  private final PersistentSearchChangeType changeType;
+
+
+
+  /**
+   * Creates a new entry change notification control with the provided
+   * information.
+   * 
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical in processing the request.
+   * @param changeType
+   *          The change type for this change notification control.
+   */
+  public EntryChangeNotificationControl(boolean isCritical,
+      PersistentSearchChangeType changeType)
+  {
+    super(OID_ENTRY_CHANGE_NOTIFICATION, isCritical);
+
+    Validator.ensureNotNull(changeType);
+    this.changeType = changeType;
+
+    previousDN = null;
+  }
+
+
+
+  /**
+   * Creates a new entry change notification control with the provided
+   * information.
+   * 
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical in processing the request.
+   * @param changeType
+   *          The change type for this change notification control.
+   * @param previousDN
+   *          The DN that the entry had prior to a modify DN operation,
+   *          or <CODE>null</CODE> if the operation was not a modify DN.
+   * @param changeNumber
+   *          The change number for the associated change, or a negative
+   *          value if no change number is available.
+   */
+  public EntryChangeNotificationControl(boolean isCritical,
+      PersistentSearchChangeType changeType, DN previousDN,
+      long changeNumber)
+  {
+    super(OID_ENTRY_CHANGE_NOTIFICATION, isCritical);
+
+    Validator.ensureNotNull(changeType);
+    this.changeType = changeType;
+
+    if (previousDN != null)
+    {
+      this.previousDN = previousDN.toString();
+    }
+    this.changeNumber = changeNumber;
+  }
+
+
+
+  /**
+   * Creates a new entry change notification control with the provided
+   * information.
+   * 
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical in processing the request.
+   * @param changeType
+   *          The change type for this change notification control.
+   * @param previousDN
+   *          The DN that the entry had prior to a modify DN operation,
+   *          or <CODE>null</CODE> if the operation was not a modify DN.
+   * @param changeNumber
+   *          The change number for the associated change, or a negative
+   *          value if no change number is available.
+   */
+  public EntryChangeNotificationControl(boolean isCritical,
+      PersistentSearchChangeType changeType, String previousDN,
+      long changeNumber)
+  {
+    super(OID_ENTRY_CHANGE_NOTIFICATION, isCritical);
+
+    Validator.ensureNotNull(changeType);
+    this.changeType = changeType;
+    this.previousDN = previousDN;
+    this.changeNumber = changeNumber;
+  }
+
+
+
+  /**
+   * Creates a new entry change notification control with the provided
+   * information.
+   * 
+   * @param changeType
+   *          The change type for this change notification control.
+   */
+  public EntryChangeNotificationControl(
+      PersistentSearchChangeType changeType)
+  {
+    this(false, changeType);
+  }
+
+
+
+  /**
+   * Creates a new entry change notification control with the provided
+   * information.
+   * 
+   * @param changeType
+   *          The change type for this change notification control.
+   * @param previousDN
+   *          The DN that the entry had prior to a modify DN operation,
+   *          or <CODE>null</CODE> if the operation was not a modify DN.
+   * @param changeNumber
+   *          The change number for the associated change, or a negative
+   *          value if no change number is available.
+   */
+  public EntryChangeNotificationControl(
+      PersistentSearchChangeType changeType, DN previousDN,
+      long changeNumber)
+  {
+    this(false, changeType, previousDN, changeNumber);
+  }
+
+
+
+  /**
+   * Creates a new entry change notification control with the provided
+   * information.
+   * 
+   * @param changeType
+   *          The change type for this change notification control.
+   * @param previousDN
+   *          The DN that the entry had prior to a modify DN operation,
+   *          or <CODE>null</CODE> if the operation was not a modify DN.
+   * @param changeNumber
+   *          The change number for the associated change, or a negative
+   *          value if no change number is available.
+   */
+  public EntryChangeNotificationControl(
+      PersistentSearchChangeType changeType, String previousDN,
+      long changeNumber)
+  {
+    this(false, changeType, previousDN, changeNumber);
+  }
+
+
+
+  /**
+   * Retrieves the change number for this entry change notification
+   * control.
+   * 
+   * @return The change number for this entry change notification
+   *         control, or a negative value if no change number is
+   *         available.
+   */
+  public long getChangeNumber()
+  {
+    return changeNumber;
+  }
+
+
+
+  /**
+   * Retrieves the change type for this entry change notification
+   * control.
+   * 
+   * @return The change type for this entry change notification control.
+   */
+  public PersistentSearchChangeType getChangeType()
+  {
+    return changeType;
+  }
+
+
+
+  /**
+   * Retrieves the previous DN for this entry change notification
+   * control.
+   * 
+   * @return The previous DN for this entry change notification control,
+   *         or <CODE>null</CODE> if there is none.
+   */
+  public String getPreviousDN()
+  {
+    return previousDN;
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+    try
+    {
+      writeValue(writer);
+      return buffer.toByteString();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Appends a string representation of this entry change notification
+   * control to the provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("EntryChangeNotificationControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", changeType=");
+    buffer.append(changeType.toString());
+    buffer.append(", previousDN=\"");
+    buffer.append(previousDN);
+    buffer.append("\"");
+    buffer.append(", changeNumber=");
+    buffer.append(changeNumber);
+    buffer.append(")");
+  }
+
+
+
+  /**
+   * Writes this control's value to an ASN.1 writer. The value (if any)
+   * must be written as an ASN1OctetString.
+   * 
+   * @param writer
+   *          The ASN.1 output stream to write to.
+   * @throws java.io.IOException
+   *           If a problem occurs while writing to the stream.
+   */
+  public void writeValue(ASN1Writer writer) throws IOException
+  {
+    writer.writeStartSequence();
+    writer.writeInteger(changeType.intValue());
+
+    if (previousDN != null)
+    {
+      writer.writeOctetString(previousDN);
+    }
+
+    if (changeNumber > 0)
+    {
+      writer.writeInteger(changeNumber);
+    }
+    writer.writeEndSequence();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/GenericControl.java b/sdk/src/org/opends/sdk/controls/GenericControl.java
new file mode 100644
index 0000000..019648d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/GenericControl.java
@@ -0,0 +1,157 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * A generic raw request. A raw control is a control whose parameters
+ * have not been fully decoded.
+ * <p>
+ * TODO: this should be hooked into the remaining controls class
+ * hierarchy.
+ * <p>
+ * TODO: push ASN1 encoding into ASN1 package.
+ */
+public final class GenericControl extends Control
+{
+
+  // The control value.
+  private final ByteString value;
+
+
+
+  /**
+   * Creates a new control with the specified OID. It will not be
+   * critical, and will not have a value.
+   * 
+   * @param oid
+   *          The OID for this control.
+   */
+  public GenericControl(String oid)
+  {
+    this(oid, false, null);
+  }
+
+
+
+  /**
+   * Creates a new raw control with the specified OID and criticality.
+   * It will not have a value.
+   * 
+   * @param oid
+   *          The OID for this control.
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical.
+   */
+  public GenericControl(String oid, boolean isCritical)
+  {
+    this(oid, isCritical, null);
+  }
+
+
+
+  /**
+   * Creates a new raw control with the specified OID, criticality, and
+   * value.
+   * 
+   * @param oid
+   *          The OID for this control.
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical.
+   * @param value
+   *          The value for this control.
+   */
+  public GenericControl(String oid, boolean isCritical, ByteString value)
+  {
+    super(oid, isCritical);
+    this.value = value;
+  }
+
+
+
+  /**
+   * Retrieves the value for this control.
+   * 
+   * @return The value for this control, or <CODE>null</CODE> if there
+   *         is no value.
+   */
+  @Override
+  public ByteString getValue()
+  {
+    return value;
+  }
+
+
+
+  /**
+   * Indicates whether this control has a value.
+   * 
+   * @return <CODE>true</CODE> if this control has a value, or
+   *         <CODE>false</CODE> if it does not.
+   */
+  @Override
+  public boolean hasValue()
+  {
+    return (value != null);
+  }
+
+
+
+  /**
+   * Appends a string representation of this control to the provided
+   * buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("Control(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+
+    if (value != null)
+    {
+      buffer.append(", value=");
+      StaticUtils.toHexPlusAscii(value, buffer, 4);
+    }
+
+    buffer.append(")");
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java b/sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java
new file mode 100644
index 0000000..cbce065
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/GetEffectiveRightsRequestControl.java
@@ -0,0 +1,271 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_GETEFFECTIVERIGHTS_DECODE_ERROR;
+import static org.opends.messages.ProtocolMessages.INFO_GETEFFECTIVERIGHTS_INVALID_AUTHZID;
+
+import java.io.IOException;
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class partially implements the geteffectiverights control as
+ * defined
+ * in draft-ietf-ldapext-acl-model-08.txt. The main differences are:
+ * - The response control is not supported. Instead the dseecompat
+ * geteffectiverights control implementation creates attributes
+ * containing
+ * right information strings and adds those attributes to the
+ * entry being returned. The attribute type names are dynamically
+ * created;
+ * see the dseecompat's AciGetEffectiveRights class for details.
+ * - The dseecompat implementation allows additional attribute types
+ * in the request control for which rights information can be returned.
+ * These are known as the specified attribute types.
+ * The dseecompat request control value is the following: <BR>
+ * 
+ * <PRE>
+ *  GetRightsControl ::= SEQUENCE {
+ *    authzId    authzId
+ *    attributes  SEQUENCE OF AttributeType
+ *  }
+ *   -- Only the "dn:DN form is supported.
+ * 
+ * </PRE>
+ **/
+public class GetEffectiveRightsRequestControl extends Control
+{
+  /**
+   * The OID for the get effective rights control.
+   */
+  public static final String OID_GET_EFFECTIVE_RIGHTS = "1.3.6.1.4.1.42.2.27.9.5.2";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private static final class Decoder implements
+      ControlDecoder<GetEffectiveRightsRequestControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public GetEffectiveRightsRequestControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      // If the value is null create a GetEffectiveRightsRequestControl
+      // class with null authzDN and attribute list, else try to
+      // decode the value.
+      if (value == null)
+        return new GetEffectiveRightsRequestControl(isCritical,
+            (String) null);
+      else
+      {
+        ASN1Reader reader = ASN1.getReader(value);
+        String authzDN;
+        List<String> attrs = Collections.emptyList();
+        try
+        {
+          reader.readStartSequence();
+          String authzIDString = reader.readOctetStringAsString();
+          String lowerAuthzIDString = authzIDString.toLowerCase();
+          // Make sure authzId starts with "dn:" and is a valid DN.
+          if (lowerAuthzIDString.startsWith("dn:"))
+            authzDN = authzIDString.substring(3);
+          else
+          {
+            Message message = INFO_GETEFFECTIVERIGHTS_INVALID_AUTHZID
+                .get(lowerAuthzIDString);
+            throw DecodeException.error(message);
+          }
+          // There is an sequence containing an attribute list, try to
+          // decode it.
+          if (reader.hasNextElement())
+          {
+            attrs = new LinkedList<String>();
+            reader.readStartSequence();
+            while (reader.hasNextElement())
+            {
+              // Decode as an octet string.
+              attrs.add(reader.readOctetStringAsString());
+            }
+            reader.readEndSequence();
+          }
+          reader.readEndSequence();
+        }
+        catch (IOException e)
+        {
+          Message message = INFO_GETEFFECTIVERIGHTS_DECODE_ERROR.get(e
+              .getMessage());
+          throw DecodeException.error(message);
+        }
+
+        return new GetEffectiveRightsRequestControl(isCritical,
+            authzDN, attrs);
+      }
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_GET_EFFECTIVE_RIGHTS;
+    }
+
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<GetEffectiveRightsRequestControl> DECODER = new Decoder();
+
+  // The raw DN representing the authzId
+  private String authorizationDN = null;
+
+  // The raw DN representing the authzId
+  private List<String> attributes = null;
+
+
+
+  public GetEffectiveRightsRequestControl(boolean isCritical,
+      String authorizationDN, String... attributes)
+  {
+    super(OID_GET_EFFECTIVE_RIGHTS, isCritical);
+
+    this.authorizationDN = authorizationDN;
+    if (attributes != null)
+    {
+      this.attributes = new ArrayList<String>(attributes.length);
+      this.attributes.addAll(Arrays.asList(attributes));
+    }
+    else
+    {
+      this.attributes = Collections.emptyList();
+    }
+  }
+
+
+
+  public GetEffectiveRightsRequestControl(boolean isCritical,
+      DN authorizationDN, AttributeType... attributes)
+  {
+    super(OID_GET_EFFECTIVE_RIGHTS, isCritical);
+
+    Validator.ensureNotNull(authorizationDN, attributes);
+
+    this.authorizationDN = authorizationDN.toString();
+
+    if (attributes != null)
+    {
+      for (AttributeType attr : attributes)
+      {
+        this.attributes = new ArrayList<String>(attributes.length);
+        this.attributes.add(attr.getNameOrOID());
+      }
+    }
+    else
+    {
+      this.attributes = Collections.emptyList();
+    }
+  }
+
+
+
+  private GetEffectiveRightsRequestControl(boolean isCritical,
+      String authorizationDN, List<String> attributes)
+  {
+    super(OID_GET_EFFECTIVE_RIGHTS, isCritical);
+
+    Validator.ensureNotNull(authorizationDN, attributes);
+
+    this.authorizationDN = authorizationDN;
+    this.attributes = attributes;
+  }
+
+
+
+  public ByteString getValue()
+  {
+    if (authorizationDN == null && attributes.isEmpty())
+    {
+      return ByteString.empty();
+    }
+
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+    try
+    {
+      writer.writeStartSequence();
+      if (authorizationDN != null)
+      {
+        writer.writeOctetString("dn:" + authorizationDN);
+      }
+
+      if (!attributes.isEmpty())
+      {
+        writer.writeStartSequence();
+        for (String attr : attributes)
+        {
+          writer.writeOctetString(attr);
+        }
+        writer.writeEndSequence();
+      }
+      writer.writeEndSequence();
+      return buffer.toByteString();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+  }
+
+
+
+  public boolean hasValue()
+  {
+    return authorizationDN != null || !attributes.isEmpty();
+  }
+
+
+
+  /**
+   * Appends a string representation of this proxied auth v2 control to
+   * the provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("GetEffectiveRightsRequestControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", authorizationDN=\"");
+    buffer.append(authorizationDN);
+    buffer.append("\"");
+    buffer.append(", attributes=(");
+    buffer.append(attributes);
+    buffer.append("))");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/MatchedValuesControl.java b/sdk/src/org/opends/sdk/controls/MatchedValuesControl.java
new file mode 100644
index 0000000..8162bc0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/MatchedValuesControl.java
@@ -0,0 +1,351 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.AbstractFilterVisitor;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.Filter;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * This class implements the matched values control as defined in RFC
+ * 3876. It may be included in a search request to indicate that only
+ * attribute values matching one or more filters contained in the
+ * matched values control should be returned to the client.
+ */
+public class MatchedValuesControl extends Control
+{
+  /**
+   * The OID for the matched values control used to specify which
+   * particular attribute values should be returned in a search result
+   * entry.
+   */
+  public static final String OID_MATCHED_VALUES = "1.2.826.0.1.3344810.2.3";
+
+
+
+  /**
+   * Visitor for validating matched values filters.
+   */
+  private static final class FilterValidator extends
+      AbstractFilterVisitor<LocalizedIllegalArgumentException, Filter>
+  {
+
+    @Override
+    public LocalizedIllegalArgumentException visitAndFilter(Filter p,
+        List<Filter> subFilters)
+    {
+      Message message = ERR_MVFILTER_BAD_FILTER_AND.get(p.toString());
+      return new LocalizedIllegalArgumentException(message);
+    }
+
+
+
+    @Override
+    public LocalizedIllegalArgumentException visitExtensibleMatchFilter(
+        Filter p, String matchingRule, String attributeDescription,
+        ByteSequence assertionValue, boolean dnAttributes)
+    {
+      if (dnAttributes)
+      {
+        Message message = ERR_MVFILTER_BAD_FILTER_EXT.get(p.toString());
+        return new LocalizedIllegalArgumentException(message);
+      }
+      else
+      {
+        return null;
+      }
+    }
+
+
+
+    @Override
+    public LocalizedIllegalArgumentException visitNotFilter(Filter p,
+        Filter subFilter)
+    {
+      Message message = ERR_MVFILTER_BAD_FILTER_NOT.get(p.toString());
+      return new LocalizedIllegalArgumentException(message);
+    }
+
+
+
+    @Override
+    public LocalizedIllegalArgumentException visitOrFilter(Filter p,
+        List<Filter> subFilters)
+    {
+      Message message = ERR_MVFILTER_BAD_FILTER_OR.get(p.toString());
+      return new LocalizedIllegalArgumentException(message);
+    }
+
+
+
+    @Override
+    public LocalizedIllegalArgumentException visitUnrecognizedFilter(
+        Filter p, byte filterTag, ByteSequence filterBytes)
+    {
+      Message message = ERR_MVFILTER_BAD_FILTER_UNRECOGNIZED.get(p
+          .toString(), filterTag);
+      return new LocalizedIllegalArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Decodes a matched values control from a byte string.
+   */
+  private final static class Decoder implements
+      ControlDecoder<MatchedValuesControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public MatchedValuesControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_MATCHEDVALUES_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+        if (!reader.hasNextElement())
+        {
+          Message message = ERR_MATCHEDVALUES_NO_FILTERS.get();
+          throw DecodeException.error(message);
+        }
+
+        LinkedList<Filter> filters = new LinkedList<Filter>();
+        do
+        {
+          Filter filter = LDAPUtils.decodeFilter(reader);
+
+          try
+          {
+            validateFilter(filter);
+          }
+          catch (LocalizedIllegalArgumentException e)
+          {
+            throw DecodeException
+                .error(e.getMessageObject());
+          }
+
+          filters.add(filter);
+        } while (reader.hasNextElement());
+
+        reader.readEndSequence();
+
+        return new MatchedValuesControl(isCritical, Collections
+            .unmodifiableList(filters));
+      }
+      catch (IOException e)
+      {
+        StaticUtils.DEBUG_LOG.throwing("MatchedValuesControl.Decoder",
+            "decode", e);
+
+        Message message = ERR_MATCHEDVALUES_CANNOT_DECODE_VALUE_AS_SEQUENCE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getOID()
+    {
+      return OID_MATCHED_VALUES;
+    }
+
+  }
+
+
+
+  /**
+   * A control decoder which can be used to decode matched values
+   * controls.
+   */
+  public static final ControlDecoder<MatchedValuesControl> DECODER = new Decoder();
+
+  private static final FilterValidator FILTER_VALIDATOR = new FilterValidator();
+
+
+
+  private static void validateFilter(final Filter filter)
+      throws LocalizedIllegalArgumentException
+  {
+    LocalizedIllegalArgumentException e = filter.accept(
+        FILTER_VALIDATOR, filter);
+    if (e != null)
+    {
+      throw e;
+    }
+  }
+
+
+
+  private List<Filter> filters;
+
+
+
+  /**
+   * Creates a new matched values control using the default OID and the
+   * provided criticality and set of filters.
+   * 
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical to the operation processing.
+   * @param filters
+   *          The list of matched value filters.
+   * @throws LocalizedIllegalArgumentException
+   *           If one of the filters is not permitted by the matched
+   *           values control.
+   */
+  public MatchedValuesControl(boolean isCritical, Filter... filters)
+      throws LocalizedIllegalArgumentException
+  {
+    super(OID_MATCHED_VALUES, isCritical);
+
+    Validator.ensureNotNull((Object) filters);
+    Validator.ensureTrue(filters.length > 0, "filters is empty");
+
+    if (filters.length == 1)
+    {
+      validateFilter(filters[0]);
+      this.filters = Collections.singletonList(filters[0]);
+    }
+    else
+    {
+      LinkedList<Filter> list = new LinkedList<Filter>();
+      for (Filter filter : filters)
+      {
+        validateFilter(filter);
+        list.add(filter);
+      }
+      this.filters = Collections.unmodifiableList(list);
+    }
+  }
+
+
+
+  private MatchedValuesControl(boolean isCritical, List<Filter> filters)
+  {
+    super(OID_MATCHED_VALUES, isCritical);
+    this.filters = filters;
+  }
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the list of filters
+   * associated with this matched values control.
+   * 
+   * @return An {@code Iterable} containing the list of filters.
+   */
+  public Iterable<Filter> getFilters()
+  {
+    return filters;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public ByteString getValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+    try
+    {
+      writer.writeStartSequence();
+      for (Filter f : filters)
+      {
+        LDAPUtils.encodeFilter(writer, f);
+      }
+      writer.writeEndSequence();
+      return buffer.toByteString();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("MatchingValuesControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PagedResultsControl.java b/sdk/src/org/opends/sdk/controls/PagedResultsControl.java
new file mode 100644
index 0000000..fc01e7c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PagedResultsControl.java
@@ -0,0 +1,258 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_COOKIE;
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_NULL;
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_SEQUENCE;
+import static org.opends.messages.ProtocolMessages.ERR_LDAP_PAGED_RESULTS_DECODE_SIZE;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class represents a paged results control value as defined in RFC
+ * 2696. The searchControlValue is an OCTET STRING wrapping the
+ * BER-encoded version of the following SEQUENCE: realSearchControlValue
+ * ::= SEQUENCE { size INTEGER (0..maxInt), -- requested page size from
+ * client -- result set size estimate from server cookie OCTET STRING }
+ */
+public class PagedResultsControl extends Control
+{
+  /**
+   * The OID for the paged results control defined in RFC 2696.
+   */
+  public static final String OID_PAGED_RESULTS_CONTROL = "1.2.840.113556.1.4.319";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class Decoder implements
+      ControlDecoder<PagedResultsControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public PagedResultsControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_LDAP_PAGED_RESULTS_DECODE_NULL.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+      }
+      catch (Exception e)
+      {
+        StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+            "decode", e);
+
+        Message message = ERR_LDAP_PAGED_RESULTS_DECODE_SEQUENCE
+            .get(String.valueOf(e));
+        throw DecodeException.error(message, e);
+      }
+
+      int size;
+      try
+      {
+        size = (int) reader.readInteger();
+      }
+      catch (Exception e)
+      {
+        StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+            "decode", e);
+
+        Message message = ERR_LDAP_PAGED_RESULTS_DECODE_SIZE.get(String
+            .valueOf(e));
+        throw DecodeException.error(message, e);
+      }
+
+      ByteString cookie;
+      try
+      {
+        cookie = reader.readOctetString();
+      }
+      catch (Exception e)
+      {
+        StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+            "decode", e);
+
+        Message message = ERR_LDAP_PAGED_RESULTS_DECODE_COOKIE
+            .get(String.valueOf(e));
+        throw DecodeException.error(message, e);
+      }
+
+      try
+      {
+        reader.readEndSequence();
+      }
+      catch (Exception e)
+      {
+        StaticUtils.DEBUG_LOG.throwing("PagedResultsControl.Decoder",
+            "decode", e);
+
+        Message message = ERR_LDAP_PAGED_RESULTS_DECODE_SEQUENCE
+            .get(String.valueOf(e));
+        throw DecodeException.error(message, e);
+      }
+
+      return new PagedResultsControl(isCritical, size, cookie);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_PAGED_RESULTS_CONTROL;
+    }
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<PagedResultsControl> DECODER = new Decoder();
+
+  /**
+   * The control value size element, which is either the requested page
+   * size from the client, or the result set size estimate from the
+   * server.
+   */
+  private final int size;
+
+  /**
+   * The control value cookie element.
+   */
+  private final ByteString cookie;
+
+
+
+  /**
+   * Creates a new paged results control with the specified information.
+   * 
+   * @param isCritical
+   *          Indicates whether this control should be considered
+   *          critical in processing the request.
+   * @param size
+   *          The size element.
+   * @param cookie
+   *          The cookie element.
+   */
+  public PagedResultsControl(boolean isCritical, int size,
+      ByteString cookie)
+  {
+    super(OID_PAGED_RESULTS_CONTROL, isCritical);
+
+    Validator.ensureNotNull(cookie);
+    this.size = size;
+    this.cookie = cookie;
+  }
+
+
+
+  /**
+   * Creates a new paged results control with the specified information.
+   * 
+   * @param size
+   *          The size element.
+   * @param cookie
+   *          The cookie element.
+   */
+  public PagedResultsControl(int size, ByteString cookie)
+  {
+    this(false, size, cookie);
+  }
+
+
+
+  /**
+   * Get the control value cookie element.
+   * 
+   * @return The control value cookie element.
+   */
+  public ByteString getCookie()
+  {
+    return cookie;
+  }
+
+
+
+  /**
+   * Get the control value size element, which is either the requested
+   * page size from the client, or the result set size estimate from the
+   * server.
+   * 
+   * @return The control value size element.
+   */
+  public int getSize()
+  {
+    return size;
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+    try
+    {
+      writer.writeStartSequence();
+      writer.writeInteger(size);
+      writer.writeOctetString(cookie);
+      writer.writeEndSequence();
+      return buffer.toByteString();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("PagedResultsControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", size=");
+    buffer.append(size);
+    buffer.append(", cookie=");
+    buffer.append(cookie);
+    buffer.append(")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java b/sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java
new file mode 100644
index 0000000..a84c432
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordExpiredControl.java
@@ -0,0 +1,137 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PWEXPIRED_CONTROL_INVALID_VALUE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the Netscape password expired control. The
+ * value for this control should be a string that indicates the length
+ * of time until the password expires, but because it is already expired
+ * it will always be "0".
+ */
+public class PasswordExpiredControl extends Control
+{
+  /**
+   * The OID for the Netscape password expired control.
+   */
+  public static final String OID_NS_PASSWORD_EXPIRED = "2.16.840.1.113730.3.4.4";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class Decoder implements
+      ControlDecoder<PasswordExpiredControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public PasswordExpiredControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (value != null)
+      {
+        try
+        {
+          Integer.parseInt(value.toString());
+        }
+        catch (Exception e)
+        {
+          Message message = ERR_PWEXPIRED_CONTROL_INVALID_VALUE.get();
+          throw DecodeException.error(message);
+        }
+      }
+
+      return new PasswordExpiredControl(isCritical);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_NS_PASSWORD_EXPIRED;
+    }
+
+  }
+
+
+
+  private final static ByteString CONTROL_VALUE = ByteString
+      .valueOf("0");
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<PasswordExpiredControl> DECODER = new Decoder();
+
+
+
+  /**
+   * Creates a new instance of the password expired control with the
+   * default settings.
+   */
+  public PasswordExpiredControl()
+  {
+    this(false);
+  }
+
+
+
+  /**
+   * Creates a new instance of the password expired control with the
+   * provided information.
+   * 
+   * @param isCritical
+   *          Indicates whether support for this control should be
+   *          considered a critical part of the client processing.
+   */
+  public PasswordExpiredControl(boolean isCritical)
+  {
+    super(OID_NS_PASSWORD_EXPIRED, isCritical);
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    return CONTROL_VALUE;
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Appends a string representation of this password expired control to
+   * the provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("PasswordExpiredControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java b/sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java
new file mode 100644
index 0000000..b5c24bf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordExpiringControl.java
@@ -0,0 +1,175 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PWEXPIRING_CANNOT_DECODE_SECONDS_UNTIL_EXPIRATION;
+import static org.opends.messages.ProtocolMessages.ERR_PWEXPIRING_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the Netscape password expiring control, which
+ * serves as a warning to clients that the user's password is about to
+ * expire. The only element contained in the control value is a string
+ * representation of the number of seconds until expiration.
+ */
+public class PasswordExpiringControl extends Control
+{
+  /**
+   * The OID for the Netscape password expiring control.
+   */
+  public static final String OID_NS_PASSWORD_EXPIRING = "2.16.840.1.113730.3.4.5";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class Decoder implements
+      ControlDecoder<PasswordExpiringControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public PasswordExpiringControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_PWEXPIRING_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      int secondsUntilExpiration;
+      try
+      {
+        secondsUntilExpiration = Integer.parseInt(value.toString());
+      }
+      catch (Exception e)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "PasswordExpiringControl.Decoder", "decode", e);
+
+        Message message = ERR_PWEXPIRING_CANNOT_DECODE_SECONDS_UNTIL_EXPIRATION
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message);
+      }
+
+      return new PasswordExpiringControl(isCritical,
+          secondsUntilExpiration);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_NS_PASSWORD_EXPIRING;
+    }
+
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<PasswordExpiringControl> DECODER = new Decoder();
+
+  // The length of time in seconds until the password actually expires.
+  private final int secondsUntilExpiration;
+
+
+
+  /**
+   * Creates a new instance of the password expiring control with the
+   * provided information.
+   * 
+   * @param isCritical
+   *          Indicates whether support for this control should be
+   *          considered a critical part of the client processing.
+   * @param secondsUntilExpiration
+   *          The length of time in seconds until the password actually
+   *          expires.
+   */
+  public PasswordExpiringControl(boolean isCritical,
+      int secondsUntilExpiration)
+  {
+    super(OID_NS_PASSWORD_EXPIRING, isCritical);
+
+    this.secondsUntilExpiration = secondsUntilExpiration;
+  }
+
+
+
+  /**
+   * Creates a new instance of the password expiring control with the
+   * provided information.
+   * 
+   * @param secondsUntilExpiration
+   *          The length of time in seconds until the password actually
+   *          expires.
+   */
+  public PasswordExpiringControl(int secondsUntilExpiration)
+  {
+    this(false, secondsUntilExpiration);
+  }
+
+
+
+  /**
+   * Retrieves the length of time in seconds until the password actually
+   * expires.
+   * 
+   * @return The length of time in seconds until the password actually
+   *         expires.
+   */
+  public int getSecondsUntilExpiration()
+  {
+    return secondsUntilExpiration;
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    return ByteString.valueOf(String.valueOf(secondsUntilExpiration));
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Appends a string representation of this password expiring control
+   * to the provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("PasswordExpiringControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", secondsUntilExpiration=");
+    buffer.append(secondsUntilExpiration);
+    buffer.append(")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java b/sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java
new file mode 100644
index 0000000..9671bbd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordPolicyControl.java
@@ -0,0 +1,412 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the password policy control defined in
+ * draft-behera-ldap-password-policy.
+ */
+public class PasswordPolicyControl
+{
+  /**
+   * The OID for the password policy control from
+   * draft-behera-ldap-password-policy.
+   */
+  public static final String OID_PASSWORD_POLICY_CONTROL = "1.3.6.1.4.1.42.2.27.8.5.1";
+
+
+
+  /**
+   * This class implements the password policy request control defined
+   * in draft-behera-ldap-password-policy. It does not have a value.
+   */
+  public static class Request extends Control
+  {
+    public Request()
+    {
+      super(OID_PASSWORD_POLICY_CONTROL, false);
+    }
+
+
+
+    public Request(boolean isCritical)
+    {
+      super(OID_PASSWORD_POLICY_CONTROL, isCritical);
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      return null;
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return false;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("PasswordPolicyRequestControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * This class implements the password policy response control defined
+   * in draft-behera-ldap-password-policy. The value may have zero, one,
+   * or two elements, which may include flags to indicate a warning
+   * and/or an error.
+   */
+  public static class Response extends Control
+  {
+    // The warning value for this password policy response control.
+    private int warningValue;
+
+    // The error type for this password policy response control.
+    private PasswordPolicyErrorType errorType;
+
+    // The warning type for the password policy response control.
+    private PasswordPolicyWarningType warningType;
+
+
+
+    /**
+     * Creates a new instance of the password policy response control
+     * with the default OID and criticality, and without either a
+     * warning or an error flag.
+     */
+    public Response()
+    {
+      this(false);
+    }
+
+
+
+    /**
+     * Creates a new instance of the password policy response control
+     * with the default OID and criticality, and without either a
+     * warning or an error flag.
+     * 
+     * @param isCritical
+     *          Indicates whether support for this control should be
+     *          considered a critical part of the client processing.
+     */
+    public Response(boolean isCritical)
+    {
+      super(OID_PASSWORD_POLICY_CONTROL, isCritical);
+
+      warningType = null;
+      errorType = null;
+      warningValue = -1;
+    }
+
+
+
+    /**
+     * Retrieves the password policy error type contained in this
+     * control.
+     * 
+     * @return The password policy error type contained in this control,
+     *         or <CODE>null</CODE> if there is no error type.
+     */
+    public PasswordPolicyErrorType getErrorType()
+    {
+      return errorType;
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writer.writeStartSequence();
+        if (warningType != null)
+        {
+          // Just write the CHOICE element as a single element SEQUENCE.
+          writer.writeStartSequence(TYPE_WARNING_ELEMENT);
+          writer.writeInteger((byte) (0x80 | warningType.intValue()),
+              warningValue);
+          writer.writeEndSequence();
+        }
+
+        if (errorType != null)
+        {
+          writer.writeInteger(TYPE_ERROR_ELEMENT, errorType.intValue());
+        }
+        writer.writeEndSequence();
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    /**
+     * Retrieves the password policy warning type contained in this
+     * control.
+     * 
+     * @return The password policy warning type contained in this
+     *         control, or <CODE>null</CODE> if there is no warning
+     *         type.
+     */
+    public PasswordPolicyWarningType getWarningType()
+    {
+      return warningType;
+    }
+
+
+
+    /**
+     * Retrieves the password policy warning value for this control. The
+     * value is undefined if there is no warning type.
+     * 
+     * @return The password policy warning value for this control.
+     */
+    public int getWarningValue()
+    {
+      return warningValue;
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    public Response setError(PasswordPolicyErrorType error)
+    {
+      Validator.ensureNotNull(error);
+      this.errorType = error;
+      return this;
+    }
+
+
+
+    public Response setWarning(PasswordPolicyWarningType type, int value)
+    {
+      Validator.ensureNotNull(type);
+      this.warningType = type;
+      this.warningValue = value;
+      return this;
+    }
+
+
+
+    /**
+     * Appends a string representation of this password policy response
+     * control to the provided buffer.
+     * 
+     * @param buffer
+     *          The buffer to which the information should be appended.
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("PasswordPolicyResponseControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", warningType=");
+      buffer.append(warningType);
+      buffer.append(", warningValue=");
+      buffer.append(warningValue);
+      buffer.append(", errorType=");
+      buffer.append(errorType);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private static final class RequestDecoder implements
+      ControlDecoder<Request>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Request decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value != null)
+      {
+        Message message = ERR_PWPOLICYREQ_CONTROL_HAS_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      return new Request(isCritical);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_PASSWORD_POLICY_CONTROL;
+    }
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class ResponseDecoder implements
+      ControlDecoder<Response>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Response decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        // The response control must always have a value.
+        Message message = ERR_PWPOLICYRES_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        PasswordPolicyWarningType warningType = null;
+        PasswordPolicyErrorType errorType = null;
+        int warningValue = -1;
+
+        reader.readStartSequence();
+
+        if (reader.hasNextElement()
+            && (reader.peekType() == TYPE_WARNING_ELEMENT))
+        {
+          // Its a CHOICE element. Read as sequence to retrieve
+          // nested element.
+          reader.readStartSequence();
+          warningType = PasswordPolicyWarningType.valueOf(0x7F & reader
+              .peekType());
+          warningValue = (int) reader.readInteger();
+          if (warningType == null)
+          {
+            Message message = ERR_PWPOLICYRES_INVALID_WARNING_TYPE
+                .get(byteToHex(reader.peekType()));
+            throw DecodeException.error(message);
+          }
+          reader.readEndSequence();
+        }
+        if (reader.hasNextElement()
+            && (reader.peekType() == TYPE_ERROR_ELEMENT))
+        {
+          int errorValue = (int) reader.readInteger();
+          errorType = PasswordPolicyErrorType.valueOf(errorValue);
+          if (errorType == null)
+          {
+            Message message = ERR_PWPOLICYRES_INVALID_ERROR_TYPE
+                .get(errorValue);
+            throw DecodeException.error(message);
+          }
+        }
+
+        reader.readEndSequence();
+
+        Response response = new Response(isCritical);
+        if (warningType != null)
+        {
+          response.setWarning(warningType, warningValue);
+        }
+        if (errorType != null)
+        {
+          response.setError(errorType);
+        }
+        return response;
+      }
+      catch (IOException e)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "PasswordPolicyControl.ResponseDecoder", "decode", e);
+
+        Message message = ERR_PWPOLICYRES_DECODE_ERROR
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message);
+      }
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_PASSWORD_POLICY_CONTROL;
+    }
+
+  }
+
+
+
+  /**
+   * The BER type value for the warning element of the control value.
+   */
+  private static final byte TYPE_WARNING_ELEMENT = (byte) 0xA0;
+
+  /**
+   * The BER type value for the error element of the control value.
+   */
+  private static final byte TYPE_ERROR_ELEMENT = (byte) 0x81;
+
+  /**
+   * The Control Decoder that can be used to decode the request control.
+   */
+  public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+  /**
+   * The Control Decoder that can be used to decode the response
+   * control.
+   */
+  public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java b/sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java
new file mode 100644
index 0000000..08d3f45
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordPolicyErrorType.java
@@ -0,0 +1,128 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This enumeration defines the set of password policy warnings that may
+ * be included in the password policy response control defined in
+ * draft-behera-ldap-password-policy.
+ */
+public class PasswordPolicyErrorType
+{
+  private static final PasswordPolicyErrorType[] ELEMENTS = new PasswordPolicyErrorType[9];
+
+  public static final PasswordPolicyErrorType PASSWORD_EXPIRED = register(
+      0, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_EXPIRED.get());
+
+  public static final PasswordPolicyErrorType ACCOUNT_LOCKED = register(
+      1, INFO_PWPERRTYPE_DESCRIPTION_ACCOUNT_LOCKED.get());
+
+  public static final PasswordPolicyErrorType CHANGE_AFTER_RESET = register(
+      2, INFO_PWPERRTYPE_DESCRIPTION_CHANGE_AFTER_RESET.get());
+
+  public static final PasswordPolicyErrorType PASSWORD_MOD_NOT_ALLOWED = register(
+      3, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_MOD_NOT_ALLOWED.get());
+
+  public static final PasswordPolicyErrorType MUST_SUPPLY_OLD_PASSWORD = register(
+      4, INFO_PWPERRTYPE_DESCRIPTION_MUST_SUPPLY_OLD_PASSWORD.get());
+
+  public static final PasswordPolicyErrorType INSUFFICIENT_PASSWORD_QUALITY = register(
+      5, INFO_PWPERRTYPE_DESCRIPTION_INSUFFICIENT_PASSWORD_QUALITY
+          .get());
+
+  public static final PasswordPolicyErrorType PASSWORD_TOO_SHORT = register(
+      6, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_TOO_SHORT.get());
+
+  public static final PasswordPolicyErrorType PASSWORD_TOO_YOUNG = register(
+      7, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_TOO_YOUNG.get());
+
+  public static final PasswordPolicyErrorType PASSWORD_IN_HISTORY = register(
+      8, INFO_PWPERRTYPE_DESCRIPTION_PASSWORD_IN_HISTORY.get());
+
+
+
+  public static PasswordPolicyErrorType valueOf(int intValue)
+  {
+    PasswordPolicyErrorType e = ELEMENTS[intValue];
+    if (e == null)
+    {
+      e = new PasswordPolicyErrorType(intValue, Message
+          .raw("undefined(" + intValue + ")"));
+    }
+    return e;
+  }
+
+
+
+  public static List<PasswordPolicyErrorType> values()
+  {
+    return Arrays.asList(ELEMENTS);
+  }
+
+
+
+  private static PasswordPolicyErrorType register(int intValue,
+      Message name)
+  {
+    PasswordPolicyErrorType t = new PasswordPolicyErrorType(intValue,
+        name);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  private final int intValue;
+
+  private final Message name;
+
+
+
+  private PasswordPolicyErrorType(int intValue, Message name)
+  {
+    this.intValue = intValue;
+    this.name = name;
+  }
+
+
+
+  @Override
+  public boolean equals(Object o)
+  {
+    return (this == o)
+        || ((o instanceof PasswordPolicyErrorType) && (this.intValue == ((PasswordPolicyErrorType) o).intValue));
+
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return intValue;
+  }
+
+
+
+  public int intValue()
+  {
+    return intValue;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    return name.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java b/sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java
new file mode 100644
index 0000000..880f561
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PasswordPolicyWarningType.java
@@ -0,0 +1,107 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_PWPWARNTYPE_DESCRIPTION_GRACE_LOGINS_REMAINING;
+import static org.opends.messages.ProtocolMessages.INFO_PWPWARNTYPE_DESCRIPTION_TIME_BEFORE_EXPIRATION;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This enumeration defines the set of password policy warnings that may
+ * be included in the password policy response control defined in
+ * draft-behera-ldap-password-policy.
+ */
+public class PasswordPolicyWarningType
+{
+  private static final PasswordPolicyWarningType[] ELEMENTS = new PasswordPolicyWarningType[2];
+
+  public static final PasswordPolicyWarningType TIME_BEFORE_EXPIRATION = register(
+      0, INFO_PWPWARNTYPE_DESCRIPTION_TIME_BEFORE_EXPIRATION.get());
+
+  public static final PasswordPolicyWarningType GRACE_LOGINS_REMAINING = register(
+      1, INFO_PWPWARNTYPE_DESCRIPTION_GRACE_LOGINS_REMAINING.get());
+
+
+
+  public static PasswordPolicyWarningType valueOf(int intValue)
+  {
+    PasswordPolicyWarningType e = ELEMENTS[intValue];
+    if (e == null)
+    {
+      e = new PasswordPolicyWarningType(intValue, Message
+          .raw("undefined(" + intValue + ")"));
+    }
+    return e;
+  }
+
+
+
+  public static List<PasswordPolicyWarningType> values()
+  {
+    return Arrays.asList(ELEMENTS);
+  }
+
+
+
+  private static PasswordPolicyWarningType register(int intValue,
+      Message name)
+  {
+    PasswordPolicyWarningType t = new PasswordPolicyWarningType(
+        intValue, name);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  private final int intValue;
+
+  private final Message name;
+
+
+
+  private PasswordPolicyWarningType(int intValue, Message name)
+  {
+    this.intValue = intValue;
+    this.name = name;
+  }
+
+
+
+  @Override
+  public boolean equals(Object o)
+  {
+    return (this == o)
+        || ((o instanceof PasswordPolicyWarningType) && (this.intValue == ((PasswordPolicyWarningType) o).intValue));
+
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return intValue;
+  }
+
+
+
+  public int intValue()
+  {
+    return intValue;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    return name.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java b/sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java
new file mode 100644
index 0000000..1749678
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PersistentSearchChangeType.java
@@ -0,0 +1,108 @@
+package org.opends.sdk.controls;
+
+
+
+import java.util.Arrays;
+import java.util.List;
+
+
+
+/**
+ * This enumeration defines the set of possible change types that may be
+ * used in conjunction with the persistent search control, as defined in
+ * draft-ietf-ldapext-psearch.
+ */
+public final class PersistentSearchChangeType
+{
+  private static final PersistentSearchChangeType[] ELEMENTS = new PersistentSearchChangeType[4];
+
+  public static final PersistentSearchChangeType ADD = register(1,
+      "add");
+
+  public static final PersistentSearchChangeType DELETE = register(2,
+      "delete");
+
+  public static final PersistentSearchChangeType MODIFY = register(4,
+      "modify");
+
+  public static final PersistentSearchChangeType MODIFY_DN = register(
+      8, "modify DN");
+
+
+
+  public static PersistentSearchChangeType valueOf(int intValue)
+  {
+    PersistentSearchChangeType e = ELEMENTS[intValue];
+    if (e == null)
+    {
+      e = new PersistentSearchChangeType(intValue, "undefined("
+          + intValue + ")");
+    }
+    return e;
+  }
+
+
+
+  public static List<PersistentSearchChangeType> values()
+  {
+    return Arrays.asList(ELEMENTS);
+  }
+
+
+
+  private static PersistentSearchChangeType register(int intValue,
+      String name)
+  {
+    PersistentSearchChangeType t = new PersistentSearchChangeType(
+        intValue, name);
+    ELEMENTS[intValue] = t;
+    return t;
+  }
+
+
+
+  private final int intValue;
+
+  private final String name;
+
+
+
+  private PersistentSearchChangeType(int intValue, String name)
+  {
+    this.intValue = intValue;
+    this.name = name;
+  }
+
+
+
+  @Override
+  public boolean equals(Object o)
+  {
+    return (this == o)
+        || ((o instanceof PersistentSearchChangeType) && (this.intValue == ((PersistentSearchChangeType) o).intValue));
+
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return intValue;
+  }
+
+
+
+  public int intValue()
+  {
+    return intValue;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    return name;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PersistentSearchControl.java b/sdk/src/org/opends/sdk/controls/PersistentSearchControl.java
new file mode 100644
index 0000000..d9afee4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PersistentSearchControl.java
@@ -0,0 +1,328 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PSEARCH_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PSEARCH_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the persistent search control defined in
+ * draft-ietf-ldapext-psearch. It makes it possible for clients to be
+ * notified of changes to information in the Directory Server as they
+ * occur.
+ */
+public class PersistentSearchControl extends Control
+{
+  /**
+   * The OID for the persistent search control.
+   */
+  public static final String OID_PERSISTENT_SEARCH = "2.16.840.1.113730.3.4.3";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class Decoder implements
+      ControlDecoder<PersistentSearchControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public PersistentSearchControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_PSEARCH_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      boolean changesOnly;
+      boolean returnECs;
+      int changeTypes;
+      try
+      {
+        reader.readStartSequence();
+
+        changeTypes = (int) reader.readInteger();
+        changesOnly = reader.readBoolean();
+        returnECs = reader.readBoolean();
+
+        reader.readEndSequence();
+      }
+      catch (IOException e)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "PersistentSearchControl.Decoder", "decode", e);
+
+        Message message = ERR_PSEARCH_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+
+      return new PersistentSearchControl(isCritical, changeTypes,
+          changesOnly, returnECs);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_PERSISTENT_SEARCH;
+    }
+
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<PersistentSearchControl> DECODER = new Decoder();
+
+  // Indicates whether to only return entries that have been updated
+  // since the
+  // beginning of the search.
+  private final boolean changesOnly;
+
+  // Indicates whether entries returned as a result of changes to
+  // directory data
+  // should include the entry change notification control.
+  private final boolean returnECs;
+
+  // The logical OR of change types associated with this control.
+  private int changeTypes;
+
+
+
+  /**
+   * Creates a new persistent search control with the provided
+   * information.
+   * 
+   * @param isCritical
+   *          Indicates whether the control should be considered
+   *          critical for the operation processing.
+   * @param changesOnly
+   *          Indicates whether to only return changes that match the
+   *          associated search criteria, or to also return all existing
+   *          entries that match the filter.
+   * @param returnECs
+   *          Indicates whether to include the entry change notification
+   *          control in updated entries that match the associated
+   *          search criteria.
+   * @param changeTypes
+   *          The change types for which to provide notification to the
+   *          client.
+   */
+  public PersistentSearchControl(boolean isCritical,
+      boolean changesOnly, boolean returnECs,
+      PersistentSearchChangeType... changeTypes)
+  {
+    super(OID_PERSISTENT_SEARCH, isCritical);
+
+    this.changeTypes = 0;
+    this.changesOnly = changesOnly;
+    this.returnECs = returnECs;
+
+    if (changeTypes != null)
+    {
+      for (PersistentSearchChangeType type : changeTypes)
+      {
+        this.changeTypes |= type.intValue();
+      }
+    }
+  }
+
+
+
+  /**
+   * Creates a new persistent search control with the provided
+   * information.
+   * 
+   * @param changesOnly
+   *          Indicates whether to only return changes that match the
+   *          associated search criteria, or to also return all existing
+   *          entries that match the filter.
+   * @param returnECs
+   *          Indicates whether to include the entry change notification
+   *          control in updated entries that match the associated
+   *          search criteria.
+   * @param changeTypes
+   *          The set of change types for which to provide notification
+   *          to the client.
+   */
+  public PersistentSearchControl(boolean changesOnly,
+      boolean returnECs, PersistentSearchChangeType... changeTypes)
+  {
+    this(true, changesOnly, returnECs, changeTypes);
+  }
+
+
+
+  private PersistentSearchControl(boolean isCritical, int changeTypes,
+      boolean changesOnly, boolean returnECs)
+  {
+    super(OID_PERSISTENT_SEARCH, isCritical);
+
+    this.changeTypes = changeTypes;
+    this.changesOnly = changesOnly;
+    this.returnECs = returnECs;
+  }
+
+
+
+  public PersistentSearchControl addChangeType(
+      PersistentSearchChangeType type)
+  {
+    changeTypes |= type.intValue();
+    return this;
+  }
+
+
+
+  /**
+   * Indicates if the change type is included in this persistent search
+   * control.
+   * 
+   * @param type
+   *          The change type whose presence is to be tested.
+   * @return <code>true</code> if the change type is included or
+   *         <code>false</code> otherwise.
+   */
+  public boolean containsChangeType(PersistentSearchChangeType type)
+  {
+    return (changeTypes & type.intValue()) == type.intValue();
+  }
+
+
+
+  /**
+   * Indicates whether to only return changes that match the associated
+   * search criteria, or to also return all existing entries that match
+   * the filter.
+   * 
+   * @return <CODE>true</CODE> if only changes to matching entries
+   *         should be returned, or <CODE>false</CODE> if existing
+   *         matches should also be included.
+   */
+  public boolean getChangesOnly()
+  {
+    return changesOnly;
+  }
+
+
+
+  /**
+   * Indicates whether to include the entry change notification control
+   * in entries returned to the client as the result of a change in the
+   * Directory Server data.
+   * 
+   * @return <CODE>true</CODE> if entry change notification controls
+   *         should be included in applicable entries, or
+   *         <CODE>false</CODE> if not.
+   */
+  public boolean getReturnECs()
+  {
+    return returnECs;
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+    try
+    {
+      writer.writeStartSequence();
+      writer.writeInteger(changeTypes);
+      writer.writeBoolean(changesOnly);
+      writer.writeBoolean(returnECs);
+      writer.writeEndSequence();
+      return buffer.toByteString();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  public PersistentSearchControl removeChangeType(
+      PersistentSearchChangeType type)
+  {
+    changeTypes &= ~type.intValue();
+    return this;
+  }
+
+
+
+  /**
+   * Appends a string representation of this persistent search control
+   * to the provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("PersistentSearchControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", changeTypes=[");
+
+    boolean comma = false;
+    for (PersistentSearchChangeType type : PersistentSearchChangeType
+        .values())
+    {
+      if (containsChangeType(type))
+      {
+        if (comma)
+        {
+          buffer.append(", ");
+        }
+        buffer.append(type);
+        comma = true;
+      }
+    }
+
+    buffer.append("](");
+    buffer.append(changeTypes);
+    buffer.append("), changesOnly=");
+    buffer.append(changesOnly);
+    buffer.append(", returnECs=");
+    buffer.append(returnECs);
+    buffer.append(")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/PostReadControl.java b/sdk/src/org/opends/sdk/controls/PostReadControl.java
new file mode 100644
index 0000000..c835007
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PostReadControl.java
@@ -0,0 +1,447 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADREQ_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADREQ_NO_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADRESP_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_POSTREADRESP_NO_CONTROL_VALUE;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the post-read control as defined in RFC 4527.
+ * This control makes it possible to retrieve an entry in the state that
+ * it held immediately after an add, modify, or modify DN operation. It
+ * may specify a specific set of attributes that should be included in
+ * that entry.
+ */
+public class PostReadControl
+{
+  /**
+   * The IANA-assigned OID for the LDAP readentry control used for
+   * retrieving an
+   * entry in the state it had immediately after an update was applied.
+   */
+  public static final String OID_LDAP_READENTRY_POSTREAD = "1.3.6.1.1.13.2";
+
+
+
+  /**
+   * This class implements the post-read request control as defined in
+   * RFC 4527. This control makes it possible to retrieve an entry in
+   * the state that it held immediately after an add, modify, or modify
+   * DN operation. It may specify a specific set of attributes that
+   * should be included in that entry. The entry will be encoded in a
+   * corresponding response control.
+   */
+  public static class Request extends Control
+  {
+    // The set of raw attributes to return in the entry.
+    private final Set<String> attributes;
+
+
+
+    /**
+     * Creates a new post-read request control with the provided
+     * information.
+     *
+     * @param isCritical
+     *          Indicates whether support for this control should be
+     *          considered a critical part of the server processing.
+     * @param attributeDescriptions
+     *          The names of the attributes to be included with the
+     *          response control.
+     */
+    public Request(boolean isCritical, String... attributeDescriptions)
+    {
+      super(OID_LDAP_READENTRY_POSTREAD, isCritical);
+
+      this.attributes = new LinkedHashSet<String>();
+      if (attributeDescriptions != null)
+      {
+        this.attributes.addAll(Arrays.asList(attributeDescriptions));
+      }
+    }
+
+
+
+    private Request(boolean isCritical, Set<String> attributes)
+    {
+      super(OID_LDAP_READENTRY_POSTREAD, isCritical);
+
+      this.attributes = attributes;
+    }
+
+
+
+    /**
+     * Adds the provided attribute name to the list of attributes to be
+     * included in the response control. Attributes that are sub-types
+     * of listed attributes are implicitly included.
+     *
+     * @param attributeDescription
+     *          The name of the attribute to be included in the response
+     *          control.
+     * @return This post-read control.
+     * @throws NullPointerException
+     *           If {@code attributeDescription} was {@code null}.
+     */
+    public Request addAttribute(String attributeDescription)
+        throws NullPointerException
+    {
+      Validator.ensureNotNull(attributeDescription);
+      attributes.add(attributeDescription);
+      return this;
+    }
+
+
+
+    /**
+     * Returns an {@code Iterable} containing the list of attributes to
+     * be included with the response control. Attributes that are
+     * sub-types of listed attributes are implicitly included.
+     *
+     * @return An {@code Iterable} containing the list of attributes.
+     */
+    public Iterable<String> getAttributes()
+    {
+      return attributes;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writer.writeStartSequence();
+        if (attributes != null)
+        {
+          for (String attr : attributes)
+          {
+            writer.writeOctetString(attr);
+          }
+        }
+        writer.writeEndSequence();
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("PostReadRequestControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", attributes=");
+      buffer.append(attributes);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * This class implements the post-read response control as defined in
+   * RFC 4527. This control holds the search result entry representing
+   * the state of the entry immediately after an add, modify, or modify
+   * DN operation.
+   */
+  public static class Response extends Control
+  {
+    private final SearchResultEntry entry;
+
+
+
+    /**
+     * Creates a new post-read response control with the provided
+     * information.
+     *
+     * @param isCritical
+     *          Indicates whether support for this control should be
+     *          considered a critical part of the server processing.
+     * @param searchEntry
+     *          The search result entry to include in the response
+     *          control.
+     */
+    public Response(boolean isCritical, SearchResultEntry searchEntry)
+    {
+      super(OID_LDAP_READENTRY_POSTREAD, isCritical);
+
+      this.entry = searchEntry;
+    }
+
+
+
+    /**
+     * Creates a new post-read response control with the provided
+     * information.
+     *
+     * @param searchEntry
+     *          The search result entry to include in the response
+     *          control.
+     */
+    public Response(SearchResultEntry searchEntry)
+    {
+      this(false, searchEntry);
+    }
+
+
+
+    /**
+     * Returns the search result entry associated with this post-read
+     * response control.
+     *
+     * @return The search result entry associated with this post-read
+     *         response control.
+     */
+    public SearchResultEntry getSearchEntry()
+    {
+      return entry;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        LDAPUtils.encodeSearchResultEntry(writer, entry);
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("PostReadResponseControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", entry=");
+      buffer.append(entry);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * Decodes a post-read request control from a byte string.
+   */
+  private final static class RequestDecoder implements
+      ControlDecoder<Request>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Request decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_POSTREADREQ_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      try
+      {
+        reader.readStartSequence();
+        while (reader.hasNextElement())
+        {
+          attributes.add(reader.readOctetStringAsString());
+        }
+        reader.readEndSequence();
+      }
+      catch (Exception ae)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "PersistentSearchControl.RequestDecoder", "decode", ae);
+
+        Message message = ERR_POSTREADREQ_CANNOT_DECODE_VALUE.get(ae
+            .getMessage());
+        throw DecodeException.error(message, ae);
+      }
+
+      return new Request(isCritical, attributes);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getOID()
+    {
+      return OID_LDAP_READENTRY_POSTREAD;
+    }
+  }
+
+
+
+  /**
+   * Decodes a post-read response control from a byte string.
+   */
+  private final static class ResponseDecoder implements
+      ControlDecoder<Response>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Response decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_POSTREADRESP_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      SearchResultEntry searchEntry;
+      try
+      {
+        searchEntry = LDAPUtils.decodeSearchResultEntry(reader, schema);
+      }
+      catch (IOException le)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "PersistentSearchControl.ResponseDecoder", "decode", le);
+
+        Message message = ERR_POSTREADRESP_CANNOT_DECODE_VALUE.get(le
+            .getMessage());
+        throw DecodeException.error(message, le);
+      }
+
+      return new Response(isCritical, searchEntry);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getOID()
+    {
+      return OID_LDAP_READENTRY_POSTREAD;
+    }
+
+  }
+
+
+
+  /**
+   * A control decoder which can be used to decode post-read request
+   * controls.
+   */
+  public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+  /**
+   * A control decoder which can be used to decode post-read respoens
+   * controls.
+   */
+  public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/PreReadControl.java b/sdk/src/org/opends/sdk/controls/PreReadControl.java
new file mode 100644
index 0000000..050aaac
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/PreReadControl.java
@@ -0,0 +1,447 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PREREADREQ_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PREREADREQ_NO_CONTROL_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PREREADRESP_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PREREADRESP_NO_CONTROL_VALUE;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.ldap.LDAPUtils;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the pre-read request control as defined in RFC
+ * 4527. This control makes it possible to retrieve an entry in the
+ * state that it held immediately before a modify, delete, or modify DN
+ * operation. It may specify a specific set of attributes that should be
+ * included in that entry.
+ */
+public class PreReadControl
+{
+  /**
+   * The IANA-assigned OID for the LDAP readentry control used for
+   * retrieving an
+   * entry in the state it had immediately before an update was applied.
+   */
+  public static final String OID_LDAP_READENTRY_PREREAD = "1.3.6.1.1.13.1";
+
+
+
+  /**
+   * This class implements the pre-read request control as defined in
+   * RFC 4527. This control makes it possible to retrieve an entry in
+   * the state that it held immediately before a modify, delete, or
+   * modify DN operation. It may specify a specific set of attributes
+   * that should be included in that entry. The entry will be encoded in
+   * a corresponding response control.
+   */
+  public static class Request extends Control
+  {
+    // The set of raw attributes to return in the entry.
+    private final Set<String> attributes;
+
+
+
+    /**
+     * Creates a new pre-read request control with the provided
+     * information.
+     *
+     * @param isCritical
+     *          Indicates whether support for this control should be
+     *          considered a critical part of the server processing.
+     * @param attributeDescriptions
+     *          The names of the attributes to be included with the
+     *          response control.
+     */
+    public Request(boolean isCritical, String... attributeDescriptions)
+    {
+      super(OID_LDAP_READENTRY_PREREAD, isCritical);
+
+      this.attributes = new LinkedHashSet<String>();
+      if (attributeDescriptions != null)
+      {
+        this.attributes.addAll(Arrays.asList(attributeDescriptions));
+      }
+    }
+
+
+
+    private Request(boolean isCritical, Set<String> attributes)
+    {
+      super(OID_LDAP_READENTRY_PREREAD, isCritical);
+
+      this.attributes = attributes;
+    }
+
+
+
+    /**
+     * Adds the provided attribute name to the list of attributes to be
+     * included in the request control. Attributes that are sub-types
+     * of listed attributes are implicitly included.
+     *
+     * @param attributeDescription
+     *          The name of the attribute to be included in the response
+     *          control.
+     * @return This post-read control.
+     * @throws NullPointerException
+     *           If {@code attributeDescription} was {@code null}.
+     */
+    public Request addAttribute(String attributeDescription)
+        throws NullPointerException
+    {
+      Validator.ensureNotNull(attributeDescription);
+      attributes.add(attributeDescription);
+      return this;
+    }
+
+
+
+    /**
+     * Returns an {@code Iterable} containing the list of attributes to
+     * be included with the response control. Attributes that are
+     * sub-types of listed attributes are implicitly included.
+     *
+     * @return An {@code Iterable} containing the list of attributes.
+     */
+    public Iterable<String> getAttributes()
+    {
+      return attributes;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writer.writeStartSequence();
+        if (attributes != null)
+        {
+          for (String attr : attributes)
+          {
+            writer.writeOctetString(attr);
+          }
+        }
+        writer.writeEndSequence();
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("PreReadRequestControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", attributes=");
+      buffer.append(attributes);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * This class implements the pre-read response control as defined in
+   * RFC 4527. This control holds the search result entry representing
+   * the state of the entry immediately before an add, modify, or modify
+   * DN operation.
+   */
+  public static class Response extends Control
+  {
+    private final SearchResultEntry entry;
+
+
+
+    /**
+     * Creates a new pre-read response control with the provided
+     * information.
+     *
+     * @param isCritical
+     *          Indicates whether support for this control should be
+     *          considered a critical part of the server processing.
+     * @param searchEntry
+     *          The search result entry to include in the response
+     *          control.
+     */
+    public Response(boolean isCritical, SearchResultEntry searchEntry)
+    {
+      super(OID_LDAP_READENTRY_PREREAD, isCritical);
+
+      this.entry = searchEntry;
+    }
+
+
+
+    /**
+     * Creates a new pre-read response control with the provided
+     * information.
+     *
+     * @param searchEntry
+     *          The search result entry to include in the response
+     *          control.
+     */
+    public Response(SearchResultEntry searchEntry)
+    {
+      this(false, searchEntry);
+    }
+
+
+
+    /**
+     * Returns the search result entry associated with this post-read
+     * response control.
+     *
+     * @return The search result entry associated with this post-read
+     *         response control.
+     */
+    public SearchResultEntry getSearchEntry()
+    {
+      return entry;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        LDAPUtils.encodeSearchResultEntry(writer, entry);
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("PreReadResponseControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", entry=");
+      buffer.append(entry);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * Decodes a pre-read request control from a byte string.
+   */
+  private final static class RequestDecoder implements
+      ControlDecoder<Request>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Request decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_PREREADREQ_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      LinkedHashSet<String> attributes = new LinkedHashSet<String>();
+      try
+      {
+        reader.readStartSequence();
+        while (reader.hasNextElement())
+        {
+          attributes.add(reader.readOctetStringAsString());
+        }
+        reader.readEndSequence();
+      }
+      catch (Exception ae)
+      {
+        StaticUtils.DEBUG_LOG.throwing("PreReadControl.RequestDecoder",
+            "decode", ae);
+
+        Message message = ERR_PREREADREQ_CANNOT_DECODE_VALUE.get(ae
+            .getMessage());
+        throw DecodeException.error(message, ae);
+      }
+
+      return new Request(isCritical, attributes);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getOID()
+    {
+      return OID_LDAP_READENTRY_PREREAD;
+    }
+  }
+
+
+
+  /**
+   * Decodes a pre-read response control from a byte string.
+   */
+  private final static class ResponseDecoder implements
+      ControlDecoder<Response>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Response decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = ERR_PREREADRESP_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      SearchResultEntry searchEntry;
+      try
+      {
+        searchEntry = LDAPUtils.decodeSearchResultEntry(reader, schema);
+      }
+      catch (IOException le)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "PersistentSearchControl.ResponseDecoder", "decode", le);
+
+        Message message = ERR_PREREADRESP_CANNOT_DECODE_VALUE.get(le
+            .getMessage());
+        throw DecodeException.error(message, le);
+      }
+
+      return new Response(isCritical, searchEntry);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getOID()
+    {
+      return OID_LDAP_READENTRY_PREREAD;
+    }
+
+  }
+
+
+
+  /**
+   * A control decoder which can be used to decode pre-read request
+   * controls.
+   */
+  public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+  /**
+   * A control decoder which can be used to decode pre-read respoens
+   * controls.
+   */
+  public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java b/sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java
new file mode 100644
index 0000000..0b51fb1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ProxiedAuthV1Control.java
@@ -0,0 +1,212 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH1_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH1_CONTROL_NOT_CRITICAL;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH1_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements version 1 of the proxied authorization control
+ * as defined in early versions of draft-weltman-ldapv3-proxy (this
+ * implementation is based on the "-04" revision). It makes it possible
+ * for one user to request that an operation be performed under the
+ * authorization of another. The target user is specified as a DN in the
+ * control value, which distinguishes it from later versions of the
+ * control (which used a different OID) in which the target user was
+ * specified using an authorization ID.
+ */
+public class ProxiedAuthV1Control extends Control
+{
+  /**
+   * The OID for the proxied authorization v1 control.
+   */
+  public static final String OID_PROXIED_AUTH_V1 = "2.16.840.1.113730.3.4.12";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class Decoder implements
+      ControlDecoder<ProxiedAuthV1Control>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public ProxiedAuthV1Control decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (!isCritical)
+      {
+        Message message = ERR_PROXYAUTH1_CONTROL_NOT_CRITICAL.get();
+        throw DecodeException.error(message);
+      }
+
+      if (value == null)
+      {
+        Message message = ERR_PROXYAUTH1_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      String authorizationDN;
+      try
+      {
+        reader.readStartSequence();
+        authorizationDN = reader.readOctetStringAsString();
+        reader.readEndSequence();
+      }
+      catch (IOException e)
+      {
+        StaticUtils.DEBUG_LOG.throwing("ProxiedAuthV1Control.Decoder",
+            "decode", e);
+
+        Message message = ERR_PROXYAUTH1_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+
+      return new ProxiedAuthV1Control(authorizationDN);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_PROXIED_AUTH_V1;
+    }
+
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<ProxiedAuthV1Control> DECODER = new Decoder();
+
+  // The raw, unprocessed authorization DN from the control value.
+  private String authorizationDN;
+
+
+
+  /**
+   * Creates a new instance of the proxied authorization v1 control with
+   * the provided information.
+   * 
+   * @param authorizationDN
+   *          The authorization DN from the control value. It must not
+   *          be {@code null}.
+   */
+  public ProxiedAuthV1Control(DN authorizationDN)
+  {
+    super(OID_PROXIED_AUTH_V1, true);
+
+    Validator.ensureNotNull(authorizationDN);
+    this.authorizationDN = authorizationDN.toString();
+  }
+
+
+
+  /**
+   * Creates a new instance of the proxied authorization v1 control with
+   * the provided information.
+   * 
+   * @param authorizationDN
+   *          The raw, unprocessed authorization DN from the control
+   *          value. It must not be {@code null}.
+   */
+  public ProxiedAuthV1Control(String authorizationDN)
+  {
+    super(OID_PROXIED_AUTH_V1, true);
+
+    Validator.ensureNotNull(authorizationDN);
+    this.authorizationDN = authorizationDN;
+  }
+
+
+
+  /**
+   * Retrieves the raw, unprocessed authorization DN from the control
+   * value.
+   * 
+   * @return The raw, unprocessed authorization DN from the control
+   *         value.
+   */
+  public String getAuthorizationDN()
+  {
+    return authorizationDN;
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    return ByteString.valueOf(authorizationDN);
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  public ProxiedAuthV1Control setAuthorizationDN(DN authorizationDN)
+  {
+    Validator.ensureNotNull(authorizationDN);
+    this.authorizationDN = authorizationDN.toString();
+    return this;
+  }
+
+
+
+  public ProxiedAuthV1Control setAuthorizationDN(String authorizationDN)
+  {
+    Validator.ensureNotNull(authorizationDN);
+    this.authorizationDN = authorizationDN;
+    return this;
+  }
+
+
+
+  /**
+   * Appends a string representation of this proxied auth v1 control to
+   * the provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("ProxiedAuthorizationV1Control(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", authorizationDN=\"");
+    buffer.append(authorizationDN);
+    buffer.append("\")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java b/sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java
new file mode 100644
index 0000000..3a1e2ec
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ProxiedAuthV2Control.java
@@ -0,0 +1,216 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH2_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL;
+import static org.opends.messages.ProtocolMessages.ERR_PROXYAUTH2_NO_CONTROL_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements version 2 of the proxied authorization control
+ * as defined in RFC 4370. It makes it possible for one user to request
+ * that an operation be performed under the authorization of another.
+ * The target user is specified using an authorization ID, which may be
+ * in the form "dn:" immediately followed by the DN of that user, or
+ * "u:" followed by a user ID string.
+ */
+public class ProxiedAuthV2Control extends Control
+{
+  /**
+   * The OID for the proxied authorization v2 control.
+   */
+  public static final String OID_PROXIED_AUTH_V2 = "2.16.840.1.113730.3.4.18";
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private static final class Decoder implements
+      ControlDecoder<ProxiedAuthV2Control>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public ProxiedAuthV2Control decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (!isCritical)
+      {
+        Message message = ERR_PROXYAUTH2_CONTROL_NOT_CRITICAL.get();
+        throw DecodeException.error(message);
+      }
+
+      if (value == null)
+      {
+        Message message = ERR_PROXYAUTH2_NO_CONTROL_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      String authorizationID;
+
+      try
+      {
+        if (reader.elementAvailable())
+        {
+          // Try the legacy encoding where the value is wrapped by an
+          // extra octet string
+          authorizationID = reader.readOctetStringAsString();
+        }
+        else
+        {
+          authorizationID = value.toString();
+        }
+      }
+      catch (IOException e)
+      {
+        StaticUtils.DEBUG_LOG.throwing("ProxiedAuthV2Control.Decoder",
+            "decode", e);
+
+        Message message = ERR_PROXYAUTH2_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+
+      return new ProxiedAuthV2Control(authorizationID);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_PROXIED_AUTH_V2;
+    }
+
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<ProxiedAuthV2Control> DECODER = new Decoder();
+
+  // The authorization ID from the control value.
+  private String authorizationID;
+
+
+
+  /**
+   * Creates a new instance of the proxied authorization v2 control with
+   * the provided information.
+   * 
+   * @param authorizationDN
+   *          The authorization DN.
+   */
+  public ProxiedAuthV2Control(DN authorizationDN)
+  {
+    super(OID_PROXIED_AUTH_V2, true);
+
+    Validator.ensureNotNull(authorizationID);
+    this.authorizationID = "dn:" + authorizationDN.toString();
+  }
+
+
+
+  /**
+   * Creates a new instance of the proxied authorization v2 control with
+   * the provided information.
+   * 
+   * @param authorizationID
+   *          The authorization ID.
+   */
+  public ProxiedAuthV2Control(String authorizationID)
+  {
+    super(OID_PROXIED_AUTH_V2, true);
+
+    Validator.ensureNotNull(authorizationID);
+    this.authorizationID = authorizationID;
+  }
+
+
+
+  /**
+   * Retrieves the authorization ID for this proxied authorization V2
+   * control.
+   * 
+   * @return The authorization ID for this proxied authorization V2
+   *         control.
+   */
+  public String getAuthorizationID()
+  {
+    return authorizationID;
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    return ByteString.valueOf(authorizationID);
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return true;
+  }
+
+
+
+  public ProxiedAuthV2Control setAuthorizationID(DN authorizationDN)
+  {
+    Validator.ensureNotNull(authorizationDN);
+    this.authorizationID = "dn:" + authorizationDN.toString();
+    return this;
+  }
+
+
+
+  public ProxiedAuthV2Control setAuthorizationID(String authorizationID)
+  {
+    Validator.ensureNotNull(authorizationID);
+    this.authorizationID = authorizationID;
+    return this;
+  }
+
+
+
+  /**
+   * Appends a string representation of this proxied auth v2 control to
+   * the provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("ProxiedAuthorizationV2Control(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(", authorizationDN=\"");
+    buffer.append(authorizationID);
+    buffer.append("\")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/ServerSideSortControl.java b/sdk/src/org/opends/sdk/controls/ServerSideSortControl.java
new file mode 100644
index 0000000..f62e780
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/ServerSideSortControl.java
@@ -0,0 +1,549 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the server-side sort control as defined in RFC
+ * 2891.
+ */
+public class ServerSideSortControl
+{
+  /**
+   * The OID for the server-side sort request control.
+   */
+  public static final String OID_SERVER_SIDE_SORT_REQUEST_CONTROL = "1.2.840.113556.1.4.473";
+
+  /**
+   * The OID for the server-side sort response control.
+   */
+  public static final String OID_SERVER_SIDE_SORT_RESPONSE_CONTROL = "1.2.840.113556.1.4.474";
+
+
+
+  /**
+   * This class implements the server-side sort request control as
+   * defined in RFC 2891 section 1.1. The ASN.1 description for the
+   * control value is: <BR>
+   * <BR>
+   * 
+   * <PRE>
+   * SortKeyList ::= SEQUENCE OF SEQUENCE {
+   *            attributeType   AttributeDescription,
+   *            orderingRule    [0] MatchingRuleId OPTIONAL,
+   *            reverseOrder    [1] BOOLEAN DEFAULT FALSE }
+   * </PRE>
+   */
+  public static class Request extends Control
+  {
+    private final List<SortKey> sortKeys = new ArrayList<SortKey>();
+
+
+
+    public Request(boolean isCritical, SortKey... sortKeys)
+    {
+      super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, isCritical);
+      addSortKey(sortKeys);
+    }
+
+
+
+    public Request(boolean isCritical, String sortOrderString)
+        throws DecodeException
+    {
+      super(OID_SERVER_SIDE_SORT_REQUEST_CONTROL, isCritical);
+
+      decodeSortOrderString(sortOrderString, sortKeys);
+    }
+
+
+
+    public Request(SortKey... sortKeys)
+    {
+      this(false, sortKeys);
+    }
+
+
+
+    public Request(String sortOrderString) throws DecodeException
+    {
+      this(false, sortOrderString);
+    }
+
+
+
+    public Request addSortKey(SortKey... sortKeys)
+    {
+      if (sortKeys != null)
+      {
+        for (SortKey sortKey : sortKeys)
+        {
+          Validator.ensureNotNull(sortKey);
+          this.sortKeys.add(sortKey);
+        }
+      }
+      return this;
+    }
+
+
+
+    public Request addSortKey(String sortOrderString)
+        throws DecodeException
+    {
+      decodeSortOrderString(sortOrderString, sortKeys);
+      return this;
+    }
+
+
+
+    public Iterable<SortKey> getSortKeys()
+    {
+      return sortKeys;
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writer.writeStartSequence();
+        for (SortKey sortKey : sortKeys)
+        {
+          writer.writeStartSequence();
+          writer.writeOctetString(sortKey.getAttributeDescription());
+
+          if (sortKey.getOrderingRule() != null)
+          {
+            writer.writeOctetString(TYPE_ORDERING_RULE_ID, sortKey
+                .getOrderingRule());
+          }
+
+          if (!sortKey.isReverseOrder())
+          {
+            writer.writeBoolean(TYPE_REVERSE_ORDER, true);
+          }
+
+          writer.writeEndSequence();
+        }
+        writer.writeEndSequence();
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("ServerSideSortRequestControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", sortKeys=");
+      buffer.append(sortKeys);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * This class implements the server-side sort response control as
+   * defined in RFC 2891 section 1.2. The ASN.1 description for the
+   * control value is: <BR>
+   * <BR>
+   * 
+   * <PRE>
+   * SortResult ::= SEQUENCE {
+   *    sortResult  ENUMERATED {
+   *        success                   (0), -- results are sorted
+   *        operationsError           (1), -- server internal failure
+   *        timeLimitExceeded         (3), -- timelimit reached before
+   *                                       -- sorting was completed
+   *        strongAuthRequired        (8), -- refused to return sorted
+   *                                       -- results via insecure
+   *                                       -- protocol
+   *        adminLimitExceeded       (11), -- too many matching entries
+   *                                       -- for the server to sort
+   *        noSuchAttribute          (16), -- unrecognized attribute
+   *                                       -- type in sort key
+   *        inappropriateMatching    (18), -- unrecognized or
+   *                                       -- inappropriate matching
+   *                                       -- rule in sort key
+   *        insufficientAccessRights (50), -- refused to return sorted
+   *                                       -- results to this client
+   *        busy                     (51), -- too busy to process
+   *        unwillingToPerform       (53), -- unable to sort
+   *        other                    (80)
+   *        },
+   *  attributeType [0] AttributeDescription OPTIONAL }
+   * </PRE>
+   */
+  public static class Response extends Control
+  {
+    private SortResult sortResult;
+
+    private String attributeDescription;
+
+
+
+    public Response(boolean isCritical, SortResult sortResult)
+    {
+      this(isCritical, sortResult, null);
+    }
+
+
+
+    public Response(boolean isCritical, SortResult sortResult,
+        String attributeDescription)
+    {
+      super(OID_SERVER_SIDE_SORT_RESPONSE_CONTROL, isCritical);
+      Validator.ensureNotNull(sortResult);
+      this.sortResult = sortResult;
+      this.attributeDescription = attributeDescription;
+    }
+
+
+
+    public Response(SortResult sortResult)
+    {
+      this(false, sortResult, null);
+    }
+
+
+
+    public Response(SortResult sortResult, String attributeDescription)
+    {
+      this(false, sortResult, attributeDescription);
+    }
+
+
+
+    public String getAttributeDescription()
+    {
+      return attributeDescription;
+    }
+
+
+
+    public SortResult getSortResult()
+    {
+      return sortResult;
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writer.writeStartSequence();
+        writer.writeEnumerated(sortResult.intValue());
+        if (attributeDescription != null)
+        {
+          writer.writeOctetString(TYPE_ATTRIBUTE_TYPE,
+              attributeDescription);
+        }
+        writer.writeEndSequence();
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    public Response setAttributeDescription(String attributeDescription)
+    {
+      this.attributeDescription = attributeDescription;
+      return this;
+    }
+
+
+
+    public Response setSortResult(SortResult sortResult)
+    {
+      Validator.ensureNotNull(sortResult);
+      this.sortResult = sortResult;
+      return this;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("ServerSideSortResponseControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", sortResult=");
+      buffer.append(sortResult);
+      buffer.append(", attributeDescription=");
+      buffer.append(attributeDescription);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class RequestDecoder implements
+      ControlDecoder<Request>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Request decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = INFO_SORTREQ_CONTROL_NO_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+        if (!reader.hasNextElement())
+        {
+          Message message = INFO_SORTREQ_CONTROL_NO_SORT_KEYS.get();
+          throw DecodeException.error(message);
+        }
+
+        Request request = new Request();
+        while (reader.hasNextElement())
+        {
+          reader.readStartSequence();
+          String attrName = reader.readOctetStringAsString();
+
+          String orderingRule = null;
+          boolean reverseOrder = false;
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_ORDERING_RULE_ID))
+          {
+            orderingRule = reader.readOctetStringAsString();
+          }
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_REVERSE_ORDER))
+          {
+            reverseOrder = reader.readBoolean();
+          }
+          reader.readEndSequence();
+
+          request.addSortKey(new SortKey(attrName, orderingRule,
+              reverseOrder));
+        }
+        reader.readEndSequence();
+
+        return request;
+      }
+      catch (IOException e)
+      {
+        Message message = INFO_SORTREQ_CONTROL_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_SERVER_SIDE_SORT_REQUEST_CONTROL;
+    }
+
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class ResponseDecoder implements
+      ControlDecoder<Response>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Response decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = INFO_SORTRES_CONTROL_NO_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+        SortResult sortResult = SortResult.valueOf(reader
+            .readEnumerated());
+
+        String attributeType = null;
+        if (reader.hasNextElement())
+        {
+          attributeType = reader.readOctetStringAsString();
+        }
+
+        return new Response(isCritical, sortResult, attributeType);
+      }
+      catch (IOException e)
+      {
+        Message message = INFO_SORTRES_CONTROL_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_SERVER_SIDE_SORT_RESPONSE_CONTROL;
+    }
+
+  }
+
+
+
+  /**
+   * The BER type to use when encoding the orderingRule element.
+   */
+  private static final byte TYPE_ORDERING_RULE_ID = (byte) 0x80;
+
+  /**
+   * The BER type to use when encoding the reverseOrder element.
+   */
+  private static final byte TYPE_REVERSE_ORDER = (byte) 0x81;
+
+  /**
+   * The BER type to use when encoding the attribute type element.
+   */
+  private static final byte TYPE_ATTRIBUTE_TYPE = (byte) 0x80;
+
+  /**
+   * The Control Decoder that can be used to decode the request control.
+   */
+  public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+  /**
+   * The Control Decoder that can be used to decode the response
+   * control.
+   */
+  public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+
+
+
+  private static void decodeSortOrderString(String sortOrderString,
+      List<SortKey> sortKeys) throws DecodeException
+  {
+    StringTokenizer tokenizer = new StringTokenizer(sortOrderString,
+        ",");
+
+    while (tokenizer.hasMoreTokens())
+    {
+      String token = tokenizer.nextToken().trim();
+      boolean reverseOrder = false;
+      if (token.startsWith("-"))
+      {
+        reverseOrder = true;
+        token = token.substring(1);
+      }
+      else if (token.startsWith("+"))
+      {
+        token = token.substring(1);
+      }
+
+      int colonPos = token.indexOf(':');
+      if (colonPos < 0)
+      {
+        if (token.length() == 0)
+        {
+          Message message = INFO_SORTREQ_CONTROL_NO_ATTR_NAME
+              .get(sortOrderString);
+          throw DecodeException.error(message);
+        }
+
+        sortKeys.add(new SortKey(token, null, reverseOrder));
+      }
+      else if (colonPos == 0)
+      {
+        Message message = INFO_SORTREQ_CONTROL_NO_ATTR_NAME
+            .get(sortOrderString);
+        throw DecodeException.error(message);
+      }
+      else if (colonPos == (token.length() - 1))
+      {
+        Message message = INFO_SORTREQ_CONTROL_NO_MATCHING_RULE
+            .get(sortOrderString);
+        throw DecodeException.error(message);
+      }
+      else
+      {
+        String attrName = token.substring(0, colonPos);
+        String ruleID = token.substring(colonPos + 1);
+
+        sortKeys.add(new SortKey(attrName, ruleID, reverseOrder));
+      }
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/SortKey.java b/sdk/src/org/opends/sdk/controls/SortKey.java
new file mode 100644
index 0000000..9ba4460
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/SortKey.java
@@ -0,0 +1,144 @@
+package org.opends.sdk.controls;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure that may be used as a sort key.
+ * It includes an attribute description and a boolean value that
+ * indicates whether the sort should be ascending or descending. It may
+ * also contain a specific ordering matching rule that should be used
+ * for the sorting process, although if none is provided it will use the
+ * default ordering matching rule for the attribute type.
+ */
+public class SortKey
+{
+  private String attributeDescription;
+
+  private String orderingRule;
+
+  boolean reverseOrder;
+
+
+
+  public SortKey(AttributeDescription attributeDescription)
+  {
+    this(attributeDescription.toString(), null, false);
+  }
+
+
+
+  public SortKey(AttributeDescription attributeDescription,
+      String orderingRule)
+  {
+    this(attributeDescription.toString(), orderingRule, false);
+  }
+
+
+
+  public SortKey(AttributeDescription attributeDescription,
+      String orderingRule, boolean reverseOrder)
+  {
+    this(attributeDescription.toString(), orderingRule, reverseOrder);
+  }
+
+
+
+  public SortKey(String attributeDescription)
+  {
+    this(attributeDescription, null, false);
+  }
+
+
+
+  public SortKey(String attributeDescription, String orderingRule)
+  {
+    this(attributeDescription, orderingRule, false);
+  }
+
+
+
+  public SortKey(String attributeDescription, String orderingRule,
+      boolean reverseOrder)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    this.attributeDescription = attributeDescription;
+    this.orderingRule = orderingRule;
+    this.reverseOrder = reverseOrder;
+  }
+
+
+
+  public String getAttributeDescription()
+  {
+    return attributeDescription;
+  }
+
+
+
+  public String getOrderingRule()
+  {
+    return orderingRule;
+  }
+
+
+
+  public boolean isReverseOrder()
+  {
+    return reverseOrder;
+  }
+
+
+
+  public SortKey setAttributeDescription(String attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    this.attributeDescription = attributeDescription;
+    return this;
+  }
+
+
+
+  public SortKey setOrderingRule(String orderingRule)
+  {
+    this.orderingRule = orderingRule;
+    return this;
+  }
+
+
+
+  public void setReverseOrder(boolean reverseOrder)
+  {
+    this.reverseOrder = reverseOrder;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder buffer = new StringBuilder();
+    toString(buffer);
+    return buffer.toString();
+  }
+
+
+
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("SortKey(attributeDescription=");
+    buffer.append(attributeDescription);
+    buffer.append(", orderingRule=");
+    buffer.append(orderingRule);
+    buffer.append(", reverseOrder=");
+    buffer.append(reverseOrder);
+    buffer.append(")");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/SortResult.java b/sdk/src/org/opends/sdk/controls/SortResult.java
new file mode 100644
index 0000000..ec35dca
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/SortResult.java
@@ -0,0 +1,113 @@
+package org.opends.sdk.controls;
+
+
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 30, 2009 Time: 4:33:56
+ * PM To change this template use File | Settings | File Templates.
+ */
+public class SortResult
+{
+  private static final SortResult[] ELEMENTS = new SortResult[81];
+
+  public static final SortResult SUCCESS = register(ResultCode.SUCCESS);
+
+  public static final SortResult OPERATIONS_ERROR = register(ResultCode.OPERATIONS_ERROR);
+
+  public static final SortResult TIME_LIMIT_EXCEEDED = register(ResultCode.TIME_LIMIT_EXCEEDED);
+
+  public static final SortResult STRONG_AUTH_REQUIRED = register(ResultCode.STRONG_AUTH_REQUIRED);
+
+  public static final SortResult ADMIN_LIMIT_EXCEEDED = register(ResultCode.ADMIN_LIMIT_EXCEEDED);
+
+  public static final SortResult NO_SUCH_ATTRIBUTE = register(ResultCode.NO_SUCH_ATTRIBUTE);
+
+  public static final SortResult INAPPROPRIATE_MATCHING = register(ResultCode.INAPPROPRIATE_MATCHING);
+
+  public static final SortResult INSUFFICIENT_ACCESS_RIGHTS = register(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+  public static final SortResult BUSY = register(ResultCode.BUSY);
+
+  public static final SortResult UNWILLING_TO_PERFORM = register(ResultCode.UNWILLING_TO_PERFORM);
+
+  public static final SortResult OTHER = register(ResultCode.OTHER);
+
+
+
+  public static SortResult register(ResultCode resultCode)
+  {
+    SortResult t = new SortResult(resultCode);
+    ELEMENTS[resultCode.intValue()] = t;
+    return t;
+  }
+
+
+
+  public static SortResult valueOf(int intValue)
+  {
+    SortResult e = ELEMENTS[intValue];
+    if (e == null)
+    {
+      e = new SortResult(ResultCode.valueOf(intValue));
+    }
+    return e;
+  }
+
+
+
+  public static List<SortResult> values()
+  {
+    return Arrays.asList(ELEMENTS);
+  }
+
+
+
+  private final ResultCode resultCode;
+
+
+
+  private SortResult(ResultCode resultCode)
+  {
+    this.resultCode = resultCode;
+  }
+
+
+
+  @Override
+  public boolean equals(Object o)
+  {
+    return (this == o)
+        || ((o instanceof SortResult) && resultCode.equals(o));
+
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return resultCode.hashCode();
+  }
+
+
+
+  public int intValue()
+  {
+    return resultCode.intValue();
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    return resultCode.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java b/sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java
new file mode 100644
index 0000000..1777b79
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/SubtreeDeleteControl.java
@@ -0,0 +1,112 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_SUBTREE_DELETE_INVALID_CONTROL_VALUE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the subtree delete control defined in
+ * draft-armijo-ldap-treedelete. It makes it possible for clients to
+ * delete subtrees of entries.
+ */
+public class SubtreeDeleteControl extends Control
+{
+  /**
+   * The OID for the subtree delete control.
+   */
+  public static final String OID_SUBTREE_DELETE_CONTROL = "1.2.840.113556.1.4.805";
+
+
+
+  /**
+   * ControlDecoder implementation to decode this control from a
+   * ByteString.
+   */
+  private final static class Decoder implements
+      ControlDecoder<SubtreeDeleteControl>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public SubtreeDeleteControl decode(boolean isCritical,
+        ByteString value, Schema schema) throws DecodeException
+    {
+      if (value != null)
+      {
+        Message message = ERR_SUBTREE_DELETE_INVALID_CONTROL_VALUE
+            .get();
+        throw DecodeException.error(message);
+      }
+
+      return new SubtreeDeleteControl(isCritical);
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_SUBTREE_DELETE_CONTROL;
+    }
+
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode this control.
+   */
+  public static final ControlDecoder<SubtreeDeleteControl> DECODER = new Decoder();
+
+
+
+  /**
+   * Creates a new subtree delete control.
+   * 
+   * @param isCritical
+   *          Indicates whether the control should be considered
+   *          critical for the operation processing.
+   */
+  public SubtreeDeleteControl(boolean isCritical)
+  {
+    super(OID_SUBTREE_DELETE_CONTROL, isCritical);
+  }
+
+
+
+  @Override
+  public ByteString getValue()
+  {
+    return null;
+  }
+
+
+
+  @Override
+  public boolean hasValue()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void toString(StringBuilder buffer)
+  {
+    buffer.append("SubtreeDeleteControl(oid=");
+    buffer.append(getOID());
+    buffer.append(", criticality=");
+    buffer.append(isCritical());
+    buffer.append(")");
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/controls/VLVControl.java b/sdk/src/org/opends/sdk/controls/VLVControl.java
new file mode 100644
index 0000000..c04f353
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/VLVControl.java
@@ -0,0 +1,654 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_VLVREQ_CONTROL_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.INFO_VLVREQ_CONTROL_NO_VALUE;
+import static org.opends.messages.ProtocolMessages.INFO_VLVRES_CONTROL_CANNOT_DECODE_VALUE;
+import static org.opends.messages.ProtocolMessages.INFO_VLVRES_CONTROL_NO_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements the virtual list view controls as defined in
+ * draft-ietf-ldapext-ldapv3-vlv.
+ */
+public class VLVControl
+{
+  /**
+   * The OID for the virtual list view request control.
+   */
+  public static final String OID_VLV_REQUEST_CONTROL = "2.16.840.1.113730.3.4.9";
+
+  /**
+   * The OID for the virtual list view request control.
+   */
+  public static final String OID_VLV_RESPONSE_CONTROL = "2.16.840.1.113730.3.4.10";
+
+
+
+  public static class Request extends Control
+  {
+    private int beforeCount;
+
+    private int afterCount;
+
+    private VLVTarget target;
+
+    private ByteString contextID;
+
+
+
+    /**
+     * Creates a new VLV request control that will identify the target
+     * entry by an assertion value.
+     * 
+     * @param isCritical
+     *          Indicates whether the control should be considered
+     *          critical.
+     * @param beforeCount
+     *          The number of entries before the target assertion value.
+     * @param afterCount
+     *          The number of entries after the target assertion value.
+     * @param assertionValue
+     *          The greaterThanOrEqual target assertion value that
+     *          indicates where to start the page of results.
+     * @param contextID
+     *          The context ID provided by the server in the last VLV
+     *          response for the same set of criteria, or {@code null}
+     *          if there was no previous VLV response or the server did
+     *          not include a context ID in the last response.
+     */
+    public Request(boolean isCritical, int beforeCount, int afterCount,
+        ByteString assertionValue, ByteString contextID)
+    {
+      this(isCritical, beforeCount, afterCount, VLVTarget
+          .greaterThanOrEqual(assertionValue), contextID);
+    }
+
+
+
+    /**
+     * Creates a new VLV request control that will identify the target
+     * entry by offset.
+     * 
+     * @param isCritical
+     *          Indicates whether or not the control is critical.
+     * @param beforeCount
+     *          The number of entries before the target offset to
+     *          retrieve in the results page.
+     * @param afterCount
+     *          The number of entries after the target offset to
+     *          retrieve in the results page.
+     * @param offset
+     *          The offset in the result set to target for the beginning
+     *          of the page of results.
+     * @param contentCount
+     *          The content count returned by the server in the last
+     *          phase of the VLV request, or zero for a new VLV request
+     *          session.
+     * @param contextID
+     *          The context ID provided by the server in the last VLV
+     *          response for the same set of criteria, or {@code null}
+     *          if there was no previous VLV response or the server did
+     *          not include a context ID in the last response.
+     */
+    public Request(boolean isCritical, int beforeCount, int afterCount,
+        int offset, int contentCount, ByteString contextID)
+    {
+      this(isCritical, beforeCount, afterCount, VLVTarget.byOffset(
+          offset, contentCount), contextID);
+    }
+
+
+
+    /**
+     * Creates a new VLV request control that will identify the target
+     * entry by an assertion value.
+     * 
+     * @param beforeCount
+     *          The number of entries before the target offset to
+     *          retrieve in the results page.
+     * @param afterCount
+     *          The number of entries after the target offset to
+     *          retrieve in the results page.
+     * @param greaterThanOrEqual
+     *          The greaterThanOrEqual target assertion value that
+     *          indicates where to start the page of results.
+     */
+    public Request(int beforeCount, int afterCount,
+        ByteString greaterThanOrEqual)
+    {
+      this(false, beforeCount, afterCount, greaterThanOrEqual, null);
+    }
+
+
+
+    /**
+     * Creates a new VLV request control that will identify the target
+     * entry by offset.
+     * 
+     * @param beforeCount
+     *          The number of entries before the target offset to
+     *          retrieve in the results page.
+     * @param afterCount
+     *          The number of entries after the target offset to
+     *          retrieve in the results page.
+     * @param offset
+     *          The offset in the result set to target for the beginning
+     *          of the page of results.
+     * @param contentCount
+     *          The content count returned by the server in the last
+     *          phase of the VLV request, or zero for a new VLV request
+     *          session.
+     */
+    public Request(int beforeCount, int afterCount, int offset,
+        int contentCount)
+    {
+      this(false, beforeCount, afterCount, offset, contentCount, null);
+    }
+
+
+
+    private Request(boolean isCritical, int beforeCount,
+        int afterCount, VLVTarget target, ByteString contextID)
+    {
+      super(OID_VLV_REQUEST_CONTROL, isCritical);
+
+      this.beforeCount = beforeCount;
+      this.afterCount = afterCount;
+      this.target = target;
+      this.contextID = contextID;
+    }
+
+
+
+    /**
+     * Retrieves the number of entries after the target offset or
+     * assertion value to include in the results page.
+     * 
+     * @return The number of entries after the target offset to include
+     *         in the results page.
+     */
+    public int getAfterCount()
+    {
+      return afterCount;
+    }
+
+
+
+    /**
+     * Retrieves the number of entries before the target offset or
+     * assertion value to include in the results page.
+     * 
+     * @return The number of entries before the target offset to include
+     *         in the results page.
+     */
+    public int getBeforeCount()
+    {
+      return beforeCount;
+    }
+
+
+
+    /**
+     * Retrieves a context ID value that should be used to resume a
+     * previous VLV results session.
+     * 
+     * @return A context ID value that should be used to resume a
+     *         previous VLV results session, or {@code null} if none is
+     *         available.
+     */
+    public ByteString getContextID()
+    {
+      return contextID;
+    }
+
+
+
+    public VLVTarget getTarget()
+    {
+      return target;
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writer.writeStartSequence();
+        writer.writeInteger(beforeCount);
+        writer.writeInteger(afterCount);
+        target.encode(writer);
+        if (contextID != null)
+        {
+          writer.writeOctetString(contextID);
+        }
+        writer.writeEndSequence();
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    public Request setAfterCount(int afterCount)
+    {
+      this.afterCount = afterCount;
+      return this;
+    }
+
+
+
+    public Request setBeforeCount(int beforeCount)
+    {
+      this.beforeCount = beforeCount;
+      return this;
+    }
+
+
+
+    public Request setContextID(ByteString contextID)
+    {
+      this.contextID = contextID;
+      return this;
+    }
+
+
+
+    public Request setTarget(ByteString assertionValue)
+    {
+      Validator.ensureNotNull(assertionValue);
+      target = VLVTarget.greaterThanOrEqual(assertionValue);
+      return this;
+    }
+
+
+
+    public Request setTarget(int offset, int contentCount)
+    {
+      target = VLVTarget.byOffset(offset, contentCount);
+      return this;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("VLVRequestControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", beforeCount=");
+      buffer.append(beforeCount);
+      buffer.append(", afterCount=");
+      buffer.append(afterCount);
+      buffer.append(", target=");
+      target.toString(buffer);
+      buffer.append(", contextID=");
+      buffer.append(contextID);
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * This class implements the virtual list view response controls as
+   * defined in draft-ietf-ldapext-ldapv3-vlv. The ASN.1 description for
+   * the control value is: <BR>
+   * <BR>
+   * 
+   * <PRE>
+   * VirtualListViewResponse ::= SEQUENCE {
+   *       targetPosition    INTEGER (0 .. maxInt),
+   *       contentCount     INTEGER (0 .. maxInt),
+   *       virtualListViewResult ENUMERATED {
+   *            success (0),
+   *            operationsError (1),
+   *            protocolError (3),
+   *            unwillingToPerform (53),
+   *            insufficientAccessRights (50),
+   *            timeLimitExceeded (3),
+   *            adminLimitExceeded (11),
+   *            innapropriateMatching (18),
+   *            sortControlMissing (60),
+   *            offsetRangeError (61),
+   *            other(80),
+   *            ... },
+   *       contextID     OCTET STRING OPTIONAL }
+   * </PRE>
+   */
+  public static class Response extends Control
+  {
+    private final int targetPosition;
+
+    private final int contentCount;
+
+    private final VLVResult vlvResult;
+
+    private final ByteString contextID;
+
+
+
+    /**
+     * Creates a new VLV response control with the provided information.
+     * 
+     * @param isCritical
+     *          Indicates whether the control should be considered
+     *          critical.
+     * @param targetPosition
+     *          The position of the target entry in the result set.
+     * @param contentCount
+     *          The content count estimating the total number of entries
+     *          in the result set.
+     * @param vlvResult
+     *          The result code for the VLV operation.
+     * @param contextID
+     *          The context ID for this VLV response control.
+     */
+    public Response(boolean isCritical, int targetPosition,
+        int contentCount, VLVResult vlvResult, ByteString contextID)
+    {
+      super(OID_VLV_RESPONSE_CONTROL, isCritical);
+
+      this.targetPosition = targetPosition;
+      this.contentCount = contentCount;
+      this.vlvResult = vlvResult;
+      this.contextID = contextID;
+    }
+
+
+
+    /**
+     * Creates a new VLV response control with the provided information.
+     * 
+     * @param targetPosition
+     *          The position of the target entry in the result set.
+     * @param contentCount
+     *          The content count estimating the total number of entries
+     *          in the result set.
+     * @param vlvResult
+     *          The result code for the VLV operation.
+     */
+    public Response(int targetPosition, int contentCount,
+        VLVResult vlvResult)
+    {
+      this(false, targetPosition, contentCount, vlvResult, null);
+    }
+
+
+
+    /**
+     * Retrieves the estimated total number of entries in the result
+     * set.
+     * 
+     * @return The estimated total number of entries in the result set.
+     */
+    public int getContentCount()
+    {
+      return contentCount;
+    }
+
+
+
+    /**
+     * Retrieves a context ID value that should be included in the next
+     * request to retrieve a page of the same result set.
+     * 
+     * @return A context ID value that should be included in the next
+     *         request to retrieve a page of the same result set, or
+     *         {@code null} if there is no context ID.
+     */
+    public ByteString getContextID()
+    {
+      return contextID;
+    }
+
+
+
+    /**
+     * Retrieves the position of the target entry in the result set.
+     * 
+     * @return The position of the target entry in the result set.
+     */
+    public int getTargetPosition()
+    {
+      return targetPosition;
+    }
+
+
+
+    @Override
+    public ByteString getValue()
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+      try
+      {
+        writer.writeStartSequence();
+        writer.writeInteger(targetPosition);
+        writer.writeInteger(contentCount);
+        writer.writeEnumerated(vlvResult.intValue());
+        if (contextID != null)
+        {
+          writer.writeOctetString(contextID);
+        }
+        writer.writeEndSequence();
+        return buffer.toByteString();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    /**
+     * Retrieves the result code for the VLV operation.
+     * 
+     * @return The result code for the VLV operation.
+     */
+    public VLVResult getVLVResult()
+    {
+      return vlvResult;
+    }
+
+
+
+    @Override
+    public boolean hasValue()
+    {
+      return true;
+    }
+
+
+
+    /**
+     * Appends a string representation of this VLV request control to
+     * the provided buffer.
+     * 
+     * @param buffer
+     *          The buffer to which the information should be appended.
+     */
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("VLVResponseControl(oid=");
+      buffer.append(getOID());
+      buffer.append(", criticality=");
+      buffer.append(isCritical());
+      buffer.append(", targetPosition=");
+      buffer.append(targetPosition);
+      buffer.append(", contentCount=");
+      buffer.append(contentCount);
+      buffer.append(", vlvResult=");
+      buffer.append(vlvResult);
+
+      if (contextID != null)
+      {
+        buffer.append(", contextID=");
+        buffer.append(contextID);
+      }
+
+      buffer.append(")");
+    }
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class RequestDecoder implements
+      ControlDecoder<Request>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Request decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = INFO_VLVREQ_CONTROL_NO_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+
+        int beforeCount = (int) reader.readInteger();
+        int afterCount = (int) reader.readInteger();
+        VLVTarget target = VLVTarget.decode(reader);
+        ByteString contextID = null;
+        if (reader.hasNextElement())
+        {
+          contextID = reader.readOctetString();
+        }
+
+        return new Request(isCritical, beforeCount, afterCount, target,
+            contextID);
+      }
+      catch (IOException e)
+      {
+        Message message = INFO_VLVREQ_CONTROL_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+    }
+
+
+
+    public String getOID()
+    {
+      return OID_VLV_REQUEST_CONTROL;
+    }
+  }
+
+
+
+  /**
+   * ControlDecoder implentation to decode this control from a
+   * ByteString.
+   */
+  private final static class ResponseDecoder implements
+      ControlDecoder<Response>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public Response decode(boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      if (value == null)
+      {
+        Message message = INFO_VLVRES_CONTROL_NO_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      ASN1Reader reader = ASN1.getReader(value);
+      try
+      {
+        reader.readStartSequence();
+
+        int targetPosition = (int) reader.readInteger();
+        int contentCount = (int) reader.readInteger();
+        VLVResult vlvResult = VLVResult
+            .valueOf(reader.readEnumerated());
+        ByteString contextID = null;
+        if (reader.hasNextElement())
+        {
+          contextID = reader.readOctetString();
+        }
+
+        return new Response(isCritical, targetPosition, contentCount,
+            vlvResult, contextID);
+      }
+      catch (IOException e)
+      {
+        Message message = INFO_VLVRES_CONTROL_CANNOT_DECODE_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getOID()
+    {
+      return OID_VLV_RESPONSE_CONTROL;
+    }
+  }
+
+
+
+  /**
+   * The Control Decoder that can be used to decode the request control.
+   */
+  public static final ControlDecoder<Request> REQUEST_DECODER = new RequestDecoder();
+
+  /**
+   * The Control Decoder that can be used to decode the response
+   * control.
+   */
+  public static final ControlDecoder<Response> RESPONSE_DECODER = new ResponseDecoder();
+}
diff --git a/sdk/src/org/opends/sdk/controls/VLVResult.java b/sdk/src/org/opends/sdk/controls/VLVResult.java
new file mode 100644
index 0000000..a8eb7bb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/VLVResult.java
@@ -0,0 +1,113 @@
+package org.opends.sdk.controls;
+
+
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 30, 2009 Time: 5:25:55
+ * PM To change this template use File | Settings | File Templates.
+ */
+public class VLVResult
+{
+  private static final VLVResult[] ELEMENTS = new VLVResult[81];
+
+  public static final VLVResult SUCCESS = register(ResultCode.SUCCESS);
+
+  public static final VLVResult OPERATIONS_ERROR = register(ResultCode.OPERATIONS_ERROR);
+
+  public static final VLVResult PROTOCOL_ERROR = register(ResultCode.PROTOCOL_ERROR);
+
+  public static final VLVResult TIME_LIMIT_EXCEEDED = register(ResultCode.TIME_LIMIT_EXCEEDED);
+
+  public static final VLVResult ADMIN_LIMIT_EXCEEDED = register(ResultCode.ADMIN_LIMIT_EXCEEDED);
+
+  public static final VLVResult INAPPROPRIATE_MATCHING = register(ResultCode.INAPPROPRIATE_MATCHING);
+
+  public static final VLVResult INSUFFICIENT_ACCESS_RIGHTS = register(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
+
+  public static final VLVResult UNWILLING_TO_PERFORM = register(ResultCode.UNWILLING_TO_PERFORM);
+
+  public static final VLVResult SORT_CONTROL_MISSING = register(ResultCode.SORT_CONTROL_MISSING);
+
+  public static final VLVResult OFFSET_RANGE_ERROR = register(ResultCode.OFFSET_RANGE_ERROR);
+
+  public static final VLVResult OTHER = register(ResultCode.OTHER);
+
+
+
+  public static VLVResult register(ResultCode resultCode)
+  {
+    VLVResult t = new VLVResult(resultCode);
+    ELEMENTS[resultCode.intValue()] = t;
+    return t;
+  }
+
+
+
+  public static VLVResult valueOf(int intValue)
+  {
+    VLVResult e = ELEMENTS[intValue];
+    if (e == null)
+    {
+      e = new VLVResult(ResultCode.valueOf(intValue));
+    }
+    return e;
+  }
+
+
+
+  public static List<VLVResult> values()
+  {
+    return Arrays.asList(ELEMENTS);
+  }
+
+
+
+  private final ResultCode resultCode;
+
+
+
+  private VLVResult(ResultCode resultCode)
+  {
+    this.resultCode = resultCode;
+  }
+
+
+
+  @Override
+  public boolean equals(Object o)
+  {
+    return (this == o)
+        || ((o instanceof VLVResult) && resultCode.equals(o));
+
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return resultCode.hashCode();
+  }
+
+
+
+  public int intValue()
+  {
+    return resultCode.intValue();
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    return resultCode.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/controls/VLVTarget.java b/sdk/src/org/opends/sdk/controls/VLVTarget.java
new file mode 100644
index 0000000..8aef176
--- /dev/null
+++ b/sdk/src/org/opends/sdk/controls/VLVTarget.java
@@ -0,0 +1,191 @@
+package org.opends.sdk.controls;
+
+
+
+import static org.opends.messages.ProtocolMessages.INFO_VLVREQ_CONTROL_INVALID_TARGET_TYPE;
+import static org.opends.sdk.util.StaticUtils.byteToHex;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 30, 2009 Time: 5:40:28
+ * PM To change this template use File | Settings | File Templates.
+ */
+public abstract class VLVTarget
+{
+  public static final class ByOffset extends VLVTarget
+  {
+    private final int offset;
+
+    private final int contentCount;
+
+
+
+    private ByOffset(int offset, int contentCount)
+    {
+      this.offset = offset;
+      this.contentCount = contentCount;
+    }
+
+
+
+    public int getContentCount()
+    {
+      return contentCount;
+    }
+
+
+
+    public int getOffset()
+    {
+      return offset;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("byOffset(offset=");
+      buffer.append(offset);
+      buffer.append(", contentCount=");
+      buffer.append(contentCount);
+      buffer.append(")");
+    }
+
+
+
+    @Override
+    void encode(ASN1Writer writer) throws IOException
+    {
+      writer.writeStartSequence(TYPE_TARGET_BYOFFSET);
+      writer.writeInteger(offset);
+      writer.writeInteger(contentCount);
+      writer.writeEndSequence();
+    }
+  }
+
+
+
+  public static final class GreaterThanOrEqual extends VLVTarget
+  {
+    private final ByteString assertionValue;
+
+
+
+    private GreaterThanOrEqual(ByteString assertionValue)
+    {
+      this.assertionValue = assertionValue;
+    }
+
+
+
+    public ByteString getAssertionValue()
+    {
+      return assertionValue;
+    }
+
+
+
+    @Override
+    public void toString(StringBuilder buffer)
+    {
+      buffer.append("greaterThanOrEqual(assertionValue=");
+      buffer.append(assertionValue);
+      buffer.append(")");
+    }
+
+
+
+    @Override
+    void encode(ASN1Writer writer) throws IOException
+    {
+      writer.writeOctetString(TYPE_TARGET_GREATERTHANOREQUAL,
+          assertionValue);
+    }
+  }
+
+
+
+  /**
+   * The BER type to use when encoding the byOffset target element.
+   */
+  private static final byte TYPE_TARGET_BYOFFSET = (byte) 0xA0;
+
+  /**
+   * The BER type to use when encoding the greaterThanOrEqual target
+   * element.
+   */
+  private static final byte TYPE_TARGET_GREATERTHANOREQUAL = (byte) 0x81;
+
+
+
+  static VLVTarget byOffset(int offset, int contentCount)
+  {
+    return new ByOffset(offset, contentCount);
+  }
+
+
+
+  static VLVTarget decode(ASN1Reader reader) throws IOException,
+      DecodeException
+  {
+    byte targetType = reader.peekType();
+    switch (targetType)
+    {
+    case TYPE_TARGET_BYOFFSET:
+      reader.readStartSequence();
+      int offset = (int) reader.readInteger();
+      int contentCount = (int) reader.readInteger();
+      reader.readEndSequence();
+      return new ByOffset(offset, contentCount);
+
+    case TYPE_TARGET_GREATERTHANOREQUAL:
+      ByteString assertionValue = reader.readOctetString();
+      return new GreaterThanOrEqual(assertionValue);
+
+    default:
+      Message message = INFO_VLVREQ_CONTROL_INVALID_TARGET_TYPE
+          .get(byteToHex(targetType));
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  static VLVTarget greaterThanOrEqual(ByteString assertionValue)
+  {
+    Validator.ensureNotNull(assertionValue);
+    return new GreaterThanOrEqual(assertionValue);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder buffer = new StringBuilder();
+    toString(buffer);
+    return buffer.toString();
+  }
+
+
+
+  public abstract void toString(StringBuilder buffer);
+
+
+
+  abstract void encode(ASN1Writer writer) throws IOException;
+}
diff --git a/sdk/src/org/opends/sdk/extensions/CancelRequest.java b/sdk/src/org/opends/sdk/extensions/CancelRequest.java
new file mode 100644
index 0000000..4e85fe2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/CancelRequest.java
@@ -0,0 +1,171 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_EXTOP_CANCEL_CANNOT_DECODE_REQUEST_VALUE;
+import static org.opends.messages.ExtensionMessages.ERR_EXTOP_CANCEL_NO_REQUEST_VALUE;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 22, 2009 Time: 4:44:51
+ * PM To change this template use File | Settings | File Templates.
+ */
+public final class CancelRequest extends
+    AbstractExtendedRequest<CancelRequest, Result>
+{
+  /**
+   * The request OID for the cancel extended operation.
+   */
+  static final String OID_CANCEL_REQUEST = "1.3.6.1.1.8";
+
+  private int cancelID;
+
+
+
+  public CancelRequest(int cancelID)
+  {
+    this.cancelID = cancelID;
+  }
+
+
+
+  public int getCancelID()
+  {
+    return cancelID;
+  }
+
+
+
+  public Operation getExtendedOperation()
+  {
+    return OPERATION;
+  }
+
+
+
+  public ByteString getRequestValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder(6);
+    ASN1Writer writer = ASN1.getWriter(buffer);
+
+    try
+    {
+      writer.writeStartSequence();
+      writer.writeInteger(cancelID);
+      writer.writeEndSequence();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+
+    return buffer.toByteString();
+  }
+
+
+
+  public CancelRequest setCancelID(int cancelID)
+  {
+    this.cancelID = cancelID;
+    return this;
+  }
+
+
+
+  public StringBuilder toString(StringBuilder builder)
+  {
+    builder.append("CancelExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", cancelID=");
+    builder.append(cancelID);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder;
+  }
+
+
+
+  private static final class Operation implements
+      ExtendedOperation<CancelRequest, Result>
+  {
+
+    public CancelRequest decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      if ((requestValue == null) || (requestValue.length() <= 0))
+      {
+        throw DecodeException
+            .error(ERR_EXTOP_CANCEL_NO_REQUEST_VALUE.get());
+      }
+
+      try
+      {
+        ASN1Reader reader = ASN1.getReader(requestValue);
+        reader.readStartSequence();
+        int idToCancel = (int) reader.readInteger();
+        reader.readEndSequence();
+        return new CancelRequest(idToCancel);
+      }
+      catch (IOException e)
+      {
+        Message message = ERR_EXTOP_CANCEL_CANNOT_DECODE_REQUEST_VALUE
+            .get(getExceptionMessage(e));
+        throw DecodeException.error(message, e);
+      }
+    }
+
+
+
+    public Result decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+    }
+
+
+
+    public Result decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      // TODO: Should we check to make sure OID and value is null?
+      return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+    }
+  }
+
+
+
+  // Singleton instance.
+  private static final Operation OPERATION = new Operation();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRequestName()
+  {
+    return OID_CANCEL_REQUEST;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/ExtendedOperation.java b/sdk/src/org/opends/sdk/extensions/ExtendedOperation.java
new file mode 100644
index 0000000..28d8ee4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/ExtendedOperation.java
@@ -0,0 +1,34 @@
+package org.opends.sdk.extensions;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.requests.ExtendedRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: digitalperk Date: Jun 19, 2009 Time:
+ * 8:39:52 PM To change this template use File | Settings | File
+ * Templates.
+ */
+public interface ExtendedOperation<R extends ExtendedRequest<S>, S extends Result>
+{
+  R decodeRequest(String requestName, ByteString requestValue)
+      throws DecodeException;
+
+
+
+  S decodeResponse(ResultCode resultCode, String matchedDN,
+      String diagnosticMessage);
+
+
+
+  S decodeResponse(ResultCode resultCode, String matchedDN,
+      String diagnosticMessage, String responseName,
+      ByteString responseValue) throws DecodeException;
+
+}
diff --git a/sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java b/sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java
new file mode 100644
index 0000000..308b62a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/GetConnectionIDRequest.java
@@ -0,0 +1,140 @@
+package org.opends.sdk.extensions;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 23, 2009 Time:
+ * 11:43:53 AM To change this template use File | Settings | File
+ * Templates.
+ */
+public final class GetConnectionIDRequest
+    extends
+    AbstractExtendedRequest<GetConnectionIDRequest, GetConnectionIDResult>
+{
+  /**
+   * The OID for the extended operation that can be used to get the
+   * client connection ID. It will be both the request and response OID.
+   */
+  static final String OID_GET_CONNECTION_ID_EXTOP = "1.3.6.1.4.1.26027.1.6.2";
+
+
+
+  public GetConnectionIDRequest()
+  {
+  }
+
+
+
+  public Operation getExtendedOperation()
+  {
+    return OPERATION;
+  }
+
+
+
+  public ByteString getRequestValue()
+  {
+    return null;
+  }
+
+
+
+  public StringBuilder toString(StringBuilder builder)
+  {
+    builder.append("GetConnectionIDExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder;
+  }
+
+
+
+  private static final class Operation implements
+      ExtendedOperation<GetConnectionIDRequest, GetConnectionIDResult>
+  {
+
+    public GetConnectionIDRequest decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      return new GetConnectionIDRequest();
+    }
+
+
+
+    public GetConnectionIDResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      if (!resultCode.isExceptional()
+          && ((responseValue == null) || (responseValue.length() <= 0)))
+      {
+        throw DecodeException.error(Message
+            .raw("Empty response value"));
+      }
+
+      try
+      {
+        ASN1Reader reader = ASN1.getReader(responseValue);
+        int connectionID = (int) reader.readInteger();
+        return new GetConnectionIDResult(resultCode, connectionID)
+            .setMatchedDN(matchedDN).setDiagnosticMessage(
+                diagnosticMessage);
+      }
+      catch (IOException e)
+      {
+        throw DecodeException.error(Message
+            .raw("Error decoding response value"), e);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public GetConnectionIDResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      if (!resultCode.isExceptional())
+      {
+        // A successful response must contain a response name and
+        // value.
+        throw new IllegalArgumentException(
+            "No response name and value for result code "
+                + resultCode.intValue());
+      }
+      return new GetConnectionIDResult(resultCode, -1).setMatchedDN(
+          matchedDN).setDiagnosticMessage(diagnosticMessage);
+    }
+  }
+
+
+
+  // Singleton instance.
+  private static final Operation OPERATION = new Operation();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRequestName()
+  {
+    return OID_GET_CONNECTION_ID_EXTOP;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java b/sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java
new file mode 100644
index 0000000..7fb1be0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/GetConnectionIDResult.java
@@ -0,0 +1,122 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.extensions;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+public class GetConnectionIDResult extends
+    AbstractExtendedResult<GetConnectionIDResult>
+{
+  private int connectionID;
+
+
+
+  public GetConnectionIDResult(ResultCode resultCode, int connectionID)
+  {
+    super(resultCode);
+    this.connectionID = connectionID;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getResponseName()
+  {
+    return GetConnectionIDRequest.OID_GET_CONNECTION_ID_EXTOP;
+  }
+
+
+
+  public int getConnectionID()
+  {
+    return connectionID;
+  }
+
+
+
+  public ByteString getResponseValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder(6);
+    ASN1Writer writer = ASN1.getWriter(buffer);
+
+    try
+    {
+      writer.writeInteger(connectionID);
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+
+    return buffer.toByteString();
+  }
+
+
+
+  public GetConnectionIDResult setConnectionID(int connectionID)
+  {
+    this.connectionID = connectionID;
+    return this;
+  }
+
+
+
+  public StringBuilder toString(StringBuilder builder)
+  {
+    builder.append("GetConnectionIDExtendedResponse(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    builder.append(", responseName=");
+    builder.append(getResponseName());
+    builder.append(", connectionID=");
+    builder.append(connectionID);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java b/sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java
new file mode 100644
index 0000000..8d0c3e9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/GetSymmetricKeyRequest.java
@@ -0,0 +1,231 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_GET_SYMMETRIC_KEY_ASN1_DECODE_EXCEPTION;
+import static org.opends.messages.ExtensionMessages.ERR_GET_SYMMETRIC_KEY_NO_VALUE;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 23, 2009 Time:
+ * 12:10:59 PM To change this template use File | Settings | File
+ * Templates.
+ */
+public final class GetSymmetricKeyRequest extends
+    AbstractExtendedRequest<GetSymmetricKeyRequest, Result>
+{
+  /**
+   * The request OID for the get symmetric key extended operation.
+   */
+  static final String OID_GET_SYMMETRIC_KEY_EXTENDED_OP = "1.3.6.1.4.1.26027.1.6.3";
+
+  private String requestSymmetricKey = null;
+
+  private String instanceKeyID = null;
+
+
+
+  public GetSymmetricKeyRequest()
+  {
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRequestName()
+  {
+    return OID_GET_SYMMETRIC_KEY_EXTENDED_OP;
+  }
+
+
+
+  public Operation getExtendedOperation()
+  {
+    return OPERATION;
+  }
+
+
+
+  public String getInstanceKeyID()
+  {
+    return instanceKeyID;
+  }
+
+
+
+  public String getRequestSymmetricKey()
+  {
+    return requestSymmetricKey;
+  }
+
+
+
+  public ByteString getRequestValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+
+    try
+    {
+      writer.writeStartSequence();
+      if (requestSymmetricKey != null)
+      {
+        writer.writeOctetString(TYPE_SYMMETRIC_KEY_ELEMENT,
+            requestSymmetricKey);
+      }
+      if (instanceKeyID != null)
+      {
+        writer.writeOctetString(TYPE_INSTANCE_KEY_ID_ELEMENT,
+            instanceKeyID);
+      }
+      writer.writeEndSequence();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+
+    return buffer.toByteString();
+  }
+
+
+
+  public GetSymmetricKeyRequest setInstanceKeyID(String instanceKeyID)
+  {
+    this.instanceKeyID = instanceKeyID;
+    return this;
+  }
+
+
+
+  public GetSymmetricKeyRequest setRequestSymmetricKey(
+      String requestSymmetricKey)
+  {
+    this.requestSymmetricKey = requestSymmetricKey;
+    return this;
+  }
+
+
+
+  public StringBuilder toString(StringBuilder builder)
+  {
+    builder.append("GetSymmetricKeyExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", requestSymmetricKey=");
+    builder.append(requestSymmetricKey);
+    builder.append(", instanceKeyID=");
+    builder.append(instanceKeyID);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder;
+  }
+
+
+
+  /**
+   * The BER type value for the symmetric key element of the operation
+   * value.
+   */
+  private static final byte TYPE_SYMMETRIC_KEY_ELEMENT = (byte) 0x80;
+
+  /**
+   * The BER type value for the instance key ID element of the operation
+   * value.
+   */
+  private static final byte TYPE_INSTANCE_KEY_ID_ELEMENT = (byte) 0x81;
+
+
+
+  private static final class Operation implements
+      ExtendedOperation<GetSymmetricKeyRequest, Result>
+  {
+
+    public GetSymmetricKeyRequest decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      if (requestValue == null)
+      {
+        // The request must always have a value.
+        Message message = ERR_GET_SYMMETRIC_KEY_NO_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      String requestSymmetricKey = null;
+      String instanceKeyID = null;
+
+      try
+      {
+        ASN1Reader reader = ASN1.getReader(requestValue);
+        reader.readStartSequence();
+        if (reader.hasNextElement()
+            && (reader.peekType() == TYPE_SYMMETRIC_KEY_ELEMENT))
+        {
+          requestSymmetricKey = reader.readOctetStringAsString();
+        }
+        if (reader.hasNextElement()
+            && (reader.peekType() == TYPE_INSTANCE_KEY_ID_ELEMENT))
+        {
+          instanceKeyID = reader.readOctetStringAsString();
+        }
+        reader.readEndSequence();
+        return new GetSymmetricKeyRequest().setRequestSymmetricKey(
+            requestSymmetricKey).setInstanceKeyID(instanceKeyID);
+      }
+      catch (IOException ae)
+      {
+        StaticUtils.DEBUG_LOG.throwing(
+            "GetSymmetricKeyRequest.Operation", "decodeRequest", ae);
+
+        Message message = ERR_GET_SYMMETRIC_KEY_ASN1_DECODE_EXCEPTION
+            .get(ae.getMessage());
+        throw DecodeException.error(message, ae);
+      }
+    }
+
+
+
+    public Result decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+    }
+
+
+
+    public Result decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      // TODO: Should we check to make sure OID and value is null?
+      return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+    }
+  }
+
+
+
+  // Singleton instance.
+  private static final Operation OPERATION = new Operation();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java b/sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java
new file mode 100644
index 0000000..fc4dff1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/PasswordModifyRequest.java
@@ -0,0 +1,278 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * This class implements the password modify extended operation response
+ * defined in RFC 3062. It includes support for requiring the user's
+ * current password as well as for generating a new password if none was
+ * provided.
+ */
+public final class PasswordModifyRequest extends
+    AbstractExtendedRequest<PasswordModifyRequest, PasswordModifyResult>
+{
+  /**
+   * The request OID for the password modify extended operation.
+   */
+  public static final String OID_PASSWORD_MODIFY_REQUEST = "1.3.6.1.4.1.4203.1.11.1";
+
+  /**
+   * The ASN.1 element type that will be used to encode the userIdentity
+   * component in a password modify extended request.
+   */
+  static final byte TYPE_PASSWORD_MODIFY_USER_ID = (byte) 0x80;
+
+  /**
+   * The ASN.1 element type that will be used to encode the oldPasswd
+   * component in a password modify extended request.
+   */
+  static final byte TYPE_PASSWORD_MODIFY_OLD_PASSWORD = (byte) 0x81;
+
+  /**
+   * The ASN.1 element type that will be used to encode the newPasswd
+   * component in a password modify extended request.
+   */
+  static final byte TYPE_PASSWORD_MODIFY_NEW_PASSWORD = (byte) 0x82;
+
+  /**
+   * The ASN.1 element type that will be used to encode the genPasswd
+   * component in a password modify extended response.
+   */
+  static final byte TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD = (byte) 0x80;
+
+  private String userIdentity;
+
+  private ByteString oldPassword;
+
+  private ByteString newPassword;
+
+
+
+  public PasswordModifyRequest()
+  {
+
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRequestName()
+  {
+    return OID_PASSWORD_MODIFY_REQUEST;
+  }
+
+
+  public ExtendedOperation<PasswordModifyRequest, PasswordModifyResult>
+  getExtendedOperation()
+  {
+    return OPERATION;
+  }
+
+  public ByteString getNewPassword()
+  {
+    return newPassword;
+  }
+
+
+
+  public ByteString getOldPassword()
+  {
+    return oldPassword;
+  }
+
+
+
+  public ByteString getRequestValue()
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder();
+    ASN1Writer writer = ASN1.getWriter(buffer);
+
+    try
+    {
+      writer.writeStartSequence();
+      if (userIdentity != null)
+      {
+        writer.writeOctetString(TYPE_PASSWORD_MODIFY_USER_ID,
+            userIdentity);
+      }
+      if (oldPassword != null)
+      {
+        writer.writeOctetString(TYPE_PASSWORD_MODIFY_OLD_PASSWORD,
+            oldPassword);
+      }
+      if (newPassword != null)
+      {
+        writer.writeOctetString(TYPE_PASSWORD_MODIFY_NEW_PASSWORD,
+            newPassword);
+      }
+      writer.writeEndSequence();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+
+    return buffer.toByteString();
+  }
+
+
+
+  public String getUserIdentity()
+  {
+    return userIdentity;
+  }
+
+
+
+  public PasswordModifyRequest setNewPassword(ByteString newPassword)
+  {
+    this.newPassword = newPassword;
+    return this;
+  }
+
+
+
+  public PasswordModifyRequest setOldPassword(ByteString oldPassword)
+  {
+    this.oldPassword = oldPassword;
+    return this;
+  }
+
+
+
+  public PasswordModifyRequest setUserIdentity(String userIdentity)
+  {
+    this.userIdentity = userIdentity;
+    return this;
+  }
+
+
+
+  public StringBuilder toString(StringBuilder builder)
+  {
+    builder.append("PasswordModifyExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", userIdentity=");
+    builder.append(userIdentity);
+    builder.append(", oldPassword=");
+    builder.append(oldPassword);
+    builder.append(", newPassword=");
+    builder.append(newPassword);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder;
+  }
+
+
+
+  private static final class Operation implements
+      ExtendedOperation<PasswordModifyRequest, PasswordModifyResult>
+  {
+
+    public PasswordModifyRequest decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      PasswordModifyRequest request = new PasswordModifyRequest();
+      if (requestValue != null)
+      {
+        try
+        {
+          ASN1Reader reader = ASN1.getReader(requestValue);
+          reader.readStartSequence();
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_PASSWORD_MODIFY_USER_ID))
+          {
+            request.setUserIdentity(reader.readOctetStringAsString());
+          }
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_PASSWORD_MODIFY_OLD_PASSWORD))
+          {
+            request.setOldPassword(reader.readOctetString());
+          }
+          if (reader.hasNextElement()
+              && (reader.peekType() == TYPE_PASSWORD_MODIFY_NEW_PASSWORD))
+          {
+            request.setNewPassword(reader.readOctetString());
+          }
+          reader.readEndSequence();
+        }
+        catch (IOException e)
+        {
+          Message message = ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST
+              .get(getExceptionMessage(e));
+          throw DecodeException.error(message, e);
+        }
+      }
+      return request;
+    }
+
+
+
+    public PasswordModifyResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      // TODO: Should we check to make sure OID is null?
+      PasswordModifyResult result = new PasswordModifyResult(resultCode)
+          .setMatchedDN(matchedDN).setDiagnosticMessage(
+              diagnosticMessage);
+      if (resultCode == ResultCode.SUCCESS && responseValue != null)
+      {
+        try
+        {
+          ASN1Reader asn1Reader = ASN1.getReader(responseValue);
+          asn1Reader.readStartSequence();
+          if (asn1Reader.peekType() == TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD)
+          {
+            result.setGenPassword(asn1Reader.readOctetString());
+          }
+          asn1Reader.readEndSequence();
+        }
+        catch (IOException e)
+        {
+          Message message = ERR_EXTOP_PASSMOD_CANNOT_DECODE_REQUEST
+              .get(getExceptionMessage(e));
+          throw DecodeException.error(message, e);
+        }
+      }
+      return result;
+    }
+
+
+
+    public PasswordModifyResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      return new PasswordModifyResult(resultCode).setMatchedDN(
+          matchedDN).setDiagnosticMessage(diagnosticMessage);
+    }
+  }
+
+
+
+  // Singleton instance.
+  private static final Operation OPERATION = new Operation();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java b/sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java
new file mode 100644
index 0000000..b73354b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/PasswordModifyResult.java
@@ -0,0 +1,119 @@
+package org.opends.sdk.extensions;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+
+
+
+/**
+ * This class implements the password modify extended operation response
+ * defined in RFC 3062. It includes support for requiring the user's
+ * current password as well as for generating a new password if none was
+ * provided.
+ */
+public class PasswordModifyResult extends
+    AbstractExtendedResult<PasswordModifyResult>
+{
+  private ByteString genPassword;
+
+
+
+  public PasswordModifyResult(ResultCode resultCode)
+  {
+    super(resultCode);
+  }
+
+
+
+  /**
+   * Get the newly generated password.
+   * 
+   * @return The password generated by the server or <code>null</code>
+   *         if it is not available.
+   */
+  public ByteString getGenPassword()
+  {
+    return genPassword;
+  }
+
+
+
+  public PasswordModifyResult setGenPassword(ByteString genPassword)
+  {
+    this.genPassword = genPassword;
+    return this;
+  }
+
+
+
+  public ByteString getResponseValue()
+  {
+    if (genPassword != null)
+    {
+      ByteStringBuilder buffer = new ByteStringBuilder();
+      ASN1Writer writer = ASN1.getWriter(buffer);
+
+      try
+      {
+        writer.writeStartSequence();
+        writer
+            .writeOctetString(
+                PasswordModifyRequest.TYPE_PASSWORD_MODIFY_GENERATED_PASSWORD,
+                genPassword);
+        writer.writeEndSequence();
+      }
+      catch (IOException ioe)
+      {
+        // This should never happen unless there is a bug somewhere.
+        throw new RuntimeException(ioe);
+      }
+
+      return buffer.toByteString();
+    }
+    return null;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("PasswordModifyExtendedResponse(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    if (genPassword != null)
+    {
+      builder.append(", genPassword=");
+      builder.append(genPassword);
+    }
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getResponseName()
+  {
+    // No response name defined.
+    return null;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java b/sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java
new file mode 100644
index 0000000..aefdd21
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/PasswordPolicyStateExtendedOperation.java
@@ -0,0 +1,1077 @@
+package org.opends.sdk.extensions;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_PWPSTATE_EXTOP_DECODE_FAILURE;
+import static org.opends.messages.ExtensionMessages.ERR_PWPSTATE_EXTOP_NO_REQUEST_VALUE;
+import static org.opends.messages.ExtensionMessages.ERR_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE;
+import static org.opends.sdk.util.StaticUtils.formatAsGeneralizedTime;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.asn1.ASN1;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class implements an LDAP extended operation that can be used to
+ * query and update elements of the Directory Server password policy
+ * state for a given user. The ASN.1 definition for the value of the
+ * extended request is: <BR>
+ * 
+ * <PRE>
+ * PasswordPolicyStateValue ::= SEQUENCE {
+ *      targetUser     LDAPDN
+ *      operations     SEQUENCE OF PasswordPolicyStateOperation OPTIONAL }
+ * PasswordPolicyStateOperation ::= SEQUENCE {
+ *      opType       ENUMERATED {
+ *           getPasswordPolicyDN                          (0),
+ *           getAccountDisabledState                      (1),
+ *           setAccountDisabledState                      (2),
+ *           clearAccountDisabledState                    (3),
+ *           getAccountExpirationTime                     (4),
+ *           setAccountExpirationTime                     (5),
+ *           clearAccountExpirationTime                   (6),
+ *           getSecondsUntilAccountExpiration             (7),
+ *           getPasswordChangedTime                       (8),
+ *           setPasswordChangedTime                       (9),
+ *           clearPasswordChangedTime                     (10),
+ *           getPasswordExpirationWarnedTime              (11),
+ *           setPasswordExpirationWarnedTime              (12),
+ *           clearPasswordExpirationWarnedTime            (13),
+ *           getSecondsUntilPasswordExpiration            (14),
+ *           getSecondsUntilPasswordExpirationWarning     (15),
+ *           getAuthenticationFailureTimes                (16),
+ *           addAuthenticationFailureTime                 (17),
+ *           setAuthenticationFailureTimes                (18),
+ *           clearAuthenticationFailureTimes              (19),
+ *           getSecondsUntilAuthenticationFailureUnlock   (20),
+ *           getRemainingAuthenticationFailureCount       (21),
+ *           getLastLoginTime                             (22),
+ *           setLastLoginTime                             (23),
+ *           clearLastLoginTime                           (24),
+ *           getSecondsUntilIdleLockout                   (25),
+ *           getPasswordResetState                        (26),
+ *           setPasswordResetState                        (27),
+ *           clearPasswordResetState                      (28),
+ *           getSecondsUntilPasswordResetLockout          (29),
+ *           getGraceLoginUseTimes                        (30),
+ *           addGraceLoginUseTime                         (31),
+ *           setGraceLoginUseTimes                        (32),
+ *           clearGraceLoginUseTimes                      (33),
+ *           getRemainingGraceLoginCount                  (34),
+ *           getPasswordChangedByRequiredTime             (35),
+ *           setPasswordChangedByRequiredTime             (36),
+ *           clearPasswordChangedByRequiredTime           (37),
+ *           getSecondsUntilRequiredChangeTime            (38),
+ *           getPasswordHistory                           (39),
+ *           clearPasswordHistory                         (40),
+ *           ... },
+ *      opValues     SEQUENCE OF OCTET STRING OPTIONAL }
+ * </PRE>
+ * 
+ * <BR>
+ * Both the request and response values use the same encoded form, and
+ * they both use the same OID of "1.3.6.1.4.1.26027.1.6.1". The response
+ * value will only include get* elements. If the request did not include
+ * any operations, then the response will include all get* elements;
+ * otherwise, the response will only include the get* elements that
+ * correspond to the state fields referenced in the request (regardless
+ * of whether that operation was included in a get*, set*, add*,
+ * remove*, or clear* operation).
+ */
+public final class PasswordPolicyStateExtendedOperation
+{
+  /**
+   * The OID for the password policy state extended operation (both the
+   * request and response types).
+   */
+  static final String OID_PASSWORD_POLICY_STATE_EXTOP = "1.3.6.1.4.1.26027.1.6.1";
+
+
+
+  public interface Operation
+  {
+    public OperationType getOperationType();
+
+
+
+    public Iterable<ByteString> getValues();
+  }
+
+
+
+  public static enum OperationType implements Operation
+  {
+    GET_PASSWORD_POLICY_DN(PASSWORD_POLICY_DN_NAME),
+
+    GET_ACCOUNT_DISABLED_STATE(ACCOUNT_DISABLED_STATE_NAME), SET_ACCOUNT_DISABLED_STATE(
+        ACCOUNT_DISABLED_STATE_NAME), CLEAR_ACCOUNT_DISABLED_STATE(
+        ACCOUNT_DISABLED_STATE_NAME),
+
+    GET_ACCOUNT_EXPIRATION_TIME(ACCOUNT_EXPIRATION_TIME_NAME), SET_ACCOUNT_EXPIRATION_TIME(
+        ACCOUNT_EXPIRATION_TIME_NAME), CLEAR_ACCOUNT_EXPIRATION_TIME(
+        ACCOUNT_EXPIRATION_TIME_NAME),
+
+    GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION(
+        SECONDS_UNTIL_ACCOUNT_EXPIRATION_NAME),
+
+    GET_PASSWORD_CHANGED_TIME(PASSWORD_CHANGED_TIME_NAME), SET_PASSWORD_CHANGED_TIME(
+        PASSWORD_CHANGED_TIME_NAME), CLEAR_PASSWORD_CHANGED_TIME(
+        PASSWORD_CHANGED_TIME_NAME),
+
+    GET_PASSWORD_EXPIRATION_WARNED_TIME(
+        PASSWORD_EXPIRATION_WARNED_TIME_NAME), SET_PASSWORD_EXPIRATION_WARNED_TIME(
+        PASSWORD_EXPIRATION_WARNED_TIME_NAME), CLEAR_PASSWORD_EXPIRATION_WARNED_TIME(
+        PASSWORD_EXPIRATION_WARNED_TIME_NAME),
+
+    GET_SECONDS_UNTIL_PASSWORD_EXPIRATION(
+        SECONDS_UNTIL_PASSWORD_EXPIRATION_NAME),
+
+    GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING(
+        SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING_NAME),
+
+    GET_AUTHENTICATION_FAILURE_TIMES(AUTHENTICATION_FAILURE_TIMES_NAME), ADD_AUTHENTICATION_FAILURE_TIMES(
+        AUTHENTICATION_FAILURE_TIMES_NAME), SET_AUTHENTICATION_FAILURE_TIMES(
+        AUTHENTICATION_FAILURE_TIMES_NAME), CLEAR_AUTHENTICATION_FAILURE_TIMES(
+        AUTHENTICATION_FAILURE_TIMES_NAME),
+
+    GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK(
+        SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK_NAME),
+
+    GET_REMAINING_AUTHENTICATION_FAILURE_COUNT(
+        REMAINING_AUTHENTICATION_FAILURE_COUNT_NAME),
+
+    GET_LAST_LOGIN_TIME(LAST_LOGIN_TIME_NAME), SET_LAST_LOGIN_TIME(
+        LAST_LOGIN_TIME_NAME), CLEAR_LAST_LOGIN_TIME(
+        LAST_LOGIN_TIME_NAME),
+
+    GET_SECONDS_UNTIL_IDLE_LOCKOUT(SECONDS_UNTIL_IDLE_LOCKOUT_NAME),
+
+    GET_PASSWORD_RESET_STATE(PASSWORD_RESET_STATE_NAME), SET_PASSWORD_RESET_STATE(
+        PASSWORD_RESET_STATE_NAME), CLEAR_PASSWORD_RESET_STATE(
+        PASSWORD_RESET_STATE_NAME),
+
+    GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT(
+        SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT_NAME),
+
+    GET_GRACE_LOGIN_USE_TIMES(GRACE_LOGIN_USE_TIMES_NAME), ADD_GRACE_LOGIN_USE_TIME(
+        GRACE_LOGIN_USE_TIMES_NAME), SET_GRACE_LOGIN_USE_TIMES(
+        GRACE_LOGIN_USE_TIMES_NAME), CLEAR_GRACE_LOGIN_USE_TIMES(
+        GRACE_LOGIN_USE_TIMES_NAME),
+
+    GET_REMAINING_GRACE_LOGIN_COUNT(REMAINING_GRACE_LOGIN_COUNT_NAME),
+
+    GET_PASSWORD_CHANGED_BY_REQUIRED_TIME(
+        PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME), SET_PASSWORD_CHANGED_BY_REQUIRED_TIME(
+        PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME), CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME(
+        PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME),
+
+    GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME(
+        SECONDS_UNTIL_REQUIRED_CHANGE_TIME_NAME),
+
+    GET_PASSWORD_HISTORY(PASSWORD_HISTORY_NAME), CLEAR_PASSWORD_HISTORY(
+        PASSWORD_HISTORY_NAME);
+
+    private String propertyName;
+
+
+
+    OperationType(String propertyName)
+    {
+      this.propertyName = propertyName;
+    }
+
+
+
+    public OperationType getOperationType()
+    {
+      return this;
+    }
+
+
+
+    public String getPropertyName()
+    {
+      return propertyName;
+    }
+
+
+
+    public Iterable<ByteString> getValues()
+    {
+      return null;
+    }
+
+
+
+    @Override
+    public String toString()
+    {
+      return propertyName;
+    }
+  }
+
+
+
+  public static class Request extends
+      AbstractExtendedRequest<Request, Response> implements
+      OperationContainer
+  {
+    private String targetUser;
+
+    private List<Operation> operations = new ArrayList<Operation>();
+
+
+
+    public Request(DN targetUser)
+    {
+      Validator.ensureNotNull(targetUser);
+      this.targetUser = targetUser.toString();
+    }
+
+
+
+    public Request(String targetUser)
+    {
+      Validator.ensureNotNull(targetUser);
+      this.targetUser = targetUser;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getRequestName()
+    {
+      return OID_PASSWORD_POLICY_STATE_EXTOP;
+    }
+
+
+
+    public void addAuthenticationFailureTime(Date date)
+    {
+      if (date == null)
+      {
+        operations.add(OperationType.ADD_AUTHENTICATION_FAILURE_TIMES);
+      }
+      else
+      {
+        operations.add(new MultiValueOperation(
+            OperationType.ADD_AUTHENTICATION_FAILURE_TIMES, ByteString
+                .valueOf(formatAsGeneralizedTime(date))));
+      }
+    }
+
+
+
+    public void addGraceLoginUseTime(Date date)
+    {
+      if (date == null)
+      {
+        operations.add(OperationType.ADD_GRACE_LOGIN_USE_TIME);
+      }
+      else
+      {
+        operations.add(new MultiValueOperation(
+            OperationType.ADD_GRACE_LOGIN_USE_TIME, ByteString
+                .valueOf(formatAsGeneralizedTime(date))));
+      }
+    }
+
+
+
+    public void addOperation(Operation operation)
+    {
+      operations.add(operation);
+    }
+
+
+
+    public void clearAccountDisabledState()
+    {
+      operations.add(OperationType.CLEAR_ACCOUNT_DISABLED_STATE);
+    }
+
+
+
+    public void clearAccountExpirationTime()
+    {
+      operations.add(OperationType.CLEAR_ACCOUNT_EXPIRATION_TIME);
+    }
+
+
+
+    public void clearAuthenticationFailureTimes()
+    {
+      operations.add(OperationType.CLEAR_AUTHENTICATION_FAILURE_TIMES);
+    }
+
+
+
+    public void clearGraceLoginUseTimes()
+    {
+      operations.add(OperationType.CLEAR_GRACE_LOGIN_USE_TIMES);
+    }
+
+
+
+    public void clearLastLoginTime()
+    {
+      operations.add(OperationType.CLEAR_LAST_LOGIN_TIME);
+    }
+
+
+
+    public void clearPasswordChangedByRequiredTime()
+    {
+      operations
+          .add(OperationType.CLEAR_PASSWORD_CHANGED_BY_REQUIRED_TIME);
+    }
+
+
+
+    public void clearPasswordChangedTime()
+    {
+      operations.add(OperationType.CLEAR_PASSWORD_CHANGED_TIME);
+    }
+
+
+
+    public void clearPasswordExpirationWarnedTime()
+    {
+      operations
+          .add(OperationType.CLEAR_PASSWORD_EXPIRATION_WARNED_TIME);
+    }
+
+
+
+    public void clearPasswordHistory()
+    {
+      operations.add(OperationType.CLEAR_PASSWORD_HISTORY);
+    }
+
+
+
+    public void clearPasswordResetState()
+    {
+      operations.add(OperationType.CLEAR_PASSWORD_RESET_STATE);
+    }
+
+
+
+    @Override
+    public OperationImpl getExtendedOperation()
+    {
+      return OPERATION_IMPL;
+    }
+
+
+
+    public Iterable<Operation> getOperations()
+    {
+      return operations;
+    }
+
+
+
+    public ByteString getRequestValue()
+    {
+      return encode(targetUser, operations);
+    }
+
+
+
+    public void requestAccountDisabledState()
+    {
+      operations.add(OperationType.GET_ACCOUNT_DISABLED_STATE);
+    }
+
+
+
+    public void requestAccountExpirationTime()
+    {
+      operations.add(OperationType.GET_ACCOUNT_EXPIRATION_TIME);
+    }
+
+
+
+    public void requestAuthenticationFailureTimes()
+    {
+      operations.add(OperationType.GET_AUTHENTICATION_FAILURE_TIMES);
+    }
+
+
+
+    public void requestGraceLoginUseTimes()
+    {
+      operations.add(OperationType.GET_GRACE_LOGIN_USE_TIMES);
+    }
+
+
+
+    public void requestLastLoginTime()
+    {
+      operations.add(OperationType.GET_LAST_LOGIN_TIME);
+    }
+
+
+
+    public void requestPasswordChangedByRequiredTime()
+    {
+      operations
+          .add(OperationType.GET_PASSWORD_CHANGED_BY_REQUIRED_TIME);
+    }
+
+
+
+    public void requestPasswordChangedTime()
+    {
+      operations.add(OperationType.GET_PASSWORD_CHANGED_TIME);
+    }
+
+
+
+    public void requestPasswordExpirationWarnedTime()
+    {
+      operations.add(OperationType.GET_PASSWORD_EXPIRATION_WARNED_TIME);
+    }
+
+
+
+    public void requestPasswordHistory()
+    {
+      operations.add(OperationType.GET_PASSWORD_HISTORY);
+    }
+
+
+
+    public void requestPasswordPolicyDN()
+    {
+      operations.add(OperationType.GET_PASSWORD_POLICY_DN);
+    }
+
+
+
+    public void requestPasswordResetState()
+    {
+      operations.add(OperationType.GET_PASSWORD_RESET_STATE);
+    }
+
+
+
+    public void requestRemainingAuthenticationFailureCount()
+    {
+      operations
+          .add(OperationType.GET_REMAINING_AUTHENTICATION_FAILURE_COUNT);
+    }
+
+
+
+    public void requestRemainingGraceLoginCount()
+    {
+      operations.add(OperationType.GET_REMAINING_GRACE_LOGIN_COUNT);
+    }
+
+
+
+    public void requestSecondsUntilAccountExpiration()
+    {
+      operations
+          .add(OperationType.GET_SECONDS_UNTIL_ACCOUNT_EXPIRATION);
+    }
+
+
+
+    public void requestSecondsUntilAuthenticationFailureUnlock()
+    {
+      operations
+          .add(OperationType.GET_SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK);
+    }
+
+
+
+    public void requestSecondsUntilIdleLockout()
+    {
+      operations.add(OperationType.GET_SECONDS_UNTIL_IDLE_LOCKOUT);
+    }
+
+
+
+    public void requestSecondsUntilPasswordExpiration()
+    {
+      operations
+          .add(OperationType.GET_SECONDS_UNTIL_PASSWORD_EXPIRATION);
+    }
+
+
+
+    public void requestSecondsUntilPasswordExpirationWarning()
+    {
+      operations
+          .add(OperationType.GET_SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING);
+    }
+
+
+
+    public void requestSecondsUntilPasswordResetLockout()
+    {
+      operations
+          .add(OperationType.GET_SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT);
+    }
+
+
+
+    public void requestSecondsUntilRequiredChangeTime()
+    {
+      operations
+          .add(OperationType.GET_SECONDS_UNTIL_REQUIRED_CHANGE_TIME);
+    }
+
+
+
+    public void setAccountDisabledState(boolean state)
+    {
+      operations.add(new MultiValueOperation(
+          OperationType.SET_ACCOUNT_DISABLED_STATE, ByteString
+              .valueOf(String.valueOf(state))));
+    }
+
+
+
+    public void setAccountExpirationTime(Date date)
+    {
+      if (date == null)
+      {
+        operations.add(OperationType.SET_ACCOUNT_EXPIRATION_TIME);
+      }
+      else
+      {
+        operations.add(new MultiValueOperation(
+            OperationType.SET_ACCOUNT_EXPIRATION_TIME, ByteString
+                .valueOf(formatAsGeneralizedTime(date))));
+      }
+    }
+
+
+
+    public void setAuthenticationFailureTimes(Date... dates)
+    {
+      if (dates == null)
+      {
+        operations.add(OperationType.SET_AUTHENTICATION_FAILURE_TIMES);
+      }
+      else
+      {
+        ArrayList<ByteString> times = new ArrayList<ByteString>(
+            dates.length);
+        for (Date date : dates)
+        {
+          times.add(ByteString.valueOf(formatAsGeneralizedTime(date)));
+        }
+        operations.add(new MultiValueOperation(
+            OperationType.SET_AUTHENTICATION_FAILURE_TIMES, times));
+      }
+    }
+
+
+
+    public void setGraceLoginUseTimes(Date... dates)
+    {
+      if (dates == null)
+      {
+        operations.add(OperationType.SET_GRACE_LOGIN_USE_TIMES);
+      }
+      else
+      {
+        ArrayList<ByteString> times = new ArrayList<ByteString>(
+            dates.length);
+        for (Date date : dates)
+        {
+          times.add(ByteString.valueOf(formatAsGeneralizedTime(date)));
+        }
+        operations.add(new MultiValueOperation(
+            OperationType.SET_GRACE_LOGIN_USE_TIMES, times));
+      }
+    }
+
+
+
+    public void setLastLoginTime(Date date)
+    {
+      if (date == null)
+      {
+        operations.add(OperationType.SET_LAST_LOGIN_TIME);
+
+      }
+      else
+      {
+        operations.add(new MultiValueOperation(
+            OperationType.SET_LAST_LOGIN_TIME, ByteString
+                .valueOf(formatAsGeneralizedTime(date))));
+      }
+    }
+
+
+
+    public void setPasswordChangedByRequiredTime(boolean state)
+    {
+      operations.add(new MultiValueOperation(
+          OperationType.SET_PASSWORD_CHANGED_BY_REQUIRED_TIME,
+          ByteString.valueOf(String.valueOf(state))));
+    }
+
+
+
+    public void setPasswordChangedTime(Date date)
+    {
+      if (date == null)
+      {
+        operations.add(OperationType.SET_PASSWORD_CHANGED_TIME);
+      }
+      else
+      {
+        operations.add(new MultiValueOperation(
+            OperationType.SET_PASSWORD_CHANGED_TIME, ByteString
+                .valueOf(formatAsGeneralizedTime(date))));
+      }
+    }
+
+
+
+    public void setPasswordExpirationWarnedTime(Date date)
+    {
+      if (date == null)
+      {
+        operations
+            .add(OperationType.SET_PASSWORD_EXPIRATION_WARNED_TIME);
+
+      }
+      else
+      {
+        operations.add(new MultiValueOperation(
+            OperationType.SET_PASSWORD_EXPIRATION_WARNED_TIME,
+            ByteString.valueOf(formatAsGeneralizedTime(date))));
+      }
+    }
+
+
+
+    public void setPasswordResetState(boolean state)
+    {
+      operations.add(new MultiValueOperation(
+          OperationType.SET_LAST_LOGIN_TIME, ByteString.valueOf(String
+              .valueOf(state))));
+    }
+
+
+
+    @Override
+    public String toString()
+    {
+      StringBuilder builder = new StringBuilder();
+      builder.append("PasswordPolicyStateExtendedRequest(requestName=");
+      builder.append(getRequestName());
+      builder.append(", targetUser=");
+      builder.append(targetUser);
+      builder.append(", operations=");
+      builder.append(operations);
+      builder.append(", controls=");
+      builder.append(getControls());
+      builder.append(")");
+      return builder.toString();
+    }
+  }
+
+
+
+  public static class Response extends AbstractExtendedResult<Response>
+      implements OperationContainer
+  {
+    private String targetUser;
+
+    private List<Operation> operations = new ArrayList<Operation>();
+
+
+
+    public Response(ResultCode resultCode, DN targetUser)
+    {
+      this(resultCode, String.valueOf(targetUser));
+    }
+
+
+
+    public Response(ResultCode resultCode, String targetUser)
+    {
+      super(resultCode);
+      this.targetUser = targetUser;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public String getResponseName()
+    {
+      // No response name defined.
+      return OID_PASSWORD_POLICY_STATE_EXTOP;
+    }
+
+
+
+    public void addOperation(Operation operation)
+    {
+      operations.add(operation);
+    }
+
+
+
+    public Iterable<Operation> getOperations()
+    {
+      return operations;
+    }
+
+
+
+    public ByteString getResponseValue()
+    {
+      return encode(targetUser, operations);
+    }
+
+
+
+    @Override
+    public String toString()
+    {
+      StringBuilder builder = new StringBuilder();
+      builder.append("PasswordPolicyStateExtendedResponse(resultCode=");
+      builder.append(getResultCode());
+      builder.append(", matchedDN=");
+      builder.append(getMatchedDN());
+      builder.append(", diagnosticMessage=");
+      builder.append(getDiagnosticMessage());
+      builder.append(", referrals=");
+      builder.append(getReferralURIs());
+      builder.append(", responseName=");
+      builder.append(getResponseName());
+      builder.append(", targetUser=");
+      builder.append(targetUser);
+      builder.append(", operations=");
+      builder.append(operations);
+      builder.append(", controls=");
+      builder.append(getControls());
+      builder.append(")");
+      return builder.toString();
+    }
+  }
+
+
+
+  private static class MultiValueOperation implements Operation
+  {
+    private OperationType property;
+
+    private List<ByteString> values;
+
+
+
+    private MultiValueOperation(OperationType property, ByteString value)
+    {
+      this.property = property;
+      this.values = Collections.singletonList(value);
+    }
+
+
+
+    private MultiValueOperation(OperationType property,
+        List<ByteString> values)
+    {
+      this.property = property;
+      this.values = values;
+    }
+
+
+
+    public OperationType getOperationType()
+    {
+      return property;
+    }
+
+
+
+    public Iterable<ByteString> getValues()
+    {
+      return values;
+    }
+
+
+
+    @Override
+    public String toString()
+    {
+      return property.getPropertyName() + ": " + values;
+    }
+  }
+
+
+
+  private interface OperationContainer
+  {
+    public void addOperation(Operation operation);
+
+
+
+    public Iterable<Operation> getOperations();
+  }
+
+
+
+  private static final String PASSWORD_POLICY_DN_NAME = "Password Policy DN";
+
+  private static final String ACCOUNT_DISABLED_STATE_NAME = "Account Disabled State";
+
+  private static final String ACCOUNT_EXPIRATION_TIME_NAME = "Account Expiration Time";
+
+  private static final String SECONDS_UNTIL_ACCOUNT_EXPIRATION_NAME = "Seconds Until Account Expiration";
+
+  private static final String PASSWORD_CHANGED_TIME_NAME = "Password Changed Time";
+
+  private static final String PASSWORD_EXPIRATION_WARNED_TIME_NAME = "Password Expiration Warned Time";
+
+  private static final String SECONDS_UNTIL_PASSWORD_EXPIRATION_NAME = "Seconds Until Password Expiration";
+
+  private static final String SECONDS_UNTIL_PASSWORD_EXPIRATION_WARNING_NAME = "Seconds Until Password Expiration Warning";
+
+  private static final String AUTHENTICATION_FAILURE_TIMES_NAME = "Authentication Failure Times";
+
+  private static final String SECONDS_UNTIL_AUTHENTICATION_FAILURE_UNLOCK_NAME = "Seconds Until Authentication Failure Unlock";
+
+  private static final String REMAINING_AUTHENTICATION_FAILURE_COUNT_NAME = "Remaining Authentication Failure Count";
+
+  private static final String LAST_LOGIN_TIME_NAME = "Last Login Time";
+
+  private static final String SECONDS_UNTIL_IDLE_LOCKOUT_NAME = "Seconds Until Idle Lockout";
+
+  private static final String PASSWORD_RESET_STATE_NAME = "Password Reset State";
+
+  private static final String SECONDS_UNTIL_PASSWORD_RESET_LOCKOUT_NAME = "Seconds Until Password Reset Lockout";
+
+  private static final String GRACE_LOGIN_USE_TIMES_NAME = "Grace Login Use Times";
+
+  private static final String REMAINING_GRACE_LOGIN_COUNT_NAME = "Remaining Grace Login Count";
+
+  private static final String PASSWORD_CHANGED_BY_REQUIRED_TIME_NAME = "Password Changed By Required Time";
+
+  private static final String SECONDS_UNTIL_REQUIRED_CHANGE_TIME_NAME = "Seconds Until Required Change Time";
+
+  private static final String PASSWORD_HISTORY_NAME = "Password History";
+
+
+
+  private static void decodeOperations(ASN1Reader reader,
+      OperationContainer container) throws IOException, DecodeException
+  {
+    // See if we have operations
+    if (reader.hasNextElement())
+    {
+      reader.readStartSequence();
+      int opType;
+      OperationType type;
+      while (reader.hasNextElement())
+      {
+        reader.readStartSequence();
+        // Read the opType
+        opType = reader.readEnumerated();
+        try
+        {
+          type = OperationType.values()[opType];
+        }
+        catch (IndexOutOfBoundsException iobe)
+        {
+          throw DecodeException.error(
+              ERR_PWPSTATE_EXTOP_UNKNOWN_OP_TYPE.get(String
+                  .valueOf(opType)), iobe);
+        }
+
+        // See if we have any values
+        if (reader.hasNextElement())
+        {
+          reader.readStartSequence();
+          ArrayList<ByteString> values = new ArrayList<ByteString>();
+          while (reader.hasNextElement())
+          {
+            values.add(reader.readOctetString());
+          }
+          reader.readEndSequence();
+          container.addOperation(new MultiValueOperation(type, values));
+        }
+        else
+        {
+          container.addOperation(type);
+        }
+        reader.readEndSequence();
+      }
+      reader.readEndSequence();
+    }
+  }
+
+
+
+  private static ByteString encode(String targetUser,
+      List<Operation> operations)
+  {
+    ByteStringBuilder buffer = new ByteStringBuilder(6);
+    ASN1Writer writer = ASN1.getWriter(buffer);
+
+    try
+    {
+      writer.writeStartSequence();
+      writer.writeOctetString(targetUser);
+      if (!operations.isEmpty())
+      {
+        writer.writeStartSequence();
+        for (Operation operation : operations)
+        {
+          writer.writeStartSequence();
+          writer
+              .writeEnumerated(operation.getOperationType().ordinal());
+          if (operation.getValues() != null)
+          {
+            writer.writeStartSequence();
+            for (ByteString value : operation.getValues())
+            {
+              writer.writeOctetString(value);
+            }
+            writer.writeEndSequence();
+          }
+          writer.writeEndSequence();
+        }
+        writer.writeEndSequence();
+      }
+      writer.writeEndSequence();
+    }
+    catch (IOException ioe)
+    {
+      // This should never happen unless there is a bug somewhere.
+      throw new RuntimeException(ioe);
+    }
+
+    return buffer.toByteString();
+  }
+
+
+
+  private static final class OperationImpl implements
+      ExtendedOperation<Request, Response>
+  {
+
+    public Request decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      if ((requestValue == null) || (requestValue.length() <= 0))
+      {
+        throw DecodeException
+            .error(ERR_PWPSTATE_EXTOP_NO_REQUEST_VALUE.get());
+      }
+
+      try
+      {
+        ASN1Reader reader = ASN1.getReader(requestValue);
+        reader.readStartSequence();
+
+        // Read the target user DN
+        Request request = new Request(reader.readOctetStringAsString());
+
+        decodeOperations(reader, request);
+        reader.readEndSequence();
+        return request;
+      }
+      catch (IOException ioe)
+      {
+        Message message = ERR_PWPSTATE_EXTOP_DECODE_FAILURE
+            .get(getExceptionMessage(ioe));
+        throw DecodeException.error(message, ioe);
+      }
+    }
+
+
+
+    public Response decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      if (!resultCode.isExceptional()
+          && ((responseValue == null) || (responseValue.length() <= 0)))
+      {
+        throw DecodeException
+            .error(ERR_PWPSTATE_EXTOP_NO_REQUEST_VALUE.get());
+      }
+
+      try
+      {
+        ASN1Reader reader = ASN1.getReader(responseValue);
+        reader.readStartSequence();
+
+        // Read the target user DN
+        Response response = new Response(resultCode, reader
+            .readOctetStringAsString()).setMatchedDN(matchedDN)
+            .setDiagnosticMessage(diagnosticMessage);
+
+        decodeOperations(reader, response);
+        reader.readEndSequence();
+        return response;
+      }
+      catch (IOException ioe)
+      {
+        Message message = ERR_PWPSTATE_EXTOP_DECODE_FAILURE
+            .get(getExceptionMessage(ioe));
+        throw DecodeException.error(message, ioe);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Response decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      if (!resultCode.isExceptional())
+      {
+        // A successful response must contain a response name and
+        // value.
+        throw new IllegalArgumentException(
+            "No response name and value for result code "
+                + resultCode.intValue());
+      }
+
+      return new Response(resultCode, (String) null).setMatchedDN(
+          matchedDN).setDiagnosticMessage(diagnosticMessage);
+    }
+  }
+
+
+
+  // Singleton instance.
+  private static final OperationImpl OPERATION_IMPL = new OperationImpl();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/StartTLSRequest.java b/sdk/src/org/opends/sdk/extensions/StartTLSRequest.java
new file mode 100644
index 0000000..5b05138
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/StartTLSRequest.java
@@ -0,0 +1,120 @@
+package org.opends.sdk.extensions;
+
+
+
+import javax.net.ssl.SSLContext;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 22, 2009 Time: 6:21:44
+ * PM To change this template use File | Settings | File Templates.
+ */
+public final class StartTLSRequest extends
+    AbstractExtendedRequest<StartTLSRequest, Result>
+{
+  private final SSLContext sslContext;
+
+  /**
+   * The request OID for the StartTLS extended operation.
+   */
+  public static final String OID_START_TLS_REQUEST = "1.3.6.1.4.1.1466.20037";
+
+
+
+  public StartTLSRequest(SSLContext sslContext)
+  {
+    this.sslContext = sslContext;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRequestName()
+  {
+    return OID_START_TLS_REQUEST;
+  }
+
+
+
+  public Operation getExtendedOperation()
+  {
+    return OPERATION;
+  }
+
+
+
+  public ByteString getRequestValue()
+  {
+    return null;
+  }
+
+
+
+  public SSLContext getSSLContext()
+  {
+    return sslContext;
+  }
+
+
+
+  public StringBuilder toString(StringBuilder builder)
+  {
+    builder.append("StartTLSExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder;
+  }
+
+
+
+  private static final class Operation implements
+      ExtendedOperation<StartTLSRequest, Result>
+  {
+
+    public StartTLSRequest decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      return new StartTLSRequest(null);
+    }
+
+
+
+    public Result decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      // TODO: Should we check oid is NOT null and matches but
+      // value is null?
+      return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+    }
+
+
+
+    public Result decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      return Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+    }
+  }
+
+
+
+  // Singleton instance.
+  private static final Operation OPERATION = new Operation();
+
+}
diff --git a/sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java b/sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java
new file mode 100644
index 0000000..c9a33c8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/WhoAmIRequest.java
@@ -0,0 +1,110 @@
+package org.opends.sdk.extensions;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.requests.AbstractExtendedRequest;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Created by IntelliJ IDEA. User: boli Date: Jun 22, 2009 Time: 6:40:06
+ * PM To change this template use File | Settings | File Templates.
+ */
+public final class WhoAmIRequest extends
+    AbstractExtendedRequest<WhoAmIRequest, WhoAmIResult>
+{
+  /**
+   * The request OID for the "Who Am I?" extended operation.
+   */
+  static final String OID_WHO_AM_I_REQUEST = "1.3.6.1.4.1.4203.1.11.3";
+
+
+
+  public WhoAmIRequest()
+  {
+
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRequestName()
+  {
+    return OID_WHO_AM_I_REQUEST;
+  }
+
+
+
+  public Operation getExtendedOperation()
+  {
+    return OPERATION;
+  }
+
+
+
+  public ByteString getRequestValue()
+  {
+    return null;
+  }
+
+
+
+  public StringBuilder toString(StringBuilder builder)
+  {
+    builder.append("WhoAmIExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder;
+  }
+
+
+
+  private static final class Operation implements
+      ExtendedOperation<WhoAmIRequest, WhoAmIResult>
+  {
+
+    public WhoAmIRequest decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      return new WhoAmIRequest();
+    }
+
+
+
+    public WhoAmIResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      // TODO: Should we check oid is null?
+      String authzId = null;
+      if (responseValue != null)
+      {
+        authzId = responseValue.toString();
+      }
+      return new WhoAmIResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage).setAuthzId(authzId);
+    }
+
+
+
+    public WhoAmIResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      return new WhoAmIResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+    }
+  }
+
+
+
+  // Singleton instance.
+  private static final Operation OPERATION = new Operation();
+}
diff --git a/sdk/src/org/opends/sdk/extensions/WhoAmIResult.java b/sdk/src/org/opends/sdk/extensions/WhoAmIResult.java
new file mode 100644
index 0000000..a59a45b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/extensions/WhoAmIResult.java
@@ -0,0 +1,113 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.extensions;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.responses.AbstractExtendedResult;
+import org.opends.sdk.util.ByteString;
+
+
+
+public class WhoAmIResult extends AbstractExtendedResult<WhoAmIResult>
+{
+  private String authzId;
+
+
+
+  public WhoAmIResult(ResultCode resultCode)
+  {
+    super(resultCode);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getResponseName()
+  {
+    // No response name defined.
+    return null;
+  }
+
+
+
+  /**
+   * Get the authzId to return or <code>null</code> if it is not
+   * available.
+   * 
+   * @return The authzID or <code>null</code>.
+   */
+  public String getAuthzId()
+  {
+    return authzId;
+  }
+
+
+
+  public ByteString getResponseValue()
+  {
+    if (authzId != null)
+    {
+      ByteString.valueOf(authzId);
+    }
+    return null;
+  }
+
+
+
+  public WhoAmIResult setAuthzId(String authzId)
+  {
+    this.authzId = authzId;
+    return this;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("WhoAmIExtendedResponse(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    builder.append(", authzId=");
+    builder.append(authzId);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java b/sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java
new file mode 100644
index 0000000..aab8ef0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ASN1StreamReader.java
@@ -0,0 +1,838 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_TYPE;
+import static org.opends.sdk.ldap.LDAPConstants.ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+
+import java.io.IOException;
+import java.nio.BufferUnderflowException;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.AbstractASN1Reader;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.utils.PoolableObject;
+
+
+
+/**
+ * Grizzly ASN1 reader implementation.
+ */
+final class ASN1StreamReader extends AbstractASN1Reader implements
+    PoolableObject, ASN1Reader
+{
+  class ChildSequenceLimiter implements SequenceLimiter
+  {
+    private SequenceLimiter parent;
+
+    private ChildSequenceLimiter child;
+
+    private int readLimit;
+
+    private int bytesRead;
+
+
+
+    public void checkLimit(int readSize) throws IOException,
+        BufferUnderflowException
+    {
+      if ((readLimit > 0) && (bytesRead + readSize > readLimit))
+      {
+        throw new BufferUnderflowException();
+      }
+
+      parent.checkLimit(readSize);
+
+      bytesRead += readSize;
+    }
+
+
+
+    public SequenceLimiter endSequence() throws IOException
+    {
+      parent.checkLimit(remaining());
+
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE)
+          && remaining() > 0)
+      {
+        StaticUtils.DEBUG_LOG.fine(String.format(
+            "Ignoring %d unused trailing bytes in ASN.1 SEQUENCE",
+            remaining()));
+      }
+
+      for (int i = 0; i < remaining(); i++)
+      {
+        streamReader.readByte();
+      }
+
+      return parent;
+    }
+
+
+
+    public int remaining()
+    {
+      return readLimit - bytesRead;
+    }
+
+
+
+    public ChildSequenceLimiter startSequence(int readLimit)
+    {
+      if (child == null)
+      {
+        child = new ChildSequenceLimiter();
+        child.parent = this;
+      }
+
+      child.readLimit = readLimit;
+      child.bytesRead = 0;
+
+      return child;
+    }
+  }
+
+
+
+  class RootSequenceLimiter implements SequenceLimiter
+  {
+    private ChildSequenceLimiter child;
+
+
+
+    public void checkLimit(int readSize)
+    {
+    }
+
+
+
+    public ChildSequenceLimiter endSequence() throws DecodeException
+    {
+      Message message = ERR_ASN1_SEQUENCE_READ_NOT_STARTED.get();
+      throw new IllegalStateException(message.toString());
+    }
+
+
+
+    public int remaining()
+    {
+      return streamReader.availableDataSize();
+    }
+
+
+
+    public ChildSequenceLimiter startSequence(int readLimit)
+    {
+      if (child == null)
+      {
+        child = new ChildSequenceLimiter();
+        child.parent = this;
+      }
+
+      child.readLimit = readLimit;
+      child.bytesRead = 0;
+
+      return child;
+    }
+  }
+
+
+
+  private interface SequenceLimiter
+  {
+    public void checkLimit(int readSize) throws IOException,
+        BufferUnderflowException;
+
+
+
+    public SequenceLimiter endSequence() throws IOException;
+
+
+
+    public int remaining();
+
+
+
+    public SequenceLimiter startSequence(int readLimit);
+  }
+
+
+
+  private static final int MAX_STRING_BUFFER_SIZE = 1024;
+
+  private int state = ELEMENT_READ_STATE_NEED_TYPE;
+
+  private byte peekType = 0;
+
+  private int peekLength = -1;
+
+  private int lengthBytesNeeded = 0;
+
+  private final int maxElementSize;
+
+  private StreamReader streamReader;
+
+  private final RootSequenceLimiter rootLimiter;
+
+  private SequenceLimiter readLimiter;
+
+  private final byte[] buffer;
+
+
+
+  /**
+   * Creates a new ASN1 reader whose source is the provided input stream
+   * and having a user defined maximum BER element size.
+   *
+   * @param maxElementSize
+   *          The maximum BER element size, or <code>0</code> to
+   *          indicate that there is no limit.
+   */
+  public ASN1StreamReader(int maxElementSize)
+  {
+    this.readLimiter = this.rootLimiter = new RootSequenceLimiter();
+    this.buffer = new byte[MAX_STRING_BUFFER_SIZE];
+    this.maxElementSize = maxElementSize;
+  }
+
+
+
+  /**
+   * Closes this ASN.1 reader and the underlying stream.
+   *
+   * @throws IOException
+   *           if an I/O error occurs
+   */
+  public void close() throws IOException
+  {
+    // close the stream reader.
+    streamReader.close();
+  }
+
+
+
+  /**
+   * Determines if a complete ASN.1 element is ready to be read from the
+   * stream reader.
+   *
+   * @return <code>true</code> if another complete element is available
+   *         or <code>false</code> otherwise.
+   * @throws IOException
+   *           If an error occurs while trying to decode an ASN1
+   *           element.
+   */
+  public boolean elementAvailable() throws IOException
+  {
+    if ((state == ELEMENT_READ_STATE_NEED_TYPE) && !needTypeState(true))
+    {
+      return false;
+    }
+    if ((state == ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE)
+        && !needFirstLengthByteState(true))
+    {
+      return false;
+    }
+    if ((state == ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES)
+        && !needAdditionalLengthBytesState(true))
+    {
+      return false;
+    }
+
+    return peekLength <= readLimiter.remaining();
+  }
+
+
+
+  /**
+   * Determines if the input stream contains at least one ASN.1 element
+   * to be read.
+   *
+   * @return <code>true</code> if another element is available or
+   *         <code>false</code> otherwise.
+   * @throws IOException
+   *           If an error occurs while trying to decode an ASN1
+   *           element.
+   */
+  public boolean hasNextElement() throws IOException
+  {
+    return (state != ELEMENT_READ_STATE_NEED_TYPE)
+        || needTypeState(true);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int peekLength() throws IOException
+  {
+    peekType();
+
+    switch (state)
+    {
+    case ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE:
+      needFirstLengthByteState(false);
+      break;
+
+    case ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES:
+      needAdditionalLengthBytesState(false);
+    }
+
+    return peekLength;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte peekType() throws IOException
+  {
+    if (state == ELEMENT_READ_STATE_NEED_TYPE)
+    {
+      needTypeState(false);
+    }
+
+    return peekType;
+  }
+
+
+
+  public void prepare()
+  {
+    // Nothing to do
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean readBoolean() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength != 1)
+    {
+      Message message = ERR_ASN1_BOOLEAN_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    readLimiter.checkLimit(peekLength);
+    byte readByte = streamReader.readByte();
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)",
+          peekType, peekLength, String.valueOf(readByte != 0x00)));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return readByte != 0x00;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readEndSequence() throws IOException,
+      IllegalStateException
+  {
+    readLimiter = readLimiter.endSequence();
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String
+          .format("READ ASN.1 END SEQUENCE"));
+    }
+
+    // Reset the state
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readEndSet() throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    readEndSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int readEnumerated() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if ((peekLength < 1) || (peekLength > 4))
+    {
+      Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    // From an implementation point of view, an enumerated value is
+    // equivalent to an integer.
+    return (int) readInteger();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public long readInteger() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if ((peekLength < 1) || (peekLength > 8))
+    {
+      Message message = ERR_ASN1_INTEGER_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    readLimiter.checkLimit(peekLength);
+    if (peekLength > 4)
+    {
+      long longValue = 0;
+      for (int i = 0; i < peekLength; i++)
+      {
+        int readByte = streamReader.readByte();
+        if ((i == 0) && (((byte) readByte) < 0))
+        {
+          longValue = 0xFFFFFFFFFFFFFFFFL;
+        }
+        longValue = (longValue << 8) | (readByte & 0xFF);
+      }
+
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return longValue;
+    }
+    else
+    {
+      int intValue = 0;
+      for (int i = 0; i < peekLength; i++)
+      {
+        int readByte = streamReader.readByte();
+        if ((i == 0) && (((byte) readByte) < 0))
+        {
+          intValue = 0xFFFFFFFF;
+        }
+        intValue = (intValue << 8) | (readByte & 0xFF);
+      }
+
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "READ ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            peekType, peekLength, intValue));
+      }
+
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return intValue;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readNull() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    // Make sure that the decoded length is exactly zero byte.
+    if (peekLength != 0)
+    {
+      Message message = ERR_ASN1_NULL_INVALID_LENGTH.get(peekLength);
+      throw DecodeException.fatalError(message);
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String
+          .format("READ ASN.1 NULL(type=0x%x, length=%d)", peekType,
+              peekLength));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString readOctetString() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength == 0)
+    {
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return ByteString.empty();
+    }
+
+    readLimiter.checkLimit(peekLength);
+    // Copy the value and construct the element to return.
+    byte[] value = new byte[peekLength];
+    streamReader.readByteArray(value);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+          peekLength));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return ByteString.wrap(value);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteStringBuilder readOctetString(ByteStringBuilder builder)
+      throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength == 0)
+    {
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return builder;
+    }
+
+    readLimiter.checkLimit(peekLength);
+    // Copy the value and construct the element to return.
+    // TODO: Is there a more efficient way to do this?
+    for (int i = 0; i < peekLength; i++)
+    {
+      builder.append(streamReader.readByte());
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 OCTETSTRING(type=0x%x, length=%d)", peekType,
+          peekLength));
+    }
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return builder;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String readOctetStringAsString() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    if (peekLength == 0)
+    {
+      state = ELEMENT_READ_STATE_NEED_TYPE;
+      return "";
+    }
+
+    byte[] readBuffer;
+    if (peekLength <= buffer.length)
+    {
+      readBuffer = buffer;
+    }
+    else
+    {
+      readBuffer = new byte[peekLength];
+    }
+
+    readLimiter.checkLimit(peekLength);
+    streamReader.readByteArray(readBuffer, 0, peekLength);
+
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+
+    String str;
+    try
+    {
+      str = new String(readBuffer, 0, peekLength, "UTF-8");
+    }
+    catch (Exception e)
+    {
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+      {
+        StaticUtils.DEBUG_LOG
+            .warning("Unable to decode ASN.1 OCTETSTRING bytes as UTF-8 string: "
+                + e.toString());
+      }
+
+      str = new String(buffer, 0, peekLength);
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 OCTETSTRING(type=0x%x, length=%d, value=%s)",
+          peekType, peekLength, str));
+    }
+
+    return str;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSequence() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    readLimiter = readLimiter.startSequence(peekLength);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "READ ASN.1 START SEQUENCE(type=0x%x, length=%d)", peekType,
+          peekLength));
+    }
+
+    // Reset the state
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void readStartSet() throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    readStartSequence();
+  }
+
+
+
+  public void release()
+  {
+    streamReader = null;
+    peekLength = -1;
+    peekType = 0;
+    readLimiter = rootLimiter;
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+  }
+
+
+
+  public void setStreamReader(StreamReader streamReader)
+  {
+    this.streamReader = streamReader;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Reader skipElement() throws IOException
+  {
+    // Read the header if haven't done so already
+    peekLength();
+
+    readLimiter.checkLimit(peekLength);
+    for (int i = 0; i < peekLength; i++)
+    {
+      streamReader.readByte();
+    }
+    state = ELEMENT_READ_STATE_NEED_TYPE;
+    return this;
+  }
+
+
+
+  /**
+   * Internal helper method reading the additional ASN.1 length bytes
+   * and transition to the next state if successful.
+   *
+   * @param ensureRead
+   *          <code>true</code> to check for availability first.
+   * @return <code>true</code> if the length bytes was successfully
+   *         read.
+   * @throws IOException
+   *           If an error occurs while reading from the stream.
+   */
+  private boolean needAdditionalLengthBytesState(boolean ensureRead)
+      throws IOException
+  {
+    if (ensureRead && (readLimiter.remaining() < lengthBytesNeeded))
+    {
+      return false;
+    }
+
+    byte readByte;
+    readLimiter.checkLimit(lengthBytesNeeded);
+    while (lengthBytesNeeded > 0)
+    {
+      readByte = streamReader.readByte();
+      peekLength = (peekLength << 8) | (readByte & 0xFF);
+      lengthBytesNeeded--;
+    }
+
+    // Make sure that the element is not larger than the maximum allowed
+    // message size.
+    if ((maxElementSize > 0) && (peekLength > maxElementSize))
+    {
+      Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
+          peekLength, maxElementSize);
+      throw DecodeException.fatalError(m);
+    }
+    state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+    return true;
+  }
+
+
+
+  /**
+   * Internal helper method reading the first length bytes and
+   * transition to the next state if successful.
+   *
+   * @param ensureRead
+   *          <code>true</code> to check for availability first.
+   * @return <code>true</code> if the length bytes was successfully read
+   * @throws IOException
+   *           If an error occurs while trying to decode an ASN1
+   *           element.
+   */
+  private boolean needFirstLengthByteState(boolean ensureRead)
+      throws IOException
+  {
+    if (ensureRead && (readLimiter.remaining() <= 0))
+    {
+      return false;
+    }
+
+    readLimiter.checkLimit(1);
+    byte readByte = streamReader.readByte();
+    peekLength = (readByte & 0x7F);
+    if (peekLength != readByte)
+    {
+      lengthBytesNeeded = peekLength;
+      if (lengthBytesNeeded > 4)
+      {
+        Message message = ERR_ASN1_INVALID_NUM_LENGTH_BYTES
+            .get(lengthBytesNeeded);
+        throw DecodeException.fatalError(message);
+      }
+      peekLength = 0x00;
+
+      if (ensureRead && (readLimiter.remaining() < lengthBytesNeeded))
+      {
+        state = ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES;
+        return false;
+      }
+
+      readLimiter.checkLimit(lengthBytesNeeded);
+      while (lengthBytesNeeded > 0)
+      {
+        readByte = streamReader.readByte();
+        peekLength = (peekLength << 8) | (readByte & 0xFF);
+        lengthBytesNeeded--;
+      }
+    }
+
+    // Make sure that the element is not larger than the maximum allowed
+    // message size.
+    if ((maxElementSize > 0) && (peekLength > maxElementSize))
+    {
+      Message m = ERR_LDAP_CLIENT_DECODE_MAX_REQUEST_SIZE_EXCEEDED.get(
+          peekLength, maxElementSize);
+      throw DecodeException.fatalError(m);
+    }
+    state = ELEMENT_READ_STATE_NEED_VALUE_BYTES;
+    return true;
+  }
+
+
+
+  /**
+   * Internal helper method reading the ASN.1 type byte and transition
+   * to the next state if successful.
+   *
+   * @param ensureRead
+   *          <code>true</code> to check for availability first.
+   * @return <code>true</code> if the type byte was successfully read
+   * @throws IOException
+   *           If an error occurs while trying to decode an ASN1
+   *           element.
+   */
+  private boolean needTypeState(boolean ensureRead) throws IOException
+  {
+    // Read just the type.
+    if (ensureRead && (readLimiter.remaining() <= 0))
+    {
+      return false;
+    }
+
+    readLimiter.checkLimit(1);
+    peekType = streamReader.readByte();
+    state = ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE;
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java b/sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java
new file mode 100644
index 0000000..9221029
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ASN1StreamWriter.java
@@ -0,0 +1,675 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.messages.ProtocolMessages.ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_FALSE;
+import static org.opends.sdk.asn1.ASN1Constants.BOOLEAN_VALUE_TRUE;
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+import org.opends.messages.Message;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.asn1.AbstractASN1Writer;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteStringBuilder;
+import org.opends.sdk.util.StaticUtils;
+
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.utils.PoolableObject;
+
+
+
+/**
+ * Grizzly ASN1 writer implementation.
+ */
+final class ASN1StreamWriter extends AbstractASN1Writer implements
+    ASN1Writer, PoolableObject
+{
+  private class ChildSequenceBuffer implements SequenceBuffer
+  {
+    private SequenceBuffer parent;
+
+    private ChildSequenceBuffer child;
+
+    private final ByteStringBuilder buffer = new ByteStringBuilder(
+        SUB_SEQUENCE_BUFFER_INIT_SIZE);
+
+
+
+    public SequenceBuffer endSequence() throws IOException
+    {
+      writeLength(parent, buffer.length());
+      parent.writeByteArray(buffer.getBackingArray(), 0, buffer
+          .length());
+
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 END SEQUENCE(length=%d)", buffer.length()));
+      }
+
+      return parent;
+    }
+
+
+
+    public SequenceBuffer startSequence(byte type) throws IOException
+    {
+      if (child == null)
+      {
+        child = new ChildSequenceBuffer();
+        child.parent = this;
+      }
+
+      buffer.append(type);
+      child.buffer.clear();
+
+      return child;
+    }
+
+
+
+    public void writeByte(byte b) throws IOException
+    {
+      buffer.append(b);
+    }
+
+
+
+    public void writeByteArray(byte[] bs, int offset, int length)
+        throws IOException
+    {
+      buffer.append(bs, offset, length);
+    }
+  }
+
+
+
+  private class RootSequenceBuffer implements SequenceBuffer
+  {
+    private ChildSequenceBuffer child;
+
+
+
+    public SequenceBuffer endSequence() throws IOException
+    {
+      Message message = ERR_ASN1_SEQUENCE_WRITE_NOT_STARTED.get();
+      throw new IllegalStateException(message.toString());
+    }
+
+
+
+    public SequenceBuffer startSequence(byte type) throws IOException
+    {
+      if (child == null)
+      {
+        child = new ChildSequenceBuffer();
+        child.parent = this;
+      }
+
+      streamWriter.writeByte(type);
+      child.buffer.clear();
+
+      return child;
+    }
+
+
+
+    public void writeByte(byte b) throws IOException
+    {
+      streamWriter.writeByte(b);
+    }
+
+
+
+    public void writeByteArray(byte[] bs, int offset, int length)
+        throws IOException
+    {
+      streamWriter.writeByteArray(bs, offset, length);
+    }
+  }
+
+
+
+  private interface SequenceBuffer
+  {
+    public SequenceBuffer endSequence() throws IOException;
+
+
+
+    public SequenceBuffer startSequence(byte type) throws IOException;
+
+
+
+    public void writeByte(byte b) throws IOException;
+
+
+
+    public void writeByteArray(byte[] bs, int offset, int length)
+        throws IOException;
+  }
+
+
+
+  private static final int SUB_SEQUENCE_BUFFER_INIT_SIZE = 1024;
+
+  private StreamWriter streamWriter;
+
+  private SequenceBuffer sequenceBuffer;
+
+  private final RootSequenceBuffer rootBuffer;
+
+
+
+  /**
+   * Creates a new ASN.1 writer that writes to a StreamWriter.
+   */
+  public ASN1StreamWriter()
+  {
+    this.sequenceBuffer = this.rootBuffer = new RootSequenceBuffer();
+  }
+
+
+
+  /**
+   * Closes this ASN.1 writer and the underlying outputstream. Any
+   * unfinished sequences will be ended.
+   *
+   * @throws IOException
+   *           if an error occurs while closing the stream.
+   */
+  public void close() throws IOException
+  {
+    streamWriter.close();
+  }
+
+
+
+  /**
+   * Flushes the stream.
+   *
+   * @throws IOException
+   *           If an I/O error occurs
+   */
+  public void flush() throws IOException
+  {
+    streamWriter.flush();
+  }
+
+
+
+  public void prepare()
+  {
+    // nothing to do
+  }
+
+
+
+  public void release()
+  {
+    streamWriter = null;
+    sequenceBuffer = rootBuffer;
+  }
+
+
+
+  public void setStreamWriter(StreamWriter streamWriter)
+  {
+    this.streamWriter = streamWriter;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeBoolean(byte type, boolean booleanValue)
+      throws IOException
+  {
+    sequenceBuffer.writeByte(type);
+    writeLength(sequenceBuffer, 1);
+    sequenceBuffer.writeByte(booleanValue ? BOOLEAN_VALUE_TRUE
+        : BOOLEAN_VALUE_FALSE);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 BOOLEAN(type=0x%x, length=%d, value=%s)", type,
+          1, String.valueOf(booleanValue)));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeEndSequence() throws IOException,
+      IllegalStateException
+  {
+    sequenceBuffer = sequenceBuffer.endSequence();
+
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeEndSet() throws IOException
+  {
+    return writeEndSequence();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeEnumerated(byte type, int intValue)
+      throws IOException
+  {
+    return writeInteger(type, intValue);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeInteger(byte type, int intValue)
+      throws IOException
+  {
+    sequenceBuffer.writeByte(type);
+    if (((intValue < 0) && ((intValue & 0xFFFFFF80) == 0xFFFFFF80))
+        || ((intValue & 0x0000007F) == intValue))
+    {
+      writeLength(sequenceBuffer, 1);
+      sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 1, intValue));
+      }
+    }
+    else if (((intValue < 0) && ((intValue & 0xFFFF8000) == 0xFFFF8000))
+        || ((intValue & 0x00007FFF) == intValue))
+    {
+      writeLength(sequenceBuffer, 2);
+      sequenceBuffer.writeByte((byte) ((intValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 2, intValue));
+      }
+    }
+    else if (((intValue < 0) && ((intValue & 0xFF800000) == 0xFF800000))
+        || ((intValue & 0x007FFFFF) == intValue))
+    {
+      writeLength(sequenceBuffer, 3);
+      sequenceBuffer.writeByte((byte) ((intValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((intValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 3, intValue));
+      }
+    }
+    else
+    {
+      writeLength(sequenceBuffer, 4);
+      sequenceBuffer.writeByte((byte) ((intValue >> 24) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((intValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((intValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (intValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 4, intValue));
+      }
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeInteger(byte type, long longValue)
+      throws IOException
+  {
+    sequenceBuffer.writeByte(type);
+    if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFFFF80L) == 0xFFFFFFFFFFFFFF80L))
+        || ((longValue & 0x000000000000007FL) == longValue))
+    {
+      writeLength(sequenceBuffer, 1);
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 1, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFFFF8000L) == 0xFFFFFFFFFFFF8000L))
+        || ((longValue & 0x0000000000007FFFL) == longValue))
+    {
+      writeLength(sequenceBuffer, 2);
+      sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 2, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFFFFFF800000L) == 0xFFFFFFFFFF800000L))
+        || ((longValue & 0x00000000007FFFFFL) == longValue))
+    {
+      writeLength(sequenceBuffer, 3);
+      sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 3, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFFFF80000000L) == 0xFFFFFFFF80000000L))
+        || ((longValue & 0x000000007FFFFFFFL) == longValue))
+    {
+      writeLength(sequenceBuffer, 4);
+      sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 4, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFFFF8000000000L) == 0xFFFFFF8000000000L))
+        || ((longValue & 0x0000007FFFFFFFFFL) == longValue))
+    {
+      writeLength(sequenceBuffer, 5);
+      sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 5, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFFFF800000000000L) == 0xFFFF800000000000L))
+        || ((longValue & 0x00007FFFFFFFFFFFL) == longValue))
+    {
+      writeLength(sequenceBuffer, 6);
+      sequenceBuffer.writeByte((byte) ((longValue >> 40) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 6, longValue));
+      }
+    }
+    else if (((longValue < 0) && ((longValue & 0xFF80000000000000L) == 0xFF80000000000000L))
+        || ((longValue & 0x007FFFFFFFFFFFFFL) == longValue))
+    {
+      writeLength(sequenceBuffer, 7);
+      sequenceBuffer.writeByte((byte) ((longValue >> 48) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 40) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 7, longValue));
+      }
+    }
+    else
+    {
+      writeLength(sequenceBuffer, 8);
+      sequenceBuffer.writeByte((byte) ((longValue >> 56) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 48) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 40) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 32) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 24) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 16) & 0xFF));
+      sequenceBuffer.writeByte((byte) ((longValue >> 8) & 0xFF));
+      sequenceBuffer.writeByte((byte) (longValue & 0xFF));
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+      {
+        StaticUtils.DEBUG_LOG.finest(String.format(
+            "WRITE ASN.1 INTEGER(type=0x%x, length=%d, value=%d)",
+            type, 8, longValue));
+      }
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeNull(byte type) throws IOException
+  {
+    sequenceBuffer.writeByte(type);
+    writeLength(sequenceBuffer, 0);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 NULL(type=0x%x, length=%d)", type, 0));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(byte type, byte[] value,
+      int offset, int length) throws IOException
+  {
+    sequenceBuffer.writeByte(type);
+    writeLength(sequenceBuffer, length);
+    sequenceBuffer.writeByteArray(value, offset, length);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String
+          .format("WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)",
+              type, length));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(byte type, ByteSequence value)
+      throws IOException
+  {
+    sequenceBuffer.writeByte(type);
+    writeLength(sequenceBuffer, value.length());
+    // TODO: Is there a more efficient way to do this?
+    for (int i = 0; i < value.length(); i++)
+    {
+      sequenceBuffer.writeByte(value.byteAt(i));
+    }
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d)", type, value
+              .length()));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeOctetString(byte type, String value)
+      throws IOException
+  {
+    sequenceBuffer.writeByte(type);
+
+    if (value == null)
+    {
+      writeLength(sequenceBuffer, 0);
+      return this;
+    }
+
+    byte[] bytes = StaticUtils.getBytes(value);
+    writeLength(sequenceBuffer, bytes.length);
+    sequenceBuffer.writeByteArray(bytes, 0, bytes.length);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 OCTETSTRING(type=0x%x, length=%d, "
+              + "value=%s)", type, bytes.length, value));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeStartSequence(byte type) throws IOException
+  {
+    // Get a child sequence buffer
+    sequenceBuffer = sequenceBuffer.startSequence(type);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINEST))
+    {
+      StaticUtils.DEBUG_LOG.finest(String.format(
+          "WRITE ASN.1 START SEQUENCE(type=0x%x)", type));
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ASN1Writer writeStartSet(byte type) throws IOException
+  {
+    // From an implementation point of view, a set is equivalent to a
+    // sequence.
+    return writeStartSequence(type);
+  }
+
+
+
+  /**
+   * Writes the provided value for use as the length of an ASN.1
+   * element.
+   *
+   * @param buffer
+   *          The sequence buffer to write to.
+   * @param length
+   *          The length to encode for use in an ASN.1 element.
+   * @throws IOException
+   *           if an error occurs while writing.
+   */
+  private void writeLength(SequenceBuffer buffer, int length)
+      throws IOException
+  {
+    if (length < 128)
+    {
+      buffer.writeByte((byte) length);
+    }
+    else if ((length & 0x000000FF) == length)
+    {
+      buffer.writeByte((byte) 0x81);
+      buffer.writeByte((byte) (length & 0xFF));
+    }
+    else if ((length & 0x0000FFFF) == length)
+    {
+      buffer.writeByte((byte) 0x82);
+      buffer.writeByte((byte) ((length >> 8) & 0xFF));
+      buffer.writeByte((byte) (length & 0xFF));
+    }
+    else if ((length & 0x00FFFFFF) == length)
+    {
+      buffer.writeByte((byte) 0x83);
+      buffer.writeByte((byte) ((length >> 16) & 0xFF));
+      buffer.writeByte((byte) ((length >> 8) & 0xFF));
+      buffer.writeByte((byte) (length & 0xFF));
+    }
+    else
+    {
+      buffer.writeByte((byte) 0x84);
+      buffer.writeByte((byte) ((length >> 24) & 0xFF));
+      buffer.writeByte((byte) ((length >> 16) & 0xFF));
+      buffer.writeByte((byte) ((length >> 8) & 0xFF));
+      buffer.writeByte((byte) (length & 0xFF));
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java b/sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java
new file mode 100644
index 0000000..925edbf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/AbstractLDAPMessageHandler.java
@@ -0,0 +1,258 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.controls.GenericControl;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Abstract LDAP message handler.
+ */
+abstract class AbstractLDAPMessageHandler implements LDAPMessageHandler
+{
+  public void handleUnrecognizedMessage(int messageID, byte messageTag,
+      ByteString messageBytes) throws UnsupportedMessageException
+  {
+    throw new UnsupportedMessageException(messageID, messageTag,
+        messageBytes);
+  }
+
+
+
+  public void handleAbandonRequest(int messageID, AbandonRequest request)
+      throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleAddRequest(int messageID, AddRequest request)
+      throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleCompareRequest(int messageID, CompareRequest request)
+      throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleDeleteRequest(int messageID, DeleteRequest request)
+      throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleExtendedRequest(int messageID,
+      GenericExtendedRequest request) throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleBindRequest(int messageID, int version,
+      GenericBindRequest request) throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleBindRequest(int messageID, int version,
+      SASLBindRequest<?> request) throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleBindRequest(int messageID, int version,
+      SimpleBindRequest request) throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleModifyDNRequest(int messageID,
+      ModifyDNRequest request) throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleModifyRequest(int messageID, ModifyRequest request)
+      throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleSearchRequest(int messageID, SearchRequest request)
+      throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleUnbindRequest(int messageID, UnbindRequest request)
+      throws UnexpectedRequestException
+  {
+    throw new UnexpectedRequestException(messageID, request);
+  }
+
+
+
+  public void handleAddResult(int messageID, Result result)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleBindResult(int messageID, BindResult result)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleCompareResult(int messageID, CompareResult result)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleDeleteResult(int messageID, Result result)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleExtendedResult(int messageID,
+      GenericExtendedResult result) throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleIntermediateResponse(int messageID,
+      GenericIntermediateResponse response)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, response);
+  }
+
+
+
+  public void handleModifyDNResult(int messageID, Result result)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleModifyResult(int messageID, Result result)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleSearchResult(int messageID, Result result)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, result);
+  }
+
+
+
+  public void handleSearchResultEntry(int messageID,
+      SearchResultEntry entry) throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, entry);
+  }
+
+
+
+  public void handleSearchResultReference(int messageID,
+      SearchResultReference reference)
+      throws UnexpectedResponseException
+  {
+    throw new UnexpectedResponseException(messageID, reference);
+  }
+
+
+
+  public Control decodeResponseControl(int messageID, String oid,
+      boolean isCritical, ByteString value, Schema schema)
+      throws DecodeException
+  {
+    return new GenericControl(oid, isCritical, value);
+  }
+
+
+
+  public Control decodeRequestControl(int messageID, String oid,
+      boolean isCritical, ByteString value, Schema schema)
+      throws DecodeException
+  {
+    return new GenericControl(oid, isCritical, value);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java b/sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java
new file mode 100644
index 0000000..91c77fd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/AbstractLDAPTransport.java
@@ -0,0 +1,244 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import com.sun.grizzly.Connection;
+import com.sun.grizzly.filterchain.*;
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.utils.ConcurrentQueuePool;
+
+
+
+/**
+ * Abstract LDAP transport.
+ */
+abstract class AbstractLDAPTransport
+{
+  private class ASN1ReaderPool extends
+      ConcurrentQueuePool<ASN1StreamReader>
+  {
+    @Override
+    public ASN1StreamReader newInstance()
+    {
+      return new ASN1StreamReader(maxASN1ElementSize);
+    }
+  }
+
+
+
+  private class ASN1WriterPool extends
+      ConcurrentQueuePool<ASN1StreamWriter>
+  {
+    @Override
+    public ASN1StreamWriter newInstance()
+    {
+      return new ASN1StreamWriter();
+    }
+  }
+
+
+
+  private class DefaultFilterChainFactory implements
+      PatternFilterChainFactory
+  {
+    private FilterChain defaultFilterChain;
+
+
+
+    private DefaultFilterChainFactory()
+    {
+      this.defaultFilterChain = new DefaultFilterChain(this);
+      this.defaultFilterChain.add(new MonitorFilter());
+      this.defaultFilterChain.add(new TransportFilter());
+      this.defaultFilterChain.add(new LDAPFilter());
+    }
+
+
+
+    public FilterChain create()
+    {
+      FilterChain filterChain = new DefaultFilterChain(this);
+      filterChain.addAll(defaultFilterChain);
+      return filterChain;
+    }
+
+
+
+    public FilterChain getFilterChainPattern()
+    {
+      return defaultFilterChain;
+    }
+
+
+
+    public void release(FilterChain chain)
+    {
+      // TODO: Nothing yet.
+    }
+
+
+
+    public void setFilterChainPattern(FilterChain chain)
+    {
+      defaultFilterChain = chain;
+    }
+  }
+
+
+
+  private class LDAPFilter extends FilterAdapter
+  {
+    @Override
+    public NextAction handleRead(FilterChainContext ctx,
+        NextAction nextAction) throws IOException
+    {
+      Connection<?> connection = ctx.getConnection();
+      StreamReader streamReader = ctx.getStreamReader();
+      LDAPMessageHandler handler = getMessageHandler(connection);
+      ASN1StreamReader asn1Reader = getASN1Reader(streamReader);
+
+      try
+      {
+        do
+        {
+          LDAPDecoder.decode(asn1Reader, handler);
+        }
+        while (asn1Reader.hasNextElement());
+      }
+      finally
+      {
+        releaseASN1Reader(asn1Reader);
+      }
+
+      return nextAction;
+    }
+  }
+
+
+
+  private class MonitorFilter extends FilterAdapter
+  {
+    @Override
+    public void exceptionOccurred(FilterChainContext ctx,
+        Throwable error)
+    {
+      Connection<?> connection = ctx.getConnection();
+      if (!connection.isOpen())
+      {
+        // Grizzly doens't not deregister the read interest from the
+        // selector so closing the connection results in an
+        // EOFException.
+        // Just ignore errors on closed connections.
+        return;
+      }
+      LDAPMessageHandler handler = getMessageHandler(connection);
+      handler.handleException(error);
+    }
+
+
+
+    @Override
+    public NextAction handleClose(FilterChainContext ctx,
+        NextAction nextAction) throws IOException
+    {
+      Connection<?> connection = ctx.getConnection();
+      removeMessageHandler(connection);
+      return nextAction;
+    }
+  }
+
+  private final PatternFilterChainFactory defaultFilterChainFactory;
+
+  private final int maxASN1ElementSize = 0;
+
+  private final ASN1ReaderPool asn1ReaderPool;
+
+  private final ASN1WriterPool asn1WriterPool;
+
+
+
+  AbstractLDAPTransport()
+  {
+    this.defaultFilterChainFactory = new DefaultFilterChainFactory();
+
+    this.asn1ReaderPool = new ASN1ReaderPool();
+    this.asn1WriterPool = new ASN1WriterPool();
+  }
+
+
+
+  ASN1StreamWriter getASN1Writer(StreamWriter streamWriter)
+  {
+    ASN1StreamWriter asn1Writer = asn1WriterPool.poll();
+    asn1Writer.setStreamWriter(streamWriter);
+    return asn1Writer;
+  }
+
+
+
+  PatternFilterChainFactory getDefaultFilterChainFactory()
+  {
+    return defaultFilterChainFactory;
+  }
+
+
+
+  void releaseASN1Writer(ASN1StreamWriter asn1Writer)
+  {
+    asn1WriterPool.offer(asn1Writer);
+  }
+
+
+
+  abstract LDAPMessageHandler getMessageHandler(Connection<?> connection);
+
+
+
+  abstract void removeMessageHandler(Connection<?> connection);
+
+
+
+  private ASN1StreamReader getASN1Reader(StreamReader streamReader)
+  {
+    ASN1StreamReader asn1Reader = asn1ReaderPool.poll();
+    asn1Reader.setStreamReader(streamReader);
+    return asn1Reader;
+  }
+
+
+
+  private void releaseASN1Reader(ASN1StreamReader asn1Reader)
+  {
+    asn1ReaderPool.offer(asn1Reader);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java
new file mode 100644
index 0000000..6bd5050
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/AbstractResultFutureImpl.java
@@ -0,0 +1,259 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.*;
+import java.util.logging.Level;
+
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Abstract result future implementation.
+ */
+abstract class AbstractResultFutureImpl<R extends Result, P> implements
+    ResultFuture<R>, Runnable
+{
+  private final LDAPConnection connection;
+
+  private final ResultHandler<? super R, P> handler;
+
+  private final ExecutorService handlerExecutor;
+
+  private final int messageID;
+
+  private final Semaphore invokerLock;
+
+  private final CountDownLatch latch = new CountDownLatch(1);
+
+  private final P p;
+
+  private volatile boolean isCancelled = false;
+
+  private volatile R result = null;
+
+
+
+  AbstractResultFutureImpl(int messageID,
+      ResultHandler<? super R, P> handler, P p,
+      LDAPConnection connection, ExecutorService handlerExecutor)
+  {
+    this.messageID = messageID;
+    this.handler = handler;
+    this.p = p;
+    this.connection = connection;
+    this.handlerExecutor = handlerExecutor;
+    if (handlerExecutor == null)
+    {
+      invokerLock = null;
+    }
+    else
+    {
+      invokerLock = new Semaphore(1);
+    }
+  }
+
+
+
+  public synchronized boolean cancel(boolean b)
+  {
+    if (!isDone())
+    {
+      isCancelled = true;
+      connection.abandon(Requests.newAbandonRequest(messageID));
+      latch.countDown();
+      return true;
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  public R get() throws InterruptedException, ErrorResultException
+  {
+    latch.await();
+    return get0();
+  }
+
+
+
+  public R get(long timeout, TimeUnit unit)
+      throws InterruptedException, TimeoutException,
+      ErrorResultException
+  {
+    if (!latch.await(timeout, unit))
+    {
+      throw new TimeoutException();
+    }
+    return get0();
+  }
+
+
+
+  public int getMessageID()
+  {
+    return messageID;
+  }
+
+
+
+  public boolean isCancelled()
+  {
+    return isCancelled;
+  }
+
+
+
+  public boolean isDone()
+  {
+    return latch.getCount() == 0;
+  }
+
+
+
+  public void run()
+  {
+    if (result.getResultCode().isExceptional())
+    {
+      ErrorResultException e = ErrorResultException.wrap(result);
+      handler.handleErrorResult(p, e);
+    }
+    else
+    {
+      handler.handleResult(p, result);
+    }
+  }
+
+
+
+  synchronized void handleErrorResult(Result result)
+  {
+    R errorResult = newErrorResult(result.getResultCode(), result
+        .getDiagnosticMessage(), result.getCause());
+    handleResult(errorResult);
+  }
+
+
+
+  abstract R newErrorResult(ResultCode resultCode,
+      String diagnosticMessage, Throwable cause);
+
+
+
+  void handleResult(R result)
+  {
+    if (!isDone())
+    {
+      this.result = result;
+      if (handler != null)
+      {
+        invokeHandler(this);
+      }
+      latch.countDown();
+    }
+  }
+
+
+
+  protected void invokeHandler(final Runnable runnable)
+  {
+    try
+    {
+      if (handlerExecutor == null)
+      {
+        runnable.run();
+      }
+      else
+      {
+        invokerLock.acquire();
+
+        try
+        {
+          handlerExecutor.submit(new Runnable()
+          {
+            public void run()
+            {
+              try
+              {
+                runnable.run();
+              }
+              finally
+              {
+                invokerLock.release();
+              }
+            }
+          });
+        }
+        catch (Exception e)
+        {
+          invokerLock.release();
+        }
+      }
+    }
+    catch (InterruptedException e)
+    {
+      // Thread has been interrupted so give up.
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+      {
+        StaticUtils.DEBUG_LOG.warning(String.format(
+            "Invoke thread interrupted: %s", StaticUtils
+                .getExceptionMessage(e)));
+      }
+    }
+  }
+
+
+
+  private R get0() throws CancellationException, ErrorResultException
+  {
+    if (isCancelled())
+    {
+      throw new CancellationException();
+    }
+    else if (result.getResultCode().isExceptional())
+    {
+      throw ErrorResultException.wrap(result);
+    }
+    else
+    {
+      return result;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java
new file mode 100644
index 0000000..1a5c0c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/BindResultFutureImpl.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.BindRequest;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.sasl.SASLContext;
+
+
+
+/**
+ * Bind result future implementation.
+ */
+final class BindResultFutureImpl<P> extends
+    AbstractResultFutureImpl<BindResult, P> implements
+    ResultFuture<BindResult>
+{
+  private final BindRequest request;
+
+  private SASLContext saslContext;
+
+
+
+  BindResultFutureImpl(int messageID, BindRequest request,
+      ResultHandler<? super BindResult, P> handler, P p,
+      LDAPConnection connection, ExecutorService handlerExecutor)
+  {
+    super(messageID, handler, p, connection, handlerExecutor);
+    this.request = request;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  BindResult newErrorResult(ResultCode resultCode,
+      String diagnosticMessage, Throwable cause)
+  {
+    return Responses.newBindResult(resultCode).setDiagnosticMessage(
+        diagnosticMessage).setCause(cause);
+  }
+
+
+
+  BindRequest getRequest()
+  {
+    return request;
+  }
+
+
+
+  void setSASLContext(SASLContext saslContext)
+  {
+    this.saslContext = saslContext;
+  }
+
+
+
+  SASLContext getSASLContext()
+  {
+    return saslContext;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java
new file mode 100644
index 0000000..b9704ea
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/CompareResultFutureImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.CompareRequest;
+import org.opends.sdk.responses.CompareResult;
+import org.opends.sdk.responses.Responses;
+
+
+
+/**
+ * Compare result future implementation.
+ */
+final class CompareResultFutureImpl<P> extends
+    AbstractResultFutureImpl<CompareResult, P> implements
+    ResultFuture<CompareResult>
+{
+  private final CompareRequest request;
+
+
+
+  CompareResultFutureImpl(int messageID, CompareRequest request,
+      ResultHandler<? super CompareResult, P> handler, P p,
+      LDAPConnection connection, ExecutorService handlerExecutor)
+  {
+    super(messageID, handler, p, connection, handlerExecutor);
+    this.request = request;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  CompareResult newErrorResult(ResultCode resultCode,
+      String diagnosticMessage, Throwable cause)
+  {
+    return Responses.newCompareResult(resultCode).setDiagnosticMessage(
+        diagnosticMessage).setCause(cause);
+  }
+
+
+
+  CompareRequest getRequest()
+  {
+    return request;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java
new file mode 100644
index 0000000..6df167b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ExtendedResultFutureImpl.java
@@ -0,0 +1,90 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.ExtendedRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Extended result future implementation.
+ */
+final class ExtendedResultFutureImpl<R extends Result, P> extends
+    AbstractResultFutureImpl<R, P> implements ResultFuture<R>
+{
+  private final ExtendedRequest<R> request;
+
+
+
+  ExtendedResultFutureImpl(int messageID, ExtendedRequest<R> request,
+      ResultHandler<? super R, P> handler, P p,
+      LDAPConnection connection, ExecutorService handlerExecutor)
+  {
+    super(messageID, handler, p, connection, handlerExecutor);
+    this.request = request;
+  }
+
+
+
+  R decodeResponse(ResultCode resultCode, String matchedDN,
+      String diagnosticMessage, String responseName,
+      ByteString responseValue) throws DecodeException
+  {
+    return request.getExtendedOperation().decodeResponse(resultCode,
+        matchedDN, diagnosticMessage, responseName, responseValue);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  R newErrorResult(ResultCode resultCode, String diagnosticMessage,
+      Throwable cause)
+  {
+    return request.getExtendedOperation().decodeResponse(resultCode,
+        "", diagnosticMessage);
+  }
+
+
+
+  ExtendedRequest<R> getRequest()
+  {
+    return request;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnection.java b/sdk/src/org/opends/sdk/ldap/LDAPConnection.java
new file mode 100644
index 0000000..b13e923
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnection.java
@@ -0,0 +1,1676 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.sdk.ldap.LDAPConstants.OID_NOTICE_OF_DISCONNECTION;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.SSLContext;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.*;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.controls.ControlDecoder;
+import org.opends.sdk.extensions.StartTLSRequest;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.sasl.SASLContext;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+import com.sun.grizzly.filterchain.Filter;
+import com.sun.grizzly.filterchain.FilterChain;
+import com.sun.grizzly.filterchain.StreamTransformerFilter;
+import com.sun.grizzly.ssl.*;
+import com.sun.grizzly.streams.StreamWriter;
+
+
+
+/**
+ * LDAP connection implementation.
+ * <p>
+ * TODO: handle illegal state exceptions.
+ */
+final class LDAPConnection implements AsynchronousConnection
+{
+
+  private final class LDAPMessageHandlerImpl extends
+      AbstractLDAPMessageHandler
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleAddResult(int messageID, Result result)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof ResultFutureImpl<?>)
+        {
+          ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+          if (future.getRequest() instanceof AddRequest)
+          {
+            future.handleResult(result);
+            return;
+          }
+        }
+        handleIncorrectResponse(pendingRequest);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleBindResult(int messageID, BindResult result)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof BindResultFutureImpl<?>)
+        {
+          BindResultFutureImpl<?> future = ((BindResultFutureImpl<?>) pendingRequest);
+          BindRequest request = future.getRequest();
+
+          if (request instanceof SASLBindRequest<?>)
+          {
+            SASLBindRequest<?> saslBind = (SASLBindRequest<?>) request;
+            SASLContext saslContext = future.getSASLContext();
+
+            if ((result.getResultCode() == ResultCode.SUCCESS || result
+                .getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
+                && !saslContext.isComplete())
+            {
+              try
+              {
+                saslContext.evaluateCredentials(result
+                    .getServerSASLCredentials());
+              }
+              catch (SaslException se)
+              {
+                pendingBindOrStartTLS = -1;
+
+                Result errorResult = adaptException(se);
+                future.handleErrorResult(errorResult);
+                return;
+              }
+            }
+
+            if (result.getResultCode() == ResultCode.SASL_BIND_IN_PROGRESS)
+            {
+              // The server is expecting a multi stage bind response.
+              messageID = nextMsgID.getAndIncrement();
+              ASN1StreamWriter asn1Writer = connFactory
+                  .getASN1Writer(streamWriter);
+
+              try
+              {
+                synchronized (writeLock)
+                {
+                  pendingRequests.put(messageID, future);
+                  try
+                  {
+                    LDAPEncoder.encodeBindRequest(asn1Writer,
+                        messageID, 3, saslBind, saslContext
+                            .getSASLCredentials());
+                    asn1Writer.flush();
+                  }
+                  catch (IOException e)
+                  {
+                    pendingRequests.remove(messageID);
+
+                    Result errorResult = adaptException(e);
+                    connectionErrorOccurred(errorResult);
+                    future.handleErrorResult(errorResult);
+                  }
+                }
+              }
+              finally
+              {
+                connFactory.releaseASN1Writer(asn1Writer);
+              }
+              return;
+            }
+
+            if ((result.getResultCode() == ResultCode.SUCCESS)
+                && saslContext.isSecure())
+            {
+              // The connection needs to be secured by the SASL
+              // mechanism.
+              installFilter(SASLFilter.getInstance(saslContext,
+                  connection));
+            }
+          }
+          pendingBindOrStartTLS = -1;
+          future.handleResult(result);
+        }
+        else
+        {
+          handleIncorrectResponse(pendingRequest);
+        }
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleCompareResult(int messageID, CompareResult result)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof CompareResultFutureImpl<?>)
+        {
+          CompareResultFutureImpl<?> future = (CompareResultFutureImpl<?>) pendingRequest;
+          future.handleResult(result);
+        }
+        else
+        {
+          handleIncorrectResponse(pendingRequest);
+        }
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleDeleteResult(int messageID, Result result)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof ResultFutureImpl<?>)
+        {
+          ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+          if (future.getRequest() instanceof DeleteRequest)
+          {
+            future.handleResult(result);
+            return;
+          }
+        }
+        handleIncorrectResponse(pendingRequest);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void handleException(Throwable throwable)
+    {
+      Result errorResult = adaptException(throwable);
+      connectionErrorOccurred(errorResult);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleExtendedResult(int messageID,
+        GenericExtendedResult result)
+    {
+      if (messageID == 0)
+      {
+        if ((result.getResponseName() != null)
+            && result.getResponseName().equals(
+                OID_NOTICE_OF_DISCONNECTION))
+        {
+
+          Result errorResult = Responses.newResult(result.getResultCode())
+              .setDiagnosticMessage(result.getDiagnosticMessage());
+          close(null, true, errorResult);
+          return;
+        }
+        else
+        {
+          // Unsolicited notification received.
+          synchronized (writeLock)
+          {
+            if (isClosed)
+            {
+              // Don't notify after connection is closed.
+              return;
+            }
+
+            for (ConnectionEventListener listener : listeners)
+            {
+              listener
+                  .connectionReceivedUnsolicitedNotification(result);
+            }
+          }
+        }
+      }
+
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+
+      if (pendingRequest instanceof ExtendedResultFutureImpl<?, ?>)
+      {
+        ExtendedResultFutureImpl<?, ?> extendedFuture = ((ExtendedResultFutureImpl<?, ?>) pendingRequest);
+        try
+        {
+          handleExtendedResult0(extendedFuture, result);
+        }
+        catch (DecodeException de)
+        {
+          // FIXME: should the connection be closed as well?
+          Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR)
+              .setDiagnosticMessage(de.getLocalizedMessage()).setCause(
+                  de);
+          extendedFuture.handleErrorResult(errorResult);
+        }
+      }
+      else
+      {
+        handleIncorrectResponse(pendingRequest);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleIntermediateResponse(int messageID,
+        GenericIntermediateResponse response)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        handleIncorrectResponse(pendingRequest);
+
+        // FIXME: intermediate responses can occur for all operations.
+
+        // if (pendingRequest instanceof ExtendedResultFutureImpl)
+        // {
+        // ExtendedResultFutureImpl extendedFuture =
+        // ((ExtendedResultFutureImpl) pendingRequest);
+        // ExtendedRequest request = extendedFuture.getRequest();
+        //
+        // try
+        // {
+        // IntermediateResponse decodedResponse =
+        // request.getExtendedOperation()
+        // .decodeIntermediateResponse(
+        // response.getResponseName(),
+        // response.getResponseValue());
+        // extendedFuture.handleIntermediateResponse(decodedResponse);
+        // }
+        // catch (DecodeException de)
+        // {
+        // pendingRequest.failure(de);
+        // }
+        // }
+        // else
+        // {
+        // handleIncorrectResponse(pendingRequest);
+        // }
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleModifyDNResult(int messageID, Result result)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof ResultFutureImpl<?>)
+        {
+          ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+          if (future.getRequest() instanceof ModifyDNRequest)
+          {
+            future.handleResult(result);
+            return;
+          }
+        }
+        handleIncorrectResponse(pendingRequest);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleModifyResult(int messageID, Result result)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof ResultFutureImpl<?>)
+        {
+          ResultFutureImpl<?> future = (ResultFutureImpl<?>) pendingRequest;
+          if (future.getRequest() instanceof ModifyRequest)
+          {
+            future.handleResult(result);
+            return;
+          }
+        }
+        handleIncorrectResponse(pendingRequest);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleSearchResult(int messageID, Result result)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .remove(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof SearchResultFutureImpl<?>)
+        {
+          ((SearchResultFutureImpl<?>) pendingRequest)
+              .handleResult(result);
+        }
+        else
+        {
+          handleIncorrectResponse(pendingRequest);
+        }
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleSearchResultEntry(int messageID,
+        SearchResultEntry entry)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .get(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof SearchResultFutureImpl<?>)
+        {
+          ((SearchResultFutureImpl<?>) pendingRequest)
+              .handleSearchResultEntry(entry);
+        }
+        else
+        {
+          handleIncorrectResponse(pendingRequest);
+        }
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void handleSearchResultReference(int messageID,
+        SearchResultReference reference)
+    {
+      AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+          .get(messageID);
+      if (pendingRequest != null)
+      {
+        if (pendingRequest instanceof SearchResultFutureImpl<?>)
+        {
+          ((SearchResultFutureImpl<?>) pendingRequest)
+              .handleSearchResultReference(reference);
+        }
+        else
+        {
+          handleIncorrectResponse(pendingRequest);
+        }
+      }
+    }
+
+
+
+    @Override
+    public Control decodeResponseControl(int messageID, String oid,
+        boolean isCritical, ByteString value, Schema schema)
+        throws DecodeException
+    {
+      ControlDecoder<?> decoder = connFactory.getControlDecoder(oid);
+      if (decoder != null)
+      {
+        return decoder.decode(isCritical, value, schema);
+      }
+      return super.decodeResponseControl(messageID, oid, isCritical,
+          value, schema);
+    }
+
+
+
+    public ResolvedSchema resolveSchema(String dn)
+        throws DecodeException
+    {
+      DN initialDN;
+
+      try
+      {
+        initialDN = DN.valueOf(dn, schema);
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        throw DecodeException.error(e.getMessageObject());
+      }
+
+      return new ResolvedSchemaImpl(schema, initialDN);
+    }
+
+
+
+    public Schema getDefaultSchema()
+    {
+      return schema;
+    }
+  }
+
+
+
+  private static final class ResolvedSchemaImpl implements
+      ResolvedSchema
+  {
+    private final DN initialDN;
+
+    private final Schema schema;
+
+
+
+    private ResolvedSchemaImpl(Schema schema, DN initialDN)
+    {
+      this.schema = schema;
+      this.initialDN = initialDN;
+    }
+
+
+
+    public AttributeDescription decodeAttributeDescription(
+        String attributeDescription) throws DecodeException
+    {
+      try
+      {
+        return AttributeDescription.valueOf(attributeDescription,
+            schema);
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        throw DecodeException.error(e.getMessageObject());
+      }
+    }
+
+
+
+    public DN decodeDN(String dn) throws DecodeException
+    {
+      try
+      {
+        return DN.valueOf(dn, schema);
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        throw DecodeException.error(e.getMessageObject());
+      }
+    }
+
+
+
+    public RDN decodeRDN(String rdn) throws DecodeException
+    {
+      try
+      {
+        return RDN.valueOf(rdn, schema);
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        throw DecodeException.error(e.getMessageObject());
+      }
+    }
+
+
+
+    public DN getInitialDN()
+    {
+      return initialDN;
+    }
+
+
+
+    public Schema getSchema()
+    {
+      return schema;
+    }
+
+  }
+
+
+
+  private final Schema schema;
+
+  private final com.sun.grizzly.Connection<?> connection;
+
+  private Result connectionInvalidReason;
+
+  private final LDAPConnectionFactoryImpl connFactory;
+
+  private FilterChain customFilterChain;
+
+  private final LDAPMessageHandler handler = new LDAPMessageHandlerImpl();
+
+  private boolean isClosed = false;
+
+  private final List<ConnectionEventListener> listeners = new LinkedList<ConnectionEventListener>();
+
+  private final AtomicInteger nextMsgID = new AtomicInteger(1);
+
+  private volatile int pendingBindOrStartTLS = -1;
+
+  private final ConcurrentHashMap<Integer, AbstractResultFutureImpl<?, ?>> pendingRequests = new ConcurrentHashMap<Integer, AbstractResultFutureImpl<?, ?>>();
+
+  private final InetSocketAddress serverAddress;
+
+  private StreamWriter streamWriter;
+
+  private final Object writeLock = new Object();
+
+
+
+  /**
+   * Creates a new LDAP connection.
+   *
+   * @param connection
+   *          The Grizzly connection.
+   * @param serverAddress
+   *          The address of the server.
+   * @param schema
+   *          The schema which will be used to decode responses from the
+   *          server.
+   * @param connFactory
+   *          The associated connection factory.
+   */
+  LDAPConnection(com.sun.grizzly.Connection<?> connection,
+      InetSocketAddress serverAddress, Schema schema,
+      LDAPConnectionFactoryImpl connFactory)
+  {
+    this.connection = connection;
+    this.serverAddress = serverAddress;
+    this.schema = schema;
+    this.connFactory = connFactory;
+    this.streamWriter = getFilterChainStreamWriter();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void abandon(AbandonRequest request)
+  {
+    AbstractResultFutureImpl<?, ?> pendingRequest = pendingRequests
+        .remove(request.getMessageID());
+    if (pendingRequest != null)
+    {
+      pendingRequest.cancel(false);
+      int messageID = nextMsgID.getAndIncrement();
+      ASN1StreamWriter asn1Writer = connFactory
+          .getASN1Writer(streamWriter);
+
+      try
+      {
+        synchronized (writeLock)
+        {
+          if (connectionInvalidReason != null)
+          {
+            return;
+          }
+          if (pendingBindOrStartTLS > 0)
+          {
+            // This is not allowed. We will just ignore this
+            // abandon request.
+          }
+          try
+          {
+            LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID,
+                request);
+            asn1Writer.flush();
+          }
+          catch (IOException e)
+          {
+            Result errorResult = adaptException(e);
+            connectionErrorOccurred(errorResult);
+          }
+        }
+      }
+      finally
+      {
+        connFactory.releaseASN1Writer(asn1Writer);
+      }
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ResultFuture<Result> add(AddRequest request,
+      ResultHandler<Result, P> handler, P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+        request, handler, p, this, connFactory.getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future
+              .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+          return future;
+        }
+        pendingRequests.put(messageID, future);
+        try
+        {
+          LDAPEncoder.encodeAddRequest(asn1Writer, messageID, request);
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void addConnectionEventListener(
+      ConnectionEventListener listener) throws IllegalStateException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(listener);
+
+    synchronized (writeLock)
+    {
+      if (isClosed)
+      {
+        throw new IllegalStateException();
+      }
+
+      listeners.add(listener);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ResultFuture<BindResult> bind(BindRequest request,
+      ResultHandler<? super BindResult, P> handler, P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    BindResultFutureImpl<P> future = new BindResultFutureImpl<P>(
+        messageID, request, handler, p, this, connFactory
+            .getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future
+              .handleResult(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+          return future;
+        }
+        if (!pendingRequests.isEmpty())
+        {
+          future
+              .handleResult(Responses.newBindResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("There are other operations pending on this connection"));
+          return future;
+        }
+
+        pendingRequests.put(messageID, future);
+        pendingBindOrStartTLS = messageID;
+
+        try
+        {
+          if (request instanceof SASLBindRequest<?>)
+          {
+            try
+            {
+              SASLBindRequest<?> saslBind = (SASLBindRequest<?>) request;
+              SASLContext saslContext = saslBind
+                  .getClientContext(serverAddress.getHostName());
+              future.setSASLContext(saslContext);
+              LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3,
+                  saslBind, saslContext.getSASLCredentials());
+            }
+            catch (SaslException e)
+            {
+              Result errorResult = adaptException(e);
+              future.handleErrorResult(errorResult);
+              return future;
+            }
+          }
+          else if (request instanceof SimpleBindRequest)
+          {
+            LDAPEncoder.encodeBindRequest(asn1Writer, messageID, 3,
+                (SimpleBindRequest) request);
+          }
+          else
+          {
+            pendingRequests.remove(messageID);
+            future.handleResult(Responses.newBindResult(ResultCode.PROTOCOL_ERROR)
+                .setDiagnosticMessage("Auth type not supported"));
+          }
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close()
+  {
+    close(Requests.newUnbindRequest());
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close(UnbindRequest request) throws NullPointerException
+  {
+    // FIXME: I18N need to internationalize this message.
+    Validator.ensureNotNull(request);
+
+    close(request, false, Responses.newResult(ResultCode.CLIENT_SIDE_USER_CANCELLED)
+        .setDiagnosticMessage("Connection closed by client"));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ResultFuture<CompareResult> compare(
+      CompareRequest request,
+      ResultHandler<? super CompareResult, P> handler, P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    CompareResultFutureImpl<P> future = new CompareResultFutureImpl<P>(
+        messageID, request, handler, p, this, connFactory
+            .getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future
+              .handleResult(Responses.newCompareResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+          return future;
+        }
+        pendingRequests.put(messageID, future);
+        try
+        {
+          LDAPEncoder.encodeCompareRequest(asn1Writer, messageID,
+              request);
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ResultFuture<Result> delete(DeleteRequest request,
+      ResultHandler<Result, P> handler, P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+        request, handler, p, this, connFactory.getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future
+              .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+          return future;
+        }
+        pendingRequests.put(messageID, future);
+        try
+        {
+          LDAPEncoder.encodeDeleteRequest(asn1Writer, messageID,
+              request);
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <R extends Result, P> ResultFuture<R> extendedRequest(
+      ExtendedRequest<R> request, ResultHandler<? super R, P> handler,
+      P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    ExtendedResultFutureImpl<R, P> future = new ExtendedResultFutureImpl<R, P>(
+        messageID, request, handler, p, this, connFactory
+            .getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future.handleResult(request.getExtendedOperation()
+              .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
+                  "Bind or Start TLS operation in progress"));
+          return future;
+        }
+        if (request.getRequestName().equals(
+            StartTLSRequest.OID_START_TLS_REQUEST))
+        {
+          if (!pendingRequests.isEmpty())
+          {
+            future.handleResult(request.getExtendedOperation()
+                .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
+                    "There are pending operations on this connection"));
+            return future;
+          }
+          if (isTLSEnabled())
+          {
+            future.handleResult(request.getExtendedOperation()
+                .decodeResponse(ResultCode.OPERATIONS_ERROR, "",
+                    "This connection is already TLS enabled"));
+          }
+          pendingBindOrStartTLS = messageID;
+        }
+        pendingRequests.put(messageID, future);
+
+        try
+        {
+          LDAPEncoder.encodeExtendedRequest(asn1Writer, messageID,
+              request);
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ResultFuture<Result> modify(ModifyRequest request,
+      ResultHandler<Result, P> handler, P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+        request, handler, p, this, connFactory.getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future
+              .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+          return future;
+        }
+        pendingRequests.put(messageID, future);
+        try
+        {
+          LDAPEncoder.encodeModifyRequest(asn1Writer, messageID,
+              request);
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ResultFuture<Result> modifyDN(ModifyDNRequest request,
+      ResultHandler<Result, P> handler, P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    ResultFutureImpl<P> future = new ResultFutureImpl<P>(messageID,
+        request, handler, p, this, connFactory.getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future
+              .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+          return future;
+        }
+        pendingRequests.put(messageID, future);
+        try
+        {
+          LDAPEncoder.encodeModifyDNRequest(asn1Writer, messageID,
+              request);
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void removeConnectionEventListener(
+      ConnectionEventListener listener) throws NullPointerException
+  {
+    Validator.ensureNotNull(listener);
+
+    synchronized (writeLock)
+    {
+      listeners.remove(listener);
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ResultFuture<Result> search(SearchRequest request,
+      ResultHandler<Result, P> resultHandler,
+      SearchResultHandler<P> searchResulthandler, P p)
+  {
+    int messageID = nextMsgID.getAndIncrement();
+    SearchResultFutureImpl<P> future = new SearchResultFutureImpl<P>(
+        messageID, request, resultHandler, searchResulthandler, p,
+        this, connFactory.getHandlerInvokers());
+    ASN1StreamWriter asn1Writer = connFactory
+        .getASN1Writer(streamWriter);
+
+    try
+    {
+      synchronized (writeLock)
+      {
+        if (connectionInvalidReason != null)
+        {
+          future.handleErrorResult(connectionInvalidReason);
+          return future;
+        }
+        if (pendingBindOrStartTLS > 0)
+        {
+          future
+              .handleResult(Responses.newResult(ResultCode.OPERATIONS_ERROR)
+                  .setDiagnosticMessage("Bind or Start TLS operation in progress"));
+          return future;
+        }
+        pendingRequests.put(messageID, future);
+        try
+        {
+          LDAPEncoder.encodeSearchRequest(asn1Writer, messageID,
+              request);
+          asn1Writer.flush();
+        }
+        catch (IOException e)
+        {
+          pendingRequests.remove(messageID);
+
+          Result errorResult = adaptException(e);
+          connectionErrorOccurred(errorResult);
+          future.handleErrorResult(errorResult);
+        }
+      }
+    }
+    finally
+    {
+      connFactory.releaseASN1Writer(asn1Writer);
+    }
+
+    return future;
+  }
+
+
+
+  /**
+   * Returns the LDAP message handler associated with this connection.
+   *
+   * @return The LDAP message handler associated with this connection.
+   */
+  LDAPMessageHandler getLDAPMessageHandler()
+  {
+    return handler;
+  }
+
+
+
+  /**
+   * Indicates whether or not TLS is enabled on this connection.
+   *
+   * @return {@code true} if TLS is enabled on this connection,
+   *         otherwise {@code false}.
+   */
+  boolean isTLSEnabled()
+  {
+    FilterChain currentFilterChain = (FilterChain) connection
+        .getProcessor();
+    return currentFilterChain.get(2) instanceof SSLFilter;
+  }
+
+
+
+  private Result adaptException(Throwable t)
+  {
+    if (t instanceof ExecutionException)
+    {
+      ExecutionException e = (ExecutionException) t;
+      t = e.getCause();
+    }
+
+    Result errorResult;
+
+    try
+    {
+      throw t;
+    }
+    catch (SaslException e)
+    {
+      // FIXME: I18N need to have a better error message.
+      // FIXME: Is this the best result code?
+      errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+          "An error occurred during SASL authentication").setCause(e);
+    }
+    catch (EOFException e)
+    {
+      // FIXME: I18N need to have a better error message.
+      // FIXME: what sort of IOExceptions can be thrown?
+      // FIXME: Is this the best result code?
+      errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_SERVER_DOWN).setDiagnosticMessage(
+          "Connection unexpectedly terminated by server").setCause(e);
+    }
+    catch (IOException e)
+    {
+      // FIXME: I18N need to have a better error message.
+      // FIXME: what sort of IOExceptions can be thrown?
+      // FIXME: Is this the best result code?
+      errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+          "An error occurred whilst attempting to send a request: "
+              + e.toString()).setCause(e);
+    }
+    catch (Throwable e)
+    {
+      // FIXME: I18N need to have a better error message.
+      // FIXME: Is this the best result code?
+      errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_LOCAL_ERROR).setDiagnosticMessage(
+          "An unknown error occurred: " + e.toString()).setCause(e);
+    }
+
+    return errorResult;
+  }
+
+
+
+  private void close(UnbindRequest unbindRequest,
+      boolean isDisconnectNotification, Result reason)
+  {
+    synchronized (writeLock)
+    {
+      boolean notifyClose = false;
+      boolean notifyErrorOccurred = false;
+
+      if (isClosed)
+      {
+        // Already closed.
+        return;
+      }
+
+      if (unbindRequest != null)
+      {
+        // User closed.
+        isClosed = true;
+        notifyClose = true;
+      }
+      else
+      {
+        notifyErrorOccurred = true;
+      }
+
+      if (connectionInvalidReason != null)
+      {
+        // Already invalid.
+        if (notifyClose)
+        {
+          // TODO: uncomment if close notification is required.
+          // for (ConnectionEventListener listener : listeners)
+          // {
+          // listener.connectionClosed(this);
+          // }
+        }
+        return;
+      }
+
+      // First abort all outstanding requests.
+      for (AbstractResultFutureImpl<?, ?> future : pendingRequests
+          .values())
+      {
+        if (pendingBindOrStartTLS <= 0)
+        {
+          ASN1StreamWriter asn1Writer = connFactory
+              .getASN1Writer(streamWriter);
+          int messageID = nextMsgID.getAndIncrement();
+          AbandonRequest abandon = Requests.newAbandonRequest(future
+              .getMessageID());
+          try
+          {
+            LDAPEncoder.encodeAbandonRequest(asn1Writer, messageID,
+                abandon);
+            asn1Writer.flush();
+          }
+          catch (IOException e)
+          {
+            // Underlying channel probably blown up. Just ignore.
+          }
+          finally
+          {
+            connFactory.releaseASN1Writer(asn1Writer);
+          }
+        }
+
+        future.handleErrorResult(reason);
+      }
+      pendingRequests.clear();
+
+      // Now try cleanly closing the connection if possible.
+      try
+      {
+        ASN1StreamWriter asn1Writer = connFactory
+            .getASN1Writer(streamWriter);
+        if (unbindRequest == null)
+        {
+          unbindRequest = Requests.newUnbindRequest();
+        }
+
+        try
+        {
+          LDAPEncoder.encodeUnbindRequest(asn1Writer, nextMsgID
+              .getAndIncrement(), unbindRequest);
+          asn1Writer.flush();
+        }
+        finally
+        {
+          connFactory.releaseASN1Writer(asn1Writer);
+        }
+      }
+      catch (IOException e)
+      {
+        // Underlying channel prob blown up. Just ignore.
+      }
+
+      try
+      {
+        streamWriter.close();
+      }
+      catch (IOException e)
+      {
+        // Ignore.
+      }
+
+      try
+      {
+        connection.close();
+      }
+      catch (IOException e)
+      {
+        // Ignore.
+      }
+
+      // Mark the connection as invalid.
+      connectionInvalidReason = reason;
+
+      // Notify listeners.
+      if (notifyClose)
+      {
+        // TODO: uncomment if close notification is required.
+        // for (ConnectionEventListener listener : listeners)
+        // {
+        // listener.connectionClosed(this);
+        // }
+      }
+
+      if (notifyErrorOccurred)
+      {
+        for (ConnectionEventListener listener : listeners)
+        {
+          listener.connectionErrorOccurred(false, ErrorResultException
+              .wrap(reason));
+        }
+      }
+    }
+  }
+
+
+
+  private void connectionErrorOccurred(Result reason)
+  {
+    close(null, false, reason);
+  }
+
+
+
+  // TODO uncomment if we decide these methods are useful.
+  // /**
+  // * {@inheritDoc}
+  // */
+  // public boolean isClosed()
+  // {
+  // synchronized (writeLock)
+  // {
+  // return isClosed;
+  // }
+  // }
+  //
+  //
+  //
+  // /**
+  // * {@inheritDoc}
+  // */
+  // public boolean isValid() throws InterruptedException
+  // {
+  // synchronized (writeLock)
+  // {
+  // return connectionInvalidReason == null;
+  // }
+  // }
+  //
+  //
+  //
+  // /**
+  // * {@inheritDoc}
+  // */
+  // public boolean isValid(long timeout, TimeUnit unit)
+  // throws InterruptedException, TimeoutException
+  // {
+  // // FIXME: no support for timeout.
+  // return isValid();
+  // }
+
+  private StreamWriter getFilterChainStreamWriter()
+  {
+    StreamWriter writer = connection.getStreamWriter();
+    FilterChain currentFilterChain = (FilterChain) connection
+        .getProcessor();
+    for (Filter filter : currentFilterChain)
+    {
+      if (filter instanceof StreamTransformerFilter)
+      {
+        writer = ((StreamTransformerFilter) filter)
+            .getStreamWriter(writer);
+      }
+    }
+
+    return writer;
+  }
+
+
+
+  // Needed in order to expose type information.
+  private <R extends Result> void handleExtendedResult0(
+      ExtendedResultFutureImpl<R, ?> future,
+      GenericExtendedResult result) throws DecodeException
+  {
+    R decodedResponse = future.decodeResponse(result.getResultCode(),
+        result.getMatchedDN(), result.getDiagnosticMessage(), result
+            .getResponseName(), result.getResponseValue());
+
+    if (future.getRequest() instanceof StartTLSRequest)
+    {
+      if (result.getResultCode() == ResultCode.SUCCESS)
+      {
+        StartTLSRequest request = (StartTLSRequest) future.getRequest();
+        try
+        {
+          startTLS(request.getSSLContext());
+        }
+        catch (ErrorResultException e)
+        {
+          future.handleErrorResult(e.getResult());
+          return;
+        }
+      }
+      pendingBindOrStartTLS = -1;
+    }
+
+    future.handleResult(decodedResponse);
+  }
+
+
+
+  private void handleIncorrectResponse(
+      AbstractResultFutureImpl<?, ?> pendingRequest)
+  {
+    // FIXME: I18N need to have a better error message.
+    Result errorResult = Responses.newResult(ResultCode.CLIENT_SIDE_DECODING_ERROR)
+        .setDiagnosticMessage("LDAP response message did not match request");
+
+    pendingRequest.handleErrorResult(errorResult);
+    connectionErrorOccurred(errorResult);
+  }
+
+
+
+  private void startTLS(SSLContext sslContext)
+      throws ErrorResultException
+  {
+    SSLHandshaker sslHandshaker = connFactory.getSslHandshaker();
+    SSLFilter sslFilter;
+    SSLEngineConfigurator sslEngineConfigurator;
+    if (sslContext == connFactory.getSSLContext())
+    {
+      // Use factory SSL objects since it is the same SSLContext
+      sslFilter = connFactory.getSSlFilter();
+      sslEngineConfigurator = connFactory.getSSlEngineConfigurator();
+    }
+    else
+    {
+      sslEngineConfigurator = new SSLEngineConfigurator(sslContext,
+          true, false, false);
+      sslFilter = new SSLFilter(sslEngineConfigurator, sslHandshaker);
+    }
+    installFilter(sslFilter);
+
+    performSSLHandshake(sslHandshaker, sslEngineConfigurator);
+  }
+
+
+
+  void performSSLHandshake(SSLHandshaker sslHandshaker,
+      SSLEngineConfigurator sslEngineConfigurator)
+      throws ErrorResultException
+  {
+    SSLStreamReader reader = new SSLStreamReader(connection
+        .getStreamReader());
+    SSLStreamWriter writer = new SSLStreamWriter(connection
+        .getStreamWriter());
+
+    try
+    {
+      sslHandshaker.handshake(reader, writer, sslEngineConfigurator)
+          .get();
+    }
+    catch (Exception e)
+    {
+      Result result = adaptException(e);
+      connectionErrorOccurred(result);
+      throw ErrorResultException.wrap(result);
+    }
+  }
+
+
+
+  synchronized void installFilter(Filter filter)
+  {
+    if (customFilterChain == null)
+    {
+      customFilterChain = connFactory.getDefaultFilterChainFactory()
+          .create();
+      connection.setProcessor(customFilterChain);
+    }
+
+    // Install the SSLFilter in the custom filter chain
+    Filter oldFilter = customFilterChain.remove(customFilterChain
+        .size() - 1);
+    customFilterChain.add(filter);
+    customFilterChain.add(oldFilter);
+
+    // Update stream writer
+    streamWriter = getFilterChainStreamWriter();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java
new file mode 100644
index 0000000..db01881
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactory.java
@@ -0,0 +1,129 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.*;
+
+
+
+/**
+ * LDAP connection factory implementation.
+ */
+public final class LDAPConnectionFactory implements
+    ConnectionFactory<AsynchronousConnection>
+{
+  // We implement the factory using the pimpl idiom in order have
+  // cleaner Javadoc which does not expose implementation methods from
+  // AbstractConnectionFactory.
+
+  private final LDAPConnectionFactoryImpl impl;
+
+
+
+  /**
+   * Creates a plain LDAP connection to the Directory Server at the
+   * specified host and port address.
+   *
+   * @param host
+   *          The host name.
+   * @param port
+   *          The port number.
+   * @return A connection to the Directory Server at the specified host
+   *         and port address.
+   * @throws ErrorResultException
+   *           If the connection request failed for some reason.
+   * @throws NullPointerException
+   *           If {@code host} was {@code null}.
+   */
+  public static Connection connect(String host, int port)
+      throws ErrorResultException, NullPointerException
+  {
+    return new LDAPConnectionFactory(host, port).getConnection();
+  }
+
+
+
+  /**
+   * Creates a new LDAP connection factory which can be used to create
+   * LDAP connections to the Directory Server at the provided host and
+   * port address using default connection options.
+   *
+   * @param host
+   *          The host name.
+   * @param port
+   *          The port number.
+   * @throws NullPointerException
+   *           If {@code host} was {@code null}.
+   */
+  public LDAPConnectionFactory(String host, int port)
+      throws NullPointerException
+  {
+    this(host, port, LDAPConnectionOptions.defaultOptions());
+  }
+
+
+
+  /**
+   * Creates a new LDAP connection factory which can be used to create
+   * LDAP connections to the Directory Server at the provided host and
+   * port address using provided connection options.
+   *
+   * @param host
+   *          The host name.
+   * @param port
+   *          The port number.
+   * @param options
+   *          The LDAP connection options to use when creating
+   *          connections.
+   * @throws NullPointerException
+   *           If {@code host} or {@code options} was {@code null}.
+   */
+  public LDAPConnectionFactory(String host, int port,
+      LDAPConnectionOptions options) throws NullPointerException
+  {
+    this.impl = new LDAPConnectionFactoryImpl(host, port, options);
+  }
+
+
+
+  public <P> ConnectionFuture<AsynchronousConnection> getAsynchronousConnection(
+      ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+      P p)
+  {
+    return impl.getAsynchronousConnection(handler, p);
+  }
+
+
+
+  public Connection getConnection() throws ErrorResultException
+  {
+    return impl.getConnection();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java
new file mode 100644
index 0000000..bdb51f9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnectionFactoryImpl.java
@@ -0,0 +1,636 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.*;
+
+import javax.net.ssl.SSLContext;
+
+import org.opends.sdk.*;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.extensions.StartTLSRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Validator;
+
+import com.sun.grizzly.TransportFactory;
+import com.sun.grizzly.attributes.Attribute;
+import com.sun.grizzly.filterchain.PatternFilterChainFactory;
+import com.sun.grizzly.nio.transport.TCPNIOTransport;
+import com.sun.grizzly.ssl.BlockingSSLHandshaker;
+import com.sun.grizzly.ssl.SSLEngineConfigurator;
+import com.sun.grizzly.ssl.SSLFilter;
+import com.sun.grizzly.ssl.SSLHandshaker;
+import com.sun.grizzly.streams.StreamWriter;
+
+
+
+/**
+ * LDAP connection factory implementation.
+ */
+final class LDAPConnectionFactoryImpl extends
+    AbstractConnectionFactory<AsynchronousConnection> implements
+    ConnectionFactory<AsynchronousConnection>
+{
+  private final class LDAPTransport extends AbstractLDAPTransport
+  {
+
+    @Override
+    LDAPMessageHandler getMessageHandler(
+        com.sun.grizzly.Connection<?> connection)
+    {
+      return ldapConnectionAttr.get(connection).getLDAPMessageHandler();
+    }
+
+
+
+    @Override
+    void removeMessageHandler(com.sun.grizzly.Connection<?> connection)
+    {
+      ldapConnectionAttr.remove(connection);
+    }
+
+  }
+
+
+
+  private static class FailedImpl implements
+      ConnectionFuture<AsynchronousConnection>
+  {
+    private volatile ErrorResultException exception;
+
+
+
+    private FailedImpl(ErrorResultException exception)
+    {
+      this.exception = exception;
+    }
+
+
+
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+      return false;
+    }
+
+
+
+    public AsynchronousConnection get() throws InterruptedException,
+        ErrorResultException
+    {
+      throw exception;
+    }
+
+
+
+    public AsynchronousConnection get(long timeout, TimeUnit unit)
+        throws InterruptedException, TimeoutException,
+        ErrorResultException
+    {
+      throw exception;
+    }
+
+
+
+    public boolean isCancelled()
+    {
+      return false;
+    }
+
+
+
+    public boolean isDone()
+    {
+      return false;
+    }
+  }
+
+
+
+  private class ConnectionFutureImpl<P> implements
+      ConnectionFuture<AsynchronousConnection>,
+      com.sun.grizzly.CompletionHandler<com.sun.grizzly.Connection>,
+      ResultHandler<Result, Void>
+  {
+    private volatile AsynchronousConnection connection;
+
+    private volatile ErrorResultException exception;
+
+    private volatile Future<com.sun.grizzly.Connection> connectFuture;
+
+    private volatile ResultFuture<?> sslFuture;
+
+    private final CountDownLatch latch = new CountDownLatch(1);
+
+    private final ConnectionResultHandler<? super AsynchronousConnection, P> handler;
+
+    private boolean cancelled;
+
+    private final P p;
+
+
+
+    private ConnectionFutureImpl(
+        ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+        P p)
+    {
+      this.handler = handler;
+      this.p = p;
+    }
+
+
+
+    public boolean cancel(boolean mayInterruptIfRunning)
+    {
+      cancelled = connectFuture.cancel(mayInterruptIfRunning)
+          || sslFuture != null
+          && sslFuture.cancel(mayInterruptIfRunning);
+      if (cancelled)
+      {
+        latch.countDown();
+      }
+      return cancelled;
+    }
+
+
+
+    public AsynchronousConnection get() throws InterruptedException,
+        ErrorResultException
+    {
+      latch.await();
+      if (cancelled)
+      {
+        throw new CancellationException();
+      }
+      if (exception != null)
+      {
+        throw exception;
+      }
+      return connection;
+    }
+
+
+
+    public AsynchronousConnection get(long timeout, TimeUnit unit)
+        throws InterruptedException, TimeoutException,
+        ErrorResultException
+    {
+      latch.await(timeout, unit);
+      if (cancelled)
+      {
+        throw new CancellationException();
+      }
+      if (exception != null)
+      {
+        throw exception;
+      }
+      return connection;
+    }
+
+
+
+    public boolean isCancelled()
+    {
+      return cancelled;
+    }
+
+
+
+    public boolean isDone()
+    {
+      return latch.getCount() == 0;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void cancelled(com.sun.grizzly.Connection connection)
+    {
+      // Ignore this.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void completed(com.sun.grizzly.Connection connection,
+        com.sun.grizzly.Connection result)
+    {
+      LDAPConnection ldapConn = adaptConnection(connection);
+      this.connection = adaptConnection(connection);
+
+      if (options.getSSLContext() != null && options.useStartTLS())
+      {
+        StartTLSRequest startTLS = new StartTLSRequest(options
+            .getSSLContext());
+        sslFuture = this.connection.extendedRequest(startTLS, this,
+            null);
+      }
+      else if (options.getSSLContext() != null)
+      {
+        try
+        {
+          ldapConn.installFilter(sslFilter);
+          ldapConn.performSSLHandshake(sslHandshaker,
+              sslEngineConfigurator);
+          latch.countDown();
+          if (handler != null)
+          {
+            handler.handleConnection(p, this.connection);
+          }
+        }
+        catch (CancellationException ce)
+        {
+          // Handshake cancelled.
+          latch.countDown();
+        }
+        catch (ErrorResultException throwable)
+        {
+          exception = throwable;
+          latch.countDown();
+          if (handler != null)
+          {
+            handler.handleConnectionError(p, exception);
+          }
+        }
+      }
+      else
+      {
+        latch.countDown();
+        if (handler != null)
+        {
+          handler.handleConnection(p, this.connection);
+        }
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void failed(com.sun.grizzly.Connection connection,
+        Throwable throwable)
+    {
+      exception = adaptConnectionException(throwable);
+      latch.countDown();
+      if (handler != null)
+      {
+        handler.handleConnectionError(p, exception);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void updated(com.sun.grizzly.Connection connection,
+        com.sun.grizzly.Connection result)
+    {
+      // Ignore this.
+    }
+
+
+
+    // This is called when the StartTLS request is successful
+    public void handleResult(Void v, Result result)
+    {
+      latch.countDown();
+      if (handler != null)
+      {
+        handler.handleConnection(p, connection);
+      }
+    }
+
+
+
+    // This is called when the StartTLS request is not successful
+    public void handleErrorResult(Void v, ErrorResultException error)
+    {
+      exception = error;
+      latch.countDown();
+      if (handler != null)
+      {
+        handler.handleConnectionError(p, exception);
+      }
+    }
+  }
+
+
+
+  private static final String LDAP_CONNECTION_OBJECT_ATTR = "LDAPConnAtr";
+
+  private static TCPNIOTransport TCP_NIO_TRANSPORT = null;
+
+
+
+  // FIXME: Need to figure out how this can be configured without
+  // exposing internal implementation details to application.
+  private static synchronized TCPNIOTransport getTCPNIOTransport()
+  {
+    if (TCP_NIO_TRANSPORT == null)
+    {
+      // Create a default transport using the Grizzly framework.
+      //
+      TCP_NIO_TRANSPORT = TransportFactory.getInstance()
+          .createTCPTransport();
+      try
+      {
+        TCP_NIO_TRANSPORT.start();
+      }
+      catch (IOException e)
+      {
+        throw new RuntimeException(
+            "Unable to create default connection factory provider", e);
+      }
+
+      Runtime.getRuntime().addShutdownHook(new Thread()
+      {
+
+        @Override
+        public void run()
+        {
+          try
+          {
+            TCP_NIO_TRANSPORT.stop();
+          }
+          catch (Exception e)
+          {
+            // Ignore.
+          }
+
+          try
+          {
+            TCP_NIO_TRANSPORT.getWorkerThreadPool().shutdown();
+          }
+          catch (Exception e)
+          {
+            // Ignore.
+          }
+        }
+
+      });
+    }
+    return TCP_NIO_TRANSPORT;
+  }
+
+
+
+  private final Attribute<LDAPConnection> ldapConnectionAttr;
+
+  private final InetSocketAddress socketAddress;
+
+  private final TCPNIOTransport transport;
+
+  private final SSLHandshaker sslHandshaker = new BlockingSSLHandshaker();
+
+  private final SSLEngineConfigurator sslEngineConfigurator;
+
+  private final SSLFilter sslFilter;
+
+  private final Map<String, ControlDecoder<?>> knownControls;
+
+  private final LDAPTransport ldapTransport = new LDAPTransport();
+
+  private final LDAPConnectionOptions options;
+
+
+
+  /**
+   * Creates a new LDAP connection factory implementation which can be
+   * used to create connections to the Directory Server at the provided
+   * host and port address using provided connection options.
+   *
+   * @param host
+   *          The host name.
+   * @param port
+   *          The port number.
+   * @param options
+   *          The LDAP connection options to use when creating
+   *          connections.
+   * @throws NullPointerException
+   *           If {@code host} or {@code options} was {@code null}.
+   */
+  LDAPConnectionFactoryImpl(String host, int port,
+      LDAPConnectionOptions options) throws NullPointerException
+  {
+    this(host, port, options, getTCPNIOTransport());
+  }
+
+
+
+  private LDAPConnectionFactoryImpl(String host, int port,
+      LDAPConnectionOptions options, TCPNIOTransport transport)
+  {
+    Validator.ensureNotNull(host, transport, options);
+
+    this.transport = transport;
+    this.ldapConnectionAttr = transport.getAttributeBuilder()
+        .createAttribute(LDAP_CONNECTION_OBJECT_ATTR);
+    this.socketAddress = new InetSocketAddress(host, port);
+    this.options = LDAPConnectionOptions.copyOf(options);
+    if (this.options.getSSLContext() == null)
+    {
+      this.sslEngineConfigurator = null;
+      this.sslFilter = null;
+    }
+    else
+    {
+      this.sslEngineConfigurator = new SSLEngineConfigurator(
+          this.options.getSSLContext(), true, false, false);
+      this.sslFilter = new SSLFilter(sslEngineConfigurator,
+          sslHandshaker);
+    }
+    this.knownControls = new HashMap<String, ControlDecoder<?>>();
+    initControls();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ConnectionFuture<AsynchronousConnection> getAsynchronousConnection(
+      ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+      P p)
+  {
+    ConnectionFutureImpl<P> future = new ConnectionFutureImpl<P>(
+        handler, p);
+
+    try
+    {
+      future.connectFuture = transport.connect(socketAddress, future);
+      return future;
+    }
+    catch (IOException e)
+    {
+      ErrorResultException result = adaptConnectionException(e);
+      return new FailedImpl(result);
+    }
+  }
+
+
+
+  ExecutorService getHandlerInvokers()
+  {
+    // TODO: Threading strategies?
+    return null;
+  }
+
+
+
+  SSLHandshaker getSslHandshaker()
+  {
+    return sslHandshaker;
+  }
+
+
+
+  SSLFilter getSSlFilter()
+  {
+    return sslFilter;
+  }
+
+
+
+  SSLContext getSSLContext()
+  {
+    return options.getSSLContext();
+  }
+
+
+
+  SSLEngineConfigurator getSSlEngineConfigurator()
+  {
+    return sslEngineConfigurator;
+  }
+
+
+
+  ASN1StreamWriter getASN1Writer(StreamWriter streamWriter)
+  {
+    return ldapTransport.getASN1Writer(streamWriter);
+  }
+
+
+
+  void releaseASN1Writer(ASN1StreamWriter asn1Writer)
+  {
+    ldapTransport.releaseASN1Writer(asn1Writer);
+  }
+
+
+
+  PatternFilterChainFactory getDefaultFilterChainFactory()
+  {
+    return ldapTransport.getDefaultFilterChainFactory();
+  }
+
+
+
+  private LDAPConnection adaptConnection(
+      com.sun.grizzly.Connection<?> connection)
+  {
+    // Test shows that its much faster with non block writes but risk
+    // running out of memory if the server is slow.
+    connection.configureBlocking(true);
+    connection.getStreamReader().setBlocking(true);
+    connection.getStreamWriter().setBlocking(true);
+    connection.setProcessor(ldapTransport
+        .getDefaultFilterChainFactory().getFilterChainPattern());
+
+    LDAPConnection ldapConnection = new LDAPConnection(connection,
+        socketAddress, options.getSchema(), this);
+    ldapConnectionAttr.set(connection, ldapConnection);
+    return ldapConnection;
+  }
+
+
+
+  private ErrorResultException adaptConnectionException(Throwable t)
+  {
+    if (t instanceof ExecutionException)
+    {
+      t = t.getCause();
+    }
+
+    Result result = Responses.newResult(ResultCode.CLIENT_SIDE_CONNECT_ERROR).setCause(t)
+        .setDiagnosticMessage(t.getMessage());
+    return ErrorResultException.wrap(result);
+  }
+
+
+
+  ControlDecoder<?> getControlDecoder(String oid)
+  {
+    return knownControls.get(oid);
+  }
+
+
+
+  private void initControls()
+  {
+    knownControls.put(
+        AccountUsabilityControl.OID_ACCOUNT_USABLE_CONTROL,
+        AccountUsabilityControl.RESPONSE_DECODER);
+    knownControls.put(
+        AuthorizationIdentityControl.OID_AUTHZID_RESPONSE,
+        AuthorizationIdentityControl.RESPONSE_DECODER);
+    knownControls.put(
+        EntryChangeNotificationControl.OID_ENTRY_CHANGE_NOTIFICATION,
+        EntryChangeNotificationControl.DECODER);
+    knownControls.put(PagedResultsControl.OID_PAGED_RESULTS_CONTROL,
+        PagedResultsControl.DECODER);
+    knownControls.put(PasswordExpiredControl.OID_NS_PASSWORD_EXPIRED,
+        PasswordExpiredControl.DECODER);
+    knownControls.put(PasswordExpiringControl.OID_NS_PASSWORD_EXPIRING,
+        PasswordExpiringControl.DECODER);
+    knownControls.put(
+        PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL,
+        PasswordPolicyControl.RESPONSE_DECODER);
+    knownControls.put(PostReadControl.OID_LDAP_READENTRY_POSTREAD,
+        PostReadControl.RESPONSE_DECODER);
+    knownControls.put(PreReadControl.OID_LDAP_READENTRY_PREREAD,
+        PreReadControl.RESPONSE_DECODER);
+    knownControls.put(
+        ServerSideSortControl.OID_SERVER_SIDE_SORT_RESPONSE_CONTROL,
+        ServerSideSortControl.RESPONSE_DECODER);
+    knownControls.put(VLVControl.OID_VLV_RESPONSE_CONTROL,
+        VLVControl.RESPONSE_DECODER);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java b/sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java
new file mode 100644
index 0000000..9104b55
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConnectionOptions.java
@@ -0,0 +1,210 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import javax.net.ssl.SSLContext;
+
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Common connection options for LDAP connections.
+ */
+public final class LDAPConnectionOptions
+{
+  private Schema schema = Schema.getDefaultSchema();
+
+  private SSLContext sslContext = null;
+
+  private boolean useStartTLS = false;
+
+
+
+  /**
+   * Creates a copy of the provided connection options.
+   *
+   * @param options
+   *          The options to be copied.
+   * @return The copy of the provided connection options.
+   */
+  public static LDAPConnectionOptions copyOf(
+      LDAPConnectionOptions options)
+  {
+    return defaultOptions().assign(options);
+  }
+
+
+
+  /**
+   * Creates a new set of connection options with default settings. SSL
+   * will not be enabled, nor will key or trust managers be defined.
+   *
+   * @return The new connection options.
+   */
+  public static LDAPConnectionOptions defaultOptions()
+  {
+    return new LDAPConnectionOptions();
+  }
+
+
+
+  // Prevent direct instantiation.
+  private LDAPConnectionOptions()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * Returns the schema which will be used to decode responses from the
+   * server. By default the schema returned by
+   * {@link Schema#getDefaultSchema()} will be used.
+   *
+   * @return The schema which will be used to decode responses from the
+   *         server.
+   */
+  public Schema getSchema()
+  {
+    return schema;
+  }
+
+
+
+  /**
+   * Sets the schema which will be used to decode responses from the
+   * server. By default the schema returned by
+   * {@link Schema#getDefaultSchema()} will be used.
+   *
+   * @param schema
+   *          The schema which will be used to decode responses from the
+   *          server.
+   * @return A reference to this LDAP connection options.
+   * @throws NullPointerException
+   *           If {@code schema} was {@code null}.
+   */
+  public LDAPConnectionOptions setSchema(Schema schema)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(schema);
+    this.schema = schema;
+    return this;
+  }
+
+
+
+  /**
+   * Returns the SSL context which will be used when initiating
+   * connections with the Directory Server. By default no SSL context
+   * will be used, indicating that connections will not be secured. If a
+   * non-{@code null} SSL context is returned then connections will be
+   * secured using either SSL or StartTLS depending on
+   * {@link #useStartTLS()}.
+   *
+   * @return The SSL context which will be used when initiating secure
+   *         connections with the Directory Server, which may be {@code
+   *         null} indicating that connections will not be secured.
+   */
+  public SSLContext getSSLContext()
+  {
+    return sslContext;
+  }
+
+
+
+  /**
+   * Sets the SSL context which will be used when initiating connections
+   * with the Directory Server. By default no SSL context will be used,
+   * indicating that connections will not be secured. If a non-{@code
+   * null} SSL context is returned then connections will be secured
+   * using either SSL or StartTLS depending on {@link #useStartTLS()}.
+   *
+   * @param sslContext
+   *          The SSL context which will be used when initiating secure
+   *          connections with the Directory Server, which may be
+   *          {@code null} indicating that connections will not be
+   *          secured.
+   * @return A reference to this LDAP connection options.
+   */
+  public LDAPConnectionOptions setSSLContext(SSLContext sslContext)
+  {
+    this.sslContext = sslContext;
+    return this;
+  }
+
+
+
+  /**
+   * Indicates whether or not SSL or StartTLS should be used for
+   * securing connections when an SSL context is specified. By default
+   * SSL will be used in preference to StartTLS.
+   *
+   * @return {@code true} if StartTLS should be used for securing
+   *         connections when an SSL context is specified, otherwise
+   *         {@code false} indicating that SSL should be used.
+   */
+  public boolean useStartTLS()
+  {
+    return useStartTLS;
+  }
+
+
+
+  /**
+   * Specifies whether or not SSL or StartTLS should be used for
+   * securing connections when an SSL context is specified. By default
+   * SSL will be used in preference to StartTLS.
+   *
+   * @param useStartTLS
+   *          {@code true} if StartTLS should be used for securing
+   *          connections when an SSL context is specified, otherwise
+   *          {@code false} indicating that SSL should be used.
+   * @return A reference to this LDAP connection options.
+   */
+  public LDAPConnectionOptions setUseStartTLS(boolean useStartTLS)
+  {
+    this.useStartTLS = useStartTLS;
+    return this;
+  }
+
+
+
+  // Assigns the provided options to this set of options.
+  LDAPConnectionOptions assign(LDAPConnectionOptions options)
+  {
+    this.schema = options.schema;
+    this.sslContext = options.sslContext;
+    this.useStartTLS = options.useStartTLS;
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPConstants.java b/sdk/src/org/opends/sdk/ldap/LDAPConstants.java
new file mode 100644
index 0000000..f0c4e71
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPConstants.java
@@ -0,0 +1,332 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.ldap;
+
+/**
+ * This class defines a number of constants used in the LDAP protocol.
+ */
+final class LDAPConstants
+{
+  /**
+   * The protocol op type for bind requests.
+   */
+  static final byte OP_TYPE_BIND_REQUEST = 0x60;
+
+  /**
+   * The protocol op type for bind responses.
+   */
+  static final byte OP_TYPE_BIND_RESPONSE = 0x61;
+
+  /**
+   * The protocol op type for unbind requests.
+   */
+  static final byte OP_TYPE_UNBIND_REQUEST = 0x42;
+
+  /**
+   * The protocol op type for search requests.
+   */
+  static final byte OP_TYPE_SEARCH_REQUEST = 0x63;
+
+  /**
+   * The protocol op type for search result entries.
+   */
+  static final byte OP_TYPE_SEARCH_RESULT_ENTRY = 0x64;
+
+  /**
+   * The protocol op type for search result references.
+   */
+  static final byte OP_TYPE_SEARCH_RESULT_REFERENCE = 0x73;
+
+  /**
+   * The protocol op type for search result done elements.
+   */
+  static final byte OP_TYPE_SEARCH_RESULT_DONE = 0x65;
+
+  /**
+   * The protocol op type for modify requests.
+   */
+  static final byte OP_TYPE_MODIFY_REQUEST = 0x66;
+
+  /**
+   * The protocol op type for modify responses.
+   */
+  static final byte OP_TYPE_MODIFY_RESPONSE = 0x67;
+
+  /**
+   * The protocol op type for add requests.
+   */
+  static final byte OP_TYPE_ADD_REQUEST = 0x68;
+
+  /**
+   * The protocol op type for add responses.
+   */
+  static final byte OP_TYPE_ADD_RESPONSE = 0x69;
+
+  /**
+   * The protocol op type for delete requests.
+   */
+  static final byte OP_TYPE_DELETE_REQUEST = 0x4A;
+
+  /**
+   * The protocol op type for delete responses.
+   */
+  static final byte OP_TYPE_DELETE_RESPONSE = 0x6B;
+
+  /**
+   * The protocol op type for modify DN requests.
+   */
+  static final byte OP_TYPE_MODIFY_DN_REQUEST = 0x6C;
+
+  /**
+   * The protocol op type for modify DN responses.
+   */
+  static final byte OP_TYPE_MODIFY_DN_RESPONSE = 0x6D;
+
+  /**
+   * The protocol op type for compare requests.
+   */
+  static final byte OP_TYPE_COMPARE_REQUEST = 0x6E;
+
+  /**
+   * The protocol op type for compare responses.
+   */
+  static final byte OP_TYPE_COMPARE_RESPONSE = 0x6F;
+
+  /**
+   * The protocol op type for abandon requests.
+   */
+  static final byte OP_TYPE_ABANDON_REQUEST = 0x50;
+
+  /**
+   * The protocol op type for extended requests.
+   */
+  static final byte OP_TYPE_EXTENDED_REQUEST = 0x77;
+
+  /**
+   * The protocol op type for extended responses.
+   */
+  static final byte OP_TYPE_EXTENDED_RESPONSE = 0x78;
+
+  /**
+   * The protocol op type for intermediate responses.
+   */
+  static final byte OP_TYPE_INTERMEDIATE_RESPONSE = 0x79;
+
+  /**
+   * The BER type to use for encoding the sequence of controls in an
+   * LDAP message.
+   */
+  static final byte TYPE_CONTROL_SEQUENCE = (byte) 0xA0;
+
+  /**
+   * The BER type to use for encoding the sequence of referral URLs in
+   * an LDAPResult element.
+   */
+  static final byte TYPE_REFERRAL_SEQUENCE = (byte) 0xA3;
+
+  /**
+   * The BER type to use for the AuthenticationChoice element in a bind
+   * request when simple authentication is to be used.
+   */
+  static final byte TYPE_AUTHENTICATION_SIMPLE = (byte) 0x80;
+
+  /**
+   * The BER type to use for the AuthenticationChoice element in a bind
+   * request when SASL authentication is to be used.
+   */
+  static final byte TYPE_AUTHENTICATION_SASL = (byte) 0xA3;
+
+  /**
+   * The BER type to use for the server SASL credentials in a bind
+   * response.
+   */
+  static final byte TYPE_SERVER_SASL_CREDENTIALS = (byte) 0x87;
+
+  /**
+   * The BER type to use for AND filter components.
+   */
+  static final byte TYPE_FILTER_AND = (byte) 0xA0;
+
+  /**
+   * The BER type to use for OR filter components.
+   */
+  static final byte TYPE_FILTER_OR = (byte) 0xA1;
+
+  /**
+   * The BER type to use for NOT filter components.
+   */
+  static final byte TYPE_FILTER_NOT = (byte) 0xA2;
+
+  /**
+   * The BER type to use for equality filter components.
+   */
+  static final byte TYPE_FILTER_EQUALITY = (byte) 0xA3;
+
+  /**
+   * The BER type to use for substring filter components.
+   */
+  static final byte TYPE_FILTER_SUBSTRING = (byte) 0xA4;
+
+  /**
+   * The BER type to use for greater than or equal to filter components.
+   */
+  static final byte TYPE_FILTER_GREATER_OR_EQUAL = (byte) 0xA5;
+
+  /**
+   * The BER type to use for less than or equal to filter components.
+   */
+  static final byte TYPE_FILTER_LESS_OR_EQUAL = (byte) 0xA6;
+
+  /**
+   * The BER type to use for presence filter components.
+   */
+  static final byte TYPE_FILTER_PRESENCE = (byte) 0x87;
+
+  /**
+   * The BER type to use for approximate filter components.
+   */
+  static final byte TYPE_FILTER_APPROXIMATE = (byte) 0xA8;
+
+  /**
+   * The BER type to use for extensible matching filter components.
+   */
+  static final byte TYPE_FILTER_EXTENSIBLE_MATCH = (byte) 0xA9;
+
+  /**
+   * The BER type to use for the subInitial component of a substring
+   * filter.
+   */
+  static final byte TYPE_SUBINITIAL = (byte) 0x80;
+
+  /**
+   * The BER type to use for the subAny component(s) of a substring
+   * filter.
+   */
+  static final byte TYPE_SUBANY = (byte) 0x81;
+
+  /**
+   * The BER type to use for the subFinal components of a substring
+   * filter.
+   */
+  static final byte TYPE_SUBFINAL = (byte) 0x82;
+
+  /**
+   * The BER type to use for the matching rule OID in a matching rule
+   * assertion.
+   */
+  static final byte TYPE_MATCHING_RULE_ID = (byte) 0x81;
+
+  /**
+   * The BER type to use for the attribute type in a matching rule
+   * assertion.
+   */
+  static final byte TYPE_MATCHING_RULE_TYPE = (byte) 0x82;
+
+  /**
+   * The BER type to use for the assertion value in a matching rule
+   * assertion.
+   */
+  static final byte TYPE_MATCHING_RULE_VALUE = (byte) 0x83;
+
+  /**
+   * The BER type to use for the DN attributes flag in a matching rule
+   * assertion.
+   */
+  static final byte TYPE_MATCHING_RULE_DN_ATTRIBUTES = (byte) 0x84;
+
+  /**
+   * The BER type to use for the newSuperior component of a modify DN
+   * request.
+   */
+  static final byte TYPE_MODIFY_DN_NEW_SUPERIOR = (byte) 0x80;
+
+  /**
+   * The BER type to use for the OID of an extended request.
+   */
+  static final byte TYPE_EXTENDED_REQUEST_OID = (byte) 0x80;
+
+  /**
+   * The BER type to use for the value of an extended request.
+   */
+  static final byte TYPE_EXTENDED_REQUEST_VALUE = (byte) 0x81;
+
+  /**
+   * The BER type to use for the OID of an extended response.
+   */
+  static final byte TYPE_EXTENDED_RESPONSE_OID = (byte) 0x8A;
+
+  /**
+   * The BER type to use for the value of an extended response.
+   */
+  static final byte TYPE_EXTENDED_RESPONSE_VALUE = (byte) 0x8B;
+
+  /**
+   * The BER type to use for the OID of an intermediate response
+   * message.
+   */
+  static final byte TYPE_INTERMEDIATE_RESPONSE_OID = (byte) 0x80;
+
+  /**
+   * The BER type to use for the value of an intermediate response
+   * message.
+   */
+  static final byte TYPE_INTERMEDIATE_RESPONSE_VALUE = (byte) 0x81;
+
+  /**
+   * The OID for the Kerberos V GSSAPI mechanism.
+   */
+  static final String OID_GSSAPI_KERBEROS_V = "1.2.840.113554.1.2.2";
+
+  /**
+   * The OID for the LDAP notice of disconnection extended operation.
+   */
+  static final String OID_NOTICE_OF_DISCONNECTION = "1.3.6.1.4.1.1466.20036";
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be the BER type for a new element.
+   */
+  static final int ELEMENT_READ_STATE_NEED_TYPE = 0;
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be the first byte for the element length.
+   */
+  static final int ELEMENT_READ_STATE_NEED_FIRST_LENGTH_BYTE = 1;
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be additional bytes of a multi-byte length.
+   */
+  static final int ELEMENT_READ_STATE_NEED_ADDITIONAL_LENGTH_BYTES = 2;
+
+  /**
+   * The ASN.1 element decoding state that indicates that the next byte
+   * read should be applied to the value of the element.
+   */
+  static final int ELEMENT_READ_STATE_NEED_VALUE_BYTES = 3;
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPDecoder.java b/sdk/src/org/opends/sdk/ldap/LDAPDecoder.java
new file mode 100644
index 0000000..3b658b3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPDecoder.java
@@ -0,0 +1,1922 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.messages.ProtocolMessages.*;
+import static org.opends.sdk.asn1.ASN1Constants.*;
+import static org.opends.sdk.ldap.LDAPConstants.*;
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+import org.opends.sdk.*;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.GenericSASLBindRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Static methods for decoding LDAP messages.
+ */
+class LDAPDecoder
+{
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * message.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle a
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  static void decode(ASN1Reader reader, LDAPMessageHandler handler)
+      throws IOException
+  {
+    reader.readStartSequence();
+    try
+    {
+      int messageID = (int) reader.readInteger();
+      decodeProtocolOp(reader, messageID, handler);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+  }
+
+
+
+  static SearchResultEntry decodeEntry(ASN1Reader reader, Schema schema)
+      throws IOException
+  {
+    SearchResultEntry message;
+
+    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
+    try
+    {
+      String dnString = reader.readOctetStringAsString();
+      DN dn;
+      try
+      {
+        dn = DN.valueOf(dnString, schema);
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        throw DecodeException.error(e.getMessageObject());
+      }
+      message = Responses.newSearchResultEntry(dn);
+
+      reader.readStartSequence();
+      try
+      {
+        while (reader.hasNextElement())
+        {
+          reader.readStartSequence();
+          try
+          {
+            String ads = reader.readOctetStringAsString();
+            AttributeDescription ad;
+            try
+            {
+              ad = AttributeDescription.valueOf(ads, schema);
+            }
+            catch (LocalizedIllegalArgumentException e)
+            {
+              throw DecodeException.error(e.getMessageObject());
+            }
+            Attribute attribute = new LinkedAttribute(ad);
+
+            reader.readStartSet();
+            try
+            {
+              while (reader.hasNextElement())
+              {
+                attribute.add(reader.readOctetString());
+              }
+              message.addAttribute(attribute);
+            }
+            finally
+            {
+              reader.readEndSet();
+            }
+          }
+          finally
+          {
+            reader.readEndSequence();
+          }
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return message;
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 read as an LDAP
+   * abandon request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeAbandonRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    int msgToAbandon = (int) reader
+        .readInteger(OP_TYPE_ABANDON_REQUEST);
+    AbandonRequest message = Requests.newAbandonRequest(msgToAbandon);
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP ABANDON REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleAbandonRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP add
+   * request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeAddRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    AddRequest message;
+    ResolvedSchema resolvedSchema;
+
+    reader.readStartSequence(OP_TYPE_ADD_REQUEST);
+    try
+    {
+      String dnString = reader.readOctetStringAsString();
+      resolvedSchema = handler.resolveSchema(dnString);
+      DN dn = resolvedSchema.getInitialDN();
+      message = Requests.newAddRequest(dn);
+
+      reader.readStartSequence();
+      try
+      {
+        while (reader.hasNextElement())
+        {
+          reader.readStartSequence();
+          try
+          {
+            String ads = reader.readOctetStringAsString();
+            AttributeDescription ad = resolvedSchema
+                .decodeAttributeDescription(ads);
+            Attribute attribute = new LinkedAttribute(ad);
+
+            reader.readStartSet();
+            try
+            {
+              while (reader.hasNextElement())
+              {
+                attribute.add(reader.readOctetString());
+              }
+              message.addAttribute(attribute);
+            }
+            finally
+            {
+              reader.readEndSet();
+            }
+          }
+          finally
+          {
+            reader.readEndSequence();
+          }
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler, resolvedSchema
+        .getSchema());
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP ADD REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleAddRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an add
+   * response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeAddResult(ASN1Reader reader, int messageID,
+      LDAPMessageHandler handler) throws IOException
+  {
+    Result message;
+
+    reader.readStartSequence(OP_TYPE_ADD_RESPONSE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID,
+          message));
+    }
+
+    handler.handleAddResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 read as an LDAP bind
+   * request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeBindRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    reader.readStartSequence(OP_TYPE_BIND_REQUEST);
+    try
+    {
+      int protocolVersion = (int) reader.readInteger();
+
+      String dnString = reader.readOctetStringAsString();
+      ResolvedSchema resolvedSchema = handler.resolveSchema(dnString);
+      DN dn = resolvedSchema.getInitialDN();
+
+      byte type = reader.peekType();
+
+      switch (type)
+      {
+      case TYPE_AUTHENTICATION_SIMPLE:
+        ByteString simplePassword = reader
+            .readOctetString(TYPE_AUTHENTICATION_SIMPLE);
+
+        SimpleBindRequest simpleBindMessage = Requests.newSimpleBindRequest(dn, simplePassword);
+
+        decodeControls(reader, simpleBindMessage, messageID, handler,
+            resolvedSchema.getSchema());
+
+        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+        {
+          StaticUtils.DEBUG_LOG
+              .finer(String
+                  .format(
+                      "DECODE LDAP BIND REQUEST(messageID=%d, auth=simple, request=%s)",
+                      messageID, simpleBindMessage));
+        }
+
+        handler.handleBindRequest(messageID, protocolVersion,
+            simpleBindMessage);
+        break;
+      case TYPE_AUTHENTICATION_SASL:
+        String saslMechanism;
+        ByteString saslCredentials;
+
+        reader.readStartSequence(TYPE_AUTHENTICATION_SASL);
+        try
+        {
+          saslMechanism = reader.readOctetStringAsString();
+          if (reader.hasNextElement()
+              && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+          {
+            saslCredentials = reader.readOctetString();
+          }
+          else
+          {
+            saslCredentials = ByteString.empty();
+          }
+        }
+        finally
+        {
+          reader.readEndSequence();
+        }
+
+        GenericSASLBindRequest rawSASLBindMessage = new GenericSASLBindRequest(
+            saslMechanism, saslCredentials);
+
+        // TODO: we can ignore the bind DN for SASL bind requests
+        // according to the RFC.
+        //
+        // rawSASLBindMessage.setName(dn);
+
+        decodeControls(reader, rawSASLBindMessage, messageID, handler,
+            resolvedSchema.getSchema());
+
+        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+        {
+          StaticUtils.DEBUG_LOG
+              .finer(String
+                  .format(
+                      "DECODE LDAP BIND REQUEST(messageID=%d, auth=SASL, request=%s)",
+                      messageID, rawSASLBindMessage));
+        }
+
+        handler.handleBindRequest(messageID, protocolVersion,
+            rawSASLBindMessage);
+        break;
+      default:
+        ByteString unknownAuthBytes = reader.readOctetString(type);
+
+        GenericBindRequest rawUnknownBindMessage = Requests.newGenericBindRequest(dn, type, unknownAuthBytes);
+
+        decodeControls(reader, rawUnknownBindMessage, messageID,
+            handler, resolvedSchema.getSchema());
+
+        if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+        {
+          StaticUtils.DEBUG_LOG
+              .finer(String
+                  .format(
+                      "DECODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)",
+                      messageID, rawUnknownBindMessage
+                          .getAuthenticationType(),
+                      rawUnknownBindMessage));
+        }
+
+        handler.handleBindRequest(messageID, protocolVersion,
+            rawUnknownBindMessage);
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a bind
+   * response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeBindResult(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    BindResult message;
+
+    reader.readStartSequence(OP_TYPE_BIND_RESPONSE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newBindResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_SERVER_SASL_CREDENTIALS))
+      {
+        message.setServerSASLCredentials(reader
+            .readOctetString(TYPE_SERVER_SASL_CREDENTIALS));
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP BIND RESULT(messageID=%d, result=%s)",
+          messageID, message));
+    }
+
+    handler.handleBindResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * compare request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeCompareRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    DN dn;
+    AttributeDescription ad;
+    ByteString assertionValue;
+    ResolvedSchema resolvedSchema;
+
+    reader.readStartSequence(OP_TYPE_COMPARE_REQUEST);
+    try
+    {
+      String dnString = reader.readOctetStringAsString();
+      resolvedSchema = handler.resolveSchema(dnString);
+      dn = resolvedSchema.getInitialDN();
+
+      reader.readStartSequence();
+      try
+      {
+        String ads = reader.readOctetStringAsString();
+        ad = resolvedSchema.decodeAttributeDescription(ads);
+        assertionValue = reader.readOctetString();
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    CompareRequest message = Requests.newCompareRequest(dn, ad, assertionValue);
+    decodeControls(reader, message, messageID, handler, resolvedSchema
+        .getSchema());
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP COMPARE REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleCompareRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a compare
+   * response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeCompareResult(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    CompareResult message;
+
+    reader.readStartSequence(OP_TYPE_COMPARE_RESPONSE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newCompareResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP COMPARE RESULT(messageID=%d, result=%s)",
+          messageID, message));
+    }
+
+    handler.handleCompareResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * control.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param request
+   *          The decoded request to decode controls for.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will decode the
+   *          control.
+   * @param schema
+   *          The schema to use when decoding control.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeControl(ASN1Reader reader, Request request,
+      int messageID, LDAPMessageHandler handler, Schema schema)
+      throws IOException
+  {
+    String oid;
+    boolean isCritical;
+    ByteString value;
+
+    reader.readStartSequence();
+    try
+    {
+      oid = reader.readOctetStringAsString();
+      isCritical = false;
+      value = null;
+      if (reader.hasNextElement()
+          && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE))
+      {
+        isCritical = reader.readBoolean();
+      }
+      if (reader.hasNextElement()
+          && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+      {
+        value = reader.readOctetString();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    try
+    {
+      Control c = handler.decodeRequestControl(messageID, oid,
+          isCritical, value, schema);
+      request.addControl(c);
+    }
+    catch (DecodeException e)
+    {
+      if (isCritical)
+      {
+        if (e.isFatal())
+        {
+          throw DecodeException.error(e.getMessageObject(), e
+              .getCause());
+        }
+        else
+        {
+          // Exceptions encountered when decoding controls are never
+          // fatal.
+          throw e;
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * control.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param response
+   *          The decoded message to decode controls for.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will decode the
+   *          control.
+   * @param schema
+   *          The schema to use when decoding control.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeControl(ASN1Reader reader,
+      Response response, int messageID, LDAPMessageHandler handler,
+      Schema schema) throws IOException
+  {
+    String oid;
+    boolean isCritical;
+    ByteString value;
+
+    reader.readStartSequence();
+    try
+    {
+      oid = reader.readOctetStringAsString();
+      isCritical = false;
+      value = null;
+      if (reader.hasNextElement()
+          && (reader.peekType() == UNIVERSAL_BOOLEAN_TYPE))
+      {
+        isCritical = reader.readBoolean();
+      }
+      if (reader.hasNextElement()
+          && (reader.peekType() == UNIVERSAL_OCTET_STRING_TYPE))
+      {
+        value = reader.readOctetString();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    try
+    {
+      Control c = handler.decodeResponseControl(messageID, oid,
+          isCritical, value, schema);
+      response.addControl(c);
+    }
+    catch (DecodeException e)
+    {
+      if (isCritical)
+      {
+        if (e.isFatal())
+        {
+          throw DecodeException.error(e.getMessageObject(), e
+              .getCause());
+        }
+        else
+        {
+          // Exceptions encountered when decoding controls are never
+          // fatal.
+          throw e;
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a set of
+   * controls using the default schema.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param request
+   *          The decoded message to decode controls for.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will decode the
+   *          controls.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeControls(ASN1Reader reader,
+      Request request, int messageID, LDAPMessageHandler handler)
+      throws IOException
+  {
+    decodeControls(reader, request, messageID, handler, handler
+        .getDefaultSchema());
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a set of
+   * controls.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param request
+   *          The decoded message to decode controls for.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will decode the
+   *          controls.
+   * @param schema
+   *          The schema to use when decoding controls.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeControls(ASN1Reader reader,
+      Request request, int messageID, LDAPMessageHandler handler,
+      Schema schema) throws IOException
+  {
+    if (reader.hasNextElement()
+        && (reader.peekType() == TYPE_CONTROL_SEQUENCE))
+    {
+      reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
+      try
+      {
+        while (reader.hasNextElement())
+        {
+          decodeControl(reader, request, messageID, handler, schema);
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a set of
+   * controls using the default schema.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param response
+   *          The decoded message to decode controls for.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will decode the
+   *          controls.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeControls(ASN1Reader reader,
+      Response response, int messageID, LDAPMessageHandler handler)
+      throws IOException
+  {
+    decodeControls(reader, response, messageID, handler, handler
+        .getDefaultSchema());
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a set of
+   * controls.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param response
+   *          The decoded message to decode controls for.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will decode the
+   *          controls.
+   * @param schema
+   *          The schema to use when decoding control.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeControls(ASN1Reader reader,
+      Response response, int messageID, LDAPMessageHandler handler,
+      Schema schema) throws IOException
+  {
+    if (reader.hasNextElement()
+        && (reader.peekType() == TYPE_CONTROL_SEQUENCE))
+    {
+      reader.readStartSequence(TYPE_CONTROL_SEQUENCE);
+      try
+      {
+        while (reader.hasNextElement())
+        {
+          decodeControl(reader, response, messageID, handler, schema);
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * delete request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeDeleteRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    String dnString = reader
+        .readOctetStringAsString(OP_TYPE_DELETE_REQUEST);
+    ResolvedSchema resolvedSchema = handler.resolveSchema(dnString);
+    DN dn = resolvedSchema.getInitialDN();
+    DeleteRequest message = Requests.newDeleteRequest(dn);
+
+    decodeControls(reader, message, messageID, handler, resolvedSchema
+        .getSchema());
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP DELETE REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleDeleteRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a delete
+   * response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeDeleteResult(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    Result message;
+
+    reader.readStartSequence(OP_TYPE_DELETE_RESPONSE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP DELETE RESULT(messageID=%d, result=%s)",
+          messageID, message));
+    }
+
+    handler.handleDeleteResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * extended request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeExtendedRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    String oid;
+    ByteString value;
+
+    reader.readStartSequence(OP_TYPE_EXTENDED_REQUEST);
+    try
+    {
+      oid = reader.readOctetStringAsString(TYPE_EXTENDED_REQUEST_OID);
+      value = null;
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_EXTENDED_REQUEST_VALUE))
+      {
+        value = reader.readOctetString(TYPE_EXTENDED_REQUEST_VALUE);
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    GenericExtendedRequest message = Requests.newGenericExtendedRequest(oid, value);
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleExtendedRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a extended
+   * response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeExtendedResult(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+
+    GenericExtendedResult message;
+
+    reader.readStartSequence(OP_TYPE_EXTENDED_RESPONSE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newGenericExtendedResult(resultCode).setMatchedDN(
+          matchedDN).setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_EXTENDED_RESPONSE_OID))
+      {
+        message.setResponseName(reader
+            .readOctetStringAsString(TYPE_EXTENDED_RESPONSE_OID));
+      }
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_EXTENDED_RESPONSE_VALUE))
+      {
+        message.setResponseValue(reader
+            .readOctetString(TYPE_EXTENDED_RESPONSE_VALUE));
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP EXTENDED RESULT(messageID=%d, result=%s)",
+          messageID, message));
+    }
+
+    handler.handleExtendedResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * intermediate response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeIntermediateResponse(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    GenericIntermediateResponse message;
+
+    reader.readStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
+    try
+    {
+      message = Responses.newGenericIntermediateResponse();
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_OID))
+      {
+        message.setResponseName(reader
+            .readOctetStringAsString(TYPE_INTERMEDIATE_RESPONSE_OID));
+      }
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_INTERMEDIATE_RESPONSE_VALUE))
+      {
+        message.setResponseValue(reader
+            .readOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE));
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG
+          .finer(String
+              .format(
+                  "DECODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)",
+                  messageID, message));
+    }
+
+    handler.handleIntermediateResponse(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a modify DN
+   * request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeModifyDNRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    ModifyDNRequest message;
+    ResolvedSchema resolvedSchema;
+
+    reader.readStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
+    try
+    {
+      String dnString = reader.readOctetStringAsString();
+      resolvedSchema = handler.resolveSchema(dnString);
+      DN dn = resolvedSchema.getInitialDN();
+
+      String newRDNString = reader.readOctetStringAsString();
+      RDN newRDN = resolvedSchema.decodeRDN(newRDNString);
+
+      message = Requests.newModifyDNRequest(dn, newRDN);
+
+      message.setDeleteOldRDN(reader.readBoolean());
+
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_MODIFY_DN_NEW_SUPERIOR))
+      {
+        String newSuperiorString = reader
+            .readOctetStringAsString(TYPE_MODIFY_DN_NEW_SUPERIOR);
+        DN newSuperior = resolvedSchema.decodeDN(newSuperiorString);
+        message.setNewSuperior(newSuperior);
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler, resolvedSchema
+        .getSchema());
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleModifyDNRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a modify DN
+   * response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeModifyDNResult(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    Result message;
+
+    reader.readStartSequence(OP_TYPE_MODIFY_DN_RESPONSE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)",
+          messageID, message));
+    }
+
+    handler.handleModifyDNResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * modify request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeModifyRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    ModifyRequest message;
+    ResolvedSchema resolvedSchema;
+
+    reader.readStartSequence(OP_TYPE_MODIFY_REQUEST);
+    try
+    {
+      String dnString = reader.readOctetStringAsString();
+      resolvedSchema = handler.resolveSchema(dnString);
+      DN dn = resolvedSchema.getInitialDN();
+      message = Requests.newModifyRequest(dn);
+
+      reader.readStartSequence();
+      try
+      {
+        while (reader.hasNextElement())
+        {
+          reader.readStartSequence();
+          try
+          {
+            int typeIntValue = reader.readEnumerated();
+            ModificationType type = ModificationType
+                .valueOf(typeIntValue);
+            if (type == null)
+            {
+              throw DecodeException
+                  .error(ERR_LDAP_MODIFICATION_DECODE_INVALID_MOD_TYPE
+                      .get(typeIntValue));
+            }
+            reader.readStartSequence();
+            try
+            {
+              String attributeDescription = reader
+                  .readOctetStringAsString();
+              Attribute attribute = new LinkedAttribute(resolvedSchema
+                  .decodeAttributeDescription(attributeDescription));
+
+              reader.readStartSet();
+              try
+              {
+                while (reader.hasNextElement())
+                {
+                  attribute.add(reader.readOctetString());
+                }
+                message.addChange(new Change(type, attribute));
+              }
+              finally
+              {
+                reader.readEndSet();
+              }
+            }
+            finally
+            {
+              reader.readEndSequence();
+            }
+          }
+          finally
+          {
+            reader.readEndSequence();
+          }
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler, resolvedSchema
+        .getSchema());
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP MODIFY REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleModifyRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a modify
+   * response protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeModifyResult(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    Result message;
+
+    reader.readStartSequence(OP_TYPE_MODIFY_RESPONSE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP MODIFY RESULT(messageID=%d, result=%s)",
+          messageID, message));
+    }
+
+    handler.handleModifyResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeProtocolOp(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    byte type = reader.peekType();
+
+    switch (type)
+    {
+    case OP_TYPE_UNBIND_REQUEST: // 0x42
+      decodeUnbindRequest(reader, messageID, handler);
+      break;
+    case 0x43: // 0x43
+    case 0x44: // 0x44
+    case 0x45: // 0x45
+    case 0x46: // 0x46
+    case 0x47: // 0x47
+    case 0x48: // 0x48
+    case 0x49: // 0x49
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    case OP_TYPE_DELETE_REQUEST: // 0x4A
+      decodeDeleteRequest(reader, messageID, handler);
+      break;
+    case 0x4B: // 0x4B
+    case 0x4C: // 0x4C
+    case 0x4D: // 0x4D
+    case 0x4E: // 0x4E
+    case 0x4F: // 0x4F
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    case OP_TYPE_ABANDON_REQUEST: // 0x50
+      decodeAbandonRequest(reader, messageID, handler);
+      break;
+    case 0x51: // 0x51
+    case 0x52: // 0x52
+    case 0x53: // 0x53
+    case 0x54: // 0x54
+    case 0x55: // 0x55
+    case 0x56: // 0x56
+    case 0x57: // 0x57
+    case 0x58: // 0x58
+    case 0x59: // 0x59
+    case 0x5A: // 0x5A
+    case 0x5B: // 0x5B
+    case 0x5C: // 0x5C
+    case 0x5D: // 0x5D
+    case 0x5E: // 0x5E
+    case 0x5F: // 0x5F
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    case OP_TYPE_BIND_REQUEST: // 0x60
+      decodeBindRequest(reader, messageID, handler);
+      break;
+    case OP_TYPE_BIND_RESPONSE: // 0x61
+      decodeBindResult(reader, messageID, handler);
+      break;
+    case 0x62: // 0x62
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    case OP_TYPE_SEARCH_REQUEST: // 0x63
+      decodeSearchRequest(reader, messageID, handler);
+      break;
+    case OP_TYPE_SEARCH_RESULT_ENTRY: // 0x64
+      decodeSearchResultEntry(reader, messageID, handler);
+      break;
+    case OP_TYPE_SEARCH_RESULT_DONE: // 0x65
+      decodeSearchResult(reader, messageID, handler);
+      break;
+    case OP_TYPE_MODIFY_REQUEST: // 0x66
+      decodeModifyRequest(reader, messageID, handler);
+      break;
+    case OP_TYPE_MODIFY_RESPONSE: // 0x67
+      decodeModifyResult(reader, messageID, handler);
+      break;
+    case OP_TYPE_ADD_REQUEST: // 0x68
+      decodeAddRequest(reader, messageID, handler);
+      break;
+    case OP_TYPE_ADD_RESPONSE: // 0x69
+      decodeAddResult(reader, messageID, handler);
+      break;
+    case 0x6A: // 0x6A
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    case OP_TYPE_DELETE_RESPONSE: // 0x6B
+      decodeDeleteResult(reader, messageID, handler);
+      break;
+    case OP_TYPE_MODIFY_DN_REQUEST: // 0x6C
+      decodeModifyDNRequest(reader, messageID, handler);
+      break;
+    case OP_TYPE_MODIFY_DN_RESPONSE: // 0x6D
+      decodeModifyDNResult(reader, messageID, handler);
+      break;
+    case OP_TYPE_COMPARE_REQUEST: // 0x6E
+      decodeCompareRequest(reader, messageID, handler);
+      break;
+    case OP_TYPE_COMPARE_RESPONSE: // 0x6F
+      decodeCompareResult(reader, messageID, handler);
+      break;
+    case 0x70: // 0x70
+    case 0x71: // 0x71
+    case 0x72: // 0x72
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    case OP_TYPE_SEARCH_RESULT_REFERENCE: // 0x73
+      decodeSearchResultReference(reader, messageID, handler);
+      break;
+    case 0x74: // 0x74
+    case 0x75: // 0x75
+    case 0x76: // 0x76
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    case OP_TYPE_EXTENDED_REQUEST: // 0x77
+      decodeExtendedRequest(reader, messageID, handler);
+      break;
+    case OP_TYPE_EXTENDED_RESPONSE: // 0x78
+      decodeExtendedResult(reader, messageID, handler);
+      break;
+    case OP_TYPE_INTERMEDIATE_RESPONSE: // 0x79
+      decodeIntermediateResponse(reader, messageID, handler);
+      break;
+    default:
+      handler.handleUnrecognizedMessage(messageID, type, reader
+          .readOctetString(type));
+      break;
+    }
+  }
+
+
+
+  private static void decodeResponseReferrals(ASN1Reader reader,
+      Result message) throws IOException
+  {
+    if (reader.hasNextElement()
+        && (reader.peekType() == TYPE_REFERRAL_SEQUENCE))
+    {
+      reader.readStartSequence(TYPE_REFERRAL_SEQUENCE);
+      try
+      {
+        // Should have at least 1.
+        do
+        {
+          message.addReferralURI((reader.readOctetStringAsString()));
+        } while (reader.hasNextElement());
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a search
+   * result done protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeSearchResult(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+
+    Result message;
+
+    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_DONE);
+    try
+    {
+      ResultCode resultCode = ResultCode.valueOf(reader
+          .readEnumerated());
+      String matchedDN = reader.readOctetStringAsString();
+      String diagnosticMessage = reader.readOctetStringAsString();
+      message = Responses.newResult(resultCode).setMatchedDN(matchedDN)
+          .setDiagnosticMessage(diagnosticMessage);
+      decodeResponseReferrals(reader, message);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP SEARCH RESULT(messageID=%d, result=%s)",
+          messageID, message));
+    }
+
+    handler.handleSearchResult(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * search result entry protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeSearchResultEntry(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    SearchResultEntry message;
+    ResolvedSchema resolvedSchema;
+
+    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
+    try
+    {
+      String dnString = reader.readOctetStringAsString();
+      resolvedSchema = handler.resolveSchema(dnString);
+      DN dn = resolvedSchema.getInitialDN();
+      message = Responses.newSearchResultEntry(dn);
+
+      reader.readStartSequence();
+      try
+      {
+        while (reader.hasNextElement())
+        {
+          reader.readStartSequence();
+          try
+          {
+            String ads = reader.readOctetStringAsString();
+            AttributeDescription ad = resolvedSchema
+                .decodeAttributeDescription(ads);
+            Attribute attribute = new LinkedAttribute(ad);
+
+            reader.readStartSet();
+            try
+            {
+              while (reader.hasNextElement())
+              {
+                attribute.add(reader.readOctetString());
+              }
+              message.addAttribute(attribute);
+            }
+            finally
+            {
+              reader.readEndSet();
+            }
+          }
+          finally
+          {
+            reader.readEndSequence();
+          }
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler, resolvedSchema
+        .getSchema());
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)",
+          messageID, message));
+    }
+
+    handler.handleSearchResultEntry(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as a search
+   * result reference protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeSearchResultReference(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    SearchResultReference message;
+
+    reader.readStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
+    try
+    {
+      message = Responses.newSearchResultReference(reader
+          .readOctetStringAsString());
+      while (reader.hasNextElement())
+      {
+        message.addURI(reader.readOctetStringAsString());
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG
+          .finer(String
+              .format(
+                  "DECODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)",
+                  messageID, message));
+    }
+
+    handler.handleSearchResultReference(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 reader as an LDAP
+   * search request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeSearchRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    SearchRequest message;
+    ResolvedSchema resolvedSchema;
+
+    reader.readStartSequence(OP_TYPE_SEARCH_REQUEST);
+    try
+    {
+      String baseDNString = reader.readOctetStringAsString();
+      resolvedSchema = handler.resolveSchema(baseDNString);
+      DN baseDN = resolvedSchema.getInitialDN();
+
+      int scopeIntValue = reader.readEnumerated();
+      SearchScope scope = SearchScope.valueOf(scopeIntValue);
+      if (scope == null)
+      {
+        throw DecodeException
+            .error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_SCOPE
+                .get(scopeIntValue));
+      }
+
+      int dereferencePolicyIntValue = reader.readEnumerated();
+      DereferenceAliasesPolicy dereferencePolicy = DereferenceAliasesPolicy
+          .valueOf(dereferencePolicyIntValue);
+      if (dereferencePolicy == null)
+      {
+        throw DecodeException
+            .error(ERR_LDAP_SEARCH_REQUEST_DECODE_INVALID_DEREF
+                .get(dereferencePolicyIntValue));
+      }
+
+      int sizeLimit = (int) reader.readInteger();
+      int timeLimit = (int) reader.readInteger();
+      boolean typesOnly = reader.readBoolean();
+      Filter filter = LDAPUtils.decodeFilter(reader);
+
+      message = Requests.newSearchRequest(baseDN, scope, filter);
+      message.setDereferenceAliasesPolicy(dereferencePolicy);
+      try
+      {
+        message.setTimeLimit(timeLimit);
+        message.setSizeLimit(sizeLimit);
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        throw DecodeException.error(e.getMessageObject());
+      }
+      message.setTypesOnly(typesOnly);
+
+      reader.readStartSequence();
+      try
+      {
+        while (reader.hasNextElement())
+        {
+          message.addAttribute(reader.readOctetStringAsString());
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    decodeControls(reader, message, messageID, handler, resolvedSchema
+        .getSchema());
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP SEARCH REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleSearchRequest(messageID, message);
+  }
+
+
+
+  /**
+   * Decodes the elements from the provided ASN.1 read as an LDAP unbind
+   * request protocol op.
+   *
+   * @param reader
+   *          The ASN.1 reader.
+   * @param messageID
+   *          The decoded message ID for this message.
+   * @param handler
+   *          The <code>LDAPMessageHandler</code> that will handle this
+   *          decoded message.
+   * @throws IOException
+   *           If an error occurred while reading bytes to decode.
+   */
+  private static void decodeUnbindRequest(ASN1Reader reader,
+      int messageID, LDAPMessageHandler handler) throws IOException
+  {
+    UnbindRequest message;
+    reader.readNull(OP_TYPE_UNBIND_REQUEST);
+    message = Requests.newUnbindRequest();
+
+    decodeControls(reader, message, messageID, handler);
+
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "DECODE LDAP UNBIND REQUEST(messageID=%d, request=%s)",
+          messageID, message));
+    }
+
+    handler.handleUnbindRequest(messageID, message);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPEncoder.java b/sdk/src/org/opends/sdk/ldap/LDAPEncoder.java
new file mode 100644
index 0000000..1fe7632
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPEncoder.java
@@ -0,0 +1,723 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.sdk.ldap.LDAPConstants.*;
+
+import java.io.IOException;
+import java.util.logging.Level;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.Change;
+import org.opends.sdk.DN;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * Static methods for encoding LDAP messages.
+ */
+final class LDAPEncoder
+{
+  static void encodeAttribute(ASN1Writer writer, Attribute attribute)
+      throws IOException
+  {
+    writer.writeStartSequence();
+    writer
+        .writeOctetString(attribute.getAttributeDescriptionAsString());
+
+    writer.writeStartSet();
+    for (ByteString value : attribute)
+    {
+      writer.writeOctetString(value);
+    }
+    writer.writeEndSequence();
+
+    writer.writeEndSequence();
+  }
+
+
+
+  static void encodeControl(ASN1Writer writer, Control control)
+      throws IOException
+  {
+    writer.writeStartSequence();
+    writer.writeOctetString(control.getOID());
+    if (control.isCritical())
+    {
+      writer.writeBoolean(control.isCritical());
+    }
+    if (control.getValue() != null)
+    {
+      writer.writeOctetString(control.getValue());
+    }
+    writer.writeEndSequence();
+  }
+
+
+
+  static void encodeEntry(ASN1Writer writer,
+      SearchResultEntry searchResultEntry) throws IOException
+  {
+    writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_ENTRY);
+    writer.writeOctetString(searchResultEntry.getName().toString());
+
+    writer.writeStartSequence();
+    for (Attribute attr : searchResultEntry.getAttributes())
+    {
+      encodeAttribute(writer, attr);
+    }
+    writer.writeEndSequence();
+    writer.writeEndSequence();
+  }
+
+
+
+  static void encodeAbandonRequest(ASN1Writer writer, int messageID,
+      AbandonRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP ABANDON REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer
+        .writeInteger(OP_TYPE_ABANDON_REQUEST, request.getMessageID());
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeAddRequest(ASN1Writer writer, int messageID,
+      AddRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP ADD REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_ADD_REQUEST);
+    writer.writeOctetString(request.getName().toString());
+
+    // Write the attributes
+    writer.writeStartSequence();
+    for (Attribute attr : request.getAttributes())
+    {
+      encodeAttribute(writer, attr);
+    }
+    writer.writeEndSequence();
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeCompareRequest(ASN1Writer writer, int messageID,
+      CompareRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP COMPARE REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_COMPARE_REQUEST);
+    writer.writeOctetString(request.getName().toString());
+
+    writer.writeStartSequence();
+    writer.writeOctetString(request.getAttributeDescription()
+        .toString());
+    writer.writeOctetString(request.getAssertionValue());
+    writer.writeEndSequence();
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeDeleteRequest(ASN1Writer writer, int messageID,
+      DeleteRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP DELETE REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeOctetString(OP_TYPE_DELETE_REQUEST, request.getName()
+        .toString());
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeExtendedRequest(ASN1Writer writer, int messageID,
+      ExtendedRequest<?> request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP EXTENDED REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_EXTENDED_REQUEST);
+    writer.writeOctetString(TYPE_EXTENDED_REQUEST_OID, request
+        .getRequestName());
+
+    ByteString requestValue = request.getRequestValue();
+    if (requestValue != null)
+    {
+      writer
+          .writeOctetString(TYPE_EXTENDED_REQUEST_VALUE, requestValue);
+    }
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeBindRequest(ASN1Writer writer, int messageID,
+      int version, GenericBindRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG
+          .finer(String
+              .format(
+                  "ENCODE LDAP BIND REQUEST(messageID=%d, auth=0x%x, request=%s)",
+                  messageID, request.getAuthenticationType(), request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
+
+    writer.writeInteger(version);
+    writer.writeOctetString(request.getName().toString());
+
+    writer.writeOctetString(request.getAuthenticationType(), request
+        .getAuthenticationValue());
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeBindRequest(ASN1Writer writer, int messageID,
+      int version, SASLBindRequest<?> request,
+      ByteString saslCredentials) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG
+          .finer(String
+              .format(
+                  "ENCODE LDAP BIND REQUEST(messageID=%d, auth=SASL, request=%s)",
+                  messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
+
+    writer.writeInteger(version);
+    writer.writeOctetString(request.getName().toString());
+
+    writer.writeStartSequence(TYPE_AUTHENTICATION_SASL);
+    writer.writeOctetString(request.getSASLMechanism());
+    if (saslCredentials != null)
+    {
+      writer.writeOctetString(saslCredentials);
+    }
+    writer.writeEndSequence();
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeBindRequest(ASN1Writer writer, int messageID,
+      int version, SimpleBindRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG
+          .finer(String
+              .format(
+                  "ENCODE LDAP BIND REQUEST(messageID=%d, auth=simple, request=%s)",
+                  messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_BIND_REQUEST);
+
+    writer.writeInteger(version);
+    writer.writeOctetString(request.getName().toString());
+    writer.writeOctetString(TYPE_AUTHENTICATION_SIMPLE, request
+        .getPassword());
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeModifyDNRequest(ASN1Writer writer, int messageID,
+      ModifyDNRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP MODIFY DN REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_MODIFY_DN_REQUEST);
+    writer.writeOctetString(request.getName().toString());
+    writer.writeOctetString(request.getNewRDN().toString());
+    writer.writeBoolean(request.isDeleteOldRDN());
+
+    DN newSuperior = request.getNewSuperior();
+    if (newSuperior != null)
+    {
+      writer.writeOctetString(TYPE_MODIFY_DN_NEW_SUPERIOR, newSuperior
+          .toString());
+    }
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeModifyRequest(ASN1Writer writer, int messageID,
+      ModifyRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP MODIFY REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_MODIFY_REQUEST);
+    writer.writeOctetString(request.getName().toString());
+
+    writer.writeStartSequence();
+    for (Change change : request.getChanges())
+    {
+      encodeChange(writer, change);
+    }
+    writer.writeEndSequence();
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeSearchRequest(ASN1Writer writer, int messageID,
+      SearchRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP SEARCH REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_SEARCH_REQUEST);
+    writer.writeOctetString(request.getName().toString());
+    writer.writeEnumerated(request.getScope().intValue());
+    writer.writeEnumerated(request.getDereferenceAliasesPolicy()
+        .intValue());
+    writer.writeInteger(request.getSizeLimit());
+    writer.writeInteger(request.getTimeLimit());
+    writer.writeBoolean(request.isTypesOnly());
+    LDAPUtils.encodeFilter(writer, request.getFilter());
+
+    writer.writeStartSequence();
+    for (String attribute : request.getAttributes())
+    {
+      writer.writeOctetString(attribute);
+    }
+    writer.writeEndSequence();
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeUnbindRequest(ASN1Writer writer, int messageID,
+      UnbindRequest request) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP UNBIND REQUEST(messageID=%d, request=%s)",
+          messageID, request));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeNull(OP_TYPE_UNBIND_REQUEST);
+    encodeMessageFooter(writer, request);
+  }
+
+
+
+  static void encodeAddResult(ASN1Writer writer, int messageID,
+      Result result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP ADD RESULT(messageID=%d, result=%s)", messageID,
+          result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_ADD_RESPONSE, result);
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeBindResult(ASN1Writer writer, int messageID,
+      BindResult result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP BIND RESULT(messageID=%d, result=%s)",
+          messageID, result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_BIND_RESPONSE, result);
+
+    if (result.getServerSASLCredentials().length() > 0)
+    {
+      writer.writeOctetString(TYPE_SERVER_SASL_CREDENTIALS, result
+          .getServerSASLCredentials());
+    }
+
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeCompareResult(ASN1Writer writer, int messageID,
+      CompareResult result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP COMPARE RESULT(messageID=%d, result=%s)",
+          messageID, result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_COMPARE_RESPONSE, result);
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeDeleteResult(ASN1Writer writer, int messageID,
+      Result result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP DELETE RESULT(messageID=%d, result=%s)",
+          messageID, result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_DELETE_RESPONSE, result);
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeExtendedResult(ASN1Writer writer, int messageID,
+      ExtendedResult result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP EXTENDED RESULT(messageID=%d, result=%s)",
+          messageID, result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_EXTENDED_RESPONSE, result);
+
+    String responseName = result.getResponseName();
+    ByteString responseValue = result.getResponseValue();
+
+    if (responseName != null)
+    {
+      writer.writeOctetString(TYPE_EXTENDED_RESPONSE_OID, responseName);
+    }
+
+    if (responseValue != null)
+    {
+      writer.writeOctetString(TYPE_EXTENDED_RESPONSE_VALUE,
+          responseValue);
+    }
+
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeIntermediateResponse(ASN1Writer writer,
+      int messageID, IntermediateResponse response) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG
+          .finer(String
+              .format(
+                  "ENCODE LDAP INTERMEDIATE RESPONSE(messageID=%d, response=%s)",
+                  messageID, response));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_INTERMEDIATE_RESPONSE);
+
+    String responseName = response.getResponseName();
+    ByteString responseValue = response.getResponseValue();
+
+    if (responseName != null)
+    {
+      writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_OID, response
+          .getResponseName());
+    }
+
+    if (responseValue != null)
+    {
+      writer.writeOctetString(TYPE_INTERMEDIATE_RESPONSE_VALUE,
+          response.getResponseValue());
+    }
+
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, response);
+  }
+
+
+
+  static void encodeModifyDNResult(ASN1Writer writer, int messageID,
+      Result result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP MODIFY DN RESULT(messageID=%d, result=%s)",
+          messageID, result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_MODIFY_DN_RESPONSE, result);
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeModifyResult(ASN1Writer writer, int messageID,
+      Result result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP MODIFY RESULT(messageID=%d, result=%s)",
+          messageID, result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_MODIFY_RESPONSE, result);
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeSearchResult(ASN1Writer writer, int messageID,
+      Result result) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP SEARCH RESULT(messageID=%d, result=%s)",
+          messageID, result));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeResultHeader(writer, OP_TYPE_SEARCH_RESULT_DONE, result);
+    encodeResultFooter(writer);
+    encodeMessageFooter(writer, result);
+  }
+
+
+
+  static void encodeSearchResultEntry(ASN1Writer writer, int messageID,
+      SearchResultEntry entry) throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG.finer(String.format(
+          "ENCODE LDAP SEARCH RESULT ENTRY(messageID=%d, entry=%s)",
+          messageID, entry));
+    }
+    encodeMessageHeader(writer, messageID);
+    encodeEntry(writer, entry);
+    encodeMessageFooter(writer, entry);
+  }
+
+
+
+  static void encodeSearchResultReference(ASN1Writer writer,
+      int messageID, SearchResultReference reference)
+      throws IOException
+  {
+    if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINER))
+    {
+      StaticUtils.DEBUG_LOG
+          .finer(String
+              .format(
+                  "ENCODE LDAP SEARCH RESULT REFERENCE(messageID=%d, reference=%s)",
+                  messageID, reference));
+    }
+    encodeMessageHeader(writer, messageID);
+    writer.writeStartSequence(OP_TYPE_SEARCH_RESULT_REFERENCE);
+    for (String url : reference.getURIs())
+    {
+      writer.writeOctetString(url);
+    }
+    writer.writeEndSequence();
+    encodeMessageFooter(writer, reference);
+  }
+
+
+
+  private static void encodeChange(ASN1Writer writer, Change change)
+      throws IOException
+  {
+    writer.writeStartSequence();
+    writer.writeEnumerated(change.getModificationType().intValue());
+    encodeAttribute(writer, change.getAttribute());
+    writer.writeEndSequence();
+  }
+
+
+
+  private static void encodeMessageFooter(ASN1Writer writer,
+      Request request) throws IOException
+  {
+    if (request.hasControls())
+    {
+      writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
+      for (Control control : request.getControls())
+      {
+        encodeControl(writer, control);
+      }
+      writer.writeEndSequence();
+    }
+
+    writer.writeEndSequence();
+  }
+
+
+
+  private static void encodeMessageFooter(ASN1Writer writer,
+      Response response) throws IOException
+  {
+    if (response.hasControls())
+    {
+      writer.writeStartSequence(TYPE_CONTROL_SEQUENCE);
+      for (Control control : response.getControls())
+      {
+        encodeControl(writer, control);
+      }
+      writer.writeEndSequence();
+    }
+
+    writer.writeEndSequence();
+  }
+
+
+
+  private static void encodeMessageHeader(ASN1Writer writer,
+      int messageID) throws IOException
+  {
+    writer.writeStartSequence();
+    writer.writeInteger(messageID);
+  }
+
+
+
+  private static void encodeResultFooter(ASN1Writer writer)
+      throws IOException
+  {
+    writer.writeEndSequence();
+  }
+
+
+
+  private static void encodeResultHeader(ASN1Writer writer,
+      byte typeTag, Result rawMessage) throws IOException
+  {
+    writer.writeStartSequence(typeTag);
+    writer.writeEnumerated(rawMessage.getResultCode().intValue());
+    writer.writeOctetString(rawMessage.getMatchedDN());
+    writer.writeOctetString(rawMessage.getDiagnosticMessage());
+
+    if (rawMessage.hasReferralURIs())
+    {
+      writer.writeStartSequence(TYPE_REFERRAL_SEQUENCE);
+      for (String s : rawMessage.getReferralURIs())
+      {
+        writer.writeOctetString(s);
+      }
+      writer.writeEndSequence();
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java b/sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java
new file mode 100644
index 0000000..4cc393d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPMessageHandler.java
@@ -0,0 +1,190 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.requests.*;
+import org.opends.sdk.responses.*;
+import org.opends.sdk.sasl.SASLBindRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * LDAP message handler interface.
+ */
+interface LDAPMessageHandler
+{
+  ResolvedSchema resolveSchema(String dn) throws DecodeException;
+
+
+
+  Schema getDefaultSchema();
+
+
+
+  void handleException(Throwable throwable);
+
+
+
+  void handleUnrecognizedMessage(int messageID, byte messageTag,
+      ByteString messageBytes) throws UnsupportedMessageException;
+
+
+
+  void handleAbandonRequest(int messageID, AbandonRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleAddRequest(int messageID, AddRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleCompareRequest(int messageID, CompareRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleDeleteRequest(int messageID, DeleteRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleExtendedRequest(int messageID,
+      GenericExtendedRequest request) throws UnexpectedRequestException;
+
+
+
+  void handleBindRequest(int messageID, int version,
+      GenericBindRequest request) throws UnexpectedRequestException;
+
+
+
+  void handleBindRequest(int messageID, int version,
+      SASLBindRequest<?> request) throws UnexpectedRequestException;
+
+
+
+  void handleBindRequest(int messageID, int version,
+      SimpleBindRequest request) throws UnexpectedRequestException;
+
+
+
+  void handleModifyDNRequest(int messageID, ModifyDNRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleModifyRequest(int messageID, ModifyRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleSearchRequest(int messageID, SearchRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleUnbindRequest(int messageID, UnbindRequest request)
+      throws UnexpectedRequestException;
+
+
+
+  void handleAddResult(int messageID, Result result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleBindResult(int messageID, BindResult result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleCompareResult(int messageID, CompareResult result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleDeleteResult(int messageID, Result result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleExtendedResult(int messageID, GenericExtendedResult result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleIntermediateResponse(int messageID,
+      GenericIntermediateResponse response)
+      throws UnexpectedResponseException;
+
+
+
+  void handleModifyDNResult(int messageID, Result result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleModifyResult(int messageID, Result result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleSearchResult(int messageID, Result result)
+      throws UnexpectedResponseException;
+
+
+
+  void handleSearchResultEntry(int messageID, SearchResultEntry entry)
+      throws UnexpectedResponseException;
+
+
+
+  void handleSearchResultReference(int messageID,
+      SearchResultReference reference)
+      throws UnexpectedResponseException;
+
+
+
+  Control decodeResponseControl(int messageID, String oid,
+      boolean isCritical, ByteString value, Schema schema)
+      throws DecodeException;
+
+
+
+  Control decodeRequestControl(int messageID, String oid,
+      boolean isCritical, ByteString value, Schema schema)
+      throws DecodeException;
+}
diff --git a/sdk/src/org/opends/sdk/ldap/LDAPUtils.java b/sdk/src/org/opends/sdk/ldap/LDAPUtils.java
new file mode 100644
index 0000000..ecf5c5f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/LDAPUtils.java
@@ -0,0 +1,738 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import static org.opends.sdk.ldap.LDAPConstants.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.Filter;
+import org.opends.sdk.FilterVisitor;
+import org.opends.sdk.asn1.ASN1Reader;
+import org.opends.sdk.asn1.ASN1Writer;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * Common LDAP utility methods which may be used when implementing new
+ * controls and extension.
+ */
+public final class LDAPUtils
+{
+
+  private static final FilterVisitor<IOException, ASN1Writer> ASN1_ENCODER = new FilterVisitor<IOException, ASN1Writer>()
+  {
+
+    public IOException visitAndFilter(ASN1Writer writer,
+        List<Filter> subFilters)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_AND);
+        for (Filter subFilter : subFilters)
+        {
+          IOException e = subFilter.accept(this, writer);
+          if (e != null)
+          {
+            return e;
+          }
+        }
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitApproxMatchFilter(ASN1Writer writer,
+        String attributeDescription, ByteSequence assertionValue)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_APPROXIMATE);
+        writer.writeOctetString(attributeDescription);
+        writer.writeOctetString(assertionValue);
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitEqualityMatchFilter(ASN1Writer writer,
+        String attributeDescription, ByteSequence assertionValue)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_EQUALITY);
+        writer.writeOctetString(attributeDescription);
+        writer.writeOctetString(assertionValue);
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitExtensibleMatchFilter(ASN1Writer writer,
+        String matchingRule, String attributeDescription,
+        ByteSequence assertionValue, boolean dnAttributes)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_EXTENSIBLE_MATCH);
+
+        if (matchingRule != null)
+        {
+          writer.writeOctetString(TYPE_MATCHING_RULE_ID, matchingRule);
+        }
+
+        if (attributeDescription != null)
+        {
+          writer.writeOctetString(TYPE_MATCHING_RULE_TYPE,
+              attributeDescription);
+        }
+
+        writer.writeOctetString(TYPE_MATCHING_RULE_VALUE,
+            assertionValue);
+
+        if (dnAttributes)
+        {
+          writer.writeBoolean(TYPE_MATCHING_RULE_DN_ATTRIBUTES, true);
+        }
+
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitGreaterOrEqualFilter(ASN1Writer writer,
+        String attributeDescription, ByteSequence assertionValue)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_GREATER_OR_EQUAL);
+        writer.writeOctetString(attributeDescription);
+        writer.writeOctetString(assertionValue);
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitLessOrEqualFilter(ASN1Writer writer,
+        String attributeDescription, ByteSequence assertionValue)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_LESS_OR_EQUAL);
+        writer.writeOctetString(attributeDescription);
+        writer.writeOctetString(assertionValue);
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitNotFilter(ASN1Writer writer,
+        Filter subFilter)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_NOT);
+        IOException e = subFilter.accept(this, writer);
+        if (e != null)
+        {
+          return e;
+        }
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitOrFilter(ASN1Writer writer,
+        List<Filter> subFilters)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_OR);
+        for (Filter subFilter : subFilters)
+        {
+          IOException e = subFilter.accept(this, writer);
+          if (e != null)
+          {
+            return e;
+          }
+        }
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitPresentFilter(ASN1Writer writer,
+        String attributeDescription)
+    {
+      try
+      {
+        writer.writeOctetString(TYPE_FILTER_PRESENCE,
+            attributeDescription);
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitSubstringsFilter(ASN1Writer writer,
+        String attributeDescription, ByteSequence initialSubstring,
+        List<ByteSequence> anySubstrings, ByteSequence finalSubstring)
+    {
+      try
+      {
+        writer.writeStartSequence(TYPE_FILTER_SUBSTRING);
+        writer.writeOctetString(attributeDescription);
+
+        writer.writeStartSequence();
+        if (initialSubstring != null)
+        {
+          writer.writeOctetString(TYPE_SUBINITIAL, initialSubstring);
+        }
+
+        for (ByteSequence anySubstring : anySubstrings)
+        {
+          writer.writeOctetString(TYPE_SUBANY, anySubstring);
+        }
+
+        if (finalSubstring != null)
+        {
+          writer.writeOctetString(TYPE_SUBFINAL, finalSubstring);
+        }
+        writer.writeEndSequence();
+
+        writer.writeEndSequence();
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+
+
+
+    public IOException visitUnrecognizedFilter(ASN1Writer writer,
+        byte filterTag, ByteSequence filterBytes)
+    {
+      try
+      {
+        writer.writeOctetString(filterTag, filterBytes);
+        return null;
+      }
+      catch (IOException e)
+      {
+        return e;
+      }
+    }
+  };
+
+
+
+  /**
+   * Reads the next ASN.1 element from the provided {@code ASN1Reader}
+   * as a {@code Filter}.
+   *
+   * @param reader
+   *          The {@code ASN1Reader} from which the ASN.1 encoded
+   *          {@code Filter} should be read.
+   * @return The decoded {@code Filter}.
+   * @throws IOException
+   *           If an error occurs while reading from {@code reader}.
+   */
+  public static Filter decodeFilter(ASN1Reader reader)
+      throws IOException
+  {
+    byte type = reader.peekType();
+
+    switch (type)
+    {
+    case TYPE_FILTER_AND:
+      return decodeAndFilter(reader);
+
+    case TYPE_FILTER_OR:
+      return decodeOrFilter(reader);
+
+    case TYPE_FILTER_NOT:
+      return decodeNotFilter(reader);
+
+    case TYPE_FILTER_EQUALITY:
+      return decodeEqualityMatchFilter(reader);
+
+    case TYPE_FILTER_GREATER_OR_EQUAL:
+      return decodeGreaterOrEqualMatchFilter(reader);
+
+    case TYPE_FILTER_LESS_OR_EQUAL:
+      return decodeLessOrEqualMatchFilter(reader);
+
+    case TYPE_FILTER_APPROXIMATE:
+      return decodeApproxMatchFilter(reader);
+
+    case TYPE_FILTER_SUBSTRING:
+      return decodeSubstringsFilter(reader);
+
+    case TYPE_FILTER_PRESENCE:
+      return Filter.newPresentFilter(reader
+          .readOctetStringAsString(type));
+
+    case TYPE_FILTER_EXTENSIBLE_MATCH:
+      return decodeExtensibleMatchFilter(reader);
+
+    default:
+      return Filter.newUnrecognizedFilter(type, reader
+          .readOctetString(type));
+    }
+  }
+
+
+
+  /**
+   * Reads the next ASN.1 element from the provided {@code ASN1Reader}
+   * as a {@code SearchResultEntry}.
+   *
+   * @param reader
+   *          The {@code ASN1Reader} from which the ASN.1 encoded
+   *          {@code SearchResultEntry} should be read.
+   * @param schema
+   *          The schema to use when decoding the entry.
+   * @return The decoded {@code SearchResultEntry}.
+   * @throws IOException
+   *           If an error occurs while reading from {@code reader}.
+   */
+  public static SearchResultEntry decodeSearchResultEntry(
+      ASN1Reader reader, Schema schema) throws IOException
+  {
+    return LDAPDecoder.decodeEntry(reader, schema);
+  }
+
+
+
+  /**
+   * Writes the ASN.1 encoding of the provided {@code Filter} to the
+   * provided {@code ASN1Writer}.
+   *
+   * @param writer
+   *          The {@code ASN1Writer} to which the ASN.1 encoding of the
+   *          provided {@code Filter} should be written.
+   * @param filter
+   *          The filter to be encoded.
+   * @return The updated {@code ASN1Writer}.
+   * @throws IOException
+   *           If an error occurs while writing to {@code writer}.
+   */
+  public static ASN1Writer encodeFilter(ASN1Writer writer, Filter filter)
+      throws IOException
+  {
+    IOException e = filter.accept(ASN1_ENCODER, writer);
+    if (e != null)
+    {
+      throw e;
+    }
+    else
+    {
+      return writer;
+    }
+  }
+
+
+
+  /**
+   * Writes the ASN.1 encoding of the provided {@code SearchResultEntry}
+   * to the provided {@code ASN1Writer}.
+   *
+   * @param writer
+   *          The {@code ASN1Writer} to which the ASN.1 encoding of the
+   *          provided {@code SearchResultEntry} should be written.
+   * @param entry
+   *          The Search Result Entry to be encoded.
+   * @return The updated {@code ASN1Writer}.
+   * @throws IOException
+   *           If an error occurs while writing to {@code writer}.
+   */
+  public static ASN1Writer encodeSearchResultEntry(ASN1Writer writer,
+      SearchResultEntry entry) throws IOException
+  {
+    LDAPEncoder.encodeEntry(writer, entry);
+    return writer;
+  }
+
+
+
+  // Decodes an and filter.
+  private static Filter decodeAndFilter(ASN1Reader reader)
+      throws IOException
+  {
+    Filter filter;
+
+    reader.readStartSequence(TYPE_FILTER_AND);
+    try
+    {
+      if (reader.hasNextElement())
+      {
+        List<Filter> subFilters = new LinkedList<Filter>();
+        do
+        {
+          subFilters.add(decodeFilter(reader));
+        } while (reader.hasNextElement());
+        filter = Filter.newAndFilter(subFilters);
+      }
+      else
+      {
+        // No sub-filters - this is an RFC 4526 absolute true filter.
+        filter = Filter.getAbsoluteTrueFilter();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return filter;
+  }
+
+
+
+  // Decodes an approximate match filter.
+  private static Filter decodeApproxMatchFilter(ASN1Reader reader)
+      throws IOException
+  {
+    String attributeDescription;
+    ByteSequence assertionValue;
+
+    reader.readStartSequence(TYPE_FILTER_APPROXIMATE);
+    try
+    {
+      attributeDescription = reader.readOctetStringAsString();
+      assertionValue = reader.readOctetString();
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return Filter.newApproxMatchFilter(attributeDescription,
+        assertionValue);
+  }
+
+
+
+  // Decodes an equality match filter.
+  private static Filter decodeEqualityMatchFilter(ASN1Reader reader)
+      throws IOException
+  {
+    String attributeDescription;
+    ByteSequence assertionValue;
+
+    reader.readStartSequence(TYPE_FILTER_EQUALITY);
+    try
+    {
+      attributeDescription = reader.readOctetStringAsString();
+      assertionValue = reader.readOctetString();
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return Filter.newEqualityMatchFilter(attributeDescription,
+        assertionValue);
+  }
+
+
+
+  // Decodes an extensible match filter.
+  private static Filter decodeExtensibleMatchFilter(ASN1Reader reader)
+      throws IOException
+  {
+    String matchingRule;
+    String attributeDescription;
+    boolean dnAttributes;
+    ByteSequence assertionValue;
+
+    reader.readStartSequence(TYPE_FILTER_EXTENSIBLE_MATCH);
+    try
+    {
+      matchingRule = null;
+      if (reader.peekType() == TYPE_MATCHING_RULE_ID)
+      {
+        matchingRule = reader
+            .readOctetStringAsString(TYPE_MATCHING_RULE_ID);
+      }
+      attributeDescription = null;
+      if (reader.peekType() == TYPE_MATCHING_RULE_TYPE)
+      {
+        attributeDescription = reader
+            .readOctetStringAsString(TYPE_MATCHING_RULE_TYPE);
+      }
+      dnAttributes = false;
+      if (reader.hasNextElement()
+          && (reader.peekType() == TYPE_MATCHING_RULE_DN_ATTRIBUTES))
+      {
+        dnAttributes = reader.readBoolean();
+      }
+      assertionValue = reader.readOctetString(TYPE_MATCHING_RULE_VALUE);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return Filter.newExtensibleMatchFilter(matchingRule,
+        attributeDescription, assertionValue, dnAttributes);
+  }
+
+
+
+  // Decodes a greater than or equal filter.
+  private static Filter decodeGreaterOrEqualMatchFilter(
+      ASN1Reader reader) throws IOException
+  {
+    String attributeDescription;
+    ByteSequence assertionValue;
+
+    reader.readStartSequence(TYPE_FILTER_GREATER_OR_EQUAL);
+    try
+    {
+      attributeDescription = reader.readOctetStringAsString();
+      assertionValue = reader.readOctetString();
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+    return Filter.newGreaterOrEqualFilter(attributeDescription,
+        assertionValue);
+  }
+
+
+
+  // Decodes a less than or equal filter.
+  private static Filter decodeLessOrEqualMatchFilter(ASN1Reader reader)
+      throws IOException
+  {
+    String attributeDescription;
+    ByteSequence assertionValue;
+
+    reader.readStartSequence(TYPE_FILTER_LESS_OR_EQUAL);
+    try
+    {
+      attributeDescription = reader.readOctetStringAsString();
+      assertionValue = reader.readOctetString();
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return Filter.newLessOrEqualFilter(attributeDescription,
+        assertionValue);
+  }
+
+
+
+  // Decodes a not filter.
+  private static Filter decodeNotFilter(ASN1Reader reader)
+      throws IOException
+  {
+    Filter subFilter;
+
+    reader.readStartSequence(TYPE_FILTER_NOT);
+    try
+    {
+      subFilter = decodeFilter(reader);
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return Filter.newNotFilter(subFilter);
+  }
+
+
+
+  // Decodes an or filter.
+  private static Filter decodeOrFilter(ASN1Reader reader)
+      throws IOException
+  {
+    Filter filter;
+
+    reader.readStartSequence(TYPE_FILTER_OR);
+    try
+    {
+      if (reader.hasNextElement())
+      {
+        List<Filter> subFilters = new LinkedList<Filter>();
+        do
+        {
+          subFilters.add(decodeFilter(reader));
+        } while (reader.hasNextElement());
+        filter = Filter.newOrFilter(subFilters);
+      }
+      else
+      {
+        // No sub-filters - this is an RFC 4526 absolute false filter.
+        filter = Filter.getAbsoluteFalseFilter();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    return filter;
+  }
+
+
+
+  // Decodes a sub-strings filter.
+  private static Filter decodeSubstringsFilter(ASN1Reader reader)
+      throws IOException
+  {
+    ByteSequence initialSubstring = null;
+    List<ByteSequence> anySubstrings = null;
+    ByteSequence finalSubstring = null;
+    String attributeDescription;
+
+    reader.readStartSequence(TYPE_FILTER_SUBSTRING);
+    try
+    {
+      attributeDescription = reader.readOctetStringAsString();
+      reader.readStartSequence();
+      try
+      {
+        // FIXME: There should be at least one element in this substring
+        // filter sequence.
+        if (reader.peekType() == TYPE_SUBINITIAL)
+        {
+          initialSubstring = reader.readOctetString(TYPE_SUBINITIAL);
+        }
+        if (reader.hasNextElement()
+            && (reader.peekType() == TYPE_SUBANY))
+        {
+          anySubstrings = new LinkedList<ByteSequence>();
+          do
+          {
+            anySubstrings.add(reader.readOctetString(TYPE_SUBANY));
+          } while (reader.hasNextElement()
+              && (reader.peekType() == TYPE_SUBANY));
+        }
+        if (reader.hasNextElement()
+            && (reader.peekType() == TYPE_SUBFINAL))
+        {
+          finalSubstring = reader.readOctetString(TYPE_SUBFINAL);
+        }
+      }
+      finally
+      {
+        reader.readEndSequence();
+      }
+    }
+    finally
+    {
+      reader.readEndSequence();
+    }
+
+    if (anySubstrings == null)
+    {
+      anySubstrings = Collections.emptyList();
+    }
+
+    return Filter.newSubstringsFilter(attributeDescription,
+        initialSubstring, anySubstrings, finalSubstring);
+  }
+
+
+
+  /**
+   * Prevent instantiation.
+   */
+  private LDAPUtils()
+  {
+    // Nothing to do.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ResolvedSchema.java b/sdk/src/org/opends/sdk/ldap/ResolvedSchema.java
new file mode 100644
index 0000000..5d57a10
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ResolvedSchema.java
@@ -0,0 +1,65 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.RDN;
+import org.opends.sdk.schema.Schema;
+
+
+
+/**
+ * A reference to a schema which should be used when decoding incoming
+ * protocol elements.
+ */
+interface ResolvedSchema
+{
+  DN getInitialDN();
+
+
+
+  Schema getSchema();
+
+
+
+  DN decodeDN(String dn) throws DecodeException;
+
+
+
+  RDN decodeRDN(String rdn) throws DecodeException;
+
+
+
+  AttributeDescription decodeAttributeDescription(
+      String attributeDescription) throws DecodeException;
+
+}
diff --git a/sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java
new file mode 100644
index 0000000..fef66e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/ResultFutureImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.requests.Request;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+
+
+
+/**
+ * Result future implementation.
+ */
+final class ResultFutureImpl<P> extends
+    AbstractResultFutureImpl<Result, P> implements ResultFuture<Result>
+{
+  private final Request request;
+
+
+
+  ResultFutureImpl(int messageID, Request request,
+      ResultHandler<Result, P> handler, P p, LDAPConnection connection,
+      ExecutorService handlerExecutor)
+  {
+    super(messageID, handler, p, connection, handlerExecutor);
+    this.request = request;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  Result newErrorResult(ResultCode resultCode,
+      String diagnosticMessage, Throwable cause)
+  {
+    return Responses.newResult(resultCode).setDiagnosticMessage(
+        diagnosticMessage).setCause(cause);
+  }
+
+
+
+  Request getRequest()
+  {
+    return request;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SASLFilter.java b/sdk/src/org/opends/sdk/ldap/SASLFilter.java
new file mode 100644
index 0000000..04b55b1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SASLFilter.java
@@ -0,0 +1,350 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.sasl.SASLContext;
+
+import com.sun.grizzly.Buffer;
+import com.sun.grizzly.Connection;
+import com.sun.grizzly.Grizzly;
+import com.sun.grizzly.attributes.Attribute;
+import com.sun.grizzly.attributes.AttributeBuilder;
+import com.sun.grizzly.attributes.AttributeStorage;
+import com.sun.grizzly.filterchain.FilterAdapter;
+import com.sun.grizzly.filterchain.FilterChainContext;
+import com.sun.grizzly.filterchain.NextAction;
+import com.sun.grizzly.filterchain.StreamTransformerFilter;
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.threadpool.WorkerThread;
+
+
+
+/**
+ * SASL filter adapter.
+ */
+final class SASLFilter extends FilterAdapter implements
+    StreamTransformerFilter
+{
+  private static SASLFilter SINGLETON = new SASLFilter();
+
+  private static final String SASL_CONTEXT_ATTR_NAME = "SASLContextAttr";
+
+  private static final String SASL_INCOMING_BUFFER_NAME = "SASLIncomingBufferAttr";
+
+  private static final String SASL_OUTGOING_BUFFER_NAME = "SASLOutgoingBufferAttr";
+
+
+
+  public static SASLFilter getInstance(SASLContext saslContext,
+      Connection<?> connection)
+  {
+    SINGLETON.saslContextAttribute.set(connection, saslContext);
+    return SINGLETON;
+  }
+
+
+
+  private final Attribute<SASLContext> saslContextAttribute;
+
+  private final Attribute<byte[]> saslIncomingBufferAttribute;
+
+  private final Attribute<byte[]> saslOutgoingBufferAttribute;
+
+
+
+  private SASLFilter()
+  {
+    AttributeBuilder attrBuilder = getAttributeBuilder();
+    saslContextAttribute = attrBuilder
+        .createAttribute(SASL_CONTEXT_ATTR_NAME);
+    saslIncomingBufferAttribute = attrBuilder
+        .createAttribute(SASL_INCOMING_BUFFER_NAME);
+    saslOutgoingBufferAttribute = attrBuilder
+        .createAttribute(SASL_OUTGOING_BUFFER_NAME);
+  }
+
+
+
+  public StreamReader getStreamReader(StreamReader parentStreamReader)
+  {
+    return new SASLStreamReader(parentStreamReader, this);
+  }
+
+
+
+  public StreamWriter getStreamWriter(StreamWriter parentStreamWriter)
+  {
+    return new SASLStreamWriter(parentStreamWriter, this);
+  }
+
+
+
+  /**
+   * Wraps {@link com.sun.grizzly.filterchain.FilterChainContext}
+   * default {@link com.sun.grizzly.streams.StreamReader} and
+   * {@link com.sun.grizzly.streams.StreamWriter} with SASL aware ones.
+   */
+  @Override
+  public NextAction handleRead(FilterChainContext ctx,
+      NextAction nextAction) throws IOException
+  {
+    StreamReader parentReader = ctx.getStreamReader();
+    StreamWriter parentWriter = ctx.getStreamWriter();
+
+    SASLStreamReader saslStreamReader = new SASLStreamReader(
+        parentReader, this);
+    SASLStreamWriter saslStreamWriter = new SASLStreamWriter(
+        parentWriter, this);
+
+    ctx.setStreamReader(saslStreamReader);
+    ctx.setStreamWriter(saslStreamWriter);
+
+    saslStreamReader.pull();
+
+    if (!saslStreamReader.hasAvailableData())
+    {
+      nextAction = ctx.getStopAction();
+    }
+    return nextAction;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public NextAction handleWrite(FilterChainContext ctx,
+      NextAction nextAction) throws IOException
+  {
+    StreamWriter writer = ctx.getStreamWriter();
+
+    Object message = ctx.getMessage();
+
+    if (message instanceof Buffer<?>)
+    {
+      writer.writeBuffer((Buffer<?>) message);
+    }
+    writer.flush();
+
+    return nextAction;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public NextAction postClose(FilterChainContext ctx,
+      NextAction nextAction) throws IOException
+  {
+    saslContextAttribute.remove(ctx.getConnection());
+    saslIncomingBufferAttribute.remove(ctx.getConnection());
+    saslOutgoingBufferAttribute.remove(ctx.getConnection());
+    return nextAction;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public NextAction postRead(FilterChainContext ctx,
+      NextAction nextAction) throws IOException
+  {
+    SASLStreamReader saslStreamReader = (SASLStreamReader) ctx
+        .getStreamReader();
+    SASLStreamWriter saslStreamWriter = (SASLStreamWriter) ctx
+        .getStreamWriter();
+
+    ctx.setStreamReader(saslStreamReader.getUnderlyingReader());
+    ctx.setStreamWriter(saslStreamWriter.getUnderlyingWriter());
+
+    return nextAction;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public NextAction postWrite(FilterChainContext ctx,
+      NextAction nextAction) throws IOException
+  {
+    return nextAction;
+  }
+
+
+
+  public byte[] unwrap(Buffer<?> incoming, Connection<?> connection)
+      throws SaslException
+  {
+    SASLContext saslClient = saslContextAttribute.get(connection);
+    byte[] incomingBuffer = obtainIncomingBuffer(incoming.capacity(),
+        connection);
+    int remaining = incoming.remaining();
+
+    incoming.get(incomingBuffer, 0, remaining);
+    return saslClient.unwrap(incomingBuffer, 0, remaining);
+  }
+
+
+
+  public byte[] wrap(Buffer<?> outgoing, Connection<?> connection)
+      throws SaslException
+  {
+    SASLContext saslClient = saslContextAttribute.get(connection);
+    byte[] outgoingBuffer = obtainOutgoingBuffer(outgoing.capacity(),
+        connection);
+    int remaining = outgoing.remaining();
+
+    outgoing.get(outgoingBuffer, 0, remaining);
+    return saslClient.wrap(outgoingBuffer, 0, remaining);
+  }
+
+
+
+  protected final AttributeBuilder getAttributeBuilder()
+  {
+    return Grizzly.DEFAULT_ATTRIBUTE_BUILDER;
+  }
+
+
+
+  /**
+   * Obtaining incoming buffer
+   *
+   * @param state
+   *          State storage
+   * @return incoming buffer
+   */
+  protected final byte[] obtainIncomingBuffer(int size,
+      AttributeStorage state)
+  {
+
+    // #1 - Try to get buffer from the attribute storage (connection)
+    byte[] buffer = saslIncomingBufferAttribute.get(state);
+
+    if ((buffer != null) && (buffer.length >= size))
+    {
+      return buffer;
+    }
+
+    // #2 - Try to get buffer from the WorkerThread (if possible)
+    Thread currentThread = Thread.currentThread();
+    boolean isWorkingThread = (currentThread instanceof WorkerThread);
+
+    if (isWorkingThread)
+    {
+      WorkerThread workerThread = (WorkerThread) currentThread;
+      buffer = saslIncomingBufferAttribute.get(workerThread);
+    }
+
+    if ((buffer != null) && (buffer.length >= size))
+    {
+      return buffer;
+    }
+
+    // #3 - Allocate new buffer
+    buffer = new byte[size];
+
+    if (isWorkingThread)
+    {
+      WorkerThread workerThread = (WorkerThread) currentThread;
+      saslIncomingBufferAttribute.set(workerThread, buffer);
+    }
+    else
+    {
+      saslIncomingBufferAttribute.set(state, buffer);
+    }
+
+    return buffer;
+  }
+
+
+
+  /**
+   * Obtaining outgoing buffer
+   *
+   * @param state
+   *          State storage
+   * @return Outgoing buffer
+   */
+  protected final byte[] obtainOutgoingBuffer(int size,
+      AttributeStorage state)
+  {
+
+    // #1 - Try to get buffer from the attribute storage (connection)
+    byte[] buffer = saslOutgoingBufferAttribute.get(state);
+
+    if ((buffer != null) && (buffer.length >= size))
+    {
+      return buffer;
+    }
+
+    // #2 - Try to get buffer from the WorkerThread (if possible)
+    Thread currentThread = Thread.currentThread();
+    boolean isWorkingThread = (currentThread instanceof WorkerThread);
+
+    if (isWorkingThread)
+    {
+      WorkerThread workerThread = (WorkerThread) currentThread;
+      buffer = saslOutgoingBufferAttribute.get(workerThread);
+    }
+
+    if ((buffer != null) && (buffer.length >= size))
+    {
+      return buffer;
+    }
+
+    // #3 - Allocate new buffer
+    buffer = new byte[size];
+
+    if (isWorkingThread)
+    {
+      WorkerThread workerThread = (WorkerThread) currentThread;
+      saslOutgoingBufferAttribute.set(workerThread, buffer);
+    }
+    else
+    {
+      saslOutgoingBufferAttribute.set(state, buffer);
+    }
+
+    return buffer;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SASLStreamReader.java b/sdk/src/org/opends/sdk/ldap/SASLStreamReader.java
new file mode 100644
index 0000000..337a12b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SASLStreamReader.java
@@ -0,0 +1,118 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import javax.security.sasl.SaslException;
+
+import com.sun.grizzly.Buffer;
+import com.sun.grizzly.streams.StreamReader;
+import com.sun.grizzly.streams.StreamReaderDecorator;
+
+
+
+/**
+ * SASL stream reader.
+ */
+final class SASLStreamReader extends StreamReaderDecorator
+{
+  private final SASLFilter saslFilter;
+
+
+
+  public SASLStreamReader(StreamReader underlyingReader,
+      SASLFilter saslFilter)
+  {
+    super(underlyingReader);
+    this.saslFilter = saslFilter;
+  }
+
+
+
+  @Override
+  public boolean appendBuffer(Buffer buffer)
+  {
+    if (buffer == null)
+    {
+      return false;
+    }
+
+    byte[] appBuffer;
+    try
+    {
+      appBuffer = saslFilter.unwrap(buffer, getConnection());
+    }
+    catch (SaslException e)
+    {
+      throw new IllegalStateException(e);
+    }
+
+    if (appBuffer.length == 0)
+    {
+      return false;
+    }
+
+    Buffer newBuffer = newBuffer(appBuffer.length);
+    newBuffer.put(appBuffer);
+
+    if (super.appendBuffer(newBuffer))
+    {
+      buffer.dispose();
+      return true;
+    }
+
+    return false;
+  }
+
+
+
+  @Override
+  protected Buffer read0() throws IOException
+  {
+    return underlyingReader.readBuffer();
+  }
+
+
+
+  @Override
+  protected Buffer unwrap(Object data)
+  {
+    return (Buffer) data;
+  }
+
+
+
+  @Override
+  protected final Object wrap(Buffer buffer)
+  {
+    return buffer;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java b/sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java
new file mode 100644
index 0000000..22a74a3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SASLStreamWriter.java
@@ -0,0 +1,87 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+import java.util.concurrent.Future;
+
+import com.sun.grizzly.Buffer;
+import com.sun.grizzly.CompletionHandler;
+import com.sun.grizzly.streams.StreamWriter;
+import com.sun.grizzly.streams.StreamWriterDecorator;
+
+
+
+/**
+ * SASL stream writer.
+ */
+final class SASLStreamWriter extends StreamWriterDecorator
+{
+  private final SASLFilter saslFilter;
+
+
+
+  public SASLStreamWriter(StreamWriter underlyingWriter,
+      SASLFilter saslFilter)
+  {
+    super(underlyingWriter);
+    this.saslFilter = saslFilter;
+  }
+
+
+
+  @Override
+  protected Future<Integer> flush0(Buffer buffer,
+      CompletionHandler<Integer> completionHandler) throws IOException
+  {
+    Future<Integer> lastWriterFuture = null;
+
+    if (buffer != null)
+    {
+      buffer.flip();
+
+      Buffer underlyingBuffer = underlyingWriter.getBuffer();
+      byte[] netBuffer = saslFilter.wrap(buffer, getConnection());
+      int remaining = netBuffer.length;
+      while (remaining > 0)
+      {
+        int writeSize = Math.min(remaining, underlyingBuffer
+            .remaining());
+        underlyingBuffer.put(netBuffer, netBuffer.length - remaining,
+            writeSize);
+        lastWriterFuture = underlyingWriter.flush();
+        remaining -= writeSize;
+      }
+      buffer.clear();
+    }
+
+    return lastWriterFuture;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java b/sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java
new file mode 100644
index 0000000..b8c7a19
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/SearchResultFutureImpl.java
@@ -0,0 +1,127 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.util.concurrent.ExecutorService;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.ResultFuture;
+import org.opends.sdk.ResultHandler;
+import org.opends.sdk.SearchResultHandler;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.*;
+
+
+
+/**
+ * Search result future implementation.
+ */
+final class SearchResultFutureImpl<P> extends
+    AbstractResultFutureImpl<Result, P> implements ResultFuture<Result>
+{
+
+  private final SearchResultHandler<P> searchResultHandler;
+
+  private final P p;
+
+  private final SearchRequest request;
+
+
+
+  SearchResultFutureImpl(int messageID, SearchRequest request,
+      ResultHandler<Result, P> resultHandler,
+      SearchResultHandler<P> searchResultHandler, P p,
+      LDAPConnection connection, ExecutorService handlerExecutor)
+  {
+    super(messageID, resultHandler, p, connection, handlerExecutor);
+    this.request = request;
+    this.searchResultHandler = searchResultHandler;
+    this.p = p;
+  }
+
+
+
+  synchronized void handleSearchResultEntry(
+      final SearchResultEntry entry)
+  {
+    if (!isDone())
+    {
+      if (searchResultHandler != null)
+      {
+        invokeHandler(new Runnable()
+        {
+          public void run()
+          {
+            searchResultHandler.handleEntry(p, entry);
+          }
+        });
+      }
+    }
+  }
+
+
+
+  synchronized void handleSearchResultReference(
+      final SearchResultReference reference)
+  {
+    if (!isDone())
+    {
+      if (searchResultHandler != null)
+      {
+        invokeHandler(new Runnable()
+        {
+          public void run()
+          {
+            searchResultHandler.handleReference(p, reference);
+          }
+        });
+      }
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Result newErrorResult(ResultCode resultCode,
+      String diagnosticMessage, Throwable cause)
+  {
+    return Responses.newResult(resultCode).setDiagnosticMessage(
+        diagnosticMessage).setCause(cause);
+  }
+
+
+
+  SearchRequest getRequest()
+  {
+    return request;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java b/sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java
new file mode 100644
index 0000000..d854fd4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/UnexpectedRequestException.java
@@ -0,0 +1,71 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.requests.Request;
+
+
+
+/**
+ * Thrown when an expected LDAP request is received.
+ */
+@SuppressWarnings("serial")
+final class UnexpectedRequestException extends IOException
+{
+  private final int messageID;
+  private final Request request;
+
+
+
+  public UnexpectedRequestException(int messageID, Request request)
+  {
+    super(Message.raw("Unexpected LDAP request: id=%d, message=%s",
+        messageID, request).toString());
+    this.messageID = messageID;
+    this.request = request;
+  }
+
+
+
+  public int getMessageID()
+  {
+    return messageID;
+  }
+
+
+
+  public Request getRequest()
+  {
+    return request;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java b/sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java
new file mode 100644
index 0000000..1f038dd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/UnexpectedResponseException.java
@@ -0,0 +1,71 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.responses.Response;
+
+
+
+/**
+ * Thrown when an unexpected LDAP response is received.
+ */
+@SuppressWarnings("serial")
+final class UnexpectedResponseException extends IOException
+{
+  private final int messageID;
+  private final Response response;
+
+
+
+  public UnexpectedResponseException(int messageID, Response response)
+  {
+    super(Message.raw("Unexpected LDAP response: id=%d, message=%s",
+        messageID, response).toString());
+    this.messageID = messageID;
+    this.response = response;
+  }
+
+
+
+  public int getMessageID()
+  {
+    return messageID;
+  }
+
+
+
+  public Response getResponse()
+  {
+    return response;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java b/sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java
new file mode 100644
index 0000000..d2ae7d5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldap/UnsupportedMessageException.java
@@ -0,0 +1,81 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldap;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Thrown when an unsupported LDAP message is received.
+ */
+@SuppressWarnings("serial")
+final class UnsupportedMessageException extends IOException
+{
+  private final int id;
+  private final byte tag;
+  private final ByteString content;
+
+
+
+  public UnsupportedMessageException(int id, byte tag,
+      ByteString content)
+  {
+    super(org.opends.messages.Message.raw(
+        "Unsupported LDAP message: id=%d, tag=%d, content=%s", id, tag,
+        content).toString());
+    this.id = id;
+    this.tag = tag;
+    this.content = content;
+  }
+
+
+
+  public ByteString getContent()
+  {
+    return content;
+  }
+
+
+
+  public int getID()
+  {
+    return id;
+  }
+
+
+
+  public byte getTag()
+  {
+    return tag;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java b/sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java
new file mode 100644
index 0000000..54760c5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/AbstractLDIFReader.java
@@ -0,0 +1,891 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.*;
+import org.opends.sdk.util.*;
+
+
+
+/**
+ * Common LDIF reader functionality.
+ */
+abstract class AbstractLDIFReader extends AbstractLDIFStream
+{
+  static final class KeyValuePair
+  {
+    String key;
+
+    String value;
+  }
+
+
+
+  /**
+   * LDIF reader implementation interface.
+   */
+  interface LDIFReaderImpl
+  {
+
+    /**
+     * Closes any resources associated with this LDIF reader
+     * implementation.
+     *
+     * @throws IOException
+     *           If an error occurs while closing.
+     */
+    void close() throws IOException;
+
+
+
+    /**
+     * Reads the next line of LDIF from the underlying LDIF source.
+     * Implementations must remove trailing line delimiters.
+     *
+     * @return The next line of LDIF, or {@code null} if the end of the
+     *         LDIF source has been reached.
+     * @throws IOException
+     *           If an error occurs while reading from the LDIF source.
+     */
+    String readLine() throws IOException;
+  }
+
+
+
+  final class LDIFRecord
+  {
+    final Iterator<String> iterator;
+
+    final LinkedList<String> ldifLines;
+
+    final long lineNumber;
+
+
+
+    private LDIFRecord(long lineNumber, LinkedList<String> ldifLines)
+    {
+      this.lineNumber = lineNumber;
+      this.ldifLines = ldifLines;
+      this.iterator = ldifLines.iterator();
+    }
+  }
+
+
+
+  /**
+   * LDIF output stream writer implementation.
+   */
+  private final class LDIFReaderInputStreamImpl implements
+      LDIFReaderImpl
+  {
+
+    private BufferedReader reader;
+
+
+
+    /**
+     * Creates a new LDIF input stream reader implementation.
+     *
+     * @param in
+     *          The input stream to use.
+     */
+    LDIFReaderInputStreamImpl(InputStream in)
+    {
+      this.reader = new BufferedReader(new InputStreamReader(in));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws IOException
+    {
+      reader.close();
+      reader = null;
+    }
+
+
+
+    /**
+     *{@inheritDoc}
+     */
+    public String readLine() throws IOException
+    {
+      String line = null;
+      if (reader != null)
+      {
+        line = reader.readLine();
+        if (line == null)
+        {
+          // Automatically close.
+          close();
+        }
+      }
+      return line;
+    }
+  }
+
+
+
+  /**
+   * LDIF output stream writer implementation.
+   */
+  private final class LDIFReaderListImpl implements LDIFReaderImpl
+  {
+
+    private final Iterator<String> iterator;
+
+
+
+    /**
+     * Creates a new LDIF list reader.
+     *
+     * @param ldifLines
+     *          The string list.
+     */
+    LDIFReaderListImpl(List<String> ldifLines)
+    {
+      this.iterator = ldifLines.iterator();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws IOException
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     *{@inheritDoc}
+     */
+    public String readLine() throws IOException
+    {
+      if (iterator.hasNext())
+      {
+        return iterator.next();
+      }
+      else
+      {
+        return null;
+      }
+    }
+  }
+
+
+
+  boolean validateSchema = true;
+
+  private final LDIFReaderImpl impl;
+
+  private long lineNumber = 0;
+
+
+
+  /**
+   * Creates a new LDIF entry reader whose source is the provided input
+   * stream.
+   *
+   * @param in
+   *          The input stream to use.
+   */
+  AbstractLDIFReader(InputStream in)
+  {
+    Validator.ensureNotNull(in);
+    this.impl = new LDIFReaderInputStreamImpl(in);
+  }
+
+
+
+  /**
+   * Creates a new LDIF entry reader which will read lines of LDIF from
+   * the provided list.
+   *
+   * @param ldifLines
+   *          The list from which lines of LDIF should be read.
+   */
+  AbstractLDIFReader(List<String> ldifLines)
+  {
+    Validator.ensureNotNull(ldifLines);
+    this.impl = new LDIFReaderListImpl(ldifLines);
+  }
+
+
+
+  final void close0() throws IOException
+  {
+    impl.close();
+  }
+
+
+
+  final int parseColonPosition(LDIFRecord record, String ldifLine)
+      throws DecodeException
+  {
+    final int colonPos = ldifLine.indexOf(":");
+    if (colonPos <= 0)
+    {
+      final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+          record.lineNumber, ldifLine);
+      throw DecodeException.error(message);
+    }
+    return colonPos;
+  }
+
+
+
+  final ByteString parseSingleValue(LDIFRecord record, String ldifLine,
+      DN entryDN, int colonPos, String attrName) throws DecodeException
+  {
+
+    // Look at the character immediately after the colon. If there is
+    // none, then assume an attribute with an empty value. If it is
+    // another colon, then the value must be base64-encoded. If it is a
+    // less-than sign, then assume that it is a URL. Otherwise, it is a
+    // regular value.
+    final int length = ldifLine.length();
+    ByteString value;
+    if (colonPos == length - 1)
+    {
+      value = ByteString.empty();
+    }
+    else
+    {
+      final char c = ldifLine.charAt(colonPos + 1);
+      if (c == ':')
+      {
+        // The value is base64-encoded. Find the first non-blank
+        // character, take the rest of the line, and base64-decode it.
+        int pos = colonPos + 2;
+        while (pos < length && ldifLine.charAt(pos) == ' ')
+        {
+          pos++;
+        }
+
+        try
+        {
+          value = Base64.decode(ldifLine.substring(pos));
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          // The value did not have a valid base64-encoding.
+          final Message message = ERR_LDIF_COULD_NOT_BASE64_DECODE_ATTR
+              .get(entryDN.toString(), record.lineNumber, ldifLine, e
+                  .getMessageObject());
+          throw DecodeException.error(message);
+        }
+      }
+      else if (c == '<')
+      {
+        // Find the first non-blank character, decode the rest of the
+        // line as a URL, and read its contents.
+        int pos = colonPos + 2;
+        while (pos < length && ldifLine.charAt(pos) == ' ')
+        {
+          pos++;
+        }
+
+        URL contentURL;
+        try
+        {
+          contentURL = new URL(ldifLine.substring(pos));
+        }
+        catch (final Exception e)
+        {
+          // The URL was malformed or had an invalid protocol.
+          final Message message = ERR_LDIF_INVALID_URL.get(entryDN
+              .toString(), record.lineNumber, attrName, String
+              .valueOf(e));
+          throw DecodeException.error(message);
+        }
+
+        InputStream inputStream = null;
+        ByteStringBuilder builder = null;
+        try
+        {
+          builder = new ByteStringBuilder();
+          inputStream = contentURL.openConnection().getInputStream();
+
+          int bytesRead;
+          final byte[] buffer = new byte[4096];
+          while ((bytesRead = inputStream.read(buffer)) > 0)
+          {
+            builder.append(buffer, 0, bytesRead);
+          }
+
+          value = builder.toByteString();
+        }
+        catch (final Exception e)
+        {
+          // We were unable to read the contents of that URL for some
+          // reason.
+          final Message message = ERR_LDIF_URL_IO_ERROR.get(entryDN
+              .toString(), record.lineNumber, attrName, String
+              .valueOf(contentURL), String.valueOf(e));
+          throw DecodeException.error(message);
+        }
+        finally
+        {
+          if (inputStream != null)
+          {
+            try
+            {
+              inputStream.close();
+            }
+            catch (final Exception e)
+            {
+              // Ignore.
+            }
+          }
+        }
+      }
+      else
+      {
+        // The rest of the line should be the value. Skip over any
+        // spaces and take the rest of the line as the value.
+        int pos = colonPos + 1;
+        while (pos < length && ldifLine.charAt(pos) == ' ')
+        {
+          pos++;
+        }
+
+        value = ByteString.valueOf(ldifLine.substring(pos));
+      }
+    }
+    return value;
+  }
+
+
+
+  final LDIFRecord readLDIFRecord() throws IOException
+  {
+    // Read the entry lines into a buffer.
+    final StringBuilder lastLineBuilder = new StringBuilder();
+    final LinkedList<String> ldifLines = new LinkedList<String>();
+    long recordLineNumber = 0;
+
+    final int START = 0;
+    final int START_COMMENT_LINE = 1;
+    final int GOT_LDIF_LINE = 2;
+    final int GOT_COMMENT_LINE = 3;
+    final int APPENDING_LDIF_LINE = 4;
+
+    int state = START;
+
+    while (true)
+    {
+      final String line = readLine();
+
+      switch (state)
+      {
+      case START:
+        if (line == null)
+        {
+          // We have reached the end of the LDIF source.
+          return null;
+        }
+        else if (line.length() == 0)
+        {
+          // Skip leading blank lines.
+        }
+        else if (line.charAt(0) == '#')
+        {
+          // This is a comment at the start of the LDIF record.
+          state = START_COMMENT_LINE;
+        }
+        else if (isContinuationLine(line))
+        {
+          // Fatal: got a continuation line at the start of the record.
+          final Message message = ERR_LDIF_INVALID_LEADING_SPACE.get(
+              lineNumber, line);
+          throw DecodeException.fatalError(message);
+        }
+        else
+        {
+          // Got the first line of LDIF.
+          ldifLines.add(line);
+          recordLineNumber = lineNumber;
+          state = GOT_LDIF_LINE;
+        }
+        break;
+      case START_COMMENT_LINE:
+        if (line == null)
+        {
+          // We have reached the end of the LDIF source.
+          return null;
+        }
+        else if (line.length() == 0)
+        {
+          // Skip leading blank lines and comments.
+          state = START;
+        }
+        else if (line.charAt(0) == '#')
+        {
+          // This is another comment at the start of the LDIF record.
+        }
+        else if (isContinuationLine(line))
+        {
+          // Skip comment continuation lines.
+        }
+        else
+        {
+          // Got the first line of LDIF.
+          ldifLines.add(line);
+          recordLineNumber = lineNumber;
+          state = GOT_LDIF_LINE;
+        }
+        break;
+      case GOT_LDIF_LINE:
+        if (line == null)
+        {
+          // We have reached the end of the LDIF source.
+          return new LDIFRecord(recordLineNumber, ldifLines);
+        }
+        else if (line.length() == 0)
+        {
+          // We have reached the end of the LDIF record.
+          return new LDIFRecord(recordLineNumber, ldifLines);
+        }
+        else if (line.charAt(0) == '#')
+        {
+          // This is a comment.
+          state = GOT_COMMENT_LINE;
+        }
+        else if (isContinuationLine(line))
+        {
+          // Got a continuation line for the previous line.
+          lastLineBuilder.setLength(0);
+          lastLineBuilder.append(ldifLines.removeLast());
+          lastLineBuilder.append(line.substring(1));
+          state = APPENDING_LDIF_LINE;
+        }
+        else
+        {
+          // Got the next line of LDIF.
+          ldifLines.add(line);
+          state = GOT_LDIF_LINE;
+        }
+        break;
+      case GOT_COMMENT_LINE:
+        if (line == null)
+        {
+          // We have reached the end of the LDIF source.
+          return new LDIFRecord(recordLineNumber, ldifLines);
+        }
+        else if (line.length() == 0)
+        {
+          // We have reached the end of the LDIF record.
+          return new LDIFRecord(recordLineNumber, ldifLines);
+        }
+        else if (line.charAt(0) == '#')
+        {
+          // This is another comment.
+          state = GOT_COMMENT_LINE;
+        }
+        else if (isContinuationLine(line))
+        {
+          // Skip comment continuation lines.
+        }
+        else
+        {
+          // Got the next line of LDIF.
+          ldifLines.add(line);
+          state = GOT_LDIF_LINE;
+        }
+        break;
+      case APPENDING_LDIF_LINE:
+        if (line == null)
+        {
+          // We have reached the end of the LDIF source.
+          ldifLines.add(lastLineBuilder.toString());
+          return new LDIFRecord(recordLineNumber, ldifLines);
+        }
+        else if (line.length() == 0)
+        {
+          // We have reached the end of the LDIF record.
+          ldifLines.add(lastLineBuilder.toString());
+          return new LDIFRecord(recordLineNumber, ldifLines);
+        }
+        else if (line.charAt(0) == '#')
+        {
+          // This is a comment.
+          ldifLines.add(lastLineBuilder.toString());
+          state = GOT_COMMENT_LINE;
+        }
+        else if (isContinuationLine(line))
+        {
+          // Got another continuation line for the previous line.
+          lastLineBuilder.append(line.substring(1));
+        }
+        else
+        {
+          // Got the next line of LDIF.
+          ldifLines.add(lastLineBuilder.toString());
+          ldifLines.add(line);
+          state = GOT_LDIF_LINE;
+        }
+        break;
+      }
+    }
+  }
+
+
+
+  final void readLDIFRecordAttributeValue(LDIFRecord record,
+      String ldifLine, Entry entry) throws DecodeException
+  {
+    // Parse the attribute description.
+    final int colonPos = parseColonPosition(record, ldifLine);
+    final String attrDescr = ldifLine.substring(0, colonPos);
+
+    AttributeDescription attributeDescription;
+    try
+    {
+      attributeDescription = AttributeDescription.valueOf(attrDescr,
+          schema);
+    }
+    catch (final LocalizedIllegalArgumentException e)
+    {
+      throw DecodeException.error(e.getMessageObject());
+    }
+
+    // Now parse the attribute value.
+    final ByteString value = parseSingleValue(record, ldifLine, entry
+        .getName(), colonPos, attrDescr);
+
+    // Skip the attribute if requested before performing any schema
+    // checking: the attribute may have been excluded because it is
+    // known to violate the schema.
+    if (isAttributeExcluded(attributeDescription))
+    {
+      return;
+    }
+
+    // Ensure that the binary option is present if required.
+    if (!attributeDescription.getAttributeType().getSyntax()
+        .isBEREncodingRequired())
+    {
+      if (validateSchema
+          && attributeDescription.containsOption("binary"))
+      {
+        final Message message = ERR_LDIF_INVALID_ATTR_OPTION.get(entry
+            .getName().toString(), record.lineNumber, attrDescr);
+        throw DecodeException.error(message);
+      }
+    }
+    else
+    {
+      attributeDescription = AttributeDescription.create(
+          attributeDescription, "binary");
+    }
+
+    Attribute attribute = entry.getAttribute(attributeDescription);
+    if (attribute == null)
+    {
+      if (validateSchema)
+      {
+        final MessageBuilder invalidReason = new MessageBuilder();
+        if (!attributeDescription.getAttributeType().getSyntax()
+            .valueIsAcceptable(value, invalidReason))
+        {
+          final Message message = WARN_LDIF_VALUE_VIOLATES_SYNTAX.get(
+              entry.getName().toString(), record.lineNumber, value
+                  .toString(), attrDescr, invalidReason);
+          throw DecodeException.error(message);
+        }
+      }
+
+      attribute = new LinkedAttribute(attributeDescription, value);
+      entry.addAttribute(attribute);
+    }
+    else
+    {
+      if (validateSchema)
+      {
+        final MessageBuilder invalidReason = new MessageBuilder();
+        if (!attributeDescription.getAttributeType().getSyntax()
+            .valueIsAcceptable(value, invalidReason))
+        {
+          final Message message = WARN_LDIF_VALUE_VIOLATES_SYNTAX.get(
+              entry.getName().toString(), record.lineNumber, value
+                  .toString(), attrDescr, invalidReason);
+          throw DecodeException.error(message);
+        }
+
+        if (!attribute.add(value))
+        {
+          final Message message = WARN_LDIF_DUPLICATE_ATTR.get(entry
+              .getName().toString(), record.lineNumber, attrDescr,
+              value.toString());
+          throw DecodeException.error(message);
+        }
+
+        if (attributeDescription.getAttributeType().isSingleValue())
+        {
+          final Message message = ERR_LDIF_MULTIPLE_VALUES_FOR_SINGLE_VALUED_ATTR
+              .get(entry.getName().toString(), record.lineNumber,
+                  attrDescr);
+          throw DecodeException.error(message);
+        }
+      }
+      else
+      {
+        attribute.add(value);
+      }
+    }
+  }
+
+
+
+  final DN readLDIFRecordDN(LDIFRecord record) throws DecodeException
+  {
+    String ldifLine = record.iterator.next();
+    int colonPos = ldifLine.indexOf(":");
+    if (colonPos <= 0)
+    {
+      final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+          record.lineNumber, ldifLine.toString());
+      throw DecodeException.error(message);
+    }
+
+    String attrName = toLowerCase(ldifLine.substring(0, colonPos));
+    if (attrName.equals("version"))
+    {
+      // This is the version line, try the next line if there is one.
+      if (!record.iterator.hasNext())
+      {
+        return null;
+      }
+
+      ldifLine = record.iterator.next();
+      colonPos = ldifLine.indexOf(":");
+      if (colonPos <= 0)
+      {
+        final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+            record.lineNumber, ldifLine.toString());
+        throw DecodeException.error(message);
+      }
+
+      attrName = toLowerCase(ldifLine.substring(0, colonPos));
+    }
+
+    if (!attrName.equals("dn"))
+    {
+      final Message message = ERR_LDIF_NO_DN.get(record.lineNumber,
+          ldifLine.toString());
+      throw DecodeException.error(message);
+    }
+
+    // Look at the character immediately after the colon. If there is
+    // none, then assume the null DN. If it is another colon, then the
+    // DN must be base64-encoded. Otherwise, it may be one or more
+    // spaces.
+    final int length = ldifLine.length();
+    if (colonPos == length - 1)
+    {
+      return DN.rootDN();
+    }
+
+    String dnString = null;
+
+    if (ldifLine.charAt(colonPos + 1) == ':')
+    {
+      // The DN is base64-encoded. Find the first non-blank character
+      // and take the rest of the line and base64-decode it.
+      int pos = colonPos + 2;
+      while (pos < length && ldifLine.charAt(pos) == ' ')
+      {
+        pos++;
+      }
+
+      final String base64DN = ldifLine.substring(pos);
+      try
+      {
+        dnString = Base64.decode(base64DN).toString();
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        // The value did not have a valid base64-encoding.
+        final Message message = ERR_LDIF_COULD_NOT_BASE64_DECODE_DN
+            .get(record.lineNumber, ldifLine, e.getMessageObject());
+        throw DecodeException.error(message);
+      }
+    }
+    else
+    {
+      // The rest of the value should be the DN. Skip over any spaces
+      // and attempt to decode the rest of the line as the DN.
+      int pos = colonPos + 1;
+      while (pos < length && ldifLine.charAt(pos) == ' ')
+      {
+        pos++;
+      }
+
+      dnString = ldifLine.substring(pos);
+    }
+
+    try
+    {
+      return DN.valueOf(dnString, schema);
+    }
+    catch (final LocalizedIllegalArgumentException e)
+    {
+      final Message message = ERR_LDIF_INVALID_DN.get(
+          record.lineNumber, ldifLine, e.getMessageObject());
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  final String readLDIFRecordKeyValuePair(LDIFRecord record,
+      KeyValuePair pair, boolean allowBase64) throws DecodeException
+  {
+    final String ldifLine = record.iterator.next();
+    final int colonPos = ldifLine.indexOf(":");
+    if (colonPos <= 0)
+    {
+      final Message message = ERR_LDIF_NO_ATTR_NAME.get(
+          record.lineNumber, ldifLine);
+      throw DecodeException.error(message);
+    }
+    pair.key = ldifLine.substring(0, colonPos);
+
+    // Look at the character immediately after the colon. If there is
+    // none, then no value was specified. Throw an exception
+    final int length = ldifLine.length();
+    if (colonPos == length - 1)
+    {
+      // FIXME: improve error.
+      final Message message = Message
+          .raw("Malformed changetype attribute");
+      throw DecodeException.error(message);
+    }
+
+    if (allowBase64 && ldifLine.charAt(colonPos + 1) == ':')
+    {
+      // The value is base64-encoded. Find the first non-blank
+      // character, take the rest of the line, and base64-decode it.
+      int pos = colonPos + 2;
+      while (pos < length && ldifLine.charAt(pos) == ' ')
+      {
+        pos++;
+      }
+
+      try
+      {
+        pair.value = Base64.decode(ldifLine.substring(pos)).toString();
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        // The value did not have a valid base64-encoding.
+        // FIXME: improve error.
+        final Message message = Message
+            .raw("Malformed base64 changetype attribute");
+        throw DecodeException.error(message);
+      }
+    }
+    else
+    {
+      // The rest of the value should be the changetype. Skip over any
+      // spaces and attempt to decode the rest of the line as the
+      // changetype string.
+      int pos = colonPos + 1;
+      while (pos < length && ldifLine.charAt(pos) == ' ')
+      {
+        pos++;
+      }
+
+      pair.value = ldifLine.substring(pos);
+    }
+
+    return ldifLine;
+  }
+
+
+
+  final void rejectLDIFRecord(LDIFRecord record, Message message)
+      throws DecodeException
+  {
+    // FIXME: not yet implemented.
+    throw DecodeException.error(message);
+  }
+
+
+
+  final void skipLDIFRecord(LDIFRecord record, Message message)
+  {
+    // FIXME: not yet implemented.
+  }
+
+
+
+  // Determine whether the provided line is a continuation line. Note
+  // that while RFC 2849 technically only allows a space in this
+  // position, both OpenLDAP and the Sun Java System Directory Server
+  // allow a tab as well, so we will too for compatibility reasons. See
+  // issue #852 for details.
+  private boolean isContinuationLine(String line)
+  {
+    return line.charAt(0) == ' ' || line.charAt(0) == '\t';
+  }
+
+
+
+  private String readLine() throws IOException
+  {
+    final String line = impl.readLine();
+    if (line != null)
+    {
+      lineNumber++;
+    }
+    return line;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java b/sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java
new file mode 100644
index 0000000..5e7ca7e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/AbstractLDIFStream.java
@@ -0,0 +1,174 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.Matcher;
+import org.opends.sdk.schema.AttributeType;
+import org.opends.sdk.schema.Schema;
+
+
+
+/**
+ * Common LDIF reader/writer functionality.
+ */
+abstract class AbstractLDIFStream
+{
+
+  final Set<AttributeDescription> excludeAttributes =
+      new HashSet<AttributeDescription>();
+
+  boolean excludeOperationalAttributes = false;
+
+  boolean excludeUserAttributes = false;
+
+  final Set<AttributeDescription> includeAttributes =
+      new HashSet<AttributeDescription>();
+
+  Schema schema = Schema.getDefaultSchema();
+
+  final Set<DN> includeBranches = new HashSet<DN>();
+
+  final Set<DN> excludeBranches = new HashSet<DN>();
+
+  final List<Matcher> includeFilters = new LinkedList<Matcher>();
+
+  final List<Matcher> excludeFilters = new LinkedList<Matcher>();
+
+
+
+  /**
+   * Creates a new abstract LDIF stream.
+   */
+  AbstractLDIFStream()
+  {
+    // Nothing to do.
+  }
+
+
+
+  final boolean isAttributeExcluded(
+      AttributeDescription attributeDescription)
+  {
+    if (!excludeAttributes.isEmpty()
+        && excludeAttributes.contains(attributeDescription))
+    {
+      return true;
+    }
+
+    // Let explicit include override more general exclude.
+    if (!includeAttributes.isEmpty())
+    {
+      return !includeAttributes.contains(attributeDescription);
+    }
+
+    final AttributeType type = attributeDescription.getAttributeType();
+
+    if (excludeOperationalAttributes && type.isOperational())
+    {
+      return true;
+    }
+
+    if (excludeUserAttributes && !type.isOperational())
+    {
+      return true;
+    }
+
+    return false;
+  }
+
+
+
+  final boolean isBranchExcluded(DN dn)
+  {
+    if (!excludeBranches.isEmpty())
+    {
+      for (final DN excludeBranch : excludeBranches)
+      {
+        if (excludeBranch.isSuperiorOrEqualTo(dn))
+        {
+          return true;
+        }
+      }
+    }
+
+    if (!includeBranches.isEmpty())
+    {
+      for (final DN includeBranch : includeBranches)
+      {
+        if (includeBranch.isSuperiorOrEqualTo(dn))
+        {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+
+
+  final boolean isEntryExcluded(Entry entry)
+  {
+    if (!excludeFilters.isEmpty())
+    {
+      for (final Matcher excludeFilter : excludeFilters)
+      {
+        if (excludeFilter.matches(entry).toBoolean())
+        {
+          return true;
+        }
+      }
+    }
+
+    if (!includeFilters.isEmpty())
+    {
+      for (final Matcher includeFilter : includeFilters)
+      {
+        if (includeFilter.matches(entry).toBoolean())
+        {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    return false;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java b/sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java
new file mode 100644
index 0000000..ecf3c1f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/AbstractLDIFWriter.java
@@ -0,0 +1,545 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Base64;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Common LDIF writer functionality.
+ */
+abstract class AbstractLDIFWriter extends AbstractLDIFStream
+{
+
+  /**
+   * LDIF writer implementation interface.
+   */
+  interface LDIFWriterImpl
+  {
+
+    /**
+     * Closes any resources associated with this LDIF writer
+     * implementation.
+     * 
+     * @throws IOException
+     *           If an error occurs while closing.
+     */
+    void close() throws IOException;
+
+
+
+    /**
+     * Flushes this LDIF writer implementation so that any buffered data
+     * is written immediately to underlying stream, flushing the stream
+     * if it is also {@code Flushable}.
+     * <p>
+     * If the intended destination of this stream is an abstraction
+     * provided by the underlying operating system, for example a file,
+     * then flushing the stream guarantees only that bytes previously
+     * written to the stream are passed to the operating system for
+     * writing; it does not guarantee that they are actually written to
+     * a physical device such as a disk drive.
+     * 
+     * @throws IOException
+     *           If an error occurs while flushing.
+     */
+    void flush() throws IOException;
+
+
+
+    /**
+     * Prints the provided {@code CharSequence}. Implementations must
+     * not add a new-line character sequence.
+     * 
+     * @param s
+     *          The {@code CharSequence} to be printed.
+     * @throws IOException
+     *           If an error occurs while printing {@code s}.
+     */
+    void print(CharSequence s) throws IOException;
+
+
+
+    /**
+     * Prints a new-line character sequence.
+     * 
+     * @throws IOException
+     *           If an error occurs while printing the new-line
+     *           character sequence.
+     */
+    void println() throws IOException;
+  }
+
+
+
+  /**
+   * LDIF string list writer implementation.
+   */
+  private final class LDIFWriterListImpl implements LDIFWriterImpl
+  {
+
+    private final StringBuilder builder = new StringBuilder();
+
+    private final List<String> ldifLines;
+
+
+
+    /**
+     * Creates a new LDIF list writer.
+     * 
+     * @param ldifLines
+     *          The string list.
+     */
+    LDIFWriterListImpl(List<String> ldifLines)
+    {
+      this.ldifLines = ldifLines;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws IOException
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void flush() throws IOException
+    {
+      // Nothing to do.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void print(CharSequence s) throws IOException
+    {
+      builder.append(s);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void println() throws IOException
+    {
+      ldifLines.add(builder.toString());
+      builder.setLength(0);
+    }
+  }
+
+
+
+  /**
+   * LDIF output stream writer implementation.
+   */
+  private final class LDIFWriterOutputStreamImpl implements
+      LDIFWriterImpl
+  {
+
+    private final BufferedWriter writer;
+
+
+
+    /**
+     * Creates a new LDIF output stream writer.
+     * 
+     * @param out
+     *          The output stream.
+     */
+    LDIFWriterOutputStreamImpl(OutputStream out)
+    {
+      this.writer = new BufferedWriter(new OutputStreamWriter(out));
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws IOException
+    {
+      writer.close();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void flush() throws IOException
+    {
+      writer.flush();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void print(CharSequence s) throws IOException
+    {
+      writer.append(s);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void println() throws IOException
+    {
+      writer.newLine();
+    }
+  }
+
+  // Regular expression used for splitting comments on line-breaks.
+  private static final Pattern SPLIT_NEWLINE =
+      Pattern.compile("\\r?\\n");
+
+  boolean addUserFriendlyComments = false;
+
+  final LDIFWriterImpl impl;
+
+  int wrapColumn = 0;
+
+  private final StringBuilder builder = new StringBuilder(80);
+
+
+
+  /**
+   * Creates a new LDIF entry writer which will append lines of LDIF to
+   * the provided list.
+   * 
+   * @param ldifLines
+   *          The list to which lines of LDIF should be appended.
+   */
+  public AbstractLDIFWriter(List<String> ldifLines)
+  {
+    Validator.ensureNotNull(ldifLines);
+    this.impl = new LDIFWriterListImpl(ldifLines);
+  }
+
+
+
+  /**
+   * Creates a new LDIF entry writer whose destination is the provided
+   * output stream.
+   * 
+   * @param out
+   *          The output stream to use.
+   */
+  public AbstractLDIFWriter(OutputStream out)
+  {
+    Validator.ensureNotNull(out);
+    this.impl = new LDIFWriterOutputStreamImpl(out);
+  }
+
+
+
+  final void close0() throws IOException
+  {
+    flush0();
+    impl.close();
+  }
+
+
+
+  final void flush0() throws IOException
+  {
+    impl.flush();
+  }
+
+
+
+  final void writeComment0(CharSequence comment) throws IOException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(comment);
+
+    // First, break up the comment into multiple lines to preserve the
+    // original spacing that it contained.
+    final String[] lines = SPLIT_NEWLINE.split(comment);
+
+    // Now iterate through the lines and write them out, prefixing and
+    // wrapping them as necessary.
+    for (final String line : lines)
+    {
+      if (!shouldWrap())
+      {
+        impl.print("# ");
+        impl.print(line);
+        impl.println();
+      }
+      else
+      {
+        final int breakColumn = wrapColumn - 2;
+
+        if (line.length() <= breakColumn)
+        {
+          impl.print("# ");
+          impl.print(line);
+          impl.println();
+        }
+        else
+        {
+          int startPos = 0;
+          outerLoop: while (startPos < line.length())
+          {
+            if (startPos + breakColumn >= line.length())
+            {
+              impl.print("# ");
+              impl.print(line.substring(startPos));
+              impl.println();
+              startPos = line.length();
+            }
+            else
+            {
+              final int endPos = startPos + breakColumn;
+
+              int i = endPos - 1;
+              while (i > startPos)
+              {
+                if (line.charAt(i) == ' ')
+                {
+                  impl.print("# ");
+                  impl.print(line.substring(startPos, i));
+                  impl.println();
+
+                  startPos = i + 1;
+                  continue outerLoop;
+                }
+
+                i--;
+              }
+
+              // If we've gotten here, then there are no spaces on the
+              // entire line. If that happens, then we'll have to break
+              // in the middle of a word.
+              impl.print("# ");
+              impl.print(line.substring(startPos, endPos));
+              impl.println();
+
+              startPos = endPos;
+            }
+          }
+        }
+      }
+    }
+  }
+
+
+
+  final void writeControls(Iterable<Control> controls)
+      throws IOException
+  {
+    for (final Control control : controls)
+    {
+      final StringBuilder key = new StringBuilder("control: ");
+      key.append(control.getOID());
+      key.append(control.isCritical() ? " true" : " false");
+
+      if (control.hasValue())
+      {
+        writeKeyAndValue(key, control.getValue());
+      }
+      else
+      {
+        writeLine(key);
+      }
+    }
+  }
+
+
+
+  final void writeKeyAndValue(CharSequence key, ByteSequence value)
+      throws IOException
+  {
+    builder.setLength(0);
+
+    // If the value is empty, then just append a single colon and a
+    // single space.
+    if (value.length() == 0)
+    {
+      builder.append(key);
+      builder.append(": ");
+    }
+    else if (needsBase64Encoding(value))
+    {
+      if (addUserFriendlyComments)
+      {
+        // TODO: Only display comments for valid UTF-8 values, not
+        // binary values.
+      }
+
+      builder.setLength(0);
+      builder.append(key);
+      builder.append(":: ");
+      builder.append(Base64.encode(value));
+    }
+    else
+    {
+      builder.append(key);
+      builder.append(": ");
+      builder.append(value.toString());
+    }
+
+    writeLine(builder);
+  }
+
+
+
+  final void writeKeyAndValue(CharSequence key, CharSequence value)
+      throws IOException
+  {
+    // FIXME: We should optimize this at some point.
+    writeKeyAndValue(key, ByteString.valueOf(value.toString()));
+  }
+
+
+
+  final void writeLine(CharSequence line) throws IOException
+  {
+    final int length = line.length();
+    if (shouldWrap() && length > wrapColumn)
+    {
+      impl.print(line.subSequence(0, wrapColumn));
+      impl.println();
+      int pos = wrapColumn;
+      while (pos < length)
+      {
+        final int writeLength = Math.min(wrapColumn - 1, length - pos);
+        impl.print(" ");
+        impl.print(line.subSequence(pos, pos + writeLength));
+        impl.println();
+        pos += wrapColumn - 1;
+      }
+    }
+    else
+    {
+      impl.print(line);
+      impl.println();
+    }
+  }
+
+
+
+  private boolean needsBase64Encoding(ByteSequence bytes)
+  {
+    final int length = bytes.length();
+    if (length == 0)
+    {
+      return false;
+    }
+
+    // If the value starts with a space, colon, or less than, then it
+    // needs to be base64 encoded.
+    switch (bytes.byteAt(0))
+    {
+    case 0x20: // Space
+    case 0x3A: // Colon
+    case 0x3C: // Less-than
+      return true;
+    }
+
+    // If the value ends with a space, then it needs to be
+    // base64 encoded.
+    if (length > 1 && bytes.byteAt(length - 1) == 0x20)
+    {
+      return true;
+    }
+
+    // If the value contains a null, newline, or return character, then
+    // it needs to be base64 encoded.
+    byte b;
+    for (int i = 0; i < bytes.length(); i++)
+    {
+      b = bytes.byteAt(i);
+      if (b > 127 || b < 0)
+      {
+        return true;
+      }
+
+      switch (b)
+      {
+      case 0x00: // Null
+      case 0x0A: // New line
+      case 0x0D: // Carriage return
+        return true;
+      }
+    }
+
+    // If we've made it here, then there's no reason to base64 encode.
+    return false;
+  }
+
+
+
+  private boolean shouldWrap()
+  {
+    return wrapColumn > 1;
+  }
+
+
+
+  @SuppressWarnings("unused")
+  private void writeKeyAndURL(CharSequence key, CharSequence url)
+      throws IOException
+  {
+    builder.setLength(0);
+
+    builder.append(key);
+    builder.append(":: ");
+    builder.append(url);
+
+    writeLine(builder);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecord.java b/sdk/src/org/opends/sdk/ldif/ChangeRecord.java
new file mode 100644
index 0000000..37a1167
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecord.java
@@ -0,0 +1,71 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+import org.opends.sdk.DN;
+
+
+
+/**
+ * A request to modify the content of the Directory in some way. A
+ * change record represents one of the following operations:
+ * <ul>
+ * <li>An {@code Add} operation.
+ * <li>An {@code Delete} operation.
+ * <li>An {@code Modify} operation.
+ * <li>An {@code ModifyDN} operation.
+ * </ul>
+ */
+public interface ChangeRecord
+{
+  /**
+   * Applies a {@code ChangeRecordVisitor} to this {@code ChangeRecord}.
+   *
+   * @param <R>
+   *          The return type of the visitor's methods.
+   * @param <P>
+   *          The type of the additional parameters to the visitor's
+   *          methods.
+   * @param v
+   *          The change record visitor.
+   * @param p
+   *          Optional additional visitor parameter.
+   * @return A result as specified by the visitor.
+   */
+  <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+  /**
+   * Returns the distinguished name of the entry being modified by this
+   * {@code ChangeRecord}.
+   *
+   * @return The distinguished name of the entry being modified.
+   */
+  DN getName();
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java
new file mode 100644
index 0000000..8894159
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordReader.java
@@ -0,0 +1,86 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.opends.sdk.DecodeException;
+
+
+
+/**
+ * An interface for reading change records from a data source, typically
+ * an LDIF file.
+ * <p>
+ * Implementations must specify the following:
+ * <ul>
+ * <li>Whether or not it is possible for the implementation to encounter
+ * malformed change records and, if it is possible, how they are
+ * handled.
+ * <li>Any synchronization limitations.
+ * </ul>
+ * <p>
+ * TODO: LDIFInputStreamReader
+ * <p>
+ * TODO: SearchResultEntryReader
+ */
+public interface ChangeRecordReader extends Closeable
+{
+
+  /**
+   * Closes this change record reader if it not already closed. Note
+   * that this method does not need to be called if a previous call of
+   * {@link #readChangeRecord()} has returned {@code null}.
+   *
+   * @throws IOException
+   *           If an unexpected IO error occurred while closing.
+   */
+  void close() throws IOException;
+
+
+
+  /**
+   * Reads the next change record, blocking if necessary until a change
+   * record is available. If the next change record does not contain a
+   * change type then it will be treated as an {@code Add} change
+   * record.
+   *
+   * @return The next change record, or {@code null} if there are no
+   *         more change records to be read.
+   * @throws DecodeException
+   *           If the change record could not be decoded because it was
+   *           malformed.
+   * @throws IOException
+   *           If an unexpected IO error occurred while reading the
+   *           change record.
+   */
+  ChangeRecord readChangeRecord() throws DecodeException, IOException;
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java
new file mode 100644
index 0000000..6cf718a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitor.java
@@ -0,0 +1,110 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+
+
+
+/**
+ * A visitor of {@code ChangeRecord}s, in the style of the visitor
+ * design pattern.
+ * <p>
+ * Classes implementing this interface can query change records in a
+ * type-safe manner. When a visitor is passed to a change record's
+ * accept method, the corresponding visit method most applicable to that
+ * change record is invoked.
+ *
+ * @param <R>
+ *          The return type of this visitor's methods. Use
+ *          {@link java.lang.Void} for visitors that do not need to
+ *          return results.
+ * @param <P>
+ *          The type of the additional parameter to this visitor's
+ *          methods. Use {@link java.lang.Void} for visitors that do not
+ *          need an additional parameter.
+ */
+public interface ChangeRecordVisitor<R, P>
+{
+
+  /**
+   * Visits an {@code Add} change record.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param change
+   *          The {@code Add} change record.
+   * @return Returns a visitor specified result.
+   */
+  R visitChangeRecord(P p, AddRequest change);
+
+
+
+  /**
+   * Visits an {@code Delete} change record.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param change
+   *          The {@code Delete} change record.
+   * @return Returns a visitor specified result.
+   */
+  R visitChangeRecord(P p, DeleteRequest change);
+
+
+
+  /**
+   * Visits an {@code ModifyDN} change record.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param change
+   *          The {@code ModifyDN} change record.
+   * @return Returns a visitor specified result.
+   */
+  R visitChangeRecord(P p, ModifyDNRequest change);
+
+
+
+  /**
+   * Visits an {@code Modify} change record.
+   *
+   * @param p
+   *          A visitor specified parameter.
+   * @param change
+   *          The {@code Modify} change record.
+   * @return Returns a visitor specified result.
+   */
+  R visitChangeRecord(P p, ModifyRequest change);
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java
new file mode 100644
index 0000000..6a09c24
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordVisitorWriter.java
@@ -0,0 +1,106 @@
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+
+
+
+/**
+ * A visitor which can be used to write generic change records.
+ */
+final class ChangeRecordVisitorWriter implements
+    ChangeRecordVisitor<IOException, ChangeRecordWriter>
+{
+  // Visitor used for writing generic change records.
+  private static final ChangeRecordVisitorWriter VISITOR =
+      new ChangeRecordVisitorWriter();
+
+
+
+  /**
+   * Returns the singleton instance.
+   * 
+   * @return The instance.
+   */
+  static ChangeRecordVisitorWriter getInstance()
+  {
+    return VISITOR;
+  }
+
+
+
+  private ChangeRecordVisitorWriter()
+  {
+    // Nothing to do.
+  }
+
+
+
+  public IOException visitChangeRecord(ChangeRecordWriter p,
+      AddRequest change)
+  {
+    try
+    {
+      p.writeChangeRecord(change);
+      return null;
+    }
+    catch (final IOException e)
+    {
+      return e;
+    }
+  }
+
+
+
+  public IOException visitChangeRecord(ChangeRecordWriter p,
+      DeleteRequest change)
+  {
+    try
+    {
+      p.writeChangeRecord(change);
+      return null;
+    }
+    catch (final IOException e)
+    {
+      return e;
+    }
+  }
+
+
+
+  public IOException visitChangeRecord(ChangeRecordWriter p,
+      ModifyDNRequest change)
+  {
+    try
+    {
+      p.writeChangeRecord(change);
+      return null;
+    }
+    catch (final IOException e)
+    {
+      return e;
+    }
+  }
+
+
+
+  public IOException visitChangeRecord(ChangeRecordWriter p,
+      ModifyRequest change)
+  {
+    try
+    {
+      p.writeChangeRecord(change);
+      return null;
+    }
+    catch (final IOException e)
+    {
+      return e;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java b/sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java
new file mode 100644
index 0000000..8c5a713
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ChangeRecordWriter.java
@@ -0,0 +1,185 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+
+
+
+/**
+ * An interface for writing change records to a data source, typically
+ * an LDIF file.
+ * <p>
+ * TODO: FilteredChangeRecordWriter
+ */
+public interface ChangeRecordWriter extends Closeable, Flushable
+{
+  /**
+   * Closes this change record writer, flushing it first. Closing a
+   * previously closed change record writer has no effect.
+   *
+   * @throws IOException
+   *           If an unexpected IO error occurred while closing.
+   */
+  void close() throws IOException;
+
+
+
+  /**
+   * Flushes this change record writer so that any buffered data is
+   * written immediately to underlying stream, flushing the stream if it
+   * is also {@code Flushable}.
+   * <p>
+   * If the intended destination of this stream is an abstraction
+   * provided by the underlying operating system, for example a file,
+   * then flushing the stream guarantees only that bytes previously
+   * written to the stream are passed to the operating system for
+   * writing; it does not guarantee that they are actually written to a
+   * physical device such as a disk drive.
+   *
+   * @throws IOException
+   *           If an unexpected IO error occurred while flushing.
+   */
+  void flush() throws IOException;
+
+
+
+  /**
+   * Writes an {@code Add} change record.
+   *
+   * @param change
+   *          The {@code AddRequest} to be written as an {@code Add}
+   *          change record.
+   * @return A reference to this change record writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           change record.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  ChangeRecordWriter writeChangeRecord(AddRequest change)
+      throws IOException, NullPointerException;
+
+
+
+  /**
+   * Writes a change record.
+   *
+   * @param change
+   *          The {@code ChangeRecord} to be written.
+   * @return A reference to this change record writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           change record.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  ChangeRecordWriter writeChangeRecord(ChangeRecord change)
+      throws IOException, NullPointerException;
+
+
+
+  /**
+   * Writes a {@code Delete} change record.
+   *
+   * @param change
+   *          The {@code DeleteRequest} to be written as an {@code
+   *          Delete} change record.
+   * @return A reference to this change record writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           change record.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  ChangeRecordWriter writeChangeRecord(DeleteRequest change)
+      throws IOException, NullPointerException;
+
+
+
+  /**
+   * Writes a {@code ModifyDN} change record.
+   *
+   * @param change
+   *          The {@code ModifyDNRequest} to be written as an {@code
+   *          ModifyDN} change record.
+   * @return A reference to this change record writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           change record.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  ChangeRecordWriter writeChangeRecord(ModifyDNRequest change)
+      throws IOException, NullPointerException;
+
+
+
+  /**
+   * Writes a {@code Modify} change record.
+   *
+   * @param change
+   *          The {@code ModifyRequest} to be written as an {@code
+   *          Modify} change record.
+   * @return A reference to this change record writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           change record.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  ChangeRecordWriter writeChangeRecord(ModifyRequest change)
+      throws IOException, NullPointerException;
+
+
+
+  /**
+   * Writes a comment.
+   *
+   * @param comment
+   *          The {@code CharSequence} to be written as a comment.
+   * @return A reference to this change record writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           comment.
+   * @throws NullPointerException
+   *           If {@code comment} was {@code null}.
+   */
+  ChangeRecordWriter writeComment(CharSequence comment)
+      throws IOException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java b/sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java
new file mode 100644
index 0000000..fd63d88
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ConnectionChangeRecordWriter.java
@@ -0,0 +1,323 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+
+import org.opends.sdk.Connection;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ErrorResultIOException;
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A {@code ConnectionChangeRecordWriter} is a bridge from {@code
+ * Connection}s to {@code ChangeRecordWriter}s. A connection change
+ * record writer writes change records by sending appropriate update
+ * requests (Add, Delete, Modify, or ModifyDN) to an underlying
+ * connection.
+ * <p>
+ * All update requests are performed synchronously, blocking until an
+ * update result is received. If an update result indicates that an
+ * update request has failed for some reason then the error result is
+ * propagated to the caller using an {@code ErrorResultIOException}.
+ * <p>
+ * <b>Note:</b> comments are not supported by connection change record
+ * writers. Attempts to write comments will be ignored.
+ */
+public final class ConnectionChangeRecordWriter implements
+    ChangeRecordWriter
+{
+  private final Connection connection;
+
+
+
+  /**
+   * Creates a new connection change record writer whose destination is
+   * the provided connection.
+   *
+   * @param connection
+   *          The connection to use.
+   * @throws NullPointerException
+   *           If {@code connection} was {@code null}.
+   */
+  public ConnectionChangeRecordWriter(Connection connection)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(connection);
+    this.connection = connection;
+  }
+
+
+
+  /**
+   * Closes this connection change record writer, including the
+   * underlying connection. Closing a previously closed change record
+   * writer has no effect.
+   */
+  public void close()
+  {
+    connection.close();
+  }
+
+
+
+  /**
+   * Connection change record writers do not require flushing, so this
+   * method has no effect.
+   */
+  public void flush()
+  {
+    // Do nothing.
+  }
+
+
+
+  /**
+   * Writes the provided Add request to the underlying connection,
+   * blocking until the request completes.
+   *
+   * @param change
+   *          The {@code AddRequest} to be written.
+   * @return A reference to this connection change record writer.
+   * @throws ErrorResultIOException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedIOException
+   *           If the current thread was interrupted while waiting.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  public ConnectionChangeRecordWriter writeChangeRecord(
+      AddRequest change) throws ErrorResultIOException,
+      InterruptedIOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+    try
+    {
+      connection.add(change);
+    }
+    catch (final ErrorResultException e)
+    {
+      throw new ErrorResultIOException(e);
+    }
+    catch (InterruptedException e)
+    {
+      throw new InterruptedIOException(e.getMessage());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Writes the provided change record to the underlying connection,
+   * blocking until the request completes.
+   *
+   * @param change
+   *          The change record to be written.
+   * @return A reference to this connection change record writer.
+   * @throws ErrorResultIOException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedIOException
+   *           If the current thread was interrupted while waiting.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  public ConnectionChangeRecordWriter writeChangeRecord(
+      ChangeRecord change) throws ErrorResultIOException,
+      InterruptedIOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+
+    final IOException e = change.accept(ChangeRecordVisitorWriter
+        .getInstance(), this);
+    try
+    {
+      if (e != null)
+      {
+        throw e;
+      }
+    }
+    catch (final ErrorResultIOException e1)
+    {
+      throw e1;
+    }
+    catch (InterruptedIOException e1)
+    {
+      throw e1;
+    }
+    catch (final IOException e1)
+    {
+      // Should not happen.
+      throw new RuntimeException(e1);
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Writes the provided Delete request to the underlying connection,
+   * blocking until the request completes.
+   *
+   * @param change
+   *          The {@code DeleteRequest} to be written.
+   * @return A reference to this connection change record writer.
+   * @throws ErrorResultIOException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedIOException
+   *           If the current thread was interrupted while waiting.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  public ConnectionChangeRecordWriter writeChangeRecord(
+      DeleteRequest change) throws ErrorResultIOException,
+      InterruptedIOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+    try
+    {
+      connection.delete(change);
+    }
+    catch (final ErrorResultException e)
+    {
+      throw new ErrorResultIOException(e);
+    }
+    catch (InterruptedException e)
+    {
+      throw new InterruptedIOException(e.getMessage());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Writes the provided ModifyDN request to the underlying connection,
+   * blocking until the request completes.
+   *
+   * @param change
+   *          The {@code ModifyDNRequest} to be written.
+   * @return A reference to this connection change record writer.
+   * @throws ErrorResultIOException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedIOException
+   *           If the current thread was interrupted while waiting.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  public ConnectionChangeRecordWriter writeChangeRecord(
+      ModifyDNRequest change) throws ErrorResultIOException,
+      InterruptedIOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+    try
+    {
+      connection.modifyDN(change);
+    }
+    catch (final ErrorResultException e)
+    {
+      throw new ErrorResultIOException(e);
+    }
+    catch (InterruptedException e)
+    {
+      throw new InterruptedIOException(e.getMessage());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Writes the provided Modify request to the underlying connection,
+   * blocking until the request completes.
+   *
+   * @param change
+   *          The {@code ModifyRequest} to be written.
+   * @return A reference to this connection change record writer.
+   * @throws ErrorResultIOException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedIOException
+   *           If the current thread was interrupted while waiting.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  public ConnectionChangeRecordWriter writeChangeRecord(
+      ModifyRequest change) throws ErrorResultIOException,
+      InterruptedIOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+    try
+    {
+      connection.modify(change);
+    }
+    catch (final ErrorResultException e)
+    {
+      throw new ErrorResultIOException(e);
+    }
+    catch (InterruptedException e)
+    {
+      throw new InterruptedIOException(e.getMessage());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Connection change record writers do not support comments, so the
+   * provided comment will be ignored.
+   *
+   * @param comment
+   *          The {@code CharSequence} to be written as a comment.
+   * @return A reference to this connection change record writer.
+   * @throws NullPointerException
+   *           If {@code comment} was {@code null}.
+   */
+  public ConnectionChangeRecordWriter writeComment(CharSequence comment)
+  {
+    Validator.ensureNotNull(comment);
+
+    // Do nothing.
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java b/sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java
new file mode 100644
index 0000000..71acb1d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/ConnectionEntryWriter.java
@@ -0,0 +1,156 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.InterruptedIOException;
+
+import org.opends.sdk.Connection;
+import org.opends.sdk.Entry;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ErrorResultIOException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A {@code ConnectionEntryWriter} is a bridge from {@code Connection}s
+ * to {@code EntryWriter}s. A connection entry writer writes entries by
+ * sending Add requests to an underlying connection.
+ * <p>
+ * All Add requests are performed synchronously, blocking until an Add
+ * result is received. If an Add result indicates that an Add request
+ * has failed for some reason then the error result is propagated to the
+ * caller using an {@code ErrorResultIOException}.
+ * <p>
+ * <b>Note:</b> comments are not supported by connection change record
+ * writers. Attempts to write comments will be ignored.
+ */
+public final class ConnectionEntryWriter implements EntryWriter
+{
+  private final Connection connection;
+
+
+
+  /**
+   * Creates a new connection entry writer whose destination is the
+   * provided connection.
+   *
+   * @param connection
+   *          The connection to use.
+   * @throws NullPointerException
+   *           If {@code connection} was {@code null}.
+   */
+  public ConnectionEntryWriter(Connection connection)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(connection);
+    this.connection = connection;
+  }
+
+
+
+  /**
+   * Closes this connection entry writer, including the underlying
+   * connection. Closing a previously closed entry writer has no effect.
+   */
+  public void close()
+  {
+    connection.close();
+  }
+
+
+
+  /**
+   * Connection entry writers do not require flushing, so this method
+   * has no effect.
+   */
+  public void flush()
+  {
+    // Do nothing.
+  }
+
+
+
+  /**
+   * Connection entry writers do not support comments, so the provided
+   * comment will be ignored.
+   *
+   * @param comment
+   *          The {@code CharSequence} to be written as a comment.
+   * @return A reference to this connection entry writer.
+   * @throws NullPointerException
+   *           If {@code comment} was {@code null}.
+   */
+  public ConnectionEntryWriter writeComment(CharSequence comment)
+  {
+    Validator.ensureNotNull(comment);
+
+    // Do nothing.
+    return this;
+  }
+
+
+
+  /**
+   * Writes an entry to the underlying connection using an Add request,
+   * blocking until the request completes.
+   *
+   * @param entry
+   *          The {@code Entry} to be written.
+   * @return A reference to this connection entry writer.
+   * @throws ErrorResultIOException
+   *           If the result code indicates that the request failed for
+   *           some reason.
+   * @throws InterruptedIOException
+   *           If the current thread was interrupted while waiting.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null}.
+   */
+  public ConnectionEntryWriter writeEntry(Entry entry)
+      throws ErrorResultIOException, InterruptedIOException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(entry);
+    try
+    {
+      connection.add(entry);
+    }
+    catch (final ErrorResultException e)
+    {
+      throw new ErrorResultIOException(e);
+    }
+    catch (InterruptedException e)
+    {
+      throw new InterruptedIOException(e.getMessage());
+    }
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/EntryReader.java b/sdk/src/org/opends/sdk/ldif/EntryReader.java
new file mode 100644
index 0000000..0a01687
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/EntryReader.java
@@ -0,0 +1,85 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.IOException;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.Entry;
+
+
+
+/**
+ * An interface for reading entries from a data source, typically an
+ * LDIF file.
+ * <p>
+ * Implementations must specify the following:
+ * <ul>
+ * <li>Whether or not it is possible for the implementation to encounter
+ * malformed change records and, if it is possible, how they are
+ * handled.
+ * <li>Any synchronization limitations.
+ * </ul>
+ * <p>
+ * TODO: LDIFInputStreamReader
+ * <p>
+ * TODO: SearchResultEntryReader
+ */
+public interface EntryReader extends Closeable
+{
+
+  /**
+   * Closes this entry reader if it is not already closed. Note that
+   * this method does not need to be called if a previous call of
+   * {@link #readEntry()} has returned {@code null}.
+   *
+   * @throws IOException
+   *           If an unexpected IO error occurred while closing.
+   */
+  void close() throws IOException;
+
+
+
+  /**
+   * Reads the next entry, blocking if necessary until an entry is
+   * available.
+   *
+   * @return The next entry or {@code null} if there are no more entries
+   *         to be read.
+   * @throws DecodeException
+   *           If the entry could not be decoded because it was
+   *           malformed.
+   * @throws IOException
+   *           If an unexpected IO error occurred while reading the
+   *           entry.
+   */
+  Entry readEntry() throws DecodeException, IOException;
+}
diff --git a/sdk/src/org/opends/sdk/ldif/EntryWriter.java b/sdk/src/org/opends/sdk/ldif/EntryWriter.java
new file mode 100644
index 0000000..594a2b6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/EntryWriter.java
@@ -0,0 +1,110 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.Closeable;
+import java.io.Flushable;
+import java.io.IOException;
+
+import org.opends.sdk.Entry;
+
+
+
+/**
+ * An interface for writing entries to a data source, typically an LDIF
+ * file.
+ * <p>
+ * TODO: FilteredChangeRecordWriter
+ */
+public interface EntryWriter extends Closeable, Flushable
+{
+  /**
+   * Closes this entry writer, flushing it first. Closing a previously
+   * closed entry writer has no effect.
+   *
+   * @throws IOException
+   *           If an unexpected IO error occurred while closing.
+   */
+  void close() throws IOException;
+
+
+
+  /**
+   * Flushes this entry writer so that any buffered data is written
+   * immediately to underlying stream, flushing the stream if it is also
+   * {@code Flushable}.
+   * <p>
+   * If the intended destination of this stream is an abstraction
+   * provided by the underlying operating system, for example a file,
+   * then flushing the stream guarantees only that bytes previously
+   * written to the stream are passed to the operating system for
+   * writing; it does not guarantee that they are actually written to a
+   * physical device such as a disk drive.
+   *
+   * @throws IOException
+   *           If an unexpected IO error occurred while flushing.
+   */
+  void flush() throws IOException;
+
+
+
+  /**
+   * Writes a comment.
+   *
+   * @param comment
+   *          The {@code CharSequence} to be written as a comment.
+   * @return A reference to this entry writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           comment.
+   * @throws NullPointerException
+   *           If {@code comment} was {@code null}.
+   */
+  EntryWriter writeComment(CharSequence comment) throws IOException,
+      NullPointerException;
+
+
+
+  /**
+   * Writes an entry.
+   *
+   * @param entry
+   *          The {@code Entry} to be written.
+   * @return A reference to this entry writer.
+   * @throws IOException
+   *           If an unexpected IO error occurred while writing the
+   *           entry.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null}.
+   */
+  EntryWriter writeEntry(Entry entry) throws IOException,
+      NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java
new file mode 100644
index 0000000..c9cc3a8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordReader.java
@@ -0,0 +1,719 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF change record reader reads change records using the LDAP Data
+ * Interchange Format (LDIF) from a user defined source.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ *      Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFChangeRecordReader extends AbstractLDIFReader
+    implements ChangeRecordReader
+{
+  /**
+   * Parses the provided array of LDIF lines as a single LDIF change
+   * record.
+   *
+   * @param ldifLines
+   *          The lines of LDIF to be parsed.
+   * @return The parsed LDIF change record.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} did not contain an LDIF change
+   *           record, if it contained multiple change records, if
+   *           contained malformed LDIF, or if the change record could
+   *           not be decoded using the default schema.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null}.
+   */
+  public static ChangeRecord valueOfLDIFChangeRecord(
+      String... ldifLines) throws LocalizedIllegalArgumentException,
+      NullPointerException
+  {
+    // LDIF change record reader is tolerant to missing change types.
+    LDIFChangeRecordReader reader = new LDIFChangeRecordReader(
+        ldifLines);
+    try
+    {
+      ChangeRecord record = reader.readChangeRecord();
+
+      if (record == null)
+      {
+        // No change record found.
+        Message message = WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND
+            .get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      if (reader.readChangeRecord() != null)
+      {
+        // Multiple change records found.
+        Message message = WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND
+            .get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      return record;
+    }
+    catch (DecodeException e)
+    {
+      // Badly formed LDIF.
+      throw new LocalizedIllegalArgumentException(e.getMessageObject());
+    }
+    catch (IOException e)
+    {
+      // This should never happen for a String based reader.
+      Message message = WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR.get(e
+          .getMessage());
+      throw new LocalizedIllegalArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Creates a new LDIF change record reader whose source is the
+   * provided input stream.
+   *
+   * @param in
+   *          The input stream to use.
+   * @throws NullPointerException
+   *           If {@code in} was {@code null}.
+   */
+  public LDIFChangeRecordReader(InputStream in)
+      throws NullPointerException
+  {
+    super(in);
+  }
+
+
+
+  /**
+   * Creates a new LDIF change record reader which will read lines of
+   * LDIF from the provided list of LDIF lines.
+   *
+   * @param ldifLines
+   *          The lines of LDIF to be read.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null}.
+   */
+  public LDIFChangeRecordReader(List<String> ldifLines)
+      throws NullPointerException
+  {
+    super(ldifLines);
+  }
+
+
+
+  /**
+   * Creates a new LDIF change record reader which will read lines of
+   * LDIF from the provided array of LDIF lines.
+   *
+   * @param ldifLines
+   *          The lines of LDIF to be read.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null}.
+   */
+  public LDIFChangeRecordReader(String... ldifLines)
+      throws NullPointerException
+  {
+    super(Arrays.asList(ldifLines));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException
+  {
+    close0();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ChangeRecord readChangeRecord() throws DecodeException,
+      IOException
+  {
+    // Continue until an unfiltered entry is obtained.
+    while (true)
+    {
+      LDIFRecord record = null;
+
+      // Read the set of lines that make up the next entry.
+      record = readLDIFRecord();
+      if (record == null)
+      {
+        return null;
+      }
+
+      // Read the DN of the entry and see if it is one that should be
+      // included in the import.
+      DN entryDN;
+      try
+      {
+        entryDN = readLDIFRecordDN(record);
+        if (entryDN == null)
+        {
+          // Skip version record.
+          continue;
+        }
+      }
+      catch (final DecodeException e)
+      {
+        rejectLDIFRecord(record, e.getMessageObject());
+        continue;
+      }
+
+      // Skip if branch containing the entry DN is excluded.
+      if (isBranchExcluded(entryDN))
+      {
+        final Message message = Message
+            .raw("Skipping entry because it is in excluded branch");
+        skipLDIFRecord(record, message);
+        continue;
+      }
+
+      ChangeRecord changeRecord = null;
+      try
+      {
+        if (!record.iterator.hasNext())
+        {
+          // FIXME: improve error.
+          final Message message = Message.raw("Missing changetype");
+          throw DecodeException.error(message);
+        }
+
+        final KeyValuePair pair = new KeyValuePair();
+        final String ldifLine = readLDIFRecordKeyValuePair(record,
+            pair, false);
+
+        if (!toLowerCase(pair.key).equals("changetype"))
+        {
+          // Default to add change record.
+          changeRecord = parseAddChangeRecordEntry(entryDN, ldifLine,
+              record);
+        }
+        else
+        {
+          final String changeType = toLowerCase(pair.value);
+          if (changeType.equals("add"))
+          {
+            changeRecord = parseAddChangeRecordEntry(entryDN, null,
+                record);
+          }
+          else if (changeType.equals("delete"))
+          {
+            changeRecord = parseDeleteChangeRecordEntry(entryDN, record);
+          }
+          else if (changeType.equals("modify"))
+          {
+            changeRecord = parseModifyChangeRecordEntry(entryDN, record);
+          }
+          else if (changeType.equals("modrdn"))
+          {
+            changeRecord = parseModifyDNChangeRecordEntry(entryDN,
+                record);
+          }
+          else if (changeType.equals("moddn"))
+          {
+            changeRecord = parseModifyDNChangeRecordEntry(entryDN,
+                record);
+          }
+          else
+          {
+            // FIXME: improve error.
+            final Message message = ERR_LDIF_INVALID_CHANGETYPE_ATTRIBUTE
+                .get(pair.value, "add, delete, modify, moddn, modrdn");
+            throw DecodeException.error(message);
+          }
+        }
+      }
+      catch (final DecodeException e)
+      {
+        rejectLDIFRecord(record, e.getMessageObject());
+        continue;
+      }
+
+      if (changeRecord != null)
+      {
+        return changeRecord;
+      }
+    }
+  }
+
+
+
+  /**
+   * Specifies whether or not all operational attributes should be
+   * excluded from any change records that are read from LDIF. The
+   * default is {@code false}.
+   *
+   * @param excludeOperationalAttributes
+   *          {@code true} if all operational attributes should be
+   *          excluded, or {@code false} otherwise.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setExcludeAllOperationalAttributes(
+      boolean excludeOperationalAttributes)
+  {
+    this.excludeOperationalAttributes = excludeOperationalAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not all user attributes should be excluded
+   * from any change records that are read from LDIF. The default is
+   * {@code false}.
+   *
+   * @param excludeUserAttributes
+   *          {@code true} if all user attributes should be excluded, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setExcludeAllUserAttributes(
+      boolean excludeUserAttributes)
+  {
+    this.excludeUserAttributes = excludeUserAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Excludes the named attribute from any change records that are read
+   * from LDIF. By default all attributes are included unless explicitly
+   * excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be excluded.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setExcludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    excludeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Excludes all change records which target entries beneath the named
+   * entry (inclusive) from being read from LDIF. By default all change
+   * records are read unless explicitly excluded or included.
+   *
+   * @param excludeBranch
+   *          The distinguished name of the branch to be excluded.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setExcludeBranch(DN excludeBranch)
+  {
+    Validator.ensureNotNull(excludeBranch);
+    excludeBranches.add(excludeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that the named attribute is not excluded from any change
+   * records that are read from LDIF. By default all attributes are
+   * included unless explicitly excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be included.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setIncludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    includeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that all change records which target entries beneath the
+   * named entry (inclusive) are read from LDIF. By default all change
+   * records are read unless explicitly excluded or included.
+   *
+   * @param includeBranch
+   *          The distinguished name of the branch to be included.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setIncludeBranch(DN includeBranch)
+  {
+    Validator.ensureNotNull(includeBranch);
+    includeBranches.add(includeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Sets the schema which should be used for decoding change records
+   * that are read from LDIF. The default schema is used if no other is
+   * specified.
+   *
+   * @param schema
+   *          The schema which should be used for decoding change
+   *          records that are read from LDIF.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setSchema(Schema schema)
+  {
+    Validator.ensureNotNull(schema);
+    this.schema = schema;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not schema validation should be performed for
+   * change records that are read from LDIF. The default is {@code true}
+   * .
+   *
+   * @param validateSchema
+   *          {@code true} if schema validation should be performed, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFChangeRecordReader}.
+   */
+  public LDIFChangeRecordReader setValidateSchema(boolean validateSchema)
+  {
+    this.validateSchema = validateSchema;
+    return this;
+  }
+
+
+
+  private ChangeRecord parseAddChangeRecordEntry(DN entryDN,
+      String lastLDIFLine, LDIFRecord record) throws DecodeException
+  {
+    // Use an Entry for the AttributeSequence.
+    final Entry entry = new SortedEntry(entryDN);
+
+    if (lastLDIFLine != null)
+    {
+      // This line was read when looking for the change type.
+      readLDIFRecordAttributeValue(record, lastLDIFLine, entry);
+    }
+
+    while (record.iterator.hasNext())
+    {
+      final String ldifLine = record.iterator.next();
+      readLDIFRecordAttributeValue(record, ldifLine, entry);
+    }
+
+    return Requests.newAddRequest(entry);
+  }
+
+
+
+  private ChangeRecord parseDeleteChangeRecordEntry(DN entryDN,
+      LDIFRecord record) throws DecodeException
+  {
+    if (record.iterator.hasNext())
+    {
+      // FIXME: include line number in error.
+      final Message message = ERR_LDIF_INVALID_DELETE_ATTRIBUTES.get();
+      throw DecodeException.error(message);
+    }
+
+    return Requests.newDeleteRequest(entryDN);
+  }
+
+
+
+  private ChangeRecord parseModifyChangeRecordEntry(DN entryDN,
+      LDIFRecord record) throws DecodeException
+  {
+    final ModifyRequest modifyRequest = Requests
+        .newModifyRequest(entryDN);
+
+    final KeyValuePair pair = new KeyValuePair();
+    final List<ByteString> attributeValues = new ArrayList<ByteString>();
+
+    while (record.iterator.hasNext())
+    {
+      readLDIFRecordKeyValuePair(record, pair, false);
+      final String changeType = toLowerCase(pair.key);
+
+      ModificationType modType;
+      if (changeType.equals("add"))
+      {
+        modType = ModificationType.ADD;
+      }
+      else if (changeType.equals("delete"))
+      {
+        modType = ModificationType.DELETE;
+      }
+      else if (changeType.equals("replace"))
+      {
+        modType = ModificationType.REPLACE;
+      }
+      else if (changeType.equals("increment"))
+      {
+        modType = ModificationType.INCREMENT;
+      }
+      else
+      {
+        // FIXME: improve error.
+        final Message message = ERR_LDIF_INVALID_MODIFY_ATTRIBUTE.get(
+            pair.key, "add, delete, replace, increment");
+        throw DecodeException.error(message);
+      }
+
+      AttributeDescription attributeDescription;
+      try
+      {
+        attributeDescription = AttributeDescription.valueOf(pair.value,
+            schema);
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        throw DecodeException.error(e.getMessageObject());
+      }
+
+      // Skip the attribute if requested before performing any schema
+      // checking: the attribute may have been excluded because it is
+      // known to violate the schema.
+      if (isAttributeExcluded(attributeDescription))
+      {
+        continue;
+      }
+
+      // Ensure that the binary option is present if required.
+      if (!attributeDescription.getAttributeType().getSyntax()
+          .isBEREncodingRequired())
+      {
+        if (validateSchema
+            && attributeDescription.containsOption("binary"))
+        {
+          final Message message = ERR_LDIF_INVALID_ATTR_OPTION.get(
+              entryDN.toString(), record.lineNumber, pair.value);
+          throw DecodeException.error(message);
+        }
+      }
+      else
+      {
+        attributeDescription = AttributeDescription.create(
+            attributeDescription, "binary");
+      }
+
+      // Now go through the rest of the attributes until the "-" line is
+      // reached.
+      attributeValues.clear();
+      while (record.iterator.hasNext())
+      {
+        final String ldifLine = record.iterator.next();
+        if (ldifLine.equals("-"))
+        {
+          break;
+        }
+
+        // Parse the attribute description.
+        final int colonPos = parseColonPosition(record, ldifLine);
+        final String attrDescr = ldifLine.substring(0, colonPos);
+
+        AttributeDescription attributeDescription2;
+        try
+        {
+          attributeDescription2 = AttributeDescription.valueOf(
+              attrDescr, schema);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          throw DecodeException.error(e.getMessageObject());
+        }
+
+        // Ensure that the binary option is present if required.
+        if (attributeDescription.getAttributeType().getSyntax()
+            .isBEREncodingRequired())
+        {
+          attributeDescription2 = AttributeDescription.create(
+              attributeDescription2, "binary");
+        }
+
+        if (!attributeDescription2.equals(attributeDescription))
+        {
+          // TODO: include line number.
+          final Message message = ERR_LDIF_INVALID_CHANGERECORD_ATTRIBUTE
+              .get(attributeDescription2.toString(),
+                  attributeDescription.toString());
+          throw DecodeException.error(message);
+        }
+
+        // Now parse the attribute value.
+        attributeValues.add(parseSingleValue(record, ldifLine, entryDN,
+            colonPos, attrDescr));
+      }
+
+      Change change = new Change(modType, new LinkedAttribute(
+          attributeDescription, attributeValues));
+      modifyRequest.addChange(change);
+    }
+
+    return modifyRequest;
+  }
+
+
+
+  private ChangeRecord parseModifyDNChangeRecordEntry(DN entryDN,
+      LDIFRecord record) throws DecodeException
+  {
+    ModifyDNRequest modifyDNRequest;
+
+    // Parse the newrdn.
+    if (!record.iterator.hasNext())
+    {
+      // TODO: include line number.
+      final Message message = ERR_LDIF_NO_MOD_DN_ATTRIBUTES.get();
+      throw DecodeException.error(message);
+    }
+
+    final KeyValuePair pair = new KeyValuePair();
+    String ldifLine = record.iterator.next();
+    readLDIFRecordKeyValuePair(record, pair, true);
+    if (!toLowerCase(pair.key).equals("newrdn"))
+    {
+      // FIXME: improve error.
+      final Message message = Message.raw("Missing newrdn");
+      throw DecodeException.error(message);
+    }
+
+    try
+    {
+      final RDN newRDN = RDN.valueOf(pair.value, schema);
+      modifyDNRequest = Requests.newModifyDNRequest(entryDN, newRDN);
+    }
+    catch (final LocalizedIllegalArgumentException e)
+    {
+      final Message message = ERR_LDIF_INVALID_DN.get(
+          record.lineNumber, ldifLine, e.getMessageObject());
+      throw DecodeException.error(message);
+    }
+
+    // Parse the deleteoldrdn.
+    if (!record.iterator.hasNext())
+    {
+      // TODO: include line number.
+      final Message message = ERR_LDIF_NO_DELETE_OLDRDN_ATTRIBUTE.get();
+      throw DecodeException.error(message);
+    }
+
+    ldifLine = record.iterator.next();
+    readLDIFRecordKeyValuePair(record, pair, true);
+    if (!toLowerCase(pair.key).equals("deleteoldrdn"))
+    {
+      // FIXME: improve error.
+      final Message message = Message.raw("Missing deleteoldrdn");
+      throw DecodeException.error(message);
+    }
+
+    final String delStr = toLowerCase(pair.value);
+    if (delStr.equals("false") || delStr.equals("no")
+        || delStr.equals("0"))
+    {
+      modifyDNRequest.setDeleteOldRDN(false);
+    }
+    else if (delStr.equals("true") || delStr.equals("yes")
+        || delStr.equals("1"))
+    {
+      modifyDNRequest.setDeleteOldRDN(true);
+    }
+    else
+    {
+      // FIXME: improve error.
+      final Message message = ERR_LDIF_INVALID_DELETE_OLDRDN_ATTRIBUTE
+          .get(pair.value);
+      throw DecodeException.error(message);
+    }
+
+    // Parse the newsuperior if present.
+    if (record.iterator.hasNext())
+    {
+      ldifLine = record.iterator.next();
+      readLDIFRecordKeyValuePair(record, pair, true);
+      if (!toLowerCase(pair.key).equals("newsuperior"))
+      {
+        // FIXME: improve error.
+        final Message message = Message.raw("Missing newsuperior");
+        throw DecodeException.error(message);
+      }
+
+      try
+      {
+        final DN newSuperiorDN = DN.valueOf(pair.value, schema);
+        modifyDNRequest.setNewSuperior(newSuperiorDN.toString());
+      }
+      catch (final LocalizedIllegalArgumentException e)
+      {
+        final Message message = ERR_LDIF_INVALID_DN.get(
+            record.lineNumber, ldifLine, e.getMessageObject());
+        throw DecodeException.error(message);
+      }
+    }
+
+    return modifyDNRequest;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java
new file mode 100644
index 0000000..2346a23
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFChangeRecordWriter.java
@@ -0,0 +1,478 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.opends.sdk.*;
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF change record writer writes change records using the LDAP
+ * Data Interchange Format (LDIF) to a user defined destination.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ *      Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFChangeRecordWriter extends AbstractLDIFWriter
+    implements ChangeRecordWriter
+{
+
+  /**
+   * Creates a new LDIF change record writer which will append lines of
+   * LDIF to the provided list.
+   *
+   * @param ldifLines
+   *          The list to which lines of LDIF should be appended.
+   */
+  public LDIFChangeRecordWriter(List<String> ldifLines)
+  {
+    super(ldifLines);
+  }
+
+
+
+  /**
+   * Creates a new LDIF change record writer whose destination is the
+   * provided output stream.
+   *
+   * @param out
+   *          The output stream to use.
+   */
+  public LDIFChangeRecordWriter(OutputStream out)
+  {
+    super(out);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException
+  {
+    close0();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void flush() throws IOException
+  {
+    flush0();
+  }
+
+
+
+  /**
+   * Specifies whether or not user-friendly comments should be added
+   * whenever distinguished names or UTF-8 attribute values are
+   * encountered which contained non-ASCII characters. The default is
+   * {@code false}.
+   *
+   * @param addUserFriendlyComments
+   *          {@code true} if user-friendly comments should be added, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFChangeRecordWriter setAddUserFriendlyComments(
+      boolean addUserFriendlyComments)
+  {
+    this.addUserFriendlyComments = addUserFriendlyComments;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not all operational attributes should be
+   * excluded from any change records that are written to LDIF. The
+   * default is {@code false}.
+   *
+   * @param excludeOperationalAttributes
+   *          {@code true} if all operational attributes should be
+   *          excluded, or {@code false} otherwise.
+   * @return A reference to this {@code LDIFChangeRecordWriter}.
+   */
+  public LDIFChangeRecordWriter setExcludeAllOperationalAttributes(
+      boolean excludeOperationalAttributes)
+  {
+    this.excludeOperationalAttributes = excludeOperationalAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not all user attributes should be excluded
+   * from any change records that are written to LDIF. The default is
+   * {@code false}.
+   *
+   * @param excludeUserAttributes
+   *          {@code true} if all user attributes should be excluded, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFChangeRecordWriter}.
+   */
+  public LDIFChangeRecordWriter setExcludeAllUserAttributes(
+      boolean excludeUserAttributes)
+  {
+    this.excludeUserAttributes = excludeUserAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Excludes the named attribute from any change records that are
+   * written to LDIF. By default all attributes are included unless
+   * explicitly excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be excluded.
+   * @return A reference to this {@code LDIFChangeRecordWriter}.
+   */
+  public LDIFChangeRecordWriter setExcludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    excludeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Excludes all change records which target entries beneath the named
+   * entry (inclusive) from being written to LDIF. By default all change
+   * records are written unless explicitly excluded or included.
+   *
+   * @param excludeBranch
+   *          The distinguished name of the branch to be excluded.
+   * @return A reference to this {@code LDIFChangeRecordWriter}.
+   */
+  public LDIFChangeRecordWriter setExcludeBranch(DN excludeBranch)
+  {
+    Validator.ensureNotNull(excludeBranch);
+    excludeBranches.add(excludeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that the named attribute is not excluded from any change
+   * records that are written to LDIF. By default all attributes are
+   * included unless explicitly excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be included.
+   * @return A reference to this {@code LDIFChangeRecordWriter}.
+   */
+  public LDIFChangeRecordWriter setIncludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    includeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that all change records which target entries beneath the
+   * named entry (inclusive) are written to LDIF. By default all change
+   * records are written unless explicitly excluded or included.
+   *
+   * @param includeBranch
+   *          The distinguished name of the branch to be included.
+   * @return A reference to this {@code LDIFChangeRecordWriter}.
+   */
+  public LDIFChangeRecordWriter setIncludeBranch(DN includeBranch)
+  {
+    Validator.ensureNotNull(includeBranch);
+    includeBranches.add(includeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Sets the schema which should be used when filtering change records
+   * (not required if no filtering is to be performed). The default
+   * schema is used if no other is specified.
+   *
+   * @param schema
+   *          The schema which should be used when filtering change
+   *          records.
+   * @return A reference to this {@code LDIFChangeRecordWriter}.
+   */
+  public LDIFChangeRecordWriter setSchema(Schema schema)
+  {
+    Validator.ensureNotNull(schema);
+    this.schema = schema;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies the column at which long lines should be wrapped. A value
+   * less than or equal to zero (the default) indicates that no wrapping
+   * should be performed.
+   *
+   * @param wrapColumn
+   *          The column at which long lines should be wrapped.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFChangeRecordWriter setWrapColumn(int wrapColumn)
+  {
+    this.wrapColumn = wrapColumn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFChangeRecordWriter writeChangeRecord(AddRequest change)
+      throws IOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+
+    // Skip if branch containing the entry is excluded.
+    if (isBranchExcluded(change.getName()))
+    {
+      return this;
+    }
+
+    writeKeyAndValue("dn", change.getName().toString());
+    writeControls(change.getControls());
+    writeLine("changetype: add");
+    for (final Attribute attribute : change.getAttributes())
+    {
+      // Filter the attribute if required.
+      if (isAttributeExcluded(attribute.getAttributeDescription()))
+      {
+        continue;
+      }
+
+      final String attributeDescription =
+          attribute.getAttributeDescriptionAsString();
+      for (final ByteString value : attribute)
+      {
+        writeKeyAndValue(attributeDescription, value);
+      }
+    }
+
+    // Make sure there is a blank line after the entry.
+    impl.println();
+
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFChangeRecordWriter writeChangeRecord(ChangeRecord change)
+      throws IOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+
+    // Skip if branch containing the entry is excluded.
+    if (isBranchExcluded(change.getName()))
+    {
+      return this;
+    }
+
+    final IOException e =
+        change.accept(ChangeRecordVisitorWriter.getInstance(), this);
+    if (e != null)
+    {
+      throw e;
+    }
+    else
+    {
+      return this;
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFChangeRecordWriter writeChangeRecord(DeleteRequest change)
+      throws IOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+
+    // Skip if branch containing the entry is excluded.
+    if (isBranchExcluded(change.getName()))
+    {
+      return this;
+    }
+
+    writeKeyAndValue("dn", change.getName().toString());
+    writeControls(change.getControls());
+    writeLine("changetype: delete");
+
+    // Make sure there is a blank line after the entry.
+    impl.println();
+
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFChangeRecordWriter writeChangeRecord(ModifyDNRequest change)
+      throws IOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+
+    // Skip if branch containing the entry is excluded.
+    if (isBranchExcluded(change.getName()))
+    {
+      return this;
+    }
+
+    writeKeyAndValue("dn", change.getName().toString());
+    writeControls(change.getControls());
+
+    // Write the changetype. Some older tools may not support the
+    // "moddn" changetype, so only use it if a newSuperior element has
+    // been provided, but use modrdn elsewhere.
+    if (change.getNewSuperior() == null)
+    {
+      writeLine("changetype: modrdn");
+    }
+    else
+    {
+      writeLine("changetype: moddn");
+    }
+
+    writeKeyAndValue("newrdn", change.getNewRDN().toString());
+    writeKeyAndValue("deleteoldrdn", change.isDeleteOldRDN() ? "1"
+        : "0");
+    if (change.getNewSuperior() != null)
+    {
+      writeKeyAndValue("newsuperior", change.getNewSuperior()
+          .toString());
+    }
+
+    // Make sure there is a blank line after the entry.
+    impl.println();
+
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFChangeRecordWriter writeChangeRecord(ModifyRequest change)
+      throws IOException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+
+    // If there aren't any modifications, then there's nothing to do.
+    if (!change.hasChanges())
+    {
+      return this;
+    }
+
+    // Skip if branch containing the entry is excluded.
+    if (isBranchExcluded(change.getName()))
+    {
+      return this;
+    }
+
+    writeKeyAndValue("dn", change.getName().toString());
+    writeControls(change.getControls());
+    writeLine("changetype: modify");
+
+    for (final Change modification : change.getChanges())
+    {
+      final ModificationType type = modification.getModificationType();
+      final Attribute attribute = modification.getAttribute();
+      final String attributeDescription =
+          attribute.getAttributeDescriptionAsString();
+
+      // Filter the attribute if required.
+      if (isAttributeExcluded(attribute.getAttributeDescription()))
+      {
+        continue;
+      }
+
+      writeKeyAndValue(type.toString(), attributeDescription);
+      for (final ByteString value : attribute)
+      {
+        writeKeyAndValue(attributeDescription, value);
+      }
+      writeLine("-");
+    }
+
+    // Make sure there is a blank line after the entry.
+    impl.println();
+
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFChangeRecordWriter writeComment(CharSequence comment)
+      throws IOException, NullPointerException
+  {
+    writeComment0(comment);
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java b/sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java
new file mode 100644
index 0000000..6c7a475
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFEntryReader.java
@@ -0,0 +1,432 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND;
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND;
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF entry reader reads attribute value records (entries) using
+ * the LDAP Data Interchange Format (LDIF) from a user defined source.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ *      Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFEntryReader extends AbstractLDIFReader implements
+    EntryReader
+{
+  /**
+   * Parses the provided array of LDIF lines as a single LDIF entry.
+   *
+   * @param ldifLines
+   *          The lines of LDIF to be parsed.
+   * @return The parsed LDIF entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} did not contain an LDIF entry, if it
+   *           contained multiple entries, if contained malformed LDIF,
+   *           or if the entry could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null}.
+   */
+  public static Entry valueOfLDIFEntry(String... ldifLines)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    LDIFEntryReader reader = new LDIFEntryReader(ldifLines);
+    try
+    {
+      Entry entry = reader.readEntry();
+
+      if (entry == null)
+      {
+        // No change record found.
+        Message message = WARN_READ_LDIF_RECORD_NO_CHANGE_RECORD_FOUND
+            .get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      if (reader.readEntry() != null)
+      {
+        // Multiple change records found.
+        Message message = WARN_READ_LDIF_RECORD_MULTIPLE_CHANGE_RECORDS_FOUND
+            .get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      return entry;
+    }
+    catch (DecodeException e)
+    {
+      // Badly formed LDIF.
+      throw new LocalizedIllegalArgumentException(e.getMessageObject());
+    }
+    catch (IOException e)
+    {
+      // This should never happen for a String based reader.
+      Message message = WARN_READ_LDIF_RECORD_UNEXPECTED_IO_ERROR.get(e
+          .getMessage());
+      throw new LocalizedIllegalArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Creates a new LDIF entry reader whose source is the provided input
+   * stream.
+   *
+   * @param in
+   *          The input stream to use.
+   * @throws NullPointerException
+   *           If {@code in} was {@code null}.
+   */
+  public LDIFEntryReader(InputStream in) throws NullPointerException
+  {
+    super(in);
+  }
+
+
+
+  /**
+   * Creates a new LDIF entry reader which will read lines of LDIF from
+   * the provided list of LDIF lines.
+   *
+   * @param ldifLines
+   *          The lines of LDIF to be read.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null}.
+   */
+  public LDIFEntryReader(List<String> ldifLines)
+      throws NullPointerException
+  {
+    super(ldifLines);
+  }
+
+
+
+  /**
+   * Creates a new LDIF entry reader which will read lines of LDIF from
+   * the provided array of LDIF lines.
+   *
+   * @param ldifLines
+   *          The lines of LDIF to be read.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null}.
+   */
+  public LDIFEntryReader(String... ldifLines)
+      throws NullPointerException
+  {
+    super(Arrays.asList(ldifLines));
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException
+  {
+    close0();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Entry readEntry() throws DecodeException, IOException
+  {
+    // Continue until an unfiltered entry is obtained.
+    while (true)
+    {
+      LDIFRecord record = null;
+
+      // Read the set of lines that make up the next entry.
+      record = readLDIFRecord();
+      if (record == null)
+      {
+        return null;
+      }
+
+      // Read the DN of the entry and see if it is one that should be
+      // included in the import.
+      DN entryDN;
+      try
+      {
+        entryDN = readLDIFRecordDN(record);
+        if (entryDN == null)
+        {
+          // Skip version record.
+          continue;
+        }
+      }
+      catch (final DecodeException e)
+      {
+        rejectLDIFRecord(record, e.getMessageObject());
+        continue;
+      }
+
+      // Skip if branch containing the entry DN is excluded.
+      if (isBranchExcluded(entryDN))
+      {
+        final Message message = Message
+            .raw("Skipping entry because it is in excluded branch");
+        skipLDIFRecord(record, message);
+        continue;
+      }
+
+      // Use an Entry for the AttributeSequence.
+      final Entry entry = new SortedEntry(entryDN);
+      try
+      {
+        while (record.iterator.hasNext())
+        {
+          final String ldifLine = record.iterator.next();
+          readLDIFRecordAttributeValue(record, ldifLine, entry);
+        }
+      }
+      catch (final DecodeException e)
+      {
+        rejectLDIFRecord(record, e.getMessageObject());
+        continue;
+      }
+
+      // Skip if the entry is excluded by any filters.
+      if (isEntryExcluded(entry))
+      {
+        final Message message = Message
+            .raw("Skipping entry due to exclusing filters");
+        skipLDIFRecord(record, message);
+        continue;
+      }
+
+      return entry;
+    }
+  }
+
+
+
+  /**
+   * Specifies whether or not all operational attributes should be
+   * excluded from any entries that are read from LDIF. The default is
+   * {@code false}.
+   *
+   * @param excludeOperationalAttributes
+   *          {@code true} if all operational attributes should be
+   *          excluded, or {@code false} otherwise.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setExcludeAllOperationalAttributes(
+      boolean excludeOperationalAttributes)
+  {
+    this.excludeOperationalAttributes = excludeOperationalAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not all user attributes should be excluded
+   * from any entries that are read from LDIF. The default is {@code
+   * false}.
+   *
+   * @param excludeUserAttributes
+   *          {@code true} if all user attributes should be excluded, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setExcludeAllUserAttributes(
+      boolean excludeUserAttributes)
+  {
+    this.excludeUserAttributes = excludeUserAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Excludes the named attribute from any entries that are read from
+   * LDIF. By default all attributes are included unless explicitly
+   * excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be excluded.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setExcludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    excludeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Excludes all entries beneath the named entry (inclusive) from being
+   * read from LDIF. By default all entries are written unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param excludeBranch
+   *          The distinguished name of the branch to be excluded.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setExcludeBranch(DN excludeBranch)
+  {
+    Validator.ensureNotNull(excludeBranch);
+    excludeBranches.add(excludeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Excludes all entries which match the provided filter matcher from
+   * being read from LDIF. By default all entries are read unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param excludeFilter
+   *          The filter matcher.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setExcludeFilter(Matcher excludeFilter)
+  {
+    Validator.ensureNotNull(excludeFilter);
+    excludeFilters.add(excludeFilter);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that the named attribute is not excluded from any entries
+   * that are read from LDIF. By default all attributes are included
+   * unless explicitly excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be included.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setIncludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    includeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that all entries beneath the named entry (inclusive) are
+   * read from LDIF. By default all entries are written unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param includeBranch
+   *          The distinguished name of the branch to be included.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setIncludeBranch(DN includeBranch)
+  {
+    Validator.ensureNotNull(includeBranch);
+    includeBranches.add(includeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that all entries which match the provided filter matcher
+   * are read from LDIF. By default all entries are read unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param includeFilter
+   *          The filter matcher.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setIncludeFilter(Matcher includeFilter)
+  {
+    Validator.ensureNotNull(includeFilter);
+    includeFilters.add(includeFilter);
+    return this;
+  }
+
+
+
+  /**
+   * Sets the schema which should be used for decoding entries that are
+   * read from LDIF. The default schema is used if no other is
+   * specified.
+   *
+   * @param schema
+   *          The schema which should be used for decoding entries that
+   *          are read from LDIF.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setSchema(Schema schema)
+  {
+    Validator.ensureNotNull(schema);
+    this.schema = schema;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not schema validation should be performed for
+   * entries that are read from LDIF. The default is {@code true}.
+   *
+   * @param validateSchema
+   *          {@code true} if schema validation should be performed, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFEntryReader}.
+   */
+  public LDIFEntryReader setValidateSchema(boolean validateSchema)
+  {
+    this.validateSchema = validateSchema;
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java b/sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java
new file mode 100644
index 0000000..eed7202
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/LDIFEntryWriter.java
@@ -0,0 +1,360 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.ldif;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.opends.sdk.*;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An LDIF entry writer writes attribute value records (entries) using
+ * the LDAP Data Interchange Format (LDIF) to a user defined
+ * destination.
+ *
+ * @see <a href="http://tools.ietf.org/html/rfc2849">RFC 2849 - The LDAP
+ *      Data Interchange Format (LDIF) - Technical Specification </a>
+ */
+public final class LDIFEntryWriter extends AbstractLDIFWriter implements
+    EntryWriter
+{
+
+  /**
+   * Creates a new LDIF entry writer which will append lines of LDIF to
+   * the provided list.
+   *
+   * @param ldifLines
+   *          The list to which lines of LDIF should be appended.
+   */
+  public LDIFEntryWriter(List<String> ldifLines)
+  {
+    super(ldifLines);
+  }
+
+
+
+  /**
+   * Creates a new LDIF entry writer whose destination is the provided
+   * output stream.
+   *
+   * @param out
+   *          The output stream to use.
+   */
+  public LDIFEntryWriter(OutputStream out)
+  {
+    super(out);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException
+  {
+    close0();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void flush() throws IOException
+  {
+    flush0();
+  }
+
+
+
+  /**
+   * Specifies whether or not user-friendly comments should be added
+   * whenever distinguished names or UTF-8 attribute values are
+   * encountered which contained non-ASCII characters. The default is
+   * {@code false}.
+   *
+   * @param addUserFriendlyComments
+   *          {@code true} if user-friendly comments should be added, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setAddUserFriendlyComments(
+      boolean addUserFriendlyComments)
+  {
+    this.addUserFriendlyComments = addUserFriendlyComments;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not all operational attributes should be
+   * excluded from any entries that are written to LDIF. The default is
+   * {@code false}.
+   *
+   * @param excludeOperationalAttributes
+   *          {@code true} if all operational attributes should be
+   *          excluded, or {@code false} otherwise.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setExcludeAllOperationalAttributes(
+      boolean excludeOperationalAttributes)
+  {
+    this.excludeOperationalAttributes = excludeOperationalAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not all user attributes should be excluded
+   * from any entries that are written to LDIF. The default is {@code
+   * false}.
+   *
+   * @param excludeUserAttributes
+   *          {@code true} if all user attributes should be excluded, or
+   *          {@code false} otherwise.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setExcludeAllUserAttributes(
+      boolean excludeUserAttributes)
+  {
+    this.excludeUserAttributes = excludeUserAttributes;
+    return this;
+  }
+
+
+
+  /**
+   * Excludes the named attribute from any entries that are written to
+   * LDIF. By default all attributes are included unless explicitly
+   * excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be excluded.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setExcludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    excludeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Excludes all entries beneath the named entry (inclusive) from being
+   * written to LDIF. By default all entries are written unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param excludeBranch
+   *          The distinguished name of the branch to be excluded.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setExcludeBranch(DN excludeBranch)
+  {
+    Validator.ensureNotNull(excludeBranch);
+    excludeBranches.add(excludeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Excludes all entries which match the provided filter matcher from
+   * being written to LDIF. By default all entries are written unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param excludeFilter
+   *          The filter matcher.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setExcludeFilter(Matcher excludeFilter)
+  {
+    Validator.ensureNotNull(excludeFilter);
+    excludeFilters.add(excludeFilter);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that the named attribute is not excluded from any entries
+   * that are written to LDIF. By default all attributes are included
+   * unless explicitly excluded.
+   *
+   * @param attributeDescription
+   *          The name of the attribute to be included.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setIncludeAttribute(
+      AttributeDescription attributeDescription)
+  {
+    Validator.ensureNotNull(attributeDescription);
+    includeAttributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that all entries beneath the named entry (inclusive) are
+   * written to LDIF. By default all entries are written unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param includeBranch
+   *          The distinguished name of the branch to be included.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setIncludeBranch(DN includeBranch)
+  {
+    Validator.ensureNotNull(includeBranch);
+    includeBranches.add(includeBranch);
+    return this;
+  }
+
+
+
+  /**
+   * Ensures that all entries which match the provided filter matcher
+   * are written to LDIF. By default all entries are written unless
+   * explicitly excluded or included by branches or filters.
+   *
+   * @param includeFilter
+   *          The filter matcher.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setIncludeFilter(Matcher includeFilter)
+  {
+    Validator.ensureNotNull(includeFilter);
+    includeFilters.add(includeFilter);
+    return this;
+  }
+
+
+
+  /**
+   * Sets the schema which should be used when filtering entries (not
+   * required if no filtering is to be performed). The default schema is
+   * used if no other is specified.
+   *
+   * @param schema
+   *          The schema which should be used when filtering entries.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setSchema(Schema schema)
+  {
+    Validator.ensureNotNull(schema);
+    this.schema = schema;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies the column at which long lines should be wrapped. A value
+   * less than or equal to zero (the default) indicates that no wrapping
+   * should be performed.
+   *
+   * @param wrapColumn
+   *          The column at which long lines should be wrapped.
+   * @return A reference to this {@code LDIFEntryWriter}.
+   */
+  public LDIFEntryWriter setWrapColumn(int wrapColumn)
+  {
+    this.wrapColumn = wrapColumn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFEntryWriter writeComment(CharSequence comment)
+      throws IOException, NullPointerException
+  {
+    writeComment0(comment);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public LDIFEntryWriter writeEntry(Entry entry) throws IOException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(entry);
+
+    // Skip if branch containing the entry is excluded.
+    if (isBranchExcluded(entry.getName()))
+    {
+      return this;
+    }
+
+    // Skip if the entry is excluded by any filters.
+    if (isEntryExcluded(entry))
+    {
+      return this;
+    }
+
+    writeKeyAndValue("dn", entry.getName().toString());
+    for (final Attribute attribute : entry.getAttributes())
+    {
+      // Filter the attribute if required.
+      if (isAttributeExcluded(attribute.getAttributeDescription()))
+      {
+        continue;
+      }
+
+      final String attributeDescription =
+          attribute.getAttributeDescriptionAsString();
+      for (final ByteString value : attribute)
+      {
+        writeKeyAndValue(attributeDescription, value);
+      }
+    }
+
+    // Make sure there is a blank line after the entry.
+    impl.println();
+
+    return this;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/ldif/package-info.java b/sdk/src/org/opends/sdk/ldif/package-info.java
new file mode 100755
index 0000000..d471266
--- /dev/null
+++ b/sdk/src/org/opends/sdk/ldif/package-info.java
@@ -0,0 +1,46 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+/**
+ * OpenDS LDIF support.
+ *
+ * <h1>TO DO</h1>
+ * <ul>
+ * <li>Make LDIFEntryReader concurrent and support DN reservation.
+ * <li>LDIF*Reader Reject and skip support
+ * <li>Remaining schema checking (e.g. binary option)
+ * <li>Fix error messages (prefix with file/lineno)
+ * <li>Support multiple LDIF*Reader sources
+ * <li>Support EntryWriter splitting
+ * <li>Support LDIFConnectionFactory
+ * <li>Comments and optional charset encoding?
+ * </ul>
+ */
+package org.opends.sdk.ldif;
+
+
+
diff --git a/sdk/src/org/opends/sdk/package-info.java b/sdk/src/org/opends/sdk/package-info.java
new file mode 100755
index 0000000..a7bc4bb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/package-info.java
@@ -0,0 +1,87 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+/**
+ * OpenDS SDK.
+ *
+ * <h1>TODO</h1>
+ * <ul>
+ * <li>LDIF support <b>[Matt]</b>
+ * <ul>
+ * <li>LDIFReader
+ * <ul>
+ * <li>filtered reader (this should wrap an entry enumeration)
+ * <li>should implement generic entry enumeration API.
+ * </ul>
+ * <li>LDIFWriter
+ * <ul>
+ * <li>add comments for DNs
+ * <li>comments in native charset
+ * <li>rest of output must be in ASCII
+ * </ul>
+ * </ul>
+ * <li>Messages
+ * <li>Logging?
+ * <li>Single entry search, blocking search <b>[Bo]</b>
+ * <li>Exceptions sub-types for ErrorResultException (e.g. referrals, assertion failures, client side errors).
+ * <li>Refactor non-schema aware request / response APIs - how should they handle duplicate attribute descriptions and values? I.e. what matching should be performed: do they have List or Set semantics? [Matt]
+ * <ul>
+ * <li>AttributeValueSequence -> AttributeValueCollection?
+ * <li>AttributeSequence -> AttributeCollection?
+ * <li>SearchResultEntry must be cheap to decode in non schema case.
+ * <li>Schema aware versions of these should provide set semantics w.r.t. attribute descriptions and attribute values.
+ * </ul>
+ * <li>How should non-default Grizzly transport be specified by the application?
+ * <li>Unmodifiable requests and responses
+ * <li>Check that it is possible to create SearchResultEntry objects with empty attributes.
+ * <li>Nameable? All objects that have a getName() method
+ * <li>DN, RDN - check APIs. <b>[Matt]</b>
+ * <li>Schema - clean up abstract stuff. Ensure exception handling is correct. <b>[Matt]</b>
+ * <li>Enum / GeneralizedTime parsing function
+ * <li>LDAP connection request timeouts configured using LDAPConnectionOptions.
+ * <li>Re-instate Connection.isValid()
+ * <li>Support parameters in result handlers.
+ * <li>Javadoc
+ * <li>Unit tests
+ * <li>Move to standalone source tree
+ * <li>LDAP URL support and referral support
+ * <li>Thread safe DN caching
+ * <li>Escapes in substring filter
+ * <li>Threading model for decoding messages and calling result handlers
+ * <li>SASL for CLI tools
+ * <li>IBM JVM SSL support?
+ * <li>Intermediate response support.
+ * <li>Consider using Collections instead of Iterables.
+ * <li>Get rid of write lock on connections so encoding can be done in parallel using Grizzly's buffers
+ * <li>Should we dispose of the SASLContext on rebind?
+ * </ul>
+ *
+ */
+package org.opends.sdk;
+
+
+
diff --git a/sdk/src/org/opends/sdk/requests/AbandonRequest.java b/sdk/src/org/opends/sdk/requests/AbandonRequest.java
new file mode 100644
index 0000000..e1d07e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbandonRequest.java
@@ -0,0 +1,147 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The Abandon operation allows a client to request that the server
+ * abandon an uncompleted operation.
+ * <p>
+ * Abandon, Bind, Unbind, and StartTLS operations cannot be abandoned.
+ */
+public interface AbandonRequest extends Request
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  AbandonRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  AbandonRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the message ID of the request to be abandoned.
+   * 
+   * @return The message ID of the request to be abandoned.
+   */
+  int getMessageID();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the message ID of the request to be abandoned.
+   * 
+   * @param id
+   *          The message ID of the request to be abandoned.
+   * @return This abandon request.
+   * @throws UnsupportedOperationException
+   *           If this abandon request does not permit the message ID to
+   *           be set.
+   */
+  AbandonRequest setMessageID(int id)
+      throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java b/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java
new file mode 100644
index 0000000..88fa737
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbandonRequestImpl.java
@@ -0,0 +1,95 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+/**
+ * Abandon request implementation.
+ */
+final class AbandonRequestImpl extends
+    AbstractRequestImpl<AbandonRequest> implements AbandonRequest
+{
+
+  private int messageID;
+
+
+
+  /**
+   * Creates a new abandon request using the provided message ID.
+   * 
+   * @param messageID
+   *          The message ID of the request to be abandoned.
+   */
+  AbandonRequestImpl(int messageID)
+  {
+    this.messageID = messageID;
+  }
+
+
+
+  public int getMessageID()
+  {
+    return messageID;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AbandonRequest setMessageID(int id)
+      throws UnsupportedOperationException
+  {
+    this.messageID = id;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("AbandonRequest(messageID=");
+    builder.append(getMessageID());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  AbandonRequest getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractBindRequest.java b/sdk/src/org/opends/sdk/requests/AbstractBindRequest.java
new file mode 100644
index 0000000..612740b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractBindRequest.java
@@ -0,0 +1,73 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+
+
+
+/**
+ * An abstract Bind request which can be used as the basis for
+ * implementing new authentication methods.
+ * 
+ * @param <R>
+ *          The type of Bind request.
+ */
+public abstract class AbstractBindRequest<R extends BindRequest>
+    extends AbstractRequestImpl<R> implements BindRequest
+{
+
+  /**
+   * Creates a new abstract bind request.
+   */
+  protected AbstractBindRequest()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract DN getName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  final R getThis()
+  {
+    return (R) this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java b/sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java
new file mode 100644
index 0000000..9422db6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractExtendedRequest.java
@@ -0,0 +1,117 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An abstract Extended request which can be used as the basis for
+ * implementing new Extended operations.
+ * 
+ * @param <R>
+ *          The type of extended request.
+ * @param <S>
+ *          The type of result.
+ */
+public abstract class AbstractExtendedRequest<R extends ExtendedRequest<S>, S extends Result>
+    extends AbstractRequestImpl<R> implements ExtendedRequest<S>
+{
+
+  /**
+   * Creates a new abstract extended request.
+   */
+  protected AbstractExtendedRequest()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * Returns the extended operation associated with this extended
+   * request.
+   * <p>
+   * FIXME: this should not be exposed to clients.
+   * 
+   * @return The extended operation associated with this extended
+   *         request.
+   */
+  public abstract ExtendedOperation<R, S> getExtendedOperation();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract String getRequestName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract ByteString getRequestValue();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("ExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", requestValue=");
+    final ByteString value = getRequestValue();
+    builder.append(value == null ? ByteString.empty() : value);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  final R getThis()
+  {
+    return (R) this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java b/sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java
new file mode 100644
index 0000000..6cdfea1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractRequestImpl.java
@@ -0,0 +1,170 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Abstract request implementation.
+ * 
+ * @param <R>
+ *          The type of request.
+ */
+abstract class AbstractRequestImpl<R extends Request> implements
+    Request
+{
+  private final List<Control> controls = new LinkedList<Control>();
+
+
+
+  /**
+   * Creates a new abstract request implementation.
+   */
+  AbstractRequestImpl()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final R addControl(Control control)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(control);
+    controls.add(control);
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final R clearControls()
+  {
+    controls.clear();
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control getControl(String oid)
+  {
+    Validator.ensureNotNull(oid);
+
+    // Avoid creating an iterator if possible.
+    if (controls.isEmpty())
+    {
+      return null;
+    }
+
+    for (final Control control : controls)
+    {
+      if (control.getOID().equals(oid))
+      {
+        return control;
+      }
+    }
+
+    return null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Iterable<Control> getControls()
+  {
+    return controls;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean hasControls()
+  {
+    return !controls.isEmpty();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control removeControl(String oid)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(oid);
+
+    // Avoid creating an iterator if possible.
+    if (controls.isEmpty())
+    {
+      return null;
+    }
+
+    final Iterator<Control> iterator = controls.iterator();
+    while (iterator.hasNext())
+    {
+      final Control control = iterator.next();
+      if (control.getOID().equals(oid))
+      {
+        iterator.remove();
+        return control;
+      }
+    }
+
+    return null;
+  }
+
+
+
+  public abstract String toString();
+
+
+
+  abstract R getThis();
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java b/sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java
new file mode 100644
index 0000000..98132db
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AbstractUnmodifiableRequestImpl.java
@@ -0,0 +1,138 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Iterables;
+
+
+
+/**
+ * Unmodifiable request implementation.
+ * 
+ * @param <R>
+ *          The type of request.
+ */
+abstract class AbstractUnmodifiableRequestImpl<R extends Request>
+    implements Request
+{
+
+  private final R impl;
+
+
+
+  /**
+   * Creates a new unmodifiable request implementation.
+   * 
+   * @param impl
+   *          The underlying request implementation to be made
+   *          unmodifiable.
+   */
+  AbstractUnmodifiableRequestImpl(R impl)
+  {
+    this.impl = impl;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final R addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final R clearControls() throws UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control getControl(String oid)
+      throws NullPointerException
+  {
+    // FIXME: ensure that controls are immutable.
+    return impl.getControl(oid);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Iterable<Control> getControls()
+  {
+    // FIXME: ensure that controls are immutable.
+    return Iterables.unmodifiable(impl.getControls());
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean hasControls()
+  {
+    return impl.hasControls();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String toString()
+  {
+    return impl.toString();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AddRequest.java b/sdk/src/org/opends/sdk/requests/AddRequest.java
new file mode 100644
index 0000000..6629aae
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AddRequest.java
@@ -0,0 +1,341 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Add operation allows a client to request the addition of an entry
+ * into the Directory.
+ * <p>
+ * The RDN attribute(s) may or may not be included in the Add request.
+ * NO-USER-MODIFICATION attributes such as the {@code createTimestamp}
+ * or {@code creatorsName} attributes must not be included, since the
+ * server maintains these automatically.
+ * <p>
+ * FIXME: clean up methods, clearly define schema behavior.
+ */
+public interface AddRequest extends Request, ChangeRecord, Entry
+{
+  /**
+   * {@inheritDoc}
+   */
+  <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean addAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean addAttribute(Attribute attribute,
+      Collection<ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  AddRequest addAttribute(String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided control to this request.
+   *
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  AddRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  AddRequest clearAttributes() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   *
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  AddRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean containsAttribute(AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean containsAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean containsObjectClass(ObjectClass objectClass)
+      throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean containsObjectClass(String objectClass)
+      throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Attribute> findAttributes(
+      AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Attribute> findAttributes(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Attribute getAttribute(AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Attribute getAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  int getAttributeCount();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Attribute> getAttributes();
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   *
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   *
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  DN getName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<String> getObjectClasses();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   *
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean removeAttribute(Attribute attribute,
+      Collection<ByteString> missingValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean removeAttribute(AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  AddRequest removeAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  AddRequest removeAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   *
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean replaceAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  AddRequest replaceAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  AddRequest setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  AddRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/AddRequestImpl.java b/sdk/src/org/opends/sdk/requests/AddRequestImpl.java
new file mode 100644
index 0000000..91cb043
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/AddRequestImpl.java
@@ -0,0 +1,387 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Add request implementation.
+ */
+final class AddRequestImpl extends AbstractRequestImpl<AddRequest>
+    implements AddRequest
+{
+
+  private final Entry entry;
+
+
+
+  /**
+   * Creates a new add request backed by the provided entry.
+   * Modifications made to {@code entry} will be reflected in the
+   * returned add request. The returned add request supports updates to
+   * its list of controls, as well as updates to the name and attributes
+   * if the underlying entry allows.
+   *
+   * @param entry
+   *          The entry to be added.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null} .
+   */
+  AddRequestImpl(Entry entry) throws NullPointerException
+  {
+    this.entry = entry;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+  {
+    return v.visitChangeRecord(p, this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.addAttribute(attribute);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAttribute(Attribute attribute,
+      Collection<ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.addAttribute(attribute, duplicateValues);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AddRequestImpl addAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.addAttribute(attributeDescription, values);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AddRequestImpl clearAttributes()
+      throws UnsupportedOperationException
+  {
+    entry.clearAttributes();
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    return entry.containsAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return entry.containsAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsObjectClass(ObjectClass objectClass)
+      throws NullPointerException
+  {
+    return entry.containsObjectClass(objectClass);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsObjectClass(String objectClass)
+      throws NullPointerException
+  {
+    return entry.containsObjectClass(objectClass);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> findAttributes(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    return entry.findAttributes(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> findAttributes(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return entry.findAttributes(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Attribute getAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    return entry.getAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Attribute getAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return entry.getAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getAttributeCount()
+  {
+    return entry.getAttributeCount();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> getAttributes()
+  {
+    return entry.getAttributes();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return entry.getName();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<String> getObjectClasses()
+  {
+    return entry.getObjectClasses();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(Attribute attribute,
+      Collection<ByteString> missingValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.removeAttribute(attribute, missingValues);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(
+      AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.removeAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AddRequestImpl removeAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.removeAttribute(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AddRequestImpl removeAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.removeAttribute(attributeDescription, values);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean replaceAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.replaceAttribute(attribute);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AddRequestImpl replaceAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.replaceAttribute(attributeDescription, values);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AddRequestImpl setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    entry.setName(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AddRequestImpl setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.setName(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("AddRequest(name=");
+    builder.append(getName());
+    builder.append(", attributes=");
+    builder.append(getAttributes());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  AddRequest getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/BindRequest.java b/sdk/src/org/opends/sdk/requests/BindRequest.java
new file mode 100644
index 0000000..62a466d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/BindRequest.java
@@ -0,0 +1,137 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The Bind operation allows authentication information to be exchanged
+ * between the client and server. The Bind operation should be thought
+ * of as the "authenticate" operation.
+ */
+public interface BindRequest extends Request
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  BindRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  BindRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the distinguished name of the Directory object that the
+   * client wishes to bind as. The distinguished name may be empty (but
+   * never {@code null}) when used for of anonymous binds, or when using
+   * SASL authentication. The server shall not dereference any aliases
+   * in locating the named object.
+   * 
+   * @return The distinguished name of the Directory object that the
+   *         client wishes to bind as.
+   */
+  DN getName();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/CompareRequest.java b/sdk/src/org/opends/sdk/requests/CompareRequest.java
new file mode 100644
index 0000000..0172292
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/CompareRequest.java
@@ -0,0 +1,288 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Compare operation allows a client to compare an assertion value
+ * with the values of a particular attribute in a particular entry in
+ * the Directory.
+ * <p>
+ * Note that some directory systems may establish access controls that
+ * permit the values of certain attributes (such as {@code userPassword}
+ * ) to be compared but not interrogated by other means.
+ */
+public interface CompareRequest extends Request
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  CompareRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  CompareRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the assertion value to be compared.
+   * 
+   * @return The assertion value.
+   */
+  ByteString getAssertionValue();
+
+
+
+  /**
+   * Returns the assertion value to be compared decoded as a UTF-8
+   * string.
+   * 
+   * @return The assertion value decoded as a UTF-8 string.
+   */
+  String getAssertionValueAsString();
+
+
+
+  /**
+   * Returns the name of the attribute to be compared.
+   * 
+   * @return The name of the attribute.
+   */
+  AttributeDescription getAttributeDescription();
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the distinguished name of the entry to be compared. The
+   * server shall not dereference any aliases in locating the entry to
+   * be compared.
+   * 
+   * @return The distinguished name of the entry.
+   */
+  DN getName();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the assertion value to be compared.
+   * 
+   * @param value
+   *          The assertion value to be compared.
+   * @return This compare request.
+   * @throws UnsupportedOperationException
+   *           If this compare request does not permit the assertion
+   *           value to be set.
+   * @throws NullPointerException
+   *           If {@code value} was {@code null}.
+   */
+  CompareRequest setAssertionValue(ByteString value)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the assertion value to be compared.
+   * <p>
+   * If the assertion value is not an instance of {@code ByteString}
+   * then it will be converted using the
+   * {@link ByteString#valueOf(Object)} method.
+   * 
+   * @param value
+   *          The assertion value to be compared.
+   * @return This compare request.
+   * @throws UnsupportedOperationException
+   *           If this compare request does not permit the assertion
+   *           value to be set.
+   * @throws NullPointerException
+   *           If {@code value} was {@code null}.
+   */
+  CompareRequest setAssertionValue(Object value)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the name of the attribute to be compared.
+   * 
+   * @param attributeDescription
+   *          The name of the attribute to be compared.
+   * @return This compare request.
+   * @throws UnsupportedOperationException
+   *           If this compare request does not permit the attribute
+   *           description to be set.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  CompareRequest setAttributeDescription(
+      AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the name of the attribute to be compared.
+   * 
+   * @param attributeDescription
+   *          The name of the attribute to be compared.
+   * @return This compare request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the default schema.
+   * @throws UnsupportedOperationException
+   *           If this compare request does not permit the attribute
+   *           description to be set.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  CompareRequest setAttributeDescription(String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be compared. The server
+   * shall not dereference any aliases in locating the entry to be
+   * compared.
+   * 
+   * @param dn
+   *          The distinguished name of the entry to be compared.
+   * @return This compare request.
+   * @throws UnsupportedOperationException
+   *           If this compare request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  CompareRequest setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be compared. The server
+   * shall not dereference any aliases in locating the entry to be
+   * compared.
+   * 
+   * @param dn
+   *          The distinguished name of the entry to be compared.
+   * @return This compare request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this compare request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  CompareRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/CompareRequestImpl.java b/sdk/src/org/opends/sdk/requests/CompareRequestImpl.java
new file mode 100644
index 0000000..1bd45ce
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/CompareRequestImpl.java
@@ -0,0 +1,229 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Compare request implementation.
+ */
+final class CompareRequestImpl extends
+    AbstractRequestImpl<CompareRequest> implements CompareRequest
+{
+
+  private AttributeDescription attributeDescription;
+
+  private ByteString assertionValue;
+
+  private DN name;
+
+
+
+  /**
+   * Creates a new compare request using the provided distinguished
+   * name, attribute name, and assertion value.
+   * 
+   * @param name
+   *          The distinguished name of the entry to be compared.
+   * @param attributeDescription
+   *          The name of the attribute to be compared.
+   * @param assertionValue
+   *          The assertion value to be compared.
+   * @throws NullPointerException
+   *           If {@code name}, {@code attributeDescription}, or {@code
+   *           assertionValue} was {@code null}.
+   */
+  CompareRequestImpl(DN name,
+      AttributeDescription attributeDescription,
+      ByteString assertionValue) throws NullPointerException
+  {
+    this.name = name;
+    this.attributeDescription = attributeDescription;
+    this.assertionValue = assertionValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getAssertionValue()
+  {
+    return assertionValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getAssertionValueAsString()
+  {
+    return assertionValue.toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public AttributeDescription getAttributeDescription()
+  {
+    return attributeDescription;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public CompareRequest setAssertionValue(ByteString value)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(value);
+    this.assertionValue = value;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public CompareRequest setAssertionValue(Object value)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(value);
+    this.assertionValue = ByteString.valueOf(value);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public CompareRequest setAttributeDescription(
+      AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+    this.attributeDescription = attributeDescription;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public CompareRequest setAttributeDescription(
+      String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+    this.attributeDescription = AttributeDescription
+        .valueOf(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public CompareRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public CompareRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = DN.valueOf(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("CompareRequest(name=");
+    builder.append(getName());
+    builder.append(", attributeDescription=");
+    builder.append(getAttributeDescription());
+    builder.append(", assertionValue=");
+    builder.append(getAssertionValueAsString());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  CompareRequest getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/DeleteRequest.java b/sdk/src/org/opends/sdk/requests/DeleteRequest.java
new file mode 100644
index 0000000..0425c6f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/DeleteRequest.java
@@ -0,0 +1,190 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Delete operation allows a client to request the removal of an
+ * entry from the Directory.
+ * <p>
+ * Only leaf entries (those with no subordinate entries) can be deleted
+ * with this operation. However, addition of the {@code
+ * SubtreeDeleteControl} permits whole sub-trees to be deleted using a
+ * single Delete request.
+ */
+public interface DeleteRequest extends Request, ChangeRecord
+{
+  /**
+   * {@inheritDoc}
+   */
+  <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  DeleteRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  DeleteRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the distinguished name of the entry to be deleted. The
+   * server shall not dereference any aliases in locating the entry to
+   * be deleted.
+   * 
+   * @return The distinguished name of the entry.
+   */
+  DN getName();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be deleted. The server
+   * shall not dereference any aliases in locating the entry to be
+   * deleted.
+   * 
+   * @param dn
+   *          The distinguished name of the entry to be deleted.
+   * @return This delete request.
+   * @throws UnsupportedOperationException
+   *           If this delete request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  DeleteRequest setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be deleted. The server
+   * shall not dereference any aliases in locating the entry to be
+   * deleted.
+   * 
+   * @param dn
+   *          The distinguished name of the entry to be deleted.
+   * @return This delete request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this delete request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  DeleteRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java b/sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java
new file mode 100644
index 0000000..b9e4646
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/DeleteRequestImpl.java
@@ -0,0 +1,133 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Delete request implementation.
+ */
+final class DeleteRequestImpl extends
+    AbstractRequestImpl<DeleteRequest> implements DeleteRequest
+{
+  private DN name;
+
+
+
+  /**
+   * Creates a new delete request using the provided distinguished name.
+   * 
+   * @param name
+   *          The distinguished name of the entry to be deleted.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  DeleteRequestImpl(DN name) throws NullPointerException
+  {
+    this.name = name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+  {
+    return v.visitChangeRecord(p, this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DeleteRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DeleteRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = DN.valueOf(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("DeleteRequest(name=");
+    builder.append(getName());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  DeleteRequest getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ExtendedRequest.java b/sdk/src/org/opends/sdk/requests/ExtendedRequest.java
new file mode 100644
index 0000000..26cd503
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ExtendedRequest.java
@@ -0,0 +1,165 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.extensions.StartTLSRequest;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * The Extended operation allows additional operations to be defined for
+ * services not already available in the protocol; for example, to
+ * implement an operation which installs transport layer security (see
+ * {@link StartTLSRequest}).
+ * 
+ * @param <S>
+ *          The type of result.
+ */
+public interface ExtendedRequest<S extends Result> extends Request
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  ExtendedRequest<S> addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  ExtendedRequest<S> clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the extended operation associated with this extended
+   * request.
+   * <p>
+   * FIXME: this should not be exposed in the public API.
+   * 
+   * @return The extended operation associated with this extended
+   *         request.
+   */
+  ExtendedOperation<?, S> getExtendedOperation();
+
+
+
+  /**
+   * Returns the dotted-decimal representation of the unique OID
+   * corresponding to this extended request.
+   * 
+   * @return The dotted-decimal representation of the unique OID.
+   */
+  String getRequestName();
+
+
+
+  /**
+   * Returns the content of this extended request in a form defined by
+   * the extended request.
+   * 
+   * @return The content of this extended request, or {@code null} if
+   *         there is no content.
+   */
+  ByteString getRequestValue();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericBindRequest.java b/sdk/src/org/opends/sdk/requests/GenericBindRequest.java
new file mode 100644
index 0000000..c8e1ab5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericBindRequest.java
@@ -0,0 +1,243 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * A generic Bind request which should be used for unsupported
+ * authentication methods. Servers that do not support a choice supplied
+ * by a client return a Bind response with the result code set to
+ * {@link ResultCode#AUTH_METHOD_NOT_SUPPORTED}.
+ */
+public interface GenericBindRequest extends BindRequest
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  GenericBindRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  GenericBindRequest clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the authentication mechanism identifier for this generic
+   * bind request. Note that value {@code 0} is reserved for simple
+   * authentication, {@code 1} and {@code 2} are reserved but unused,
+   * and {@code 3} is reserved for SASL authentication.
+   * 
+   * @return The authentication mechanism identifier.
+   */
+  byte getAuthenticationType();
+
+
+
+  /**
+   * Returns the authentication information for this generic bind
+   * request in a form defined by the authentication mechanism.
+   * 
+   * @return The authentication information.
+   */
+  ByteString getAuthenticationValue();
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  DN getName();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the authentication mechanism identifier for this generic bind
+   * request. Note that value {@code 0} is reserved for simple
+   * authentication, {@code 1} and {@code 2} are reserved but unused,
+   * and {@code 3} is reserved for SASL authentication.
+   * 
+   * @param type
+   *          The authentication mechanism identifier for this generic
+   *          bind request.
+   * @return This generic bind request.
+   * @throws UnsupportedOperationException
+   *           If this generic bind request does not permit the
+   *           authentication type to be set.
+   */
+  GenericBindRequest setAuthenticationType(byte type)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the authentication information for this generic bind request
+   * in a form defined by the authentication mechanism.
+   * 
+   * @param bytes
+   *          The authentication information for this generic bind
+   *          request in a form defined by the authentication mechanism.
+   * @return This generic bind request.
+   * @throws UnsupportedOperationException
+   *           If this generic bind request does not permit the
+   *           authentication bytes to be set.
+   * @throws NullPointerException
+   *           If {@code bytes} was {@code null}.
+   */
+  GenericBindRequest setAuthenticationValue(ByteString bytes)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the Directory object that the client
+   * wishes to bind as. The distinguished name may be empty (but never
+   * {@code null} when used for of anonymous binds, or when using SASL
+   * authentication. The server shall not dereference any aliases in
+   * locating the named object.
+   * 
+   * @param dn
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  GenericBindRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the Directory object that the client
+   * wishes to bind as. The distinguished name may be empty (but never
+   * {@code null} when used for of anonymous binds, or when using SASL
+   * authentication. The server shall not dereference any aliases in
+   * locating the named object.
+   * 
+   * @param dn
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as.
+   * @return This bind request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  GenericBindRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java b/sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java
new file mode 100644
index 0000000..dbaa10e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericBindRequestImpl.java
@@ -0,0 +1,182 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Generic bind request implementation.
+ */
+final class GenericBindRequestImpl extends
+    AbstractBindRequest<GenericBindRequest> implements
+    GenericBindRequest
+{
+
+  private DN name;
+
+  private ByteString authenticationValue;
+
+  private byte authenticationType;
+
+
+
+  /**
+   * Creates a new generic bind request using the provided distinguished
+   * name, authentication type, and authentication information.
+   * 
+   * @param name
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as (may be empty).
+   * @param authenticationType
+   *          The authentication mechanism identifier for this generic
+   *          bind request.
+   * @param authenticationValue
+   *          The authentication information for this generic bind
+   *          request in a form defined by the authentication mechanism.
+   * @throws NullPointerException
+   *           If {@code name}, {@code authenticationType}, or {@code
+   *           authenticationValue} was {@code null}.
+   */
+  GenericBindRequestImpl(DN name, byte authenticationType,
+      ByteString authenticationValue) throws NullPointerException
+  {
+    this.name = name;
+    this.authenticationType = authenticationType;
+    this.authenticationValue = authenticationValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte getAuthenticationType()
+  {
+    return authenticationType;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getAuthenticationValue()
+  {
+    return authenticationValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericBindRequest setAuthenticationType(byte type)
+      throws UnsupportedOperationException
+  {
+    this.authenticationType = type;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericBindRequest setAuthenticationValue(ByteString bytes)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(bytes);
+    this.authenticationValue = bytes;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericBindRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericBindRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = DN.valueOf(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("GenericBindRequest(name=");
+    builder.append(getName());
+    builder.append(", authenticationType=");
+    builder.append(getAuthenticationType());
+    builder.append(", authenticationValue=");
+    builder.append(getAuthenticationValue());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java b/sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java
new file mode 100644
index 0000000..ff027f8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericExtendedRequest.java
@@ -0,0 +1,188 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.responses.GenericExtendedResult;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A generic Extended request which should be used for unsupported
+ * extended operations. Servers list the names of Extended requests they
+ * recognize in the {@code supportedExtension} attribute in the root
+ * DSE. Where the name is not recognized, the server returns
+ * {@link ResultCode#PROTOCOL_ERROR} (the server may return this error
+ * in other cases).
+ */
+public interface GenericExtendedRequest extends
+    ExtendedRequest<GenericExtendedResult>
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  GenericExtendedRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  GenericExtendedRequest clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  ExtendedOperation<GenericExtendedRequest, GenericExtendedResult> getExtendedOperation();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  String getRequestName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  ByteString getRequestValue();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the dotted-decimal representation of the unique OID
+   * corresponding to this generic extended request.
+   * 
+   * @param oid
+   *          The dotted-decimal representation of the unique OID.
+   * @return This generic extended request.
+   * @throws UnsupportedOperationException
+   *           If this generic extended request does not permit the
+   *           request name to be set.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  GenericExtendedRequest setRequestName(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the content of this generic extended request in a form defined
+   * by the extended request.
+   * 
+   * @param bytes
+   *          The content of this generic extended request in a form
+   *          defined by the extended request, or {@code null} if there
+   *          is no content.
+   * @return This generic extended request.
+   * @throws UnsupportedOperationException
+   *           If this generic extended request does not permit the
+   *           request value to be set.
+   */
+  GenericExtendedRequest setRequestValue(ByteString bytes)
+      throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java b/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java
new file mode 100644
index 0000000..b7e396e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/GenericExtendedRequestImpl.java
@@ -0,0 +1,192 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.extensions.ExtendedOperation;
+import org.opends.sdk.responses.GenericExtendedResult;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Generic extended request implementation.
+ */
+final class GenericExtendedRequestImpl
+    extends
+    AbstractExtendedRequest<GenericExtendedRequest, GenericExtendedResult>
+    implements GenericExtendedRequest
+{
+  /**
+   * Generic extended operation singleton.
+   */
+  private static final class Operation implements
+      ExtendedOperation<GenericExtendedRequest, GenericExtendedResult>
+  {
+
+    public GenericExtendedRequest decodeRequest(String requestName,
+        ByteString requestValue) throws DecodeException
+    {
+      return Requests.newGenericExtendedRequest(requestName,
+          requestValue);
+    }
+
+
+
+    public GenericExtendedResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage)
+    {
+      return Responses.newGenericExtendedResult(resultCode)
+          .setMatchedDN(matchedDN).setDiagnosticMessage(
+              diagnosticMessage);
+    }
+
+
+
+    public GenericExtendedResult decodeResponse(ResultCode resultCode,
+        String matchedDN, String diagnosticMessage,
+        String responseName, ByteString responseValue)
+        throws DecodeException
+    {
+      return Responses.newGenericExtendedResult(resultCode)
+          .setMatchedDN(matchedDN).setDiagnosticMessage(
+              diagnosticMessage).setResponseName(responseName)
+          .setResponseValue(responseValue);
+    }
+  }
+
+
+
+  private static final Operation OPERATION = new Operation();
+
+  private ByteString requestValue = ByteString.empty();
+
+  private String requestName;
+
+
+
+  /**
+   * Creates a new generic extended request using the provided name and
+   * optional value.
+   * 
+   * @param requestName
+   *          The dotted-decimal representation of the unique OID
+   *          corresponding to this extended request.
+   * @param requestValue
+   *          The content of this generic extended request in a form
+   *          defined by the extended operation, or {@code null} if
+   *          there is no content.
+   * @throws NullPointerException
+   *           If {@code requestName} was {@code null}.
+   */
+  GenericExtendedRequestImpl(String requestName, ByteString requestValue)
+      throws NullPointerException
+  {
+    this.requestName = requestName;
+    this.requestValue = requestValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ExtendedOperation<GenericExtendedRequest, GenericExtendedResult> getExtendedOperation()
+  {
+    return OPERATION;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getRequestName()
+  {
+    return requestName;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getRequestValue()
+  {
+    return requestValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericExtendedRequest setRequestName(String oid)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(oid);
+    this.requestName = oid;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericExtendedRequest setRequestValue(ByteString bytes)
+      throws UnsupportedOperationException
+  {
+    this.requestValue = bytes;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("GenericExtendedRequest(requestName=");
+    builder.append(getRequestName());
+    builder.append(", requestValue=");
+    builder.append(getRequestValue());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyDNRequest.java b/sdk/src/org/opends/sdk/requests/ModifyDNRequest.java
new file mode 100644
index 0000000..9afbe3f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyDNRequest.java
@@ -0,0 +1,334 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.RDN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Modify DN operation allows a client to change the Relative
+ * Distinguished Name (RDN) of an entry in the Directory and/or to move
+ * a subtree of entries to a new location in the Directory.
+ */
+public interface ModifyDNRequest extends Request, ChangeRecord
+{
+  /**
+   * {@inheritDoc}
+   */
+  <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  ModifyDNRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  ModifyDNRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the distinguished name of the entry to be renamed. This
+   * entry may or may not have subordinate entries. The server shall not
+   * dereference any aliases in locating the entry to be renamed.
+   * 
+   * @return The distinguished name of the entry.
+   */
+  DN getName();
+
+
+
+  /**
+   * Returns the new RDN of the entry to be renamed. The value of the
+   * old RDN is supplied when moving the entry to a new superior without
+   * changing its RDN. Attribute values of the new RDN not matching any
+   * attribute value of the entry are added to the entry, and an
+   * appropriate error is returned if this fails.
+   * 
+   * @return The new RDN of the entry.
+   */
+  RDN getNewRDN();
+
+
+
+  /**
+   * Returns the distinguished name of an existing entry that will
+   * become the immediate superior (parent) of the entry to be renamed.
+   * The server shall not dereference any aliases in locating the new
+   * superior entry.
+   * 
+   * @return The distinguished name of the new superior entry, or
+   *         {@code null} if the entry is to remain under the same
+   *         parent entry.
+   */
+  DN getNewSuperior();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Indicates whether the old RDN attribute values are to be retained
+   * as attributes of the entry or deleted from the entry.
+   * 
+   * @return {@code true} if the old RDN attribute values are to be
+   *         deleted from the entry, or {@code false} if they are to be
+   *         retained.
+   */
+  boolean isDeleteOldRDN();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Specifies whether the old RDN attribute values are to be retained
+   * as attributes of the entry or deleted from the entry.
+   * 
+   * @param deleteOldRDN
+   *          {@code true} if the old RDN attribute values are to be
+   *          deleted from the entry, or {@code false} if they are to be
+   *          retained.
+   * @return This modify DN request.
+   * @throws UnsupportedOperationException
+   *           If this modify DN request does not permit the delete old
+   *           RDN parameter to be set.
+   */
+  ModifyDNRequest setDeleteOldRDN(boolean deleteOldRDN)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be renamed. This entry
+   * may or may not have subordinate entries. The server shall not
+   * dereference any aliases in locating the entry to be renamed.
+   * 
+   * @param dn
+   *          The distinguished name of the entry to be renamed.
+   * @return This modify DN request.
+   * @throws UnsupportedOperationException
+   *           If this modify DN request does not permit the
+   *           distinguished name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  ModifyDNRequest setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be renamed. This entry
+   * may or may not have subordinate entries. The server shall not
+   * dereference any aliases in locating the entry to be renamed.
+   * 
+   * @param dn
+   *          The distinguished name of the entry to be renamed.
+   * @return This modify DN request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this modify DN request does not permit the
+   *           distinguished name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  ModifyDNRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the new RDN of the entry to be renamed. The value of the old
+   * RDN is supplied when moving the entry to a new superior without
+   * changing its RDN. Attribute values of the new RDN not matching any
+   * attribute value of the entry are added to the entry, and an
+   * appropriate error is returned if this fails.
+   * 
+   * @param rdn
+   *          The new RDN of the entry to be renamed.
+   * @return This modify DN request.
+   * @throws UnsupportedOperationException
+   *           If this modify DN request does not permit the new RDN to
+   *           be set.
+   * @throws NullPointerException
+   *           If {@code rdn} was {@code null}.
+   */
+  ModifyDNRequest setNewRDN(RDN rdn)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the new RDN of the entry to be renamed. The value of the old
+   * RDN is supplied when moving the entry to a new superior without
+   * changing its RDN. Attribute values of the new RDN not matching any
+   * attribute value of the entry are added to the entry, and an
+   * appropriate error is returned if this fails.
+   * 
+   * @param rdn
+   *          The new RDN of the entry to be renamed.
+   * @return This modify DN request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code rdn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this modify DN request does not permit the new RDN to
+   *           be set.
+   * @throws NullPointerException
+   *           If {@code rdn} was {@code null}.
+   */
+  ModifyDNRequest setNewRDN(String rdn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of an existing entry that will become
+   * the immediate superior (parent) of the entry to be renamed. The
+   * server shall not dereference any aliases in locating the new
+   * superior entry.
+   * 
+   * @param dn
+   *          The distinguished name of an existing entry that will
+   *          become the immediate superior (parent) of the entry to be
+   *          renamed, may be {@code null}.
+   * @return This modify DN request.
+   * @throws UnsupportedOperationException
+   *           If this modify DN request does not permit the new
+   *           superior to be set.
+   */
+  ModifyDNRequest setNewSuperior(DN dn)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the distinguished name of an existing entry that will become
+   * the immediate superior (parent) of the entry to be renamed. The
+   * server shall not dereference any aliases in locating the new
+   * superior entry.
+   * 
+   * @param dn
+   *          The distinguished name of an existing entry that will
+   *          become the immediate superior (parent) of the entry to be
+   *          renamed, may be {@code null}.
+   * @return This modify DN request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this modify DN request does not permit the new
+   *           superior to be set.
+   */
+  ModifyDNRequest setNewSuperior(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java b/sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java
new file mode 100644
index 0000000..ae7a5be
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyDNRequestImpl.java
@@ -0,0 +1,244 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.RDN;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modify DN request implementation.
+ */
+final class ModifyDNRequestImpl extends
+    AbstractRequestImpl<ModifyDNRequest> implements ModifyDNRequest
+{
+  private DN name;
+
+  private DN newSuperior = null;
+
+  private RDN newRDN;
+
+  private boolean deleteOldRDN = false;
+
+
+
+  /**
+   * Creates a new modify DN request using the provided distinguished
+   * name and new RDN.
+   *
+   * @param name
+   *          The distinguished name of the entry to be renamed.
+   * @param newRDN
+   *          The new RDN of the entry.
+   * @throws NullPointerException
+   *           If {@code name} or {@code newRDN} was {@code null}.
+   */
+  ModifyDNRequestImpl(DN name, RDN newRDN) throws NullPointerException
+  {
+    this.name = name;
+    this.newRDN = newRDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+  {
+    return v.visitChangeRecord(p, this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public RDN getNewRDN()
+  {
+    return newRDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getNewSuperior()
+  {
+    return newSuperior;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isDeleteOldRDN()
+  {
+    return deleteOldRDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyDNRequestImpl setDeleteOldRDN(boolean deleteOldRDN)
+      throws UnsupportedOperationException
+  {
+    this.deleteOldRDN = deleteOldRDN;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyDNRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyDNRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = DN.valueOf(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyDNRequest setNewRDN(RDN rdn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(rdn);
+    this.newRDN = rdn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyDNRequest setNewRDN(String rdn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(rdn);
+    this.newRDN = RDN.valueOf(rdn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyDNRequest setNewSuperior(DN dn)
+      throws UnsupportedOperationException
+  {
+    this.newSuperior = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyDNRequest setNewSuperior(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException
+  {
+    this.newSuperior = (dn != null) ? DN.valueOf(dn) : null;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("ModifyDNRequest(name=");
+    builder.append(getName());
+    builder.append(", newRDN=");
+    builder.append(getNewRDN());
+    builder.append(", deleteOldRDN=");
+    builder.append(isDeleteOldRDN());
+    builder.append(", newSuperior=");
+    builder.append(String.valueOf(getNewSuperior()));
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  ModifyDNRequest getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyRequest.java b/sdk/src/org/opends/sdk/requests/ModifyRequest.java
new file mode 100644
index 0000000..3aa9fa9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyRequest.java
@@ -0,0 +1,280 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.Change;
+import org.opends.sdk.DN;
+import org.opends.sdk.ModificationType;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Modify operation allows a client to request that a modification
+ * of an entry be performed on its behalf by a server.
+ */
+public interface ModifyRequest extends Request, ChangeRecord
+{
+  /**
+   * {@inheritDoc}
+   */
+  <R, P> R accept(ChangeRecordVisitor<R, P> v, P p);
+
+
+
+  /**
+   * Appends the provided change to the list of changes included with
+   * this modify request.
+   * 
+   * @param change
+   *          The change to be performed.
+   * @return This modify request.
+   * @throws UnsupportedOperationException
+   *           If this modify request does not permit changes to be
+   *           added.
+   * @throws NullPointerException
+   *           If {@code change} was {@code null}.
+   */
+  ModifyRequest addChange(Change change)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Appends the provided change to the list of changes included with
+   * this modify request.
+   * <p>
+   * If the attribute value is not an instance of {@code ByteString}
+   * then it will be converted using the
+   * {@link ByteString#valueOf(Object)} method.
+   * 
+   * @param type
+   *          The type of change to be performed.
+   * @param attributeDescription
+   *          The name of the attribute to be modified.
+   * @param values
+   *          The attribute values to be modified.
+   * @return This modify request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code attributeDescription} could not be decoded
+   *           using the default schema.
+   * @throws UnsupportedOperationException
+   *           If this modify request does not permit changes to be
+   *           added.
+   * @throws NullPointerException
+   *           If {@code type}, {@code attributeDescription}, or {@code
+   *           value} was {@code null}.
+   */
+  ModifyRequest addChange(ModificationType type,
+      String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  ModifyRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the changes included with this modify request.
+   * 
+   * @return This modify request.
+   * @throws UnsupportedOperationException
+   *           If this modify request does not permit changes to be
+   *           removed.
+   */
+  ModifyRequest clearChanges() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  ModifyRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the number of changes included with this modify request.
+   * 
+   * @return The number of changes.
+   */
+  int getChangeCount();
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the changes included with
+   * this modify request. The returned {@code Iterable} may be used to
+   * remove changes if permitted by this modify request.
+   * 
+   * @return An {@code Iterable} containing the changes.
+   */
+  Iterable<Change> getChanges();
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the distinguished name of the entry to be modified. The
+   * server shall not perform any alias dereferencing in determining the
+   * object to be modified.
+   * 
+   * @return The distinguished name of the entry to be modified.
+   */
+  DN getName();
+
+
+
+  /**
+   * Indicates whether or not this modify request has any changes.
+   * 
+   * @return {@code true} if this modify request has any changes,
+   *         otherwise {@code false}.
+   */
+  boolean hasChanges();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be modified. The server
+   * shall not perform any alias dereferencing in determining the object
+   * to be modified.
+   * 
+   * @param dn
+   *          The the distinguished name of the entry to be modified.
+   * @return This modify request.
+   * @throws UnsupportedOperationException
+   *           If this modify request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  ModifyRequest setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the entry to be modified. The server
+   * shall not perform any alias dereferencing in determining the object
+   * to be modified.
+   * 
+   * @param dn
+   *          The the distinguished name of the entry to be modified.
+   * @return This modify request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this modify request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  ModifyRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java b/sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java
new file mode 100644
index 0000000..0f7f6be
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/ModifyRequestImpl.java
@@ -0,0 +1,226 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.LinkedAttribute;
+import org.opends.sdk.Change;
+import org.opends.sdk.DN;
+import org.opends.sdk.ModificationType;
+import org.opends.sdk.ldif.ChangeRecordVisitor;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modify request implementation.
+ */
+final class ModifyRequestImpl extends
+    AbstractRequestImpl<ModifyRequest> implements ModifyRequest
+{
+  private final List<Change> changes = new LinkedList<Change>();
+
+  private DN name;
+
+
+
+  /**
+   * Creates a new modify request using the provided distinguished name.
+   *
+   * @param name
+   *          The distinguished name of the entry to be modified.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  ModifyRequestImpl(DN name) throws NullPointerException
+  {
+    this.name = name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <R, P> R accept(ChangeRecordVisitor<R, P> v, P p)
+  {
+    return v.visitChangeRecord(p, this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyRequest addChange(Change change)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(change);
+    changes.add(change);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyRequest addChange(ModificationType type,
+      String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(type, attributeDescription, values);
+    changes.add(new Change(type, new LinkedAttribute(
+        attributeDescription, values)));
+    return this;
+  }
+
+
+
+  public ModifyRequest addChange(ModificationType type,
+      String attributeDescription, Object firstValue,
+      Object... remainingValues)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    // TODO Auto-generated method stub
+    return null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyRequest clearChanges()
+      throws UnsupportedOperationException
+  {
+    changes.clear();
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getChangeCount()
+  {
+    return changes.size();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Change> getChanges()
+  {
+    return changes;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean hasChanges()
+  {
+    return !changes.isEmpty();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ModifyRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = DN.valueOf(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("ModifyRequest(dn=");
+    builder.append(getName());
+    builder.append(", changes=");
+    builder.append(getChanges());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  ModifyRequest getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/Request.java b/sdk/src/org/opends/sdk/requests/Request.java
new file mode 100644
index 0000000..ed7c60a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/Request.java
@@ -0,0 +1,124 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The base class of all Requests provides methods for querying and
+ * manipulating the set of Controls included with a Request.
+ * <p>
+ * TODO: added complete description including sub-types.
+ */
+public interface Request
+{
+
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  Request addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  Request clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/Requests.java b/sdk/src/org/opends/sdk/requests/Requests.java
new file mode 100644
index 0000000..69c1dcc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/Requests.java
@@ -0,0 +1,699 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import static org.opends.messages.UtilityMessages.WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.ldif.ChangeRecord;
+import org.opends.sdk.ldif.LDIFChangeRecordReader;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class contains various methods for creating and manipulating
+ * requests.
+ * <p>
+ * TODO: search request from LDAP URL.
+ * <p>
+ * TODO: update request from persistent search result.
+ * <p>
+ * TODO: synchronized requests?
+ * <p>
+ * TODO: copy constructors.
+ */
+public final class Requests
+{
+
+  /**
+   * Creates a new abandon request using the provided message ID.
+   *
+   * @param messageID
+   *          The message ID of the request to be abandoned.
+   * @return The new abandon request.
+   */
+  public static AbandonRequest newAbandonRequest(int messageID)
+  {
+    return new AbandonRequestImpl(messageID);
+  }
+
+
+
+  /**
+   * Creates a new add request using the provided distinguished name.
+   *
+   * @param name
+   *          The distinguished name of the entry to be added.
+   * @return The new add request.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static AddRequest newAddRequest(DN name)
+      throws NullPointerException
+  {
+    final Entry entry = new SortedEntry().setName(name);
+    return new AddRequestImpl(entry);
+  }
+
+
+
+  /**
+   * Creates a new add request backed by the provided entry.
+   * Modifications made to {@code entry} will be reflected in the
+   * returned add request. The returned add request supports updates to
+   * its list of controls, as well as updates to the name and attributes
+   * if the underlying entry allows.
+   *
+   * @param entry
+   *          The entry to be added.
+   * @return The new add request.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null} .
+   */
+  public static AddRequest newAddRequest(Entry entry)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(entry);
+    return new AddRequestImpl(entry);
+  }
+
+
+
+  /**
+   * Creates a new add request using the provided distinguished name
+   * decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the entry to be added.
+   * @return The new add request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static AddRequest newAddRequest(String name)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    final Entry entry = new SortedEntry().setName(name);
+    return new AddRequestImpl(entry);
+  }
+
+
+
+  /**
+   * Creates a new add request using the provided lines of LDIF decoded
+   * using the default schema.
+   *
+   * @param ldifLines
+   *          Lines of LDIF containing an LDIF add change record or an
+   *          LDIF entry record.
+   * @return The new add request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} was empty, or contained invalid
+   *           LDIF, or could not be decoded using the default schema.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null} .
+   */
+  public static AddRequest newAddRequest(String... ldifLines)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    // LDIF change record reader is tolerant to missing change types.
+    ChangeRecord record = LDIFChangeRecordReader
+        .valueOfLDIFChangeRecord(ldifLines);
+
+    if (record instanceof AddRequest)
+    {
+      return (AddRequest) record;
+    }
+    else
+    {
+      // Wrong change type.
+      Message message = WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE
+          .get("add");
+      throw new LocalizedIllegalArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Creates a new change record (an add, delete, modify, or modify DN
+   * request) using the provided lines of LDIF decoded using the default
+   * schema.
+   *
+   * @param ldifLines
+   *          Lines of LDIF containing an LDIF change record or an LDIF
+   *          entry record.
+   * @return The new change record.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} was empty, or contained invalid
+   *           LDIF, or could not be decoded using the default schema.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null} .
+   */
+  public static ChangeRecord newChangeRecord(String... ldifLines)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    // LDIF change record reader is tolerant to missing change types.
+    return LDIFChangeRecordReader.valueOfLDIFChangeRecord(ldifLines);
+  }
+
+
+
+  /**
+   * Creates a new compare request using the provided distinguished
+   * name, attribute name, and assertion value.
+   *
+   * @param name
+   *          The distinguished name of the entry to be compared.
+   * @param attributeDescription
+   *          The name of the attribute to be compared.
+   * @param assertionValue
+   *          The assertion value to be compared.
+   * @return The new compare request.
+   * @throws NullPointerException
+   *           If {@code name}, {@code attributeDescription}, or {@code
+   *           assertionValue} was {@code null}.
+   */
+  public static CompareRequest newCompareRequest(DN name,
+      AttributeDescription attributeDescription,
+      ByteString assertionValue) throws NullPointerException
+  {
+    Validator.ensureNotNull(name, attributeDescription, assertionValue);
+    return new CompareRequestImpl(name, attributeDescription,
+        assertionValue);
+  }
+
+
+
+  /**
+   * Creates a new compare request using the provided distinguished
+   * name, attribute name, and assertion value decoded using the default
+   * schema.
+   * <p>
+   * If the assertion value is not an instance of {@code ByteString}
+   * then it will be converted using the
+   * {@link ByteString#valueOf(Object)} method.
+   *
+   * @param name
+   *          The distinguished name of the entry to be compared.
+   * @param attributeDescription
+   *          The name of the attribute to be compared.
+   * @param assertionValue
+   *          The assertion value to be compared.
+   * @return The new compare request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} or {@code attributeDescription} could not
+   *           be decoded using the default schema.
+   * @throws NullPointerException
+   *           If {@code name}, {@code attributeDescription}, or {@code
+   *           assertionValue} was {@code null}.
+   */
+  public static CompareRequest newCompareRequest(String name,
+      String attributeDescription, Object assertionValue)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    Validator.ensureNotNull(name, attributeDescription, assertionValue);
+    return new CompareRequestImpl(DN.valueOf(name),
+        AttributeDescription.valueOf(attributeDescription), ByteString
+            .valueOf(assertionValue));
+  }
+
+
+
+  /**
+   * Creates a new delete request using the provided distinguished name.
+   *
+   * @param name
+   *          The distinguished name of the entry to be deleted.
+   * @return The new delete request.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static DeleteRequest newDeleteRequest(DN name)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(name);
+    return new DeleteRequestImpl(name);
+  }
+
+
+
+  /**
+   * Creates a new delete request using the provided distinguished name
+   * decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the entry to be deleted.
+   * @return The new delete request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static DeleteRequest newDeleteRequest(String name)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    Validator.ensureNotNull(name);
+    return new DeleteRequestImpl(DN.valueOf(name));
+  }
+
+
+
+  /**
+   * Creates a new generic bind request using an empty distinguished
+   * name, authentication type, and authentication information.
+   *
+   * @param authenticationType
+   *          The authentication mechanism identifier for this generic
+   *          bind request.
+   * @param authenticationValue
+   *          The authentication information for this generic bind
+   *          request in a form defined by the authentication mechanism.
+   * @return The new generic bind request.
+   * @throws NullPointerException
+   *           If {@code authenticationValue} was {@code null}.
+   */
+  public static GenericBindRequest newGenericBindRequest(
+      byte authenticationType, ByteString authenticationValue)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(authenticationValue);
+    return new GenericBindRequestImpl(DN.rootDN(), authenticationType,
+        authenticationValue);
+  }
+
+
+
+  /**
+   * Creates a new generic bind request using the provided distinguished
+   * name, authentication type, and authentication information.
+   *
+   * @param name
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as (may be empty).
+   * @param authenticationType
+   *          The authentication mechanism identifier for this generic
+   *          bind request.
+   * @param authenticationValue
+   *          The authentication information for this generic bind
+   *          request in a form defined by the authentication mechanism.
+   * @return The new generic bind request.
+   * @throws NullPointerException
+   *           If {@code name} or {@code authenticationValue} was
+   *           {@code null}.
+   */
+  public static GenericBindRequest newGenericBindRequest(DN name,
+      byte authenticationType, ByteString authenticationValue)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(name, authenticationValue);
+    return new GenericBindRequestImpl(name, authenticationType,
+        authenticationValue);
+  }
+
+
+
+  /**
+   * Creates a new generic bind request using the provided distinguished
+   * name, authentication type, and authentication information.
+   *
+   * @param name
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as (may be empty).
+   * @param authenticationType
+   *          The authentication mechanism identifier for this generic
+   *          bind request.
+   * @param authenticationValue
+   *          The authentication information for this generic bind
+   *          request in a form defined by the authentication mechanism.
+   * @return The new generic bind request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code name} or {@code authenticationValue} was
+   *           {@code null}.
+   */
+  public static GenericBindRequest newGenericBindRequest(String name,
+      byte authenticationType, ByteString authenticationValue)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    Validator.ensureNotNull(name, authenticationValue);
+    return new GenericBindRequestImpl(DN.valueOf(name),
+        authenticationType, authenticationValue);
+  }
+
+
+
+  /**
+   * Creates a new generic extended request using the provided name and
+   * no value.
+   *
+   * @param requestName
+   *          The dotted-decimal representation of the unique OID
+   *          corresponding to this extended request.
+   * @return The new generic extended request.
+   * @throws NullPointerException
+   *           If {@code requestName} was {@code null}.
+   */
+  public static GenericExtendedRequest newGenericExtendedRequest(
+      String requestName) throws NullPointerException
+  {
+    Validator.ensureNotNull(requestName);
+    return new GenericExtendedRequestImpl(requestName, null);
+  }
+
+
+
+  /**
+   * Creates a new generic extended request using the provided name and
+   * optional value.
+   *
+   * @param requestName
+   *          The dotted-decimal representation of the unique OID
+   *          corresponding to this extended request.
+   * @param requestValue
+   *          The content of this generic extended request in a form
+   *          defined by the extended operation, or {@code null} if
+   *          there is no content.
+   * @return The new generic extended request.
+   * @throws NullPointerException
+   *           If {@code requestName} was {@code null}.
+   */
+  public static GenericExtendedRequest newGenericExtendedRequest(
+      String requestName, ByteString requestValue)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(requestName);
+    return new GenericExtendedRequestImpl(requestName, requestValue);
+  }
+
+
+
+  /**
+   * Creates a new modify DN request using the provided distinguished
+   * name and new RDN.
+   *
+   * @param name
+   *          The distinguished name of the entry to be renamed.
+   * @param newRDN
+   *          The new RDN of the entry.
+   * @return The new modify DN request.
+   * @throws NullPointerException
+   *           If {@code name} or {@code newRDN} was {@code null}.
+   */
+  public static ModifyDNRequest newModifyDNRequest(DN name, RDN newRDN)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(name, newRDN);
+    return new ModifyDNRequestImpl(name, newRDN);
+  }
+
+
+
+  /**
+   * Creates a new modify DN request using the provided distinguished
+   * name and new RDN decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the entry to be renamed.
+   * @param newRDN
+   *          The new RDN of the entry.
+   * @return The new modify DN request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} or {@code newRDN} could not be decoded
+   *           using the default schema.
+   * @throws NullPointerException
+   *           If {@code name} or {@code newRDN} was {@code null}.
+   */
+  public static ModifyDNRequest newModifyDNRequest(String name,
+      String newRDN) throws LocalizedIllegalArgumentException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(name, newRDN);
+    return new ModifyDNRequestImpl(DN.valueOf(name), RDN
+        .valueOf(newRDN));
+  }
+
+
+
+  /**
+   * Creates a new modify request using the provided distinguished name.
+   *
+   * @param name
+   *          The distinguished name of the entry to be modified.
+   * @return The new modify request.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static ModifyRequest newModifyRequest(DN name)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(name);
+    return new ModifyRequestImpl(name);
+  }
+
+
+
+  /**
+   * Creates a new modify request using the provided distinguished name
+   * decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the entry to be modified.
+   * @return The new modify request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static ModifyRequest newModifyRequest(String name)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    Validator.ensureNotNull(name);
+    return new ModifyRequestImpl(DN.valueOf(name));
+  }
+
+
+
+  /**
+   * Creates a new modify request using the provided lines of LDIF
+   * decoded using the default schema.
+   *
+   * @param ldifLines
+   *          Lines of LDIF containing a single LDIF modify change
+   *          record.
+   * @return The new modify request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} was empty, or contained invalid
+   *           LDIF, or could not be decoded using the default schema.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null} .
+   */
+  public static ModifyRequest newModifyRequest(String... ldifLines)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    // LDIF change record reader is tolerant to missing change types.
+    ChangeRecord record = LDIFChangeRecordReader
+        .valueOfLDIFChangeRecord(ldifLines);
+
+    if (record instanceof ModifyRequest)
+    {
+      return (ModifyRequest) record;
+    }
+    else
+    {
+      // Wrong change type.
+      Message message = WARN_READ_LDIF_RECORD_CHANGE_RECORD_WRONG_TYPE
+          .get("modify");
+      throw new LocalizedIllegalArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Creates a new search request using the provided distinguished name,
+   * scope, and filter, decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the base entry relative to which
+   *          the search is to be performed.
+   * @param scope
+   *          The scope of the search.
+   * @param filter
+   *          The filter that defines the conditions that must be
+   *          fulfilled in order for an entry to be returned.
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with each
+   *          entry.
+   * @return The new search request.
+   * @throws NullPointerException
+   *           If the {@code name}, {@code scope}, or {@code filter}
+   *           were {@code null}.
+   */
+  public static SearchRequest newSearchRequest(DN name,
+      SearchScope scope, Filter filter, String... attributeDescriptions)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(name, scope, filter);
+    return new SearchRequestImpl(name, scope, filter)
+        .addAttribute(attributeDescriptions);
+  }
+
+
+
+  /**
+   * Creates a new search request using the provided distinguished name,
+   * scope, and filter, decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the base entry relative to which
+   *          the search is to be performed.
+   * @param scope
+   *          The scope of the search.
+   * @param filter
+   *          The filter that defines the conditions that must be
+   *          fulfilled in order for an entry to be returned.
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with each
+   *          entry.
+   * @return The new search request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema, or if {@code filter} is not a valid LDAP string
+   *           representation of a filter.
+   * @throws NullPointerException
+   *           If the {@code name}, {@code scope}, or {@code filter}
+   *           were {@code null}.
+   */
+  public static SearchRequest newSearchRequest(String name,
+      SearchScope scope, String filter, String... attributeDescriptions)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    Validator.ensureNotNull(name, scope, filter);
+    return new SearchRequestImpl(DN.valueOf(name), scope, Filter
+        .valueOf(filter)).addAttribute(attributeDescriptions);
+  }
+
+
+
+  /**
+   * Creates a new simple bind request having an empty name and password
+   * suitable for anonymous authentication.
+   *
+   * @return The new simple bind request.
+   */
+  public static SimpleBindRequest newSimpleBindRequest()
+  {
+    return new SimpleBindRequestImpl(DN.rootDN(), ByteString.empty());
+  }
+
+
+
+  /**
+   * Creates a new simple bind request having the provided name and
+   * password suitable for name/password authentication.
+   *
+   * @param name
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as, which may be empty.
+   * @param password
+   *          The password of the Directory object that the client
+   *          wishes to bind as, which may be empty indicating that an
+   *          unauthenticated bind is to be performed.
+   * @return The new simple bind request.
+   * @throws NullPointerException
+   *           If {@code name} or {@code password} was {@code null}.
+   */
+  public static SimpleBindRequest newSimpleBindRequest(DN name,
+      ByteString password) throws NullPointerException
+  {
+    Validator.ensureNotNull(name, password);
+    return new SimpleBindRequestImpl(name, password);
+  }
+
+
+
+  /**
+   * Creates a new simple bind request having the provided name and
+   * password suitable for name/password authentication. The name will
+   * be decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as, which may be empty..
+   * @param password
+   *          The password of the Directory object that the client
+   *          wishes to bind as, which may be empty indicating that an
+   *          unauthenticated bind is to be performed.
+   * @return The new simple bind request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code name} or {@code password} was {@code null}.
+   */
+  public static SimpleBindRequest newSimpleBindRequest(String name,
+      String password) throws LocalizedIllegalArgumentException,
+      NullPointerException
+  {
+    Validator.ensureNotNull(name, password);
+    return new SimpleBindRequestImpl(DN.valueOf(name), ByteString
+        .valueOf(password));
+  }
+
+
+
+  /**
+   * Creates a new unbind request.
+   *
+   * @return The new unbind request.
+   */
+  public static UnbindRequest newUnbindRequest()
+  {
+    return new UnbindRequestImpl();
+  }
+
+
+
+  private Requests()
+  {
+    // Prevent instantiation.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/requests/SearchRequest.java b/sdk/src/org/opends/sdk/requests/SearchRequest.java
new file mode 100644
index 0000000..5e60480
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SearchRequest.java
@@ -0,0 +1,527 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.DereferenceAliasesPolicy;
+import org.opends.sdk.Filter;
+import org.opends.sdk.SearchScope;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The Search operation is used to request a server to return, subject
+ * to access controls and other restrictions, a set of entries matching
+ * a complex search criterion. This can be used to read attributes from
+ * a single entry, from entries immediately subordinate to a particular
+ * entry, or from a whole subtree of entries.
+ */
+public interface SearchRequest extends Request
+{
+  /**
+   * Adds the provided attribute names to the list of attributes to be
+   * included with each entry that matches the search criteria.
+   * Attributes that are sub-types of listed attributes are implicitly
+   * included.
+   * 
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with each
+   *          entry.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit attribute names to
+   *           be added.
+   * @throws NullPointerException
+   *           If {@code attributeDescriptions} was {@code null}, or if
+   *           it contained a {@code null} element.
+   */
+  SearchRequest addAttribute(Collection<String> attributeDescriptions)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided attribute name to the list of attributes to be
+   * included with each entry that matches the search criteria.
+   * Attributes that are sub-types of listed attributes are implicitly
+   * included.
+   * 
+   * @param attributeDescription
+   *          The name of the attribute to be included with each entry.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit attribute names to
+   *           be added.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  SearchRequest addAttribute(String attributeDescription)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided attribute names to the list of attributes to be
+   * included with each entry that matches the search criteria.
+   * Attributes that are sub-types of listed attributes are implicitly
+   * included.
+   * 
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with each
+   *          entry.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit attribute names to
+   *           be added.
+   * @throws NullPointerException
+   *           If {@code attributeDescriptions} was {@code null}, or if
+   *           it contained a {@code null} element.
+   */
+  SearchRequest addAttribute(String... attributeDescriptions)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  SearchRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Clears the list of attributes to be included with each entry that
+   * matches the search criteria. Attributes that are sub-types of
+   * listed attributes are implicitly included.
+   * 
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit attributes to be
+   *           removed.
+   */
+  SearchRequest clearAttributes() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  SearchRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the list of attributes to be
+   * included with each entry that matches the search criteria.
+   * Attributes that are sub-types of listed attributes are implicitly
+   * included. The returned {@code Iterable} may be used to remove
+   * attribute names if permitted by this search request.
+   * 
+   * @return An {@code Iterable} containing the list of attributes.
+   */
+  Iterable<String> getAttributes();
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns an indication as to whether or not alias entries are to be
+   * dereferenced during the search.
+   * 
+   * @return The alias dereferencing policy.
+   */
+  DereferenceAliasesPolicy getDereferenceAliasesPolicy();
+
+
+
+  /**
+   * Returns the filter that defines the conditions that must be
+   * fulfilled in order for an entry to be returned.
+   * 
+   * @return The search filter.
+   */
+  Filter getFilter();
+
+
+
+  /**
+   * Returns the distinguished name of the base entry relative to which
+   * the search is to be performed.
+   * 
+   * @return The distinguished name of the base entry.
+   */
+  DN getName();
+
+
+
+  /**
+   * Returns the scope of the search.
+   * 
+   * @return The search scope.
+   */
+  SearchScope getScope();
+
+
+
+  /**
+   * Returns the size limit that should be used in order to restrict the
+   * maximum number of entries returned by the search.
+   * <p>
+   * A value of zero (the default) in this field indicates that no
+   * client-requested size limit restrictions are in effect. Servers may
+   * also enforce a maximum number of entries to return.
+   * 
+   * @return The size limit that should be used in order to restrict the
+   *         maximum number of entries returned by the search.
+   */
+  int getSizeLimit();
+
+
+
+  /**
+   * Returns the time limit that should be used in order to restrict the
+   * maximum time (in seconds) allowed for the search.
+   * <p>
+   * A value of zero (the default) in this field indicates that no
+   * client-requested time limit restrictions are in effect for the
+   * search. Servers may also enforce a maximum time limit for the
+   * search.
+   * 
+   * @return The time limit that should be used in order to restrict the
+   *         maximum time (in seconds) allowed for the search.
+   */
+  int getTimeLimit();
+
+
+
+  /**
+   * Indicates whether or not this search request has a list of
+   * attributes to be included with each entry that matches the search
+   * criteria.
+   * 
+   * @return {@code true} if this search request has a list of
+   *         attributes to be included with each entry, otherwise
+   *         {@code false}.
+   */
+  boolean hasAttributes();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Indicates whether search results are to contain both attribute
+   * descriptions and values, or just attribute descriptions.
+   * 
+   * @return {@code true} if only attribute descriptions (and not
+   *         values) are to be returned, or {@code false} (the default)
+   *         if both attribute descriptions and values are to be
+   *         returned.
+   */
+  boolean isTypesOnly();
+
+
+
+  /**
+   * Removes the provided attribute name from the list of attributes to
+   * be included with each entry that matches the search criteria.
+   * Attributes that are sub-types of listed attributes are implicitly
+   * included.
+   * 
+   * @param attributeDescription
+   *          The name of the attribute to be removed.
+   * @return {@code true} if the attribute name was found in the list of
+   *         attributes.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit attribute names to
+   *           be removed.
+   * @throws NullPointerException
+   *           If {@code attributeDescription} was {@code null}.
+   */
+  boolean removeAttribute(String attributeDescription)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the alias dereferencing policy to be used during the search.
+   * 
+   * @param policy
+   *          The alias dereferencing policy to be used during the
+   *          search.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the alias
+   *           dereferencing policy to be set.
+   * @throws NullPointerException
+   *           If {@code policy} was {@code null}.
+   */
+  SearchRequest setDereferenceAliasesPolicy(
+      DereferenceAliasesPolicy policy)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the filter that defines the conditions that must be fulfilled
+   * in order for an entry to be returned.
+   * 
+   * @param filter
+   *          The filter that defines the conditions that must be
+   *          fulfilled in order for an entry to be returned.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the filter to be
+   *           set.
+   * @throws NullPointerException
+   *           If {@code filter} was {@code null}.
+   */
+  SearchRequest setFilter(Filter filter)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the filter that defines the conditions that must be fulfilled
+   * in order for an entry to be returned.
+   * 
+   * @param filter
+   *          The filter that defines the conditions that must be
+   *          fulfilled in order for an entry to be returned.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the filter to be
+   *           set.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code filter} is not a valid LDAP string
+   *           representation of a filter.
+   * @throws NullPointerException
+   *           If {@code filter} was {@code null}.
+   */
+  SearchRequest setFilter(String filter)
+      throws UnsupportedOperationException,
+      LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the base entry relative to which the
+   * search is to be performed.
+   * 
+   * @param dn
+   *          The distinguished name of the base entry relative to which
+   *          the search is to be performed.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  SearchRequest setName(DN dn) throws UnsupportedOperationException,
+      NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the base entry relative to which the
+   * search is to be performed.
+   * 
+   * @param dn
+   *          The distinguished name of the base entry relative to which
+   *          the search is to be performed.
+   * @return This search request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  SearchRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the scope of the search.
+   * 
+   * @param scope
+   *          The scope of the search.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the scope to be
+   *           set.
+   * @throws NullPointerException
+   *           If {@code scope} was {@code null}.
+   */
+  SearchRequest setScope(SearchScope scope)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the size limit that should be used in order to restrict the
+   * maximum number of entries returned by the search.
+   * <p>
+   * A value of zero (the default) in this field indicates that no
+   * client-requested size limit restrictions are in effect. Servers may
+   * also enforce a maximum number of entries to return.
+   * 
+   * @param limit
+   *          The size limit that should be used in order to restrict
+   *          the maximum number of entries returned by the search.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the size limit to
+   *           be set.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code limit} was negative.
+   */
+  SearchRequest setSizeLimit(int limit)
+      throws UnsupportedOperationException,
+      LocalizedIllegalArgumentException;
+
+
+
+  /**
+   * Sets the time limit that should be used in order to restrict the
+   * maximum time (in seconds) allowed for the search.
+   * <p>
+   * A value of zero (the default) in this field indicates that no
+   * client-requested time limit restrictions are in effect for the
+   * search. Servers may also enforce a maximum time limit for the
+   * search.
+   * 
+   * @param limit
+   *          The time limit that should be used in order to restrict
+   *          the maximum time (in seconds) allowed for the search.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the time limit to
+   *           be set.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code limit} was negative.
+   */
+  SearchRequest setTimeLimit(int limit)
+      throws UnsupportedOperationException,
+      LocalizedIllegalArgumentException;
+
+
+
+  /**
+   * Specifies whether search results are to contain both attribute
+   * descriptions and values, or just attribute descriptions.
+   * 
+   * @param typesOnly
+   *          {@code true} if only attribute descriptions (and not
+   *          values) are to be returned, or {@code false} (the default)
+   *          if both attribute descriptions and values are to be
+   *          returned.
+   * @return This search request.
+   * @throws UnsupportedOperationException
+   *           If this search request does not permit the types-only
+   *           parameter to be set.
+   */
+  SearchRequest setTypesOnly(boolean typesOnly)
+      throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java b/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java
new file mode 100644
index 0000000..5c83d43
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SearchRequestImpl.java
@@ -0,0 +1,416 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.DereferenceAliasesPolicy;
+import org.opends.sdk.Filter;
+import org.opends.sdk.SearchScope;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Search request implementation.
+ */
+final class SearchRequestImpl extends
+    AbstractRequestImpl<SearchRequest> implements SearchRequest
+{
+
+  private final List<String> attributes = new LinkedList<String>();
+
+  private DN name;
+
+  private DereferenceAliasesPolicy dereferenceAliasesPolicy = DereferenceAliasesPolicy.NEVER;
+
+  private Filter filter;
+
+  private SearchScope scope;
+
+  private int sizeLimit = 0;
+
+  private int timeLimit = 0;
+
+  private boolean typesOnly = false;
+
+
+
+  /**
+   * Creates a new search request using the provided distinguished name,
+   * scope, and filter, decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the base entry relative to which
+   *          the search is to be performed.
+   * @param scope
+   *          The scope of the search.
+   * @param filter
+   *          The filter that defines the conditions that must be
+   *          fulfilled in order for an entry to be returned.
+   * @param attributeDescriptions
+   *          The names of the attributes to be included with each
+   *          entry.
+   * @throws NullPointerException
+   *           If the {@code name}, {@code scope}, or {@code filter}
+   *           were {@code null}.
+   */
+  SearchRequestImpl(DN name, SearchScope scope, Filter filter)
+      throws NullPointerException
+  {
+    this.name = name;
+    this.scope = scope;
+    this.filter = filter;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest addAttribute(
+      Collection<String> attributeDescriptions)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescriptions);
+
+    attributes.addAll(attributeDescriptions);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest addAttribute(String attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+
+    attributes.add(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest addAttribute(String... attributeDescriptions)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull((Object) attributeDescriptions);
+
+    for (final String attributeDescription : attributeDescriptions)
+    {
+      attributes.add(attributeDescription);
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest clearAttributes()
+  {
+    attributes.clear();
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<String> getAttributes()
+  {
+    return attributes;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DereferenceAliasesPolicy getDereferenceAliasesPolicy()
+  {
+    return dereferenceAliasesPolicy;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Filter getFilter()
+  {
+    return filter;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchScope getScope()
+  {
+    return scope;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getSizeLimit()
+  {
+    return sizeLimit;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getTimeLimit()
+  {
+    return timeLimit;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean hasAttributes()
+  {
+    return !attributes.isEmpty();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isTypesOnly()
+  {
+    return typesOnly;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(String attributeDescription)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(attributeDescription);
+
+    return attributes.remove(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setDereferenceAliasesPolicy(
+      DereferenceAliasesPolicy policy) throws NullPointerException
+  {
+    Validator.ensureNotNull(policy);
+
+    this.dereferenceAliasesPolicy = policy;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setFilter(Filter filter)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(filter);
+
+    this.filter = filter;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setFilter(String filter)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    this.filter = Filter.valueOf(filter);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setName(DN dn) throws NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+
+    this.name = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setName(String dn)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+
+    this.name = DN.valueOf(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setScope(SearchScope scope)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(scope);
+
+    this.scope = scope;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setSizeLimit(int limit)
+      throws LocalizedIllegalArgumentException
+  {
+    // FIXME: I18N error message.
+    Validator.ensureTrue(limit >= 0, "negative size limit");
+
+    this.sizeLimit = limit;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setTimeLimit(int limit)
+      throws LocalizedIllegalArgumentException
+  {
+    // FIXME: I18N error message.
+    Validator.ensureTrue(limit >= 0, "negative time limit");
+
+    this.timeLimit = limit;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchRequest setTypesOnly(boolean typesOnly)
+  {
+    this.typesOnly = typesOnly;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("SearchRequest(name=");
+    builder.append(getName());
+    builder.append(", scope=");
+    builder.append(getScope());
+    builder.append(", dereferenceAliasesPolicy=");
+    builder.append(getDereferenceAliasesPolicy());
+    builder.append(", sizeLimit=");
+    builder.append(getSizeLimit());
+    builder.append(", timeLimit=");
+    builder.append(getTimeLimit());
+    builder.append(", typesOnly=");
+    builder.append(isTypesOnly());
+    builder.append(", filter=");
+    builder.append(getFilter());
+    builder.append(", attributes=");
+    builder.append(getAttributes());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  SearchRequest getThis()
+  {
+    return this;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/requests/SimpleBindRequest.java b/sdk/src/org/opends/sdk/requests/SimpleBindRequest.java
new file mode 100644
index 0000000..8b0c07d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SimpleBindRequest.java
@@ -0,0 +1,254 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * The simple authentication method of the Bind Operation provides three
+ * authentication mechanisms:
+ * <ul>
+ * <li>An anonymous authentication mechanism, in which both the name
+ * (the bind DN) and password are zero length.
+ * <li>An unauthenticated authentication mechanism using credentials
+ * consisting of a name (the bind DN) and a zero length password.
+ * <li>A name/password authentication mechanism using credentials
+ * consisting of a name (the bind DN) and a password.
+ * </ul>
+ */
+public interface SimpleBindRequest extends BindRequest
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  SimpleBindRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  SimpleBindRequest clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  DN getName();
+
+
+
+  /**
+   * Returns the password of the Directory object that the client wishes
+   * to bind as. The password may be empty (but never {@code null}) when
+   * used for of anonymous or unauthenticated binds.
+   * 
+   * @return The password of the Directory object that the client wishes
+   *         to bind as.
+   */
+  ByteString getPassword();
+
+
+
+  /**
+   * Returns the password of the Directory object that the client wishes
+   * to bind as decoded as a UTF-8 string. The password may be empty
+   * (but never {@code null}) when used for of anonymous or
+   * unauthenticated binds.
+   * 
+   * @return The password of the Directory object that the client wishes
+   *         to bind as decoded as a UTF-8 string.
+   */
+  String getPasswordAsString();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the Directory object that the client
+   * wishes to bind as. The distinguished name may be empty (but never
+   * {@code null} when used for of anonymous binds, or when using SASL
+   * authentication. The server shall not dereference any aliases in
+   * locating the named object.
+   * 
+   * @param dn
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as.
+   * @return This bind request.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  SimpleBindRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the distinguished name of the Directory object that the client
+   * wishes to bind as. The distinguished name may be empty (but never
+   * {@code null} when used for of anonymous binds, or when using SASL
+   * authentication. The server shall not dereference any aliases in
+   * locating the named object.
+   * 
+   * @param dn
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as.
+   * @return This bind request.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws UnsupportedOperationException
+   *           If this bind request does not permit the distinguished
+   *           name to be set.
+   * @throws NullPointerException
+   *           If {@code dn} was {@code null}.
+   */
+  SimpleBindRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the password of the Directory object that the client wishes to
+   * bind as. The password may be empty (but never {@code null}) when
+   * used for of anonymous or unauthenticated binds.
+   * 
+   * @param password
+   *          The password of the Directory object that the client
+   *          wishes to bind as, which may be empty.
+   * @return This simple bind request.
+   * @throws UnsupportedOperationException
+   *           If this simple bind request does not permit the password
+   *           to be set.
+   * @throws NullPointerException
+   *           If {@code password} was {@code null}.
+   */
+  SimpleBindRequest setPassword(ByteString password)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the password of the Directory object that the client wishes to
+   * bind as. The password will be converted to a UTF-8 octet string.
+   * The password may be empty (but never {@code null}) when used for of
+   * anonymous or unauthenticated binds.
+   * 
+   * @param password
+   *          The password of the Directory object that the client
+   *          wishes to bind as, which may be empty.
+   * @return This simple bind request.
+   * @throws UnsupportedOperationException
+   *           If this simple bind request does not permit the password
+   *           to be set.
+   * @throws NullPointerException
+   *           If {@code password} was {@code null}.
+   */
+  SimpleBindRequest setPassword(String password)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java b/sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java
new file mode 100644
index 0000000..5856934
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/SimpleBindRequestImpl.java
@@ -0,0 +1,174 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Simple bind request implementation.
+ */
+final class SimpleBindRequestImpl extends
+    AbstractBindRequest<SimpleBindRequest> implements SimpleBindRequest
+{
+  private ByteString password = ByteString.empty();
+
+  private DN name = DN.rootDN();
+
+
+
+  /**
+   * Creates a new simple bind request having the provided name and
+   * password suitable for name/password authentication.
+   * 
+   * @param name
+   *          The distinguished name of the Directory object that the
+   *          client wishes to bind as, which may be empty.
+   * @param password
+   *          The password of the Directory object that the client
+   *          wishes to bind as, which may be empty indicating that an
+   *          unauthenticated bind is to be performed.
+   * @throws NullPointerException
+   *           If {@code name} or {@code password} was {@code null}.
+   */
+  SimpleBindRequestImpl(DN name, ByteString password)
+      throws NullPointerException
+  {
+    this.name = name;
+    this.password = password;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getPassword()
+  {
+    return password;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getPasswordAsString()
+  {
+    return password.toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SimpleBindRequest setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = dn;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SimpleBindRequest setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(dn);
+    this.name = DN.valueOf(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SimpleBindRequest setPassword(ByteString password)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(password);
+    this.password = password;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SimpleBindRequest setPassword(String password)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(password);
+    this.password = ByteString.valueOf(password);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("SimpleBindRequest(name=");
+    builder.append(getName());
+    builder.append(", authentication=simple");
+    builder.append(", password=");
+    builder.append(getPasswordAsString());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/requests/UnbindRequest.java b/sdk/src/org/opends/sdk/requests/UnbindRequest.java
new file mode 100644
index 0000000..2fc1739
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/UnbindRequest.java
@@ -0,0 +1,119 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The Unbind operation allows a client to terminate an LDAP session.
+ */
+public interface UnbindRequest extends Request
+{
+  /**
+   * Adds the provided control to this request.
+   * 
+   * @param control
+   *          The control to be added to this request.
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  UnbindRequest addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this request.
+   * 
+   * @return This request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   */
+  UnbindRequest clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this request.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this request. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this request.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Indicates whether or not this request has any controls.
+   * 
+   * @return {@code true} if this request has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this request having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this request.
+   * @throws UnsupportedOperationException
+   *           If this request does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java b/sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java
new file mode 100644
index 0000000..72a5485
--- /dev/null
+++ b/sdk/src/org/opends/sdk/requests/UnbindRequestImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.requests;
+
+/**
+ * Unbind request implementation.
+ */
+final class UnbindRequestImpl extends
+    AbstractRequestImpl<UnbindRequest> implements UnbindRequest
+{
+
+  /**
+   * Creates a new unbind request.
+   */
+  UnbindRequestImpl()
+  {
+    // Do nothing.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("UnbindRequest(controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  UnbindRequest getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java b/sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java
new file mode 100644
index 0000000..be40ebf
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractExtendedResult.java
@@ -0,0 +1,113 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An abstract Extended result which can be used as the basis for
+ * implementing new Extended operations.
+ * 
+ * @param <S>
+ *          The type of Extended result.
+ */
+public abstract class AbstractExtendedResult<S extends ExtendedResult>
+    extends AbstractResultImpl<S> implements ExtendedResult
+{
+
+  /**
+   * Creates a new extended result using the provided result code.
+   * 
+   * @param resultCode
+   *          The result code.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  protected AbstractExtendedResult(ResultCode resultCode)
+      throws NullPointerException
+  {
+    super(resultCode);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract String getResponseName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract ByteString getResponseValue();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("ExtendedResult(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    builder.append(", responseName=");
+    builder.append(getResponseName() == null ? "" : getResponseName());
+    builder.append(", responseValue=");
+    final ByteString value = getResponseValue();
+    builder.append(value == null ? ByteString.empty() : value);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  final S getThis()
+  {
+    return (S) this;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java b/sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java
new file mode 100644
index 0000000..f2278e8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractIntermediateResponse.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An abstract Intermediate response which can be used as the basis for
+ * implementing new Intermediate responses.
+ * 
+ * @param <S>
+ *          The type of Intermediate response.
+ */
+public abstract class AbstractIntermediateResponse<S extends IntermediateResponse>
+    extends AbstractResponseImpl<S> implements IntermediateResponse
+{
+
+  /**
+   * Creates a new intermediate response.
+   */
+  protected AbstractIntermediateResponse()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract String getResponseName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public abstract ByteString getResponseValue();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("IntermediateResponse(responseName=");
+    builder.append(getResponseName() == null ? "" : getResponseName());
+    builder.append(", responseValue=");
+    final ByteString value = getResponseValue();
+    builder.append(value == null ? ByteString.empty() : value);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @SuppressWarnings("unchecked")
+  final S getThis()
+  {
+    return (S) this;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java b/sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java
new file mode 100644
index 0000000..26e74e2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractResponseImpl.java
@@ -0,0 +1,170 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modifiable response implementation.
+ * 
+ * @param <S>
+ *          The type of response.
+ */
+abstract class AbstractResponseImpl<S extends Response> implements
+    Response
+{
+  private final List<Control> controls = new LinkedList<Control>();
+
+
+
+  /**
+   * Creates a new modifiable response implementation.
+   */
+  AbstractResponseImpl()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S addControl(Control control)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(control);
+    controls.add(control);
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S clearControls()
+  {
+    controls.clear();
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control getControl(String oid)
+  {
+    Validator.ensureNotNull(oid);
+
+    // Avoid creating an iterator if possible.
+    if (controls.isEmpty())
+    {
+      return null;
+    }
+
+    for (final Control control : controls)
+    {
+      if (control.getOID().equals(oid))
+      {
+        return control;
+      }
+    }
+
+    return null;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Iterable<Control> getControls()
+  {
+    return controls;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean hasControls()
+  {
+    return !controls.isEmpty();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control removeControl(String oid)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(oid);
+
+    // Avoid creating an iterator if possible.
+    if (controls.isEmpty())
+    {
+      return null;
+    }
+
+    final Iterator<Control> iterator = controls.iterator();
+    while (iterator.hasNext())
+    {
+      final Control control = iterator.next();
+      if (control.getOID().equals(oid))
+      {
+        iterator.remove();
+        return control;
+      }
+    }
+
+    return null;
+  }
+
+
+
+  public abstract String toString();
+
+
+
+  abstract S getThis();
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractResultImpl.java b/sdk/src/org/opends/sdk/responses/AbstractResultImpl.java
new file mode 100644
index 0000000..33c55f8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractResultImpl.java
@@ -0,0 +1,245 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Modifiable result implementation.
+ * 
+ * @param <S>
+ *          The type of result.
+ */
+abstract class AbstractResultImpl<S extends Result> extends
+    AbstractResponseImpl<S> implements Result
+{
+  // For local errors caused by internal exceptions.
+  private Throwable cause = null;
+
+  private String diagnosticMessage = "";
+
+  private String matchedDN = "";
+
+  private final List<String> referralURIs = new LinkedList<String>();
+
+  private ResultCode resultCode;
+
+
+
+  /**
+   * Creates a new modifiable result implementation using the provided
+   * result code.
+   * 
+   * @param resultCode
+   *          The result code.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  AbstractResultImpl(ResultCode resultCode) throws NullPointerException
+  {
+    this.resultCode = resultCode;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S addReferralURI(String uri) throws NullPointerException
+  {
+    Validator.ensureNotNull(uri);
+
+    referralURIs.add(uri);
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S clearReferralURIs()
+  {
+    referralURIs.clear();
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Throwable getCause()
+  {
+    return cause;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String getDiagnosticMessage()
+  {
+    return diagnosticMessage;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String getMatchedDN()
+  {
+    return matchedDN;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Iterable<String> getReferralURIs()
+  {
+    return referralURIs;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final ResultCode getResultCode()
+  {
+    return resultCode;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean hasReferralURIs()
+  {
+    return !referralURIs.isEmpty();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean isReferral()
+  {
+    final ResultCode code = getResultCode();
+    return code.equals(ResultCode.REFERRAL);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean isSuccess()
+  {
+    final ResultCode code = getResultCode();
+    return !code.isExceptional();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S setCause(Throwable cause)
+  {
+    this.cause = cause;
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S setDiagnosticMessage(String message)
+  {
+    if (message == null)
+    {
+      this.diagnosticMessage = "";
+    }
+    else
+    {
+      this.diagnosticMessage = message;
+    }
+
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S setMatchedDN(String dn)
+  {
+    if (dn == null)
+    {
+      this.matchedDN = "";
+    }
+    else
+    {
+      this.matchedDN = dn;
+    }
+
+    return getThis();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S setResultCode(ResultCode resultCode)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(resultCode);
+
+    this.resultCode = resultCode;
+    return getThis();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java
new file mode 100644
index 0000000..d2fad61
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResponseImpl.java
@@ -0,0 +1,137 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.Iterables;
+
+
+
+/**
+ * Unmodifiable response implementation.
+ * 
+ * @param <S>
+ *          The type of response.
+ */
+abstract class AbstractUnmodifiableResponseImpl<S extends Response>
+    implements Response
+{
+
+  private final S impl;
+
+
+
+  /**
+   * Creates a new unmodifiable response implementation.
+   * 
+   * @param impl
+   *          The underlying response implementation to be made
+   *          unmodifiable.
+   */
+  AbstractUnmodifiableResponseImpl(S impl)
+  {
+    this.impl = impl;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final S clearControls() throws UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control getControl(String oid)
+      throws NullPointerException
+  {
+    // FIXME: ensure that controls are immutable.
+    return impl.getControl(oid);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Iterable<Control> getControls()
+  {
+    // FIXME: ensure that controls are immutable.
+    return Iterables.unmodifiable(impl.getControls());
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final boolean hasControls()
+  {
+    return impl.hasControls();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public final String toString()
+  {
+    return impl.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java
new file mode 100644
index 0000000..5873fd4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/AbstractUnmodifiableResultImpl.java
@@ -0,0 +1,167 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Unmodifiable result implementation.
+ * 
+ * @param <S>
+ *          The type of result.
+ */
+abstract class AbstractUnmodifiableResultImpl<S extends Result> extends
+    AbstractUnmodifiableResponseImpl<S> implements Result
+{
+
+  private final S impl;
+
+
+
+  /**
+   * Creates a new unmodifiable result implementation.
+   * 
+   * @param impl
+   *          The underlying result implementation to be made
+   *          unmodifiable.
+   */
+  AbstractUnmodifiableResultImpl(S impl)
+  {
+    super(impl);
+    this.impl = impl;
+  }
+
+
+
+  public final S addReferralURI(String uri)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public final S clearReferralURIs()
+      throws UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public final Throwable getCause()
+  {
+    return impl.getCause();
+  }
+
+
+
+  public final String getDiagnosticMessage()
+  {
+    return impl.getDiagnosticMessage();
+  }
+
+
+
+  public final String getMatchedDN()
+  {
+    return impl.getMatchedDN();
+  }
+
+
+
+  public final Iterable<String> getReferralURIs()
+  {
+    return impl.getReferralURIs();
+  }
+
+
+
+  public final ResultCode getResultCode()
+  {
+    return impl.getResultCode();
+  }
+
+
+
+  public final boolean hasReferralURIs()
+  {
+    return impl.hasReferralURIs();
+  }
+
+
+
+  public final boolean isReferral()
+  {
+    return impl.isReferral();
+  }
+
+
+
+  public final boolean isSuccess()
+  {
+    return impl.isSuccess();
+  }
+
+
+
+  public final S setCause(Throwable cause)
+      throws UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public final S setDiagnosticMessage(String message)
+      throws UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public final S setMatchedDN(String dn)
+      throws UnsupportedOperationException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+
+
+  public final S setResultCode(ResultCode resultCode)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    throw new UnsupportedOperationException();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/BindResult.java b/sdk/src/org/opends/sdk/responses/BindResult.java
new file mode 100644
index 0000000..359ddd7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/BindResult.java
@@ -0,0 +1,208 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Bind result indicates the status of the client's request for
+ * authentication.
+ * <p>
+ * A successful Bind operation is indicated by a Bind result with a
+ * result code set to {@link ResultCode#SUCCESS} and can be determined
+ * by invoking the {@link #isSuccess} method.
+ * <p>
+ * The server SASL credentials field is used as part of a SASL-defined
+ * bind mechanism to allow the client to authenticate the server to
+ * which it is communicating, or to perform "challenge-response"
+ * authentication. If the client bound using a form of simple
+ * authentication, or the SASL mechanism does not require the server to
+ * return information to the client, then this field shall not be
+ * included in the Bind result.
+ * <p>
+ * If the server requires the client to send a new SASL Bind request in
+ * order to continue the authentication process then the result code is
+ * set to {@link ResultCode#SASL_BIND_IN_PROGRESS} and can be determined
+ * by invoking the {@link #isSASLBindInProgress} method.
+ */
+public interface BindResult extends Result
+{
+  /**
+   * {@inheritDoc}
+   */
+  BindResult addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  BindResult addReferralURI(String uri)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  BindResult clearControls() throws UnsupportedOperationException;
+
+
+
+  BindResult clearReferralURIs() throws UnsupportedOperationException;
+
+
+
+  Throwable getCause();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  String getDiagnosticMessage();
+
+
+
+  String getMatchedDN();
+
+
+
+  Iterable<String> getReferralURIs();
+
+
+
+  ResultCode getResultCode();
+
+
+
+  /**
+   * Returns the server SASL credentials associated with this bind
+   * result.
+   * 
+   * @return The server SASL credentials, or {@code null} indicating
+   *         that none was provided.
+   */
+  ByteString getServerSASLCredentials();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  boolean hasReferralURIs();
+
+
+
+  boolean isReferral();
+
+
+
+  /**
+   * Indicates whether or not the server requires the client to send a
+   * new SASL Bind request with the same SASL mechanism in order to
+   * continue the authentication process. This typically occurs during
+   * multi-stage (challenge response) authentication.
+   * <p>
+   * Specifically, this method returns {@code true} if the result code
+   * is equal to {@link ResultCode#SASL_BIND_IN_PROGRESS}.
+   * 
+   * @return {@code true} if the server requires the client to send a
+   *         new SASL Bind request, otherwise {@code false}.
+   */
+  boolean isSASLBindInProgress();
+
+
+
+  boolean isSuccess();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  BindResult setCause(Throwable cause)
+      throws UnsupportedOperationException;
+
+
+
+  BindResult setDiagnosticMessage(String message)
+      throws UnsupportedOperationException;
+
+
+
+  BindResult setMatchedDN(String dn)
+      throws UnsupportedOperationException;
+
+
+
+  BindResult setResultCode(ResultCode resultCode)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the server SASL credentials associated with this bind result.
+   * 
+   * @param credentials
+   *          The server SASL credentials associated with this bind
+   *          result, which may be {@code null} indicating that none was
+   *          provided.
+   * @return This bind result.
+   * @throws UnsupportedOperationException
+   *           If this bind result does not permit the server SASL
+   *           credentials to be set.
+   */
+  BindResult setServerSASLCredentials(ByteString credentials)
+      throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/BindResultImpl.java b/sdk/src/org/opends/sdk/responses/BindResultImpl.java
new file mode 100644
index 0000000..df56a42
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/BindResultImpl.java
@@ -0,0 +1,126 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Bind result implementation.
+ */
+final class BindResultImpl extends AbstractResultImpl<BindResult>
+    implements BindResult
+{
+  private ByteString credentials = null;
+
+
+
+  /**
+   * Creates a new bind result using the provided result code.
+   * 
+   * @param resultCode
+   *          The result code.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  BindResultImpl(ResultCode resultCode) throws NullPointerException
+  {
+    super(resultCode);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getServerSASLCredentials()
+  {
+    return credentials;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean isSASLBindInProgress()
+  {
+    final ResultCode code = getResultCode();
+    return code.equals(ResultCode.SASL_BIND_IN_PROGRESS);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public BindResult setServerSASLCredentials(ByteString credentials)
+      throws UnsupportedOperationException
+  {
+    this.credentials = credentials;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("BindResult(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    builder.append(", serverSASLCreds=");
+    builder.append(getServerSASLCredentials() == null ? ByteString
+        .empty() : getServerSASLCredentials());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  BindResult getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/CompareResult.java b/sdk/src/org/opends/sdk/responses/CompareResult.java
new file mode 100644
index 0000000..bf6fe63
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/CompareResult.java
@@ -0,0 +1,166 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * compare the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * An Compare result indicates the final status of an Compare operation.
+ * <p>
+ * If the attribute value assertion in the Compare request matched a
+ * value of the attribute or sub-type according to the attribute's
+ * equality matching rule then the result code is set to
+ * {@link ResultCode#COMPARE_TRUE} and can be determined by invoking the
+ * {@link #matched} method.
+ */
+public interface CompareResult extends Result
+{
+  /**
+   * {@inheritDoc}
+   */
+  CompareResult addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  CompareResult addReferralURI(String uri)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  CompareResult clearControls() throws UnsupportedOperationException;
+
+
+
+  CompareResult clearReferralURIs()
+      throws UnsupportedOperationException;
+
+
+
+  Throwable getCause();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  String getDiagnosticMessage();
+
+
+
+  String getMatchedDN();
+
+
+
+  Iterable<String> getReferralURIs();
+
+
+
+  ResultCode getResultCode();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  boolean hasReferralURIs();
+
+
+
+  boolean isReferral();
+
+
+
+  boolean isSuccess();
+
+
+
+  /**
+   * Indicates whether or not the attribute value assertion in the
+   * Compare request matched a value of the attribute or sub-type
+   * according to the attribute's equality matching rule.
+   * <p>
+   * Specifically, this method returns {@code true} if the result code
+   * is equal to {@link ResultCode#COMPARE_TRUE}.
+   * 
+   * @return {@code true} if the attribute value assertion matched,
+   *         otherwise {@code false}.
+   */
+  boolean matched();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  CompareResult setCause(Throwable cause)
+      throws UnsupportedOperationException;
+
+
+
+  CompareResult setDiagnosticMessage(String message)
+      throws UnsupportedOperationException;
+
+
+
+  CompareResult setMatchedDN(String dn)
+      throws UnsupportedOperationException;
+
+
+
+  CompareResult setResultCode(ResultCode resultCode)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/CompareResultImpl.java b/sdk/src/org/opends/sdk/responses/CompareResultImpl.java
new file mode 100644
index 0000000..14baaa5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/CompareResultImpl.java
@@ -0,0 +1,97 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * compare the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * Compare result implementation.
+ */
+final class CompareResultImpl extends AbstractResultImpl<CompareResult>
+    implements CompareResult
+{
+
+  /**
+   * Creates a new compare result using the provided result code.
+   * 
+   * @param resultCode
+   *          The result code.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  CompareResultImpl(ResultCode resultCode) throws NullPointerException
+  {
+    super(resultCode);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean matched()
+  {
+    final ResultCode code = getResultCode();
+    return code.equals(ResultCode.COMPARE_TRUE);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("CompareResult(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  CompareResult getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/ExtendedResult.java b/sdk/src/org/opends/sdk/responses/ExtendedResult.java
new file mode 100644
index 0000000..61ecc35
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/ExtendedResult.java
@@ -0,0 +1,172 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Extended result indicates the status of an Extended operation and
+ * any additional information associated with the Extended operation,
+ * including the optional response name and value. These can be
+ * retrieved using the {@link #getResponseName} and
+ * {@link #getResponseValue} methods respectively.
+ */
+public interface ExtendedResult extends Result
+{
+  /**
+   * {@inheritDoc}
+   */
+  ExtendedResult addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  ExtendedResult addReferralURI(String uri)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  ExtendedResult clearControls() throws UnsupportedOperationException;
+
+
+
+  ExtendedResult clearReferralURIs()
+      throws UnsupportedOperationException;
+
+
+
+  Throwable getCause();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  String getDiagnosticMessage();
+
+
+
+  String getMatchedDN();
+
+
+
+  Iterable<String> getReferralURIs();
+
+
+
+  /**
+   * Returns the dotted-decimal representation of the unique OID
+   * corresponding to this extended result.
+   * 
+   * @return The dotted-decimal representation of the unique OID, or
+   *         {@code null} if none was provided.
+   */
+  String getResponseName();
+
+
+
+  /**
+   * Returns the content of this extended result in a form defined by
+   * the extended result.
+   * 
+   * @return The content of this extended result, or {@code null} if
+   *         there is no content.
+   */
+  ByteString getResponseValue();
+
+
+
+  ResultCode getResultCode();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  boolean hasReferralURIs();
+
+
+
+  boolean isReferral();
+
+
+
+  boolean isSuccess();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  ExtendedResult setCause(Throwable cause)
+      throws UnsupportedOperationException;
+
+
+
+  ExtendedResult setDiagnosticMessage(String message)
+      throws UnsupportedOperationException;
+
+
+
+  ExtendedResult setMatchedDN(String dn)
+      throws UnsupportedOperationException;
+
+
+
+  ExtendedResult setResultCode(ResultCode resultCode)
+      throws UnsupportedOperationException, NullPointerException;
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericExtendedResult.java b/sdk/src/org/opends/sdk/responses/GenericExtendedResult.java
new file mode 100644
index 0000000..6e2090b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericExtendedResult.java
@@ -0,0 +1,198 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Generic Extended result indicates the final status of an Generic
+ * Extended operation.
+ */
+public interface GenericExtendedResult extends ExtendedResult
+{
+  /**
+   * {@inheritDoc}
+   */
+  GenericExtendedResult addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  GenericExtendedResult addReferralURI(String uri)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  GenericExtendedResult clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  GenericExtendedResult clearReferralURIs()
+      throws UnsupportedOperationException;
+
+
+
+  Throwable getCause();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  String getDiagnosticMessage();
+
+
+
+  String getMatchedDN();
+
+
+
+  Iterable<String> getReferralURIs();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  String getResponseName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  ByteString getResponseValue();
+
+
+
+  ResultCode getResultCode();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  boolean hasReferralURIs();
+
+
+
+  boolean isReferral();
+
+
+
+  boolean isSuccess();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  GenericExtendedResult setCause(Throwable cause)
+      throws UnsupportedOperationException;
+
+
+
+  GenericExtendedResult setDiagnosticMessage(String message)
+      throws UnsupportedOperationException;
+
+
+
+  GenericExtendedResult setMatchedDN(String dn)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the dotted-decimal representation of the unique OID
+   * corresponding to this generic extended result.
+   * 
+   * @param oid
+   *          The dotted-decimal representation of the unique OID, or
+   *          {@code null} if there is no response name.
+   * @return This generic extended result.
+   * @throws UnsupportedOperationException
+   *           If this generic extended result does not permit the
+   *           response name to be set.
+   */
+  GenericExtendedResult setResponseName(String oid)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the content of this generic extended result in a form defined
+   * by the extended result.
+   * 
+   * @param bytes
+   *          The content of this generic extended result in a form
+   *          defined by the extended result, or {@code null} if there
+   *          is no content.
+   * @return This generic extended result.
+   * @throws UnsupportedOperationException
+   *           If this generic extended result does not permit the
+   *           response value to be set.
+   */
+  GenericExtendedResult setResponseValue(ByteString bytes)
+      throws UnsupportedOperationException;
+
+
+
+  GenericExtendedResult setResultCode(ResultCode resultCode)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java b/sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java
new file mode 100644
index 0000000..98abda5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericExtendedResultImpl.java
@@ -0,0 +1,144 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Generic extended result implementation.
+ */
+final class GenericExtendedResultImpl extends
+    AbstractResultImpl<GenericExtendedResult> implements
+    ExtendedResult, GenericExtendedResult
+{
+
+  private String responseName = null;
+
+  private ByteString responseValue = null;
+
+
+
+  /**
+   * Creates a new generic extended result using the provided result
+   * code.
+   * 
+   * @param resultCode
+   *          The result code.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  GenericExtendedResultImpl(ResultCode resultCode)
+      throws NullPointerException
+  {
+    super(resultCode);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getResponseName()
+  {
+    return responseName;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getResponseValue()
+  {
+    return responseValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericExtendedResult setResponseName(String oid)
+      throws UnsupportedOperationException
+  {
+    this.responseName = oid;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericExtendedResult setResponseValue(ByteString bytes)
+      throws UnsupportedOperationException
+  {
+    this.responseValue = bytes;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("ExtendedResult(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    builder.append(", responseName=");
+    builder.append(getResponseName() == null ? "" : getResponseName());
+    builder.append(", responseValue=");
+    final ByteString value = getResponseValue();
+    builder.append(value == null ? ByteString.empty() : value);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  GenericExtendedResult getThis()
+  {
+    return this;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java
new file mode 100644
index 0000000..9ec733e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponse.java
@@ -0,0 +1,137 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * A Generic Intermediate response provides a mechanism for
+ * communicating unrecognized or unsupported Intermediate responses to
+ * the client.
+ */
+public interface GenericIntermediateResponse extends
+    IntermediateResponse
+{
+  /**
+   * {@inheritDoc}
+   */
+  GenericIntermediateResponse addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  GenericIntermediateResponse clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  String getResponseName();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  ByteString getResponseValue();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the dotted-decimal representation of the unique OID
+   * corresponding to this generic intermediate response.
+   * 
+   * @param oid
+   *          The dotted-decimal representation of the unique OID, or
+   *          {@code null} if there is no response name.
+   * @return This generic intermediate response.
+   * @throws UnsupportedOperationException
+   *           If this generic intermediate response does not permit the
+   *           response name to be set.
+   */
+  GenericIntermediateResponse setResponseName(String oid)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the content of this generic intermediate response in a form
+   * defined by the extended result.
+   * 
+   * @param bytes
+   *          The content of this generic intermediate response in a
+   *          form defined by the intermediate response, or {@code null}
+   *          if there is no content.
+   * @return This generic intermediate response.
+   * @throws UnsupportedOperationException
+   *           If this generic intermediate response does not permit the
+   *           response value to be set.
+   */
+  GenericIntermediateResponse setResponseValue(ByteString bytes)
+      throws UnsupportedOperationException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java
new file mode 100644
index 0000000..c5a5bf7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/GenericIntermediateResponseImpl.java
@@ -0,0 +1,133 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * generic extended the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * Generic intermediate response implementation.
+ */
+final class GenericIntermediateResponseImpl extends
+    AbstractIntermediateResponse<GenericIntermediateResponse> implements
+    GenericIntermediateResponse
+{
+
+  private String responseName = null;
+
+  private ByteString responseValue = null;
+
+
+
+  /**
+   * Creates a new generic intermediate response using the provided
+   * response name and value.
+   * 
+   * @param responseName
+   *          The dotted-decimal representation of the unique OID
+   *          corresponding to this intermediate response, which may be
+   *          {@code null} indicating that none was provided.
+   * @param responseValue
+   *          The response value associated with this generic
+   *          intermediate response, which may be {@code null}
+   *          indicating that none was provided.
+   */
+  GenericIntermediateResponseImpl(String responseName,
+      ByteString responseValue)
+  {
+    this.responseName = responseName;
+    this.responseValue = responseValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public String getResponseName()
+  {
+    return responseName;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString getResponseValue()
+  {
+    return responseValue;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericIntermediateResponse setResponseName(String oid)
+      throws UnsupportedOperationException
+  {
+    this.responseName = oid;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public GenericIntermediateResponse setResponseValue(ByteString bytes)
+      throws UnsupportedOperationException
+  {
+    this.responseValue = bytes;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("IntermediateResponse(responseName=");
+    builder.append(getResponseName() == null ? "" : getResponseName());
+    builder.append(", responseValue=");
+    final ByteString value = getResponseValue();
+    builder.append(value == null ? ByteString.empty() : value);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/responses/IntermediateResponse.java b/sdk/src/org/opends/sdk/responses/IntermediateResponse.java
new file mode 100644
index 0000000..51d83e9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/IntermediateResponse.java
@@ -0,0 +1,116 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * An Intermediate response provides a general mechanism for defining
+ * single-request/multiple-response operations. This response is
+ * intended to be used in conjunction with the Extended operation to
+ * define new single-request/multiple-response operations or in
+ * conjunction with a control when extending existing operations in a
+ * way that requires them to return Intermediate response information.
+ * <p>
+ * An Intermediate response may convey an optional response name and
+ * value. These can be retrieved using the {@link #getResponseName} and
+ * {@link #getResponseValue} methods respectively.
+ */
+public interface IntermediateResponse extends Response
+{
+  /**
+   * {@inheritDoc}
+   */
+  IntermediateResponse addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  IntermediateResponse clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the dotted-decimal representation of the unique OID
+   * corresponding to this intermediate response.
+   * 
+   * @return The dotted-decimal representation of the unique OID, or
+   *         {@code null} if none was provided.
+   */
+  String getResponseName();
+
+
+
+  /**
+   * Returns the content of this intermediate response in a form defined
+   * by the intermediate response.
+   * 
+   * @return The content of this intermediate response, or {@code null}
+   *         if there is no content.
+   */
+  ByteString getResponseValue();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/Response.java b/sdk/src/org/opends/sdk/responses/Response.java
new file mode 100644
index 0000000..940fc3b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/Response.java
@@ -0,0 +1,124 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * The base class of all Responses provides methods for querying and
+ * manipulating the set of Controls included with a Response.
+ * <p>
+ * TODO: added complete description including sub-types.
+ */
+public interface Response
+{
+
+  /**
+   * Adds the provided control to this response.
+   * 
+   * @param control
+   *          The control to be added.
+   * @return This response.
+   * @throws UnsupportedOperationException
+   *           If this response does not permit controls to be added.
+   * @throws NullPointerException
+   *           If {@code control} was {@code null}.
+   */
+  Response addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Removes all the controls included with this response.
+   * 
+   * @return This response.
+   * @throws UnsupportedOperationException
+   *           If this response does not permit controls to be removed.
+   */
+  Response clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the first control contained in this response having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be returned.
+   * @return The control, or {@code null} if the control is not included
+   *         with this response.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the controls included with
+   * this response. The returned {@code Iterable} may be used to remove
+   * controls if permitted by this response.
+   * 
+   * @return An {@code Iterable} containing the controls.
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Indicates whether or not this response has any controls.
+   * 
+   * @return {@code true} if this response has any controls, otherwise
+   *         {@code false}.
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Removes the first control contained in this response having the
+   * specified OID.
+   * 
+   * @param oid
+   *          The OID of the control to be removed.
+   * @return The removed control, or {@code null} if the control is not
+   *         included with this response.
+   * @throws UnsupportedOperationException
+   *           If this response does not permit controls to be removed.
+   * @throws NullPointerException
+   *           If {@code oid} was {@code null}.
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/Responses.java b/sdk/src/org/opends/sdk/responses/Responses.java
new file mode 100644
index 0000000..0ba85e0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/Responses.java
@@ -0,0 +1,278 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.SortedEntry;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class contains various methods for creating and manipulating
+ * responses.
+ * <p>
+ * TODO: search reference from LDAP URL.
+ * <p>
+ * TODO: referral from LDAP URL.
+ * <p>
+ * TODO: unmodifiable requests?
+ * <p>
+ * TODO: synchronized requests?
+ * <p>
+ * TODO: copy constructors.
+ */
+public final class Responses
+{
+
+  /**
+   * Creates a new bind result using the provided result code.
+   *
+   * @param resultCode
+   *          The result code.
+   * @return The new bind result.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  public static BindResult newBindResult(ResultCode resultCode)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(resultCode);
+    return new BindResultImpl(resultCode);
+  }
+
+
+
+  /**
+   * Creates a new compare result using the provided result code.
+   *
+   * @param resultCode
+   *          The result code.
+   * @return The new compare result.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  public static CompareResult newCompareResult(ResultCode resultCode)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(resultCode);
+    return new CompareResultImpl(resultCode);
+  }
+
+
+
+  /**
+   * Creates a new generic extended result using the provided result
+   * code.
+   *
+   * @param resultCode
+   *          The result code.
+   * @return The new generic extended result.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  public static GenericExtendedResult newGenericExtendedResult(
+      ResultCode resultCode) throws NullPointerException
+  {
+    Validator.ensureNotNull(resultCode);
+    return new GenericExtendedResultImpl(resultCode);
+  }
+
+
+
+  /**
+   * Creates a new generic intermediate response with no name or value.
+   *
+   * @return The new generic intermediate response.
+   */
+  public static GenericIntermediateResponse newGenericIntermediateResponse()
+  {
+    return new GenericIntermediateResponseImpl(null, null);
+  }
+
+
+
+  /**
+   * Creates a new generic intermediate response using the provided
+   * response name and value.
+   *
+   * @param responseName
+   *          The dotted-decimal representation of the unique OID
+   *          corresponding to this intermediate response, which may be
+   *          {@code null} indicating that none was provided.
+   * @param responseValue
+   *          The response value associated with this generic
+   *          intermediate response, which may be {@code null}
+   *          indicating that none was provided.
+   * @return The new generic intermediate response.
+   */
+  public static GenericIntermediateResponse newGenericIntermediateResponse(
+      String responseName, ByteString responseValue)
+  {
+    return new GenericIntermediateResponseImpl(responseName,
+        responseValue);
+  }
+
+
+
+  /**
+   * Creates a new result using the provided result code.
+   *
+   * @param resultCode
+   *          The result code.
+   * @return The new result.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  public static Result newResult(ResultCode resultCode)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(resultCode);
+    return new ResultImpl(resultCode);
+  }
+
+
+
+  /**
+   * Creates a new search result entry using the provided distinguished
+   * name.
+   *
+   * @param name
+   *          The distinguished name of the entry.
+   * @return The new search result entry.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static SearchResultEntry newSearchResultEntry(DN name)
+      throws NullPointerException
+  {
+    final Entry entry = new SortedEntry().setName(name);
+    return new SearchResultEntryImpl(entry);
+  }
+
+
+
+  /**
+   * Creates a new search result entry backed by the provided entry.
+   * Modifications made to {@code entry} will be reflected in the
+   * returned search result entry. The returned search result entry
+   * supports updates to its list of controls, as well as updates to the
+   * name and attributes if the underlying entry allows.
+   *
+   * @param entry
+   *          The entry.
+   * @return The new search result entry.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null} .
+   */
+  public static SearchResultEntry newSearchResultEntry(Entry entry)
+      throws NullPointerException
+  {
+    Validator.ensureNotNull(entry);
+    return new SearchResultEntryImpl(entry);
+  }
+
+
+
+  /**
+   * Creates a new search result entry using the provided distinguished
+   * name decoded using the default schema.
+   *
+   * @param name
+   *          The distinguished name of the entry.
+   * @return The new search result entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code name} could not be decoded using the default
+   *           schema.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public static SearchResultEntry newSearchResultEntry(String name)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    final Entry entry = new SortedEntry().setName(name);
+    return new SearchResultEntryImpl(entry);
+  }
+
+
+
+  /**
+   * Creates a new search result entry using the provided lines of LDIF
+   * decoded using the default schema.
+   *
+   * @param ldifLines
+   *          Lines of LDIF containing an LDIF add change record or an
+   *          LDIF entry record.
+   * @return The new search result entry.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code ldifLines} was empty, or contained invalid
+   *           LDIF, or could not be decoded using the default schema.
+   * @throws NullPointerException
+   *           If {@code ldifLines} was {@code null} .
+   */
+  public static SearchResultEntry newSearchResultEntry(
+      String... ldifLines) throws LocalizedIllegalArgumentException,
+      NullPointerException
+  {
+    return newSearchResultEntry(new SortedEntry(ldifLines));
+  }
+
+
+
+  /**
+   * Creates a new search result reference using the provided
+   * continuation reference URI.
+   *
+   * @param uri
+   *          The first continuation reference URI to be added to this
+   *          search result reference.
+   * @return The new search result reference.
+   * @throws NullPointerException
+   *           If {@code uri} was {@code null}.
+   */
+  public static SearchResultReference newSearchResultReference(
+      String uri) throws NullPointerException
+  {
+    Validator.ensureNotNull(uri);
+    return new SearchResultReferenceImpl(uri);
+  }
+
+
+
+  // Private constructor.
+  private Responses()
+  {
+    // Prevent instantiation.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/responses/Result.java b/sdk/src/org/opends/sdk/responses/Result.java
new file mode 100644
index 0000000..ad339e2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/Result.java
@@ -0,0 +1,279 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * A Result is used to indicate the status of an operation performed by
+ * the server. A Result is comprised of several fields:
+ * <ul>
+ * <li>The <b>result code</b> can be retrieved using the method
+ * {@link #getResultCode}. This indicates the overall outcome of the
+ * operation. In particular, whether or not it succeeded which is
+ * indicated using a value of {@link ResultCode#SUCCESS}.
+ * <li>The optional <b>diagnostic message</b> can be retrieved using the
+ * method {@link #getDiagnosticMessage}. At the server's discretion, a
+ * diagnostic message may be included in a Result in order to supplement
+ * the result code with additional human-readable information.
+ * <li>The optional <b>matched DN</b> can be retrieved using the method
+ * {@link #getMatchedDN}. For certain result codes, this is used to
+ * indicate to the client the last entry used in finding the Request's
+ * target (or base) entry.
+ * <li>The optional <b>referrals</b> can be retrieved using the method
+ * {@link #getReferralURIs}. Referrals are present in a Result if the
+ * result code is set to {@link ResultCode#REFERRAL}, and it are absent
+ * with all other result codes.
+ * </ul>
+ */
+public interface Result extends Response
+{
+  /**
+   * {@inheritDoc}
+   */
+  Result addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided referral URI to this result.
+   * 
+   * @param uri
+   *          The referral URI to be added.
+   * @return This result.
+   * @throws UnsupportedOperationException
+   *           If this result does not permit referrals to be added.
+   * @throws NullPointerException
+   *           If {@code uri} was {@code null}.
+   */
+  Result addReferralURI(String uri)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Result clearControls() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Removes all the referral URIs included with this result.
+   * 
+   * @return This result.
+   * @throws UnsupportedOperationException
+   *           If this result does not permit referral URIs to be
+   *           removed.
+   */
+  Result clearReferralURIs() throws UnsupportedOperationException;
+
+
+
+  /**
+   * Returns the throwable cause associated with this result if
+   * available. A cause may be provided in cases where a result
+   * indicates a failure due to a client-side error.
+   * 
+   * @return The throwable cause, or {@code null} if none was provided.
+   */
+  Throwable getCause();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the diagnostic message associated with this result.
+   * 
+   * @return The diagnostic message, which may be empty if none was
+   *         provided (never {@code null}).
+   */
+  String getDiagnosticMessage();
+
+
+
+  /**
+   * Returns the matched DN associated with this result.
+   * 
+   * @return The matched DN, which may be empty if none was provided
+   *         (never {@code null}).
+   */
+  String getMatchedDN();
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the referral URIs included
+   * with this result. The returned {@code Iterable} may be used to
+   * remove referral URIs if permitted by this result.
+   * 
+   * @return An {@code Iterable} containing the referral URIs.
+   */
+  Iterable<String> getReferralURIs();
+
+
+
+  /**
+   * Returns the result code associated with this result.
+   * 
+   * @return The result code.
+   */
+  ResultCode getResultCode();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Indicates whether or not this result has any referral URIs.
+   * 
+   * @return {@code true} if this result has any referral URIs,
+   *         otherwise {@code false}.
+   */
+  boolean hasReferralURIs();
+
+
+
+  /**
+   * Indicates whether or not a referral needs to be chased in order to
+   * complete the operation.
+   * <p>
+   * Specifically, this method returns {@code true} if the result code
+   * is equal to {@link ResultCode#REFERRAL}.
+   * 
+   * @return {@code true} if a referral needs to be chased, otherwise
+   *         {@code false}.
+   */
+  boolean isReferral();
+
+
+
+  /**
+   * Indicates whether or not the request succeeded or not. This method
+   * will return {code true} for all non-error responses.
+   * 
+   * @return {@code true} if the request succeeded, otherwise {@code
+   *         false}.
+   */
+  boolean isSuccess();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Sets the throwable cause associated with this result if available.
+   * A cause may be provided in cases where a result indicates a failure
+   * due to a client-side error.
+   * 
+   * @param cause
+   *          The throwable cause, which may be {@code null} indicating
+   *          that none was provided.
+   * @return This result.
+   * @throws UnsupportedOperationException
+   *           If this result does not permit the cause to be set.
+   */
+  Result setCause(Throwable cause) throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the diagnostic message associated with this result.
+   * 
+   * @param message
+   *          The diagnostic message, which may be empty or {@code null}
+   *          indicating that none was provided.
+   * @return This result.
+   * @throws UnsupportedOperationException
+   *           If this result does not permit the diagnostic message to
+   *           be set.
+   */
+  Result setDiagnosticMessage(String message)
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the matched DN associated with this result.
+   * 
+   * @param dn
+   *          The matched DN associated, which may be empty or {@code
+   *          null} indicating that none was provided.
+   * @return This result.
+   * @throws UnsupportedOperationException
+   *           If this result does not permit the matched DN to be set.
+   */
+  Result setMatchedDN(String dn) throws UnsupportedOperationException;
+
+
+
+  /**
+   * Sets the result code associated with this result.
+   * 
+   * @param resultCode
+   *          The result code.
+   * @return This result.
+   * @throws UnsupportedOperationException
+   *           If this result does not permit the result code to be set.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  Result setResultCode(ResultCode resultCode)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/ResultImpl.java b/sdk/src/org/opends/sdk/responses/ResultImpl.java
new file mode 100644
index 0000000..c050541
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/ResultImpl.java
@@ -0,0 +1,86 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.ResultCode;
+
+
+
+/**
+ * A generic result indicates the final status of an operation.
+ */
+final class ResultImpl extends AbstractResultImpl<Result> implements
+    Result
+{
+
+  /**
+   * Creates a new generic result using the provided result code.
+   * 
+   * @param resultCode
+   *          The result code.
+   * @throws NullPointerException
+   *           If {@code resultCode} was {@code null}.
+   */
+  ResultImpl(ResultCode resultCode) throws NullPointerException
+  {
+    super(resultCode);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("Result(resultCode=");
+    builder.append(getResultCode());
+    builder.append(", matchedDN=");
+    builder.append(getMatchedDN());
+    builder.append(", diagnosticMessage=");
+    builder.append(getDiagnosticMessage());
+    builder.append(", referrals=");
+    builder.append(getReferralURIs());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  Result getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultEntry.java b/sdk/src/org/opends/sdk/responses/SearchResultEntry.java
new file mode 100644
index 0000000..fb5e755
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultEntry.java
@@ -0,0 +1,230 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * A Search Result Entry represents an entry found during a Search
+ * operation.
+ * <p>
+ * Each entry returned in a Search Result Entry will contain all
+ * appropriate attributes as specified in the Search request, subject to
+ * access control and other administrative policy.
+ * <p>
+ * Note that a Search Result Entry may hold zero attributes. This may
+ * happen when none of the attributes of an entry were requested or
+ * could be returned.
+ * <p>
+ * Note also that each returned attribute may hold zero attribute
+ * values. This may happen when only attribute types are requested,
+ * access controls prevent the return of values, or other reasons.
+ */
+public interface SearchResultEntry extends Response, Entry
+{
+  boolean addAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  boolean addAttribute(Attribute attribute,
+      Collection<ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  SearchResultEntry addAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  SearchResultEntry addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  SearchResultEntry clearAttributes()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  SearchResultEntry clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  boolean containsAttribute(AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  boolean containsAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  boolean containsObjectClass(ObjectClass objectClass)
+      throws NullPointerException;
+
+
+
+  boolean containsObjectClass(String objectClass)
+      throws NullPointerException;
+
+
+
+  Iterable<Attribute> findAttributes(
+      AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  Iterable<Attribute> findAttributes(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  Attribute getAttribute(AttributeDescription attributeDescription)
+      throws NullPointerException;
+
+
+
+  Attribute getAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException;
+
+
+
+  int getAttributeCount();
+
+
+
+  Iterable<Attribute> getAttributes();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  DN getName();
+
+
+
+  Iterable<String> getObjectClasses();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  boolean removeAttribute(Attribute attribute,
+      Collection<ByteString> missingValues)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  boolean removeAttribute(AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  SearchResultEntry removeAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  SearchResultEntry removeAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  boolean replaceAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  SearchResultEntry replaceAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+
+
+  SearchResultEntry setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  SearchResultEntry setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java b/sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java
new file mode 100644
index 0000000..6125343
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultEntryImpl.java
@@ -0,0 +1,377 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.Collection;
+
+import org.opends.sdk.Attribute;
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.Entry;
+import org.opends.sdk.schema.ObjectClass;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Search result entry implementation.
+ */
+final class SearchResultEntryImpl extends
+    AbstractResponseImpl<SearchResultEntry> implements
+    SearchResultEntry
+{
+
+  private final Entry entry;
+
+
+
+  /**
+   * Creates a new search result entry backed by the provided entry.
+   * Modifications made to {@code entry} will be reflected in the
+   * returned search result entry. The returned search result entry
+   * supports updates to its list of controls, as well as updates to the
+   * name and attributes if the underlying entry allows.
+   *
+   * @param entry
+   *          The entry.
+   * @throws NullPointerException
+   *           If {@code entry} was {@code null} .
+   */
+  public SearchResultEntryImpl(Entry entry) throws NullPointerException
+  {
+    this.entry = entry;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.addAttribute(attribute);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean addAttribute(Attribute attribute,
+      Collection<ByteString> duplicateValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.addAttribute(attribute, duplicateValues);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultEntry addAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.addAttribute(attributeDescription, values);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultEntry clearAttributes()
+      throws UnsupportedOperationException
+  {
+    entry.clearAttributes();
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    return entry.containsAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return entry.containsAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsObjectClass(ObjectClass objectClass)
+      throws NullPointerException
+  {
+    return entry.containsObjectClass(objectClass);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean containsObjectClass(String objectClass)
+      throws NullPointerException
+  {
+    return entry.containsObjectClass(objectClass);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> findAttributes(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    return entry.findAttributes(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> findAttributes(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return entry.findAttributes(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Attribute getAttribute(
+      AttributeDescription attributeDescription)
+      throws NullPointerException
+  {
+    return entry.getAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Attribute getAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    return entry.getAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getAttributeCount()
+  {
+    return entry.getAttributeCount();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<Attribute> getAttributes()
+  {
+    return entry.getAttributes();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public DN getName()
+  {
+    return entry.getName();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<String> getObjectClasses()
+  {
+    return entry.getObjectClasses();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(Attribute attribute,
+      Collection<ByteString> missingValues)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.removeAttribute(attribute, missingValues);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean removeAttribute(
+      AttributeDescription attributeDescription)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.removeAttribute(attributeDescription);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultEntry removeAttribute(String attributeDescription)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.removeAttribute(attributeDescription);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultEntry removeAttribute(String attributeDescription,
+      Object... values) throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.removeAttribute(attributeDescription, values);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean replaceAttribute(Attribute attribute)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    return entry.replaceAttribute(attribute);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultEntry replaceAttribute(
+      String attributeDescription, Object... values)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.replaceAttribute(attributeDescription, values);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultEntry setName(DN dn)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    entry.setName(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultEntry setName(String dn)
+      throws LocalizedIllegalArgumentException,
+      UnsupportedOperationException, NullPointerException
+  {
+    entry.setName(dn);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("SearchResultEntry(name=");
+    builder.append(getName());
+    builder.append(", attributes=");
+    builder.append(getAttributes());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  SearchResultEntry getThis()
+  {
+    return this;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultReference.java b/sdk/src/org/opends/sdk/responses/SearchResultReference.java
new file mode 100644
index 0000000..34cefbb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultReference.java
@@ -0,0 +1,151 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import org.opends.sdk.controls.Control;
+
+
+
+/**
+ * A Search Result Reference represents an area not yet explored during
+ * a Search operation.
+ */
+public interface SearchResultReference extends Response
+{
+  /**
+   * {@inheritDoc}
+   */
+  SearchResultReference addControl(Control control)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * Adds the provided continuation reference URI to this search result
+   * reference.
+   * 
+   * @param uri
+   *          The continuation reference URI to be added.
+   * @return This search result reference.
+   * @throws UnsupportedOperationException
+   *           If this search result reference does not permit
+   *           continuation reference URI to be added.
+   * @throws NullPointerException
+   *           If {@code uri} was {@code null}.
+   */
+  SearchResultReference addURI(String uri)
+      throws UnsupportedOperationException, NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  SearchResultReference clearControls()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * Removes all the continuation reference URIs included with this
+   * search result reference.
+   * 
+   * @return This search result reference.
+   * @throws UnsupportedOperationException
+   *           If this search result reference does not permit
+   *           continuation reference URIs to be removed.
+   */
+  SearchResultReference clearURIs()
+      throws UnsupportedOperationException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control getControl(String oid) throws NullPointerException;
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Iterable<Control> getControls();
+
+
+
+  /**
+   * Returns the number of continuation reference URIs in this search
+   * result reference.
+   * 
+   * @return The number of continuation reference URIs.
+   */
+  int getURICount();
+
+
+
+  /**
+   * Returns an {@code Iterable} containing the continuation reference
+   * URIs included with this search result reference. The returned
+   * {@code Iterable} may be used to remove continuation reference URIs
+   * if permitted by this search result reference.
+   * 
+   * @return An {@code Iterable} containing the continuation reference
+   *         URIs.
+   */
+  Iterable<String> getURIs();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  boolean hasControls();
+
+
+
+  /**
+   * Indicates whether or not this search result reference has any
+   * continuation reference URIs.
+   * 
+   * @return {@code true} if this search result reference has any
+   *         continuation reference URIs, otherwise {@code false}.
+   */
+  boolean hasURIs();
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  Control removeControl(String oid)
+      throws UnsupportedOperationException, NullPointerException;
+
+}
diff --git a/sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java b/sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java
new file mode 100644
index 0000000..fe0edc4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/responses/SearchResultReferenceImpl.java
@@ -0,0 +1,145 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.responses;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Search result reference implementation.
+ */
+final class SearchResultReferenceImpl extends
+    AbstractResponseImpl<SearchResultReference> implements
+    SearchResultReference
+{
+
+  private final List<String> uris = new LinkedList<String>();
+
+
+
+  /**
+   * Creates a new search result reference using the provided
+   * continuation reference URI.
+   * 
+   * @param uri
+   *          The first continuation reference URI to be added to this
+   *          search result reference.
+   * @throws NullPointerException
+   *           If {@code uri} was {@code null}.
+   */
+  SearchResultReferenceImpl(String uri) throws NullPointerException
+  {
+    addURI(uri);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultReference addURI(String uri)
+      throws UnsupportedOperationException, NullPointerException
+  {
+    Validator.ensureNotNull(uri);
+    uris.add(uri);
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public SearchResultReference clearURIs()
+      throws UnsupportedOperationException
+  {
+    uris.clear();
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int getURICount()
+  {
+    return uris.size();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Iterable<String> getURIs()
+  {
+    return uris;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean hasURIs()
+  {
+    return !uris.isEmpty();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    final StringBuilder builder = new StringBuilder();
+    builder.append("SearchResultReference(uris=");
+    builder.append(getURIs());
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+
+
+  SearchResultReference getThis()
+  {
+    return this;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java b/sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java
new file mode 100644
index 0000000..4b97a18
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/AbstractSASLContext.java
@@ -0,0 +1,248 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import static org.opends.messages.ExtensionMessages.INFO_SASL_UNSUPPORTED_CALLBACK;
+
+import java.io.IOException;
+
+import javax.security.auth.callback.*;
+import javax.security.sasl.AuthorizeCallback;
+import javax.security.sasl.RealmCallback;
+import javax.security.sasl.RealmChoiceCallback;
+import javax.security.sasl.SaslException;
+
+
+
+/**
+ * An abstract SASL context.
+ */
+public abstract class AbstractSASLContext implements SASLContext,
+    CallbackHandler
+{
+  /**
+   * The name of the default protocol used.
+   */
+  protected static final String SASL_DEFAULT_PROTOCOL = "ldap";
+
+
+
+  public void handle(Callback[] callbacks) throws IOException,
+      UnsupportedCallbackException
+  {
+    for (Callback callback : callbacks)
+    {
+      if (callback instanceof NameCallback)
+      {
+        handle((NameCallback) callback);
+      }
+      else if (callback instanceof PasswordCallback)
+      {
+        handle((PasswordCallback) callback);
+      }
+      else if (callback instanceof AuthorizeCallback)
+      {
+        handle((AuthorizeCallback) callback);
+      }
+      else if (callback instanceof RealmCallback)
+      {
+        handle((RealmCallback) callback);
+      }
+      else if (callback instanceof RealmChoiceCallback)
+      {
+        handle((RealmChoiceCallback) callback);
+      }
+      else if (callback instanceof ChoiceCallback)
+      {
+        handle((ChoiceCallback) callback);
+      }
+      else if (callback instanceof ConfirmationCallback)
+      {
+        handle((ConfirmationCallback) callback);
+      }
+      else if (callback instanceof LanguageCallback)
+      {
+        handle((LanguageCallback) callback);
+      }
+      else if (callback instanceof TextInputCallback)
+      {
+        handle((TextInputCallback) callback);
+      }
+      else if (callback instanceof TextOutputCallback)
+      {
+        handle((TextOutputCallback) callback);
+      }
+      else
+      {
+        org.opends.messages.Message message =
+            INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+                .getSASLMechanism(), String.valueOf(callback));
+        throw new UnsupportedCallbackException(callback, message
+            .toString());
+      }
+    }
+  }
+
+
+
+  /**
+   * Default implementation just returns the copy of the bytes.
+   */
+  public byte[] unwrap(byte[] incoming, int offset, int len)
+      throws SaslException
+  {
+    byte[] copy = new byte[len];
+    System.arraycopy(incoming, offset, copy, 0, len);
+    return copy;
+  }
+
+
+
+  /**
+   * Default implemenation just returns the copy of the bytes.
+   */
+  public byte[] wrap(byte[] outgoing, int offset, int len)
+      throws SaslException
+  {
+    byte[] copy = new byte[len];
+    System.arraycopy(outgoing, offset, copy, 0, len);
+    return copy;
+  }
+
+
+
+  protected void handle(AuthorizeCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(ChoiceCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(ConfirmationCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(LanguageCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(NameCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(PasswordCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(RealmCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(RealmChoiceCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(TextInputCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+
+
+
+  protected void handle(TextOutputCallback callback)
+      throws UnsupportedCallbackException
+  {
+    org.opends.messages.Message message =
+        INFO_SASL_UNSUPPORTED_CALLBACK.get(getSASLBindRequest()
+            .getSASLMechanism(), String.valueOf(callback));
+    throw new UnsupportedCallbackException(callback, message.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java
new file mode 100644
index 0000000..2688391
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/AnonymousSASLBindRequest.java
@@ -0,0 +1,161 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Anonymous SASL bind request.
+ */
+public final class AnonymousSASLBindRequest extends
+    SASLBindRequest<AnonymousSASLBindRequest> implements SASLContext
+{
+  /**
+   * The name of the SASL mechanism that does not provide any authentication but
+   * rather uses anonymous access.
+   */
+  public static final String SASL_MECHANISM_ANONYMOUS = "ANONYMOUS";
+
+  private String traceString;
+
+  public void dispose() throws SaslException
+  {
+    // Nothing needed.
+  }
+
+  public boolean evaluateCredentials(ByteString incomingCredentials)
+      throws SaslException
+  {
+    // This is a single stage SASL bind.
+    return true;
+  }
+
+
+  public boolean isComplete()
+  {
+    return true;
+  }
+
+  public boolean isSecure()
+  {
+    return false;
+  }
+
+  public ByteString getSASLCredentials()
+  {
+    return ByteString.valueOf(traceString);
+  }
+
+
+  public byte[] unwrap(byte[] incoming, int offset, int len)
+      throws SaslException
+  {
+    byte[] copy = new byte[len];
+    System.arraycopy(incoming, offset, copy, 0, len);
+    return copy;
+  }
+
+
+  public byte[] wrap(byte[] outgoing, int offset, int len)
+      throws SaslException
+  {
+    byte[] copy = new byte[len];
+    System.arraycopy(outgoing, offset, copy, 0, len);
+    return copy;
+  }
+
+  public AnonymousSASLBindRequest()
+  {
+    this.traceString = "".intern();
+  }
+
+
+
+  public AnonymousSASLBindRequest(String traceString)
+  {
+    Validator.ensureNotNull(traceString);
+    this.traceString = traceString;
+  }
+
+
+
+  public String getSASLMechanism()
+  {
+    return SASL_MECHANISM_ANONYMOUS;
+  }
+
+
+
+  public String getTraceString()
+  {
+    return traceString;
+  }
+
+
+
+  public SASLContext getClientContext(String serverName) throws SaslException
+  {
+    return this;
+  }
+
+  public AnonymousSASLBindRequest getSASLBindRequest() {
+    return this;
+  }
+
+  public AnonymousSASLBindRequest setTraceString(String traceString)
+  {
+    Validator.ensureNotNull(traceString);
+    this.traceString = traceString;
+    return this;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("AnonymousSASLBindRequest(bindDN=");
+    builder.append(getName());
+    builder.append(", authentication=SASL");
+    builder.append(", saslMechanism=");
+    builder.append(getSASLMechanism());
+    builder.append(", traceString=");
+    builder.append(traceString);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java
new file mode 100644
index 0000000..15b3569
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/CRAMMD5SASLBindRequest.java
@@ -0,0 +1,275 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * CRAM-MD5 SASL bind request.
+ */
+public final class CRAMMD5SASLBindRequest extends
+    SASLBindRequest<CRAMMD5SASLBindRequest>
+{
+  /**
+   * The name of the SASL mechanism based on CRAM-MD5 authentication.
+   */
+  public static final String SASL_MECHANISM_CRAM_MD5 = "CRAM-MD5";
+
+  private String authenticationID;
+  private ByteString password;
+
+  private NameCallbackHandler authIDHandler;
+  private PasswordCallbackHandler passHandler;
+
+  private class CRAMMD5SASLContext extends AbstractSASLContext
+  {
+    private SaslClient saslClient;
+    private ByteString outgoingCredentials = null;
+
+    private CRAMMD5SASLContext(String serverName) throws SaslException {
+      saslClient =
+          Sasl.createSaslClient(new String[] { SASL_MECHANISM_CRAM_MD5 },
+              null, SASL_DEFAULT_PROTOCOL, serverName, null, this);
+
+      if (saslClient.hasInitialResponse())
+      {
+        byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+        if (bytes != null)
+        {
+          this.outgoingCredentials = ByteString.wrap(bytes);
+        }
+      }
+    }
+
+    public void dispose() throws SaslException
+    {
+      saslClient.dispose();
+    }
+
+    public boolean evaluateCredentials(ByteString incomingCredentials)
+        throws SaslException
+    {
+      byte[] bytes =
+          saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+      if (bytes != null)
+      {
+        this.outgoingCredentials = ByteString.wrap(bytes);
+      }
+      else
+      {
+        this.outgoingCredentials = null;
+      }
+
+      return isComplete();
+    }
+
+    public ByteString getSASLCredentials()
+    {
+      return outgoingCredentials;
+    }
+
+    public boolean isComplete()
+    {
+      return saslClient.isComplete();
+    }
+
+    public boolean isSecure()
+    {
+      return false;
+    }
+
+    @Override
+    protected void handle(NameCallback callback)
+        throws UnsupportedCallbackException
+    {
+      if (authIDHandler == null)
+      {
+        callback.setName(authenticationID);
+      }
+      else
+      {
+        if(authIDHandler.handle(callback))
+        {
+          authenticationID = callback.getName();
+          authIDHandler = null;
+        }
+      }
+    }
+
+    @Override
+    protected void handle(PasswordCallback callback)
+        throws UnsupportedCallbackException
+    {
+      if (passHandler == null)
+      {
+        callback.setPassword(password.toString().toCharArray());
+      }
+      else
+      {
+        if(passHandler.handle(callback))
+        {
+          password = ByteString.valueOf(callback.getPassword());
+          passHandler = null;
+        }
+      }
+    }
+
+    public CRAMMD5SASLBindRequest getSASLBindRequest() {
+      return CRAMMD5SASLBindRequest.this;
+    }
+  }
+
+  public CRAMMD5SASLBindRequest(DN authenticationDN, ByteString password)
+  {
+    Validator.ensureNotNull(authenticationDN, password);
+    this.authenticationID = "dn:" + authenticationDN.toString();
+    this.password = password;
+  }
+
+
+
+  public CRAMMD5SASLBindRequest(String authenticationID,
+                                ByteString password)
+  {
+    Validator.ensureNotNull(authenticationID, password);
+    this.authenticationID = authenticationID;
+    this.password = password;
+  }
+
+
+
+  public String getAuthenticationID()
+  {
+    return authenticationID;
+  }
+
+
+
+  public NameCallbackHandler getAuthIDHandler()
+  {
+    return authIDHandler;
+  }
+
+
+
+  public PasswordCallbackHandler getPassHandler()
+  {
+    return passHandler;
+  }
+
+
+
+  public ByteString getPassword()
+  {
+    return password;
+  }
+
+
+
+
+  public String getSASLMechanism()
+  {
+    return SASL_MECHANISM_CRAM_MD5;
+  }
+
+
+
+  public SASLContext getClientContext(String serverName) throws SaslException
+  {
+    return new CRAMMD5SASLContext(serverName);
+  }
+
+
+
+  public CRAMMD5SASLBindRequest setAuthenticationID(
+      String authenticationID)
+  {
+    Validator.ensureNotNull(authenticationID);
+    this.authenticationID = authenticationID;
+    return this;
+  }
+
+
+
+  public CRAMMD5SASLBindRequest setAuthIDHandler(
+      NameCallbackHandler authIDHandler)
+  {
+    this.authIDHandler = authIDHandler;
+    return this;
+  }
+
+
+
+  public CRAMMD5SASLBindRequest setPassHandler(
+      PasswordCallbackHandler passHandler)
+  {
+    this.passHandler = passHandler;
+    return this;
+  }
+
+
+
+  public CRAMMD5SASLBindRequest setPassword(ByteString password)
+  {
+    Validator.ensureNotNull(password);
+    this.password = password;
+    return this;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("CRAMMD5SASLBindRequest(bindDN=");
+    builder.append(getName());
+    builder.append(", authentication=SASL");
+    builder.append(", saslMechanism=");
+    builder.append(getSASLMechanism());
+    builder.append(", authenticationID=");
+    builder.append(authenticationID);
+    builder.append(", password=");
+    builder.append(password);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java
new file mode 100644
index 0000000..a4afac7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/DigestMD5SASLBindRequest.java
@@ -0,0 +1,436 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.RealmCallback;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Digest-MD5 SASL bind request.
+ */
+public final class DigestMD5SASLBindRequest extends
+    SASLBindRequest<DigestMD5SASLBindRequest>
+{
+  /**
+   * The name of the SASL mechanism based on DIGEST-MD5 authentication.
+   */
+  public static final String SASL_MECHANISM_DIGEST_MD5 = "DIGEST-MD5";
+
+  private String authenticationID;
+  private String authorizationID;
+  private ByteString password;
+  private String realm;
+
+  private NameCallbackHandler authIDHandler;
+  private PasswordCallbackHandler passHandler;
+  private TextInputCallbackHandler realmHandler;
+
+  private class DigestMD5SASLContext extends AbstractSASLContext
+  {
+    private SaslClient saslClient;
+    private ByteString outgoingCredentials = null;
+
+    private DigestMD5SASLContext(String serverName) throws SaslException {
+      Map<String, String> props = new HashMap<String, String>();
+      props.put(Sasl.QOP, "auth-conf,auth-int,auth");
+      saslClient =
+          Sasl.createSaslClient(
+              new String[] { SASL_MECHANISM_DIGEST_MD5 },
+              authorizationID, SASL_DEFAULT_PROTOCOL, serverName, props,
+              this);
+
+      if (saslClient.hasInitialResponse())
+      {
+        byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+        if (bytes != null)
+        {
+          this.outgoingCredentials = ByteString.wrap(bytes);
+        }
+      }
+    }
+
+    public void dispose() throws SaslException
+    {
+      saslClient.dispose();
+    }
+
+    public boolean evaluateCredentials(ByteString incomingCredentials)
+        throws SaslException
+    {
+      byte[] bytes =
+          saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+      if (bytes != null)
+      {
+        this.outgoingCredentials = ByteString.wrap(bytes);
+      }
+      else
+      {
+        this.outgoingCredentials = null;
+      }
+
+      return isComplete();
+    }
+
+    public ByteString getSASLCredentials()
+    {
+      return outgoingCredentials;
+    }
+
+
+    public boolean isComplete()
+    {
+      return saslClient.isComplete();
+    }
+
+
+
+    public boolean isSecure()
+    {
+      String qop = (String) saslClient.getNegotiatedProperty(Sasl.QOP);
+      return (qop.equalsIgnoreCase("auth-int") || qop
+          .equalsIgnoreCase("auth-conf"));
+    }
+
+    @Override
+    public byte[] unwrap(byte[] incoming, int offset, int len)
+        throws SaslException
+    {
+      return saslClient.unwrap(incoming, offset, len);
+    }
+
+
+
+    @Override
+    public byte[] wrap(byte[] outgoing, int offset, int len)
+        throws SaslException
+    {
+      return saslClient.wrap(outgoing, offset, len);
+    }
+
+
+
+    @Override
+    protected void handle(NameCallback callback)
+        throws UnsupportedCallbackException
+    {
+      if (authIDHandler == null)
+      {
+        callback.setName(authenticationID);
+      }
+      else
+      {
+        if(authIDHandler.handle(callback))
+        {
+          authenticationID = callback.getName();
+          authIDHandler = null;
+        }
+      }
+    }
+
+
+
+    @Override
+    protected void handle(PasswordCallback callback)
+        throws UnsupportedCallbackException
+    {
+      if (passHandler == null)
+      {
+        callback.setPassword(password.toString().toCharArray());
+      }
+      else
+      {
+        if(passHandler.handle(callback))
+        {
+          password = ByteString.valueOf(callback.getPassword());
+          passHandler = null;
+        }
+      }
+    }
+
+
+
+    @Override
+    protected void handle(RealmCallback callback)
+        throws UnsupportedCallbackException
+    {
+      if (realmHandler == null)
+      {
+        if (realm == null)
+        {
+          callback.setText(callback.getDefaultText());
+        }
+        else
+        {
+          callback.setText(realm);
+        }
+      }
+      else
+      {
+        if(realmHandler.handle(callback))
+        {
+          realm = callback.getText();
+          realmHandler = null;
+        }
+      }
+    }
+
+    public DigestMD5SASLBindRequest getSASLBindRequest() {
+      return DigestMD5SASLBindRequest.this;
+    }
+  }
+
+  public DigestMD5SASLBindRequest(DN authenticationDN,
+                                  ByteString password)
+  {
+    Validator.ensureNotNull(authenticationDN, password);
+    this.authenticationID = "dn:" + authenticationDN.toString();
+    this.password = password;
+  }
+
+
+
+  public DigestMD5SASLBindRequest(DN authenticationDN,
+                                  DN authorizationDN, ByteString password)
+  {
+    Validator
+        .ensureNotNull(authenticationDN, authorizationDN, password);
+    this.authenticationID = "dn:" + authenticationDN.toString();
+    this.authorizationID = "dn:" + authorizationDN.toString();
+    this.password = password;
+  }
+
+
+
+  public DigestMD5SASLBindRequest(DN authenticationDN,
+                                  DN authorizationDN, ByteString password, String realm)
+  {
+    Validator.ensureNotNull(authenticationDN, authorizationDN,
+        password);
+    this.authenticationID = "dn:" + authenticationDN.toString();
+    this.authorizationID = "dn:" + authorizationDN.toString();
+    this.password = password;
+    this.realm = realm;
+  }
+
+
+
+  public DigestMD5SASLBindRequest(String authenticationID,
+                                  ByteString password)
+  {
+    Validator.ensureNotNull(authenticationID, password);
+    this.authenticationID = authenticationID;
+    this.password = password;
+  }
+
+
+
+  public DigestMD5SASLBindRequest(String authenticationID,
+                                  String authorizationID, ByteString password)
+  {
+    Validator
+        .ensureNotNull(authenticationID, password);
+    this.authenticationID = authenticationID;
+    this.authorizationID = authorizationID;
+    this.password = password;
+  }
+
+
+
+  public DigestMD5SASLBindRequest(String authenticationID,
+                                  String authorizationID, ByteString password, String realm)
+  {
+    Validator.ensureNotNull(authenticationID, password);
+    this.authenticationID = authenticationID;
+    this.authorizationID = authorizationID;
+    this.password = password;
+    this.realm = realm;
+  }
+
+
+
+  public String getAuthenticationID()
+  {
+    return authenticationID;
+  }
+
+
+
+  public NameCallbackHandler getAuthIDHandler()
+  {
+    return authIDHandler;
+  }
+
+
+
+  public String getAuthorizationID()
+  {
+    return authorizationID;
+  }
+
+
+
+  public PasswordCallbackHandler getPassHandler()
+  {
+    return passHandler;
+  }
+
+
+
+  public ByteString getPassword()
+  {
+    return password;
+  }
+
+
+
+  public String getRealm()
+  {
+    return realm;
+  }
+
+
+
+  public TextInputCallbackHandler getRealmHandler()
+  {
+    return realmHandler;
+  }
+
+
+  @Override
+  public String getSASLMechanism()
+  {
+    return SASL_MECHANISM_DIGEST_MD5;
+  }
+
+
+
+  public SASLContext getClientContext(String serverName) throws SaslException
+  {
+    return new DigestMD5SASLContext(serverName);
+  }
+
+
+
+  public DigestMD5SASLBindRequest setAuthenticationID(
+      String authenticationID)
+  {
+    Validator.ensureNotNull(authenticationID);
+    this.authenticationID = authenticationID;
+    return this;
+  }
+
+
+
+  public DigestMD5SASLBindRequest setAuthIDHandler(
+      NameCallbackHandler authIDHandler)
+  {
+    this.authIDHandler = authIDHandler;
+    return this;
+  }
+
+
+
+  public DigestMD5SASLBindRequest setAuthorizationID(
+      String authorizationID)
+  {
+    this.authorizationID = authorizationID;
+    return this;
+  }
+
+
+
+  public DigestMD5SASLBindRequest setPassHandler(
+      PasswordCallbackHandler passHandler)
+  {
+    this.passHandler = passHandler;
+    return this;
+  }
+
+
+
+  public DigestMD5SASLBindRequest setPassword(ByteString password)
+  {
+    Validator.ensureNotNull(password);
+    this.password = password;
+    return this;
+  }
+
+
+
+  public DigestMD5SASLBindRequest setRealm(String realm)
+  {
+    this.realm = realm;
+    return this;
+  }
+
+
+
+  public void setRealmHandler(TextInputCallbackHandler realmHandler)
+  {
+    this.realmHandler = realmHandler;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("DigestMD5SASLBindRequest(bindDN=");
+    builder.append(getName());
+    builder.append(", authentication=SASL");
+    builder.append(", saslMechanism=");
+    builder.append(getSASLMechanism());
+    builder.append(", authenticationID=");
+    builder.append(authenticationID);
+    builder.append(", authorizationID=");
+    builder.append(authorizationID);
+    builder.append(", realm=");
+    builder.append(realm);
+    builder.append(", password=");
+    builder.append(password);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java
new file mode 100644
index 0000000..ed0aedd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/ExternalSASLBindRequest.java
@@ -0,0 +1,190 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * External SASL bind request.
+ */
+public final class ExternalSASLBindRequest extends
+    SASLBindRequest<ExternalSASLBindRequest>
+{
+  /**
+   * The name of the SASL mechanism based on external authentication.
+   */
+  public static final String SASL_MECHANISM_EXTERNAL = "EXTERNAL";
+
+  private String authorizationID;
+
+  private class ExternalSASLContext extends AbstractSASLContext
+  {
+    private SaslClient saslClient;
+    private ByteString outgoingCredentials = null;
+
+    private ExternalSASLContext(String serverName) throws SaslException {
+      saslClient =
+          Sasl.createSaslClient(new String[] { SASL_MECHANISM_EXTERNAL },
+              authorizationID, SASL_DEFAULT_PROTOCOL, serverName, null,
+              this);
+
+      if (saslClient.hasInitialResponse())
+      {
+        byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+        if (bytes != null)
+        {
+          this.outgoingCredentials = ByteString.wrap(bytes);
+        }
+      }
+    }
+
+    public void dispose() throws SaslException
+
+    {
+      saslClient.dispose();
+    }
+
+
+
+    public boolean evaluateCredentials(ByteString incomingCredentials)
+        throws SaslException
+    {
+      byte[] bytes =
+          saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+      if (bytes != null)
+      {
+        this.outgoingCredentials = ByteString.wrap(bytes);
+      }
+      else
+      {
+        this.outgoingCredentials = null;
+      }
+
+      return isComplete();
+    }
+
+    public ByteString getSASLCredentials()
+    {
+      return outgoingCredentials;
+    }
+
+
+
+    public boolean isComplete()
+    {
+      return saslClient.isComplete();
+    }
+
+
+
+    public boolean isSecure()
+    {
+      return false;
+    }
+
+    public ExternalSASLBindRequest getSASLBindRequest() {
+      return ExternalSASLBindRequest.this;
+    }   
+  }
+
+  public ExternalSASLBindRequest()
+  {
+  }
+
+
+
+  public ExternalSASLBindRequest(DN authorizationDN)
+  {
+    Validator.ensureNotNull(authorizationDN);
+    this.authorizationID = "dn:" + authorizationDN.toString();
+  }
+
+
+
+  public ExternalSASLBindRequest(String authorizationID)
+  {
+    this.authorizationID = authorizationID;
+  }
+
+
+
+  public String getAuthorizationID()
+  {
+    return authorizationID;
+  }
+
+
+
+  public String getSASLMechanism()
+  {
+    return SASL_MECHANISM_EXTERNAL;
+  }
+
+
+
+  public SASLContext getClientContext(String serverName) throws SaslException
+  {
+    return new ExternalSASLContext(serverName);
+  }
+
+
+
+  public ExternalSASLBindRequest setAuthorizationID(
+      String authorizationID)
+  {
+    this.authorizationID = authorizationID;
+    return this;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("ExternalSASLBindRequest(bindDN=");
+    builder.append(getName());
+    builder.append(", authentication=SASL");
+    builder.append(", saslMechanism=");
+    builder.append(getSASLMechanism());
+    builder.append(", authorizationID=");
+    builder.append(authorizationID);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java
new file mode 100644
index 0000000..7eac5d5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/GSSAPISASLBindRequest.java
@@ -0,0 +1,304 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import static org.opends.messages.ExtensionMessages.ERR_SASL_CONTEXT_CREATE_ERROR;
+import static org.opends.messages.ExtensionMessages.ERR_SASL_PROTOCOL_ERROR;
+import static org.opends.sdk.util.StaticUtils.getExceptionMessage;
+
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+import com.sun.security.auth.callback.TextCallbackHandler;
+import com.sun.security.auth.module.Krb5LoginModule;
+
+
+/**
+ * GSSAPI SASL bind request.
+ */
+public final class GSSAPISASLBindRequest extends
+    SASLBindRequest<GSSAPISASLBindRequest>
+{
+  /**
+   * The name of the SASL mechanism based on GSS-API authentication.
+   */
+  public static final String SASL_MECHANISM_GSSAPI = "GSSAPI";
+
+  private final Subject subject;
+  private String authorizationID;
+
+  private class GSSAPISASLContext extends AbstractSASLContext
+  {
+    private String serverName;
+    private SaslClient saslClient;
+    private ByteString outgoingCredentials = null;
+
+    private ByteString incomingCredentials;
+
+    PrivilegedExceptionAction<Boolean> evaluateAction =
+        new PrivilegedExceptionAction<Boolean>()
+        {
+          public Boolean run() throws Exception
+          {
+            byte[] bytes =
+                saslClient.evaluateChallenge(incomingCredentials
+                    .toByteArray());
+            if (bytes != null)
+            {
+              outgoingCredentials = ByteString.wrap(bytes);
+            }
+            else
+            {
+              outgoingCredentials = null;
+            }
+
+            return isComplete();
+          }
+        };
+
+    PrivilegedExceptionAction<Object> invokeAction =
+        new PrivilegedExceptionAction<Object>()
+        {
+          public Object run() throws Exception
+          {
+            Map<String, String> props = new HashMap<String, String>();
+            props.put(Sasl.QOP, "auth-conf,auth-int,auth");
+            saslClient =
+                Sasl.createSaslClient(
+                    new String[] { SASL_MECHANISM_GSSAPI },
+                    authorizationID, SASL_DEFAULT_PROTOCOL, serverName,
+                    props, GSSAPISASLContext.this);
+
+            if (saslClient.hasInitialResponse())
+            {
+              byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+              if (bytes != null)
+              {
+                outgoingCredentials = ByteString.wrap(bytes);
+              }
+            }
+            return null;
+          }
+        };
+
+    private GSSAPISASLContext(String serverName) throws SaslException {
+      this.serverName = serverName;
+
+      try
+      {
+        Subject.doAs(subject, invokeAction);
+      }
+      catch (PrivilegedActionException e)
+      {
+        if (e.getCause() instanceof SaslException)
+        {
+          throw (SaslException) e.getCause();
+        }
+
+        // This should not happen. Must be a bug.
+        Message msg =
+            ERR_SASL_CONTEXT_CREATE_ERROR.get(SASL_MECHANISM_GSSAPI,
+                getExceptionMessage(e));
+        throw new SaslException(msg.toString(), e.getCause());
+      }
+    }
+
+    public void dispose() throws SaslException
+    {
+      saslClient.dispose();
+    }
+
+
+
+    public boolean evaluateCredentials(ByteString incomingCredentials)
+        throws SaslException
+    {
+      this.incomingCredentials = incomingCredentials;
+      try
+      {
+        return Subject.doAs(subject, evaluateAction);
+      }
+      catch (PrivilegedActionException e)
+      {
+        if (e.getCause() instanceof SaslException)
+        {
+          throw (SaslException) e.getCause();
+        }
+
+        // This should not happen. Must be a bug.
+        Message msg =
+            ERR_SASL_PROTOCOL_ERROR.get(SASL_MECHANISM_GSSAPI,
+                getExceptionMessage(e));
+        throw new SaslException(msg.toString(), e.getCause());
+      }
+    }
+
+    public ByteString getSASLCredentials()
+    {
+      return outgoingCredentials;
+    }
+
+
+
+    public boolean isComplete()
+    {
+      return saslClient.isComplete();
+    }
+
+
+
+    public boolean isSecure()
+    {
+      String qop = (String) saslClient.getNegotiatedProperty(Sasl.QOP);
+      return (qop.equalsIgnoreCase("auth-int") || qop
+          .equalsIgnoreCase("auth-conf"));
+    }
+
+    public GSSAPISASLBindRequest getSASLBindRequest() {
+      return GSSAPISASLBindRequest.this;
+    }
+  }
+
+
+  public GSSAPISASLBindRequest(Subject subject)
+  {
+    Validator.ensureNotNull(subject);
+    this.subject = subject;
+  }
+
+
+
+  public GSSAPISASLBindRequest(Subject subject, DN authorizationDN)
+  {
+    Validator.ensureNotNull(subject, authorizationDN);
+    this.subject = subject;
+    this.authorizationID = "dn:" + authorizationDN.toString();
+  }
+
+
+
+  public GSSAPISASLBindRequest(Subject subject, String authorizationID)
+  {
+    Validator.ensureNotNull(subject);
+    this.subject = subject;
+    this.authorizationID = authorizationID;
+  }
+
+
+
+  public String getAuthorizationID()
+  {
+    return authorizationID;
+  }
+
+
+
+  public String getSASLMechanism()
+  {
+    return SASL_MECHANISM_GSSAPI;
+  }
+
+
+
+  public SASLContext getClientContext(String serverName) throws SaslException
+  {
+    return new GSSAPISASLContext(serverName);
+  }
+
+
+
+  public GSSAPISASLBindRequest setAuthorizationID(String authorizationID)
+  {
+    this.authorizationID = authorizationID;
+    return this;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("GSSAPISASLBindRequest(bindDN=");
+    builder.append(getName());
+    builder.append(", authentication=SASL");
+    builder.append(", saslMechanism=");
+    builder.append(getSASLMechanism());
+    builder.append(", subject=");
+    builder.append(subject);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+
+  public static Subject Kerberos5Login(String authenticationID,
+                                       ByteString password,
+                                       String realm, String kdc)
+      throws LoginException {
+    Map<String, Object> state = new HashMap<String, Object>();
+    state.put("javax.security.auth.login.name", authenticationID);
+    state.put("javax.security.auth.login.password",
+        password.toString().toCharArray());
+    state.put("javax.security.auth.useSubjectCredsOnly", "true");
+    state.put("java.security.krb5.realm", realm);
+    state.put("java.security.krb5.kdc", kdc);
+
+    Map<String, Object> options = new HashMap<String, Object>();
+    options.put("debug", "true");
+    options.put("tryFirstPass", "true");
+    options.put("useTicketCache", "true");
+    options.put("doNotPrompt", "true");
+    options.put("storePass", "false");
+    options.put("forwardable", "true");
+
+    Subject subject = new Subject();
+    Krb5LoginModule login = new Krb5LoginModule();
+    login.initialize(subject, new TextCallbackHandler(), state,
+        options);
+    if(login.login()){
+      login.commit();
+    }
+    return subject;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java
new file mode 100644
index 0000000..f151dd2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/GenericSASLBindRequest.java
@@ -0,0 +1,175 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+/**
+ * Generic SASL bind request.
+ */
+public class GenericSASLBindRequest extends
+    SASLBindRequest<GenericSASLBindRequest> implements SASLContext
+{
+  // The SASL credentials.
+  private ByteString saslCredentials;
+
+  // The SASL mechanism.
+  private String saslMechanism;
+
+
+
+  public GenericSASLBindRequest(String saslMechanism,
+      ByteString saslCredentials)
+  {
+    Validator.ensureNotNull(saslMechanism);
+    this.saslCredentials = saslCredentials;
+    this.saslMechanism = saslMechanism;
+  }
+
+  public void dispose() throws SaslException
+  {
+    // Nothing needed.
+  }
+
+  public boolean evaluateCredentials(ByteString incomingCredentials)
+      throws SaslException
+  {
+    // This is a single stage SASL bind.
+    return true;
+  }
+
+
+  public boolean isComplete()
+  {
+    return true;
+  }
+
+  public boolean isSecure()
+  {
+    return false;
+  }
+
+
+  public byte[] unwrap(byte[] incoming, int offset, int len)
+      throws SaslException
+  {
+    byte[] copy = new byte[len];
+    System.arraycopy(incoming, offset, copy, 0, len);
+    return copy;
+  }
+
+
+  public byte[] wrap(byte[] outgoing, int offset, int len)
+      throws SaslException
+  {
+    byte[] copy = new byte[len];
+    System.arraycopy(outgoing, offset, copy, 0, len);
+    return copy;
+  }
+
+  public ByteString getSASLCredentials()
+  {
+    return saslCredentials;
+  }
+
+  public String getSASLMechanism()
+  {
+    return saslMechanism;
+  }
+
+  public SASLContext getClientContext(String serverName) throws SaslException
+  {
+    return this;
+  }
+
+  public GenericSASLBindRequest getSASLBindRequest() {
+    return this;
+  }
+
+  /**
+   * Sets the SASL credentials for this bind request.
+   *
+   * @param saslCredentials
+   *          The SASL credentials for this bind request, or {@code
+   *          null} if there are none or if the bind does not use SASL
+   *          authentication.
+   * @return This raw bind request.
+   */
+  public GenericSASLBindRequest setSASLCredentials(
+      ByteString saslCredentials)
+  {
+    this.saslCredentials = saslCredentials;
+    return this;
+  }
+
+
+
+  /**
+   * Sets The SASL mechanism for this bind request.
+   *
+   * @param saslMechanism
+   *          The SASL mechanism for this bind request, or {@code null}
+   *          if there are none or if the bind does not use SASL
+   *          authentication.
+   * @return This raw bind request.
+   */
+  public GenericSASLBindRequest setSASLMechanism(String saslMechanism)
+  {
+    Validator.ensureNotNull(saslMechanism);
+    this.saslMechanism = saslMechanism;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("SASLBindRequest(bindDN=");
+    builder.append(getName());
+    builder.append(", authentication=SASL");
+    builder.append(", saslMechanism=");
+    builder.append(saslMechanism);
+    builder.append(", saslCredentials=");
+    builder.append(saslCredentials);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java b/sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java
new file mode 100644
index 0000000..913e3c0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/NameCallbackHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.auth.callback.NameCallback;
+
+
+
+/**
+ * Name call-back.
+ */
+public interface NameCallbackHandler
+{
+  public boolean handle(NameCallback callback);
+}
diff --git a/sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java b/sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java
new file mode 100644
index 0000000..62b3ae3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/PasswordCallbackHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.auth.callback.PasswordCallback;
+
+
+
+/**
+ * Password call-back.
+ */
+public interface PasswordCallbackHandler
+{
+  public boolean handle(PasswordCallback callback);
+}
diff --git a/sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java
new file mode 100644
index 0000000..3e50421
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/PlainSASLBindRequest.java
@@ -0,0 +1,320 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.sasl.Sasl;
+import javax.security.sasl.SaslClient;
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Plain SASL bind request.
+ */
+public final class PlainSASLBindRequest extends
+    SASLBindRequest<PlainSASLBindRequest>
+{
+  /**
+   * The name of the SASL mechanism based on PLAIN authentication.
+   */
+  public static final String SASL_MECHANISM_PLAIN = "PLAIN";
+
+  private String authenticationID;
+  private String authorizationID;
+  private ByteString password;
+
+  private NameCallbackHandler authIDHandler;
+  private PasswordCallbackHandler passHandler;
+
+  private class PlainSASLContext extends AbstractSASLContext
+  {
+    private SaslClient saslClient;
+    private ByteString outgoingCredentials = null;
+
+    private PlainSASLContext(String serverName) throws SaslException {
+      saslClient =
+          Sasl.createSaslClient(new String[] { SASL_MECHANISM_PLAIN },
+              authorizationID, SASL_DEFAULT_PROTOCOL, serverName, null,
+              this);
+
+      if (saslClient.hasInitialResponse())
+      {
+        byte[] bytes = saslClient.evaluateChallenge(new byte[0]);
+        if (bytes != null)
+        {
+          this.outgoingCredentials = ByteString.wrap(bytes);
+        }
+      }
+    }
+
+    public void dispose() throws SaslException
+    {
+      saslClient.dispose();
+    }
+
+
+
+    public boolean evaluateCredentials(ByteString incomingCredentials)
+        throws SaslException
+    {
+      byte[] bytes =
+          saslClient.evaluateChallenge(incomingCredentials.toByteArray());
+      if (bytes != null)
+      {
+        this.outgoingCredentials = ByteString.wrap(bytes);
+      }
+      else
+      {
+        this.outgoingCredentials = null;
+      }
+
+      return isComplete();
+    }
+
+
+    public ByteString getSASLCredentials()
+    {
+      return outgoingCredentials;
+    }
+
+
+
+    public boolean isComplete()
+    {
+      return saslClient.isComplete();
+    }
+
+
+
+    public boolean isSecure()
+    {
+      return false;
+    }
+
+
+
+    @Override
+    protected void handle(NameCallback callback)
+        throws UnsupportedCallbackException
+    {
+      if (authIDHandler == null)
+      {
+        callback.setName(authenticationID);
+      }
+      else
+      {
+        if(authIDHandler.handle(callback))
+        {
+          authenticationID = callback.getName();
+          authIDHandler = null;
+        }
+      }
+    }
+
+
+
+    @Override
+    protected void handle(PasswordCallback callback)
+        throws UnsupportedCallbackException
+    {
+      if (passHandler == null)
+      {
+        callback.setPassword(password.toString().toCharArray());
+      }
+      else
+      {
+        if(passHandler.handle(callback))
+        {
+          password = ByteString.valueOf(callback.getPassword());
+          passHandler = null;
+        }
+      }
+    }
+
+    public PlainSASLBindRequest getSASLBindRequest() {
+      return PlainSASLBindRequest.this;
+    }
+  }
+
+  public PlainSASLBindRequest(DN authenticationDN, ByteString password)
+  {
+    Validator.ensureNotNull(authenticationDN, password);
+    this.authenticationID = "dn:" + authenticationDN.toString();
+    this.password = password;
+  }
+
+
+
+  public PlainSASLBindRequest(DN authenticationDN, DN authorizationDN,
+                              ByteString password)
+  {
+    Validator
+        .ensureNotNull(authenticationDN, authorizationDN, password);
+    this.authenticationID = "dn:" + authenticationDN.toString();
+    this.authorizationID = "dn:" + authorizationDN.toString();
+    this.password = password;
+  }
+
+
+
+  public PlainSASLBindRequest(String authenticationID,
+                              ByteString password)
+  {
+    Validator.ensureNotNull(authenticationID, password);
+    this.authenticationID = authenticationID;
+    this.password = password;
+  }
+
+
+
+  public PlainSASLBindRequest(String authenticationID,
+                              String authorizationID, ByteString password)
+  {
+    Validator
+        .ensureNotNull(authenticationID, password);
+    this.authenticationID = authenticationID;
+    this.authorizationID = authorizationID;
+    this.password = password;
+  }
+
+
+
+  public String getAuthenticationID()
+  {
+    return authenticationID;
+  }
+
+
+
+  public NameCallbackHandler getAuthIDHandler()
+  {
+    return authIDHandler;
+  }
+
+
+
+  public String getAuthorizationID()
+  {
+    return authorizationID;
+  }
+
+
+
+  public PasswordCallbackHandler getPassHandler()
+  {
+    return passHandler;
+  }
+
+
+
+  public ByteString getPassword()
+  {
+    return password;
+  }
+
+
+
+  public String getSASLMechanism()
+  {
+    return SASL_MECHANISM_PLAIN;
+  }
+
+
+
+  public SASLContext getClientContext(String serverName) throws SaslException
+  {
+    return new PlainSASLContext(serverName);
+  }
+
+
+
+  public PlainSASLBindRequest setAuthenticationID(
+      String authenticationID)
+  {
+    Validator.ensureNotNull(authenticationID);
+    this.authenticationID = authenticationID;
+    return this;
+  }
+
+
+
+  public PlainSASLBindRequest setAuthIDHandler(
+      NameCallbackHandler authIDHandler)
+  {
+    this.authIDHandler = authIDHandler;
+    return this;
+  }
+
+
+
+  public PlainSASLBindRequest setPassHandler(
+      PasswordCallbackHandler passHandler)
+  {
+    this.passHandler = passHandler;
+    return this;
+  }
+
+
+
+  public PlainSASLBindRequest setPassword(ByteString password)
+  {
+    Validator.ensureNotNull(password);
+    this.password = password;
+    return this;
+  }
+
+
+
+  @Override
+  public String toString()
+  {
+    StringBuilder builder = new StringBuilder();
+    builder.append("PlainSASLBindRequest(bindDN=");
+    builder.append(getName());
+    builder.append(", authentication=SASL");
+    builder.append(", saslMechanism=");
+    builder.append(getSASLMechanism());
+    builder.append(", authenticationID=");
+    builder.append(authenticationID);
+    builder.append(", authorizationID=");
+    builder.append(authorizationID);
+    builder.append(", password=");
+    builder.append(password);
+    builder.append(", controls=");
+    builder.append(getControls());
+    builder.append(")");
+    return builder.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/sasl/SASLBindRequest.java b/sdk/src/org/opends/sdk/sasl/SASLBindRequest.java
new file mode 100644
index 0000000..0401e38
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/SASLBindRequest.java
@@ -0,0 +1,66 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.requests.AbstractBindRequest;
+
+
+
+/**
+ * SASL bind request.
+ */
+public abstract class SASLBindRequest<R extends SASLBindRequest<R>>
+    extends AbstractBindRequest<R>
+{
+  /**
+   * Returns the SASL mechanism for this bind request.
+   *
+   * @return The SASL mechanism for this bind request, or {@code null}
+   *         if there are none or if the bind does not use SASL
+   *         authentication.
+   */
+  public abstract String getSASLMechanism();
+
+
+
+  public abstract SASLContext getClientContext(String serverName)
+      throws SaslException;
+
+
+
+  public DN getName()
+  {
+    return DN.rootDN();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/sasl/SASLContext.java b/sdk/src/org/opends/sdk/sasl/SASLContext.java
new file mode 100644
index 0000000..d870bfd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/SASLContext.java
@@ -0,0 +1,77 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.sasl.SaslException;
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * SASL context that is used for the lifetime of the SASL bound connection.
+ */
+public interface SASLContext
+{
+  public void dispose() throws SaslException;
+
+  /**
+   * Returns the SASL credentials for this bind request.
+   *
+   * @return The SASL credentials for this bind request, or {@code null}
+   *         if there are none or if the bind does not use SASL
+   *         authentication.
+   */
+  public ByteString getSASLCredentials();
+
+
+  public boolean evaluateCredentials(ByteString saslCredentials)
+      throws SaslException;
+
+
+
+  public boolean isComplete();
+
+
+
+  public boolean isSecure();
+
+
+
+  public byte[] unwrap(byte[] incoming, int offset, int len)
+      throws SaslException;
+
+
+
+  public byte[] wrap(byte[] outgoing, int offset, int len)
+      throws SaslException;
+
+  public SASLBindRequest<?> getSASLBindRequest();
+}
diff --git a/sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java b/sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java
new file mode 100644
index 0000000..6712689
--- /dev/null
+++ b/sdk/src/org/opends/sdk/sasl/TextInputCallbackHandler.java
@@ -0,0 +1,42 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.sasl;
+
+
+
+import javax.security.auth.callback.TextInputCallback;
+
+
+
+/**
+ * Text input call-back.
+ */
+public interface TextInputCallbackHandler
+{
+  public boolean handle(TextInputCallback callback);
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java
new file mode 100644
index 0000000..d5fb099
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractMatchingRuleImpl.java
@@ -0,0 +1,132 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.Comparator;
+import java.util.List;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements a default equality or approximate matching rule
+ * that matches normalized values in byte order.
+ */
+abstract class AbstractMatchingRuleImpl implements MatchingRuleImpl
+{
+  static class DefaultEqualityAssertion implements Assertion
+  {
+    ByteSequence normalizedAssertionValue;
+
+
+
+    protected DefaultEqualityAssertion(
+        ByteSequence normalizedAssertionValue)
+    {
+      this.normalizedAssertionValue = normalizedAssertionValue;
+    }
+
+
+
+    public ConditionResult matches(ByteSequence attributeValue)
+    {
+      return normalizedAssertionValue.equals(attributeValue) ? ConditionResult.TRUE
+          : ConditionResult.FALSE;
+    }
+  }
+
+  private static final Assertion UNDEFINED_ASSERTION = new Assertion()
+  {
+    public ConditionResult matches(ByteSequence attributeValue)
+    {
+      return ConditionResult.UNDEFINED;
+    }
+  };
+
+  private static final Comparator<ByteSequence> DEFAULT_COMPARATOR =
+      new Comparator<ByteSequence>()
+      {
+        public int compare(ByteSequence o1, ByteSequence o2)
+        {
+          return o1.compareTo(o2);
+        }
+      };
+
+
+
+  AbstractMatchingRuleImpl()
+  {
+    // Nothing to do.
+  }
+
+
+
+  public Comparator<ByteSequence> comparator(Schema schema)
+  {
+    return DEFAULT_COMPARATOR;
+  }
+
+
+
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    return new DefaultEqualityAssertion(normalizeAttributeValue(schema,
+        value));
+  }
+
+
+
+  public Assertion getAssertion(Schema schema, ByteSequence subInitial,
+      List<ByteSequence> subAnyElements, ByteSequence subFinal)
+      throws DecodeException
+  {
+    return UNDEFINED_ASSERTION;
+  }
+
+
+
+  public Assertion getGreaterOrEqualAssertion(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    return UNDEFINED_ASSERTION;
+  }
+
+
+
+  public Assertion getLessOrEqualAssertion(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    return UNDEFINED_ASSERTION;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..7ebcbd7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractOrderingMatchingRuleImpl.java
@@ -0,0 +1,104 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements a default ordering matching rule that matches
+ * normalized values in byte order.
+ */
+abstract class AbstractOrderingMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  AbstractOrderingMatchingRuleImpl()
+  {
+    // Nothing to do.
+  }
+
+
+
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    final ByteString normAssertion =
+        normalizeAttributeValue(schema, value);
+    return new Assertion()
+    {
+      public ConditionResult matches(ByteSequence attributeValue)
+      {
+        return attributeValue.compareTo(normAssertion) < 0 ? ConditionResult.TRUE
+            : ConditionResult.FALSE;
+      }
+    };
+  }
+
+
+
+  @Override
+  public Assertion getGreaterOrEqualAssertion(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final ByteString normAssertion =
+        normalizeAttributeValue(schema, value);
+    return new Assertion()
+    {
+      public ConditionResult matches(ByteSequence attributeValue)
+      {
+        return attributeValue.compareTo(normAssertion) >= 0 ? ConditionResult.TRUE
+            : ConditionResult.FALSE;
+      }
+    };
+  }
+
+
+
+  @Override
+  public Assertion getLessOrEqualAssertion(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final ByteString normAssertion =
+        normalizeAttributeValue(schema, value);
+    return new Assertion()
+    {
+      public ConditionResult matches(ByteSequence attributeValue)
+      {
+        return attributeValue.compareTo(normAssertion) <= 0 ? ConditionResult.TRUE
+            : ConditionResult.FALSE;
+      }
+    };
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..7c0c1b0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractSubstringMatchingRuleImpl.java
@@ -0,0 +1,270 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.SchemaMessages;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements a default substring matching rule that matches
+ * normalized substring assertion values in byte order.
+ */
+abstract class AbstractSubstringMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  static class DefaultSubstringAssertion implements Assertion
+  {
+    private final ByteString normInitial;
+    private final ByteString[] normAnys;
+    private final ByteString normFinal;
+
+
+
+    protected DefaultSubstringAssertion(ByteString normInitial,
+        ByteString[] normAnys, ByteString normFinal)
+    {
+      this.normInitial = normInitial;
+      this.normAnys = normAnys;
+      this.normFinal = normFinal;
+    }
+
+
+
+    public ConditionResult matches(ByteSequence attributeValue)
+    {
+      final int valueLength = attributeValue.length();
+
+      int pos = 0;
+      if (normInitial != null)
+      {
+        final int initialLength = normInitial.length();
+        if (initialLength > valueLength)
+        {
+          return ConditionResult.FALSE;
+        }
+
+        for (; pos < initialLength; pos++)
+        {
+          if (normInitial.byteAt(pos) != attributeValue.byteAt(pos))
+          {
+            return ConditionResult.FALSE;
+          }
+        }
+      }
+
+      if (normAnys != null && normAnys.length != 0)
+      {
+        for (final ByteSequence element : normAnys)
+        {
+          final int anyLength = element.length();
+          if (anyLength == 0)
+          {
+            continue;
+          }
+          final int end = valueLength - anyLength;
+          boolean match = false;
+          for (; pos <= end; pos++)
+          {
+            if (element.byteAt(0) == attributeValue.byteAt(pos))
+            {
+              boolean subMatch = true;
+              for (int i = 1; i < anyLength; i++)
+              {
+                if (element.byteAt(i) != attributeValue.byteAt(pos + i))
+                {
+                  subMatch = false;
+                  break;
+                }
+              }
+
+              if (subMatch)
+              {
+                match = subMatch;
+                break;
+              }
+            }
+          }
+
+          if (match)
+          {
+            pos += anyLength;
+          }
+          else
+          {
+            return ConditionResult.FALSE;
+          }
+        }
+      }
+
+      if (normFinal != null)
+      {
+        final int finalLength = normFinal.length();
+
+        if (valueLength - finalLength < pos)
+        {
+          return ConditionResult.FALSE;
+        }
+
+        pos = valueLength - finalLength;
+        for (int i = 0; i < finalLength; i++, pos++)
+        {
+          if (normFinal.byteAt(i) != attributeValue.byteAt(pos))
+          {
+            return ConditionResult.FALSE;
+          }
+        }
+      }
+
+      return ConditionResult.TRUE;
+    }
+  }
+
+
+
+  AbstractSubstringMatchingRuleImpl()
+  {
+    // Nothing to do.
+  }
+
+
+
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    if (value.length() == 0)
+    {
+      throw DecodeException.error(
+          SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_EMPTY.get());
+    }
+
+    ByteSequence initialString = null;
+    ByteSequence finalString = null;
+    List<ByteSequence> anyStrings = null;
+
+    final String valueString = value.toString();
+
+    if (valueString.length() == 1 && valueString.charAt(0) == '*')
+    {
+      return getAssertion(schema, initialString, anyStrings,
+          finalString);
+    }
+
+    final char[] escapeChars = new char[] { '*' };
+    final SubstringReader reader = new SubstringReader(valueString);
+
+    ByteString bytes =
+        StaticUtils.evaluateEscapes(reader, escapeChars, false);
+    if (bytes.length() > 0)
+    {
+      initialString = normalizeSubString(schema, bytes);
+    }
+    if (reader.remaining() == 0)
+    {
+      throw DecodeException.error(
+          SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_NO_WILDCARDS
+              .get(value.toString()));
+    }
+    while (true)
+    {
+      reader.read();
+      bytes = StaticUtils.evaluateEscapes(reader, escapeChars, false);
+      if (reader.remaining() > 0)
+      {
+        if (bytes.length() == 0)
+        {
+          throw DecodeException.error(
+              SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_CONSECUTIVE_WILDCARDS
+                  .get(value.toString(), reader.pos()));
+        }
+        if (anyStrings == null)
+        {
+          anyStrings = new LinkedList<ByteSequence>();
+        }
+        anyStrings.add(normalizeSubString(schema, bytes));
+      }
+      else
+      {
+        if (bytes.length() > 0)
+        {
+          finalString = normalizeSubString(schema, bytes);
+        }
+        break;
+      }
+    }
+
+    return getAssertion(schema, initialString, anyStrings, finalString);
+  }
+
+
+
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence subInitial,
+      List<ByteSequence> subAnyElements, ByteSequence subFinal)
+      throws DecodeException
+  {
+    final ByteString normInitial =
+        subInitial == null ? null : normalizeSubString(schema,
+            subInitial);
+
+    ByteString[] normAnys = null;
+    if (subAnyElements != null && !subAnyElements.isEmpty())
+    {
+      normAnys = new ByteString[subAnyElements.size()];
+      for (int i = 0; i < subAnyElements.size(); i++)
+      {
+        normAnys[i] = normalizeSubString(schema, subAnyElements.get(i));
+      }
+    }
+    final ByteString normFinal =
+        subFinal == null ? null : normalizeSubString(schema, subFinal);
+
+    return new DefaultSubstringAssertion(normInitial, normAnys,
+        normFinal);
+  }
+
+
+
+  ByteString normalizeSubString(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    return normalizeAttributeValue(schema, value);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java
new file mode 100644
index 0000000..22c1717
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AbstractSyntaxImpl.java
@@ -0,0 +1,77 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This class defines the set of methods and structures that must be
+ * implemented to define a new attribute syntax.
+ */
+abstract class AbstractSyntaxImpl implements SyntaxImpl
+{
+  AbstractSyntaxImpl()
+  {
+    // Nothing to do.
+  }
+
+
+
+  public String getApproximateMatchingRule()
+  {
+    return null;
+  }
+
+
+
+  public String getEqualityMatchingRule()
+  {
+    return null;
+  }
+
+
+
+  public String getOrderingMatchingRule()
+  {
+    return null;
+  }
+
+
+
+  public String getSubstringMatchingRule()
+  {
+    return null;
+  }
+
+
+
+  public boolean isBEREncodingRequired()
+  {
+    return false;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AttributeType.java b/sdk/src/org/opends/sdk/schema/AttributeType.java
new file mode 100644
index 0000000..f338af2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AttributeType.java
@@ -0,0 +1,908 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.SCHEMA_PROPERTY_APPROX_RULE;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * an attribute type, which contains information about the format of an
+ * attribute and the syntax and matching rules that should be used when
+ * interacting with it.
+ * <p>
+ * Where ordered sets of names, or extra properties are provided, the
+ * ordering will be preserved when the associated fields are accessed
+ * via their getters or via the {@link #toString()} methods.
+ */
+public final class AttributeType extends SchemaElement implements
+    Comparable<AttributeType>
+{
+
+  // The approximate matching rule for this attribute type.
+  private final String approximateMatchingRuleOID;
+
+  // The attribute usage for this attribute type.
+  private final AttributeUsage attributeUsage;
+
+  // The definition string used to create this objectclass.
+  private final String definition;
+
+  // The equality matching rule for this attribute type.
+  private final String equalityMatchingRuleOID;
+
+  // Indicates whether this attribute type is declared "collective".
+  private final boolean isCollective;
+
+  // Indicates whether this attribute type is declared
+  // "no-user-modification".
+  private final boolean isNoUserModification;
+
+  // Indicates whether this definition is declared "obsolete".
+  private final boolean isObsolete;
+
+  // Indicates whether this attribute type is declared "single-value".
+  private final boolean isSingleValue;
+
+  // The set of user defined names for this definition.
+  private final List<String> names;
+
+  // The OID that may be used to reference this definition.
+  private final String oid;
+
+  // The ordering matching rule for this attribute type.
+  private final String orderingMatchingRuleOID;
+
+  // The substring matching rule for this attribute type.
+  private final String substringMatchingRuleOID;
+
+  // The superior attribute type from which this attribute type
+  // inherits.
+  private final String superiorTypeOID;
+
+  // The syntax for this attribute type.
+  private final String syntaxOID;
+
+  // True if this type has OID 2.5.4.0.
+  private final boolean isObjectClassType;
+
+  // The normalized name of this attribute type.
+  private final String normalizedName;
+
+  // The superior attribute type from which this attribute type
+  // inherits.
+  private AttributeType superiorType;
+
+  // The equality matching rule for this attribute type.
+  private MatchingRule equalityMatchingRule;
+
+  // The ordering matching rule for this attribute type.
+  private MatchingRule orderingMatchingRule;
+
+  // The substring matching rule for this attribute type.
+  private MatchingRule substringMatchingRule;
+
+  // The approximate matching rule for this attribute type.
+  private MatchingRule approximateMatchingRule;
+
+  // The syntax for this attribute type.
+  private Syntax syntax;
+
+
+
+  AttributeType(String oid, List<String> names, String description,
+      boolean obsolete, String superiorType,
+      String equalityMatchingRule, String orderingMatchingRule,
+      String substringMatchingRule, String approximateMatchingRule,
+      String syntax, boolean singleValue, boolean collective,
+      boolean noUserModification, AttributeUsage attributeUsage,
+      Map<String, List<String>> extraProperties, String definition)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(oid, names, description, attributeUsage);
+    Validator.ensureTrue(superiorType != null || syntax != null,
+        "superiorType and/or syntax must not be null");
+    Validator.ensureNotNull(extraProperties);
+
+    this.oid = oid;
+    this.names = names;
+    this.isObsolete = obsolete;
+    this.superiorTypeOID = superiorType;
+    this.equalityMatchingRuleOID = equalityMatchingRule;
+    this.orderingMatchingRuleOID = orderingMatchingRule;
+    this.substringMatchingRuleOID = substringMatchingRule;
+    this.approximateMatchingRuleOID = approximateMatchingRule;
+    this.syntaxOID = syntax;
+    this.isSingleValue = singleValue;
+    this.isCollective = collective;
+    this.isNoUserModification = noUserModification;
+    this.attributeUsage = attributeUsage;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+
+    this.isObjectClassType = oid.equals("2.5.4.0");
+    this.normalizedName = StaticUtils.toLowerCase(getNameOrOID());
+  }
+
+
+
+  AttributeType(String oid, List<String> names, String description,
+      MatchingRule equalityMatchingRule, Syntax syntax)
+  {
+    super(description, Collections.<String, List<String>> emptyMap());
+
+    Validator.ensureNotNull(oid, names, description);
+
+    this.oid = oid;
+    this.names = names;
+    this.isObsolete = false;
+    this.superiorTypeOID = null;
+    this.superiorType = null;
+    this.equalityMatchingRuleOID = equalityMatchingRule.getOID();
+    this.equalityMatchingRule = equalityMatchingRule;
+    this.orderingMatchingRuleOID = null;
+    this.substringMatchingRuleOID = null;
+    this.approximateMatchingRuleOID = null;
+    this.syntaxOID = syntax.getOID();
+    this.syntax = syntax;
+    this.isSingleValue = false;
+    this.isCollective = false;
+    this.isNoUserModification = false;
+    this.attributeUsage = AttributeUsage.USER_APPLICATIONS;
+    this.definition = buildDefinition();
+
+    this.isObjectClassType = oid.equals("2.5.4.0");
+    this.normalizedName = StaticUtils.toLowerCase(getNameOrOID());
+  }
+
+
+
+  /**
+   * Compares this attribute type to the provided attribute type. The
+   * sort-order is defined as follows:
+   * <ul>
+   * <li>The {@code objectClass} attribute is less than all other
+   * attribute types.
+   * <li>User attributes are less than operational attributes.
+   * <li>Lexicographic comparison of the primary name or OID.
+   * </ul>
+   *
+   * @param type
+   *          The attribute type to be compared.
+   * @return A negative integer, zero, or a positive integer as this
+   *         attribute type is less than, equal to, or greater than the
+   *         specified attribute type.
+   * @throws NullPointerException
+   *           If {@code name} was {@code null}.
+   */
+  public int compareTo(AttributeType type) throws NullPointerException
+  {
+    if (isObjectClassType)
+    {
+      return type.isObjectClassType ? 0 : -1;
+    }
+    else if (type.isObjectClassType)
+    {
+      return 1;
+    }
+    else
+    {
+      final boolean isOperational = getUsage().isOperational();
+      final boolean typeIsOperational = type.getUsage().isOperational();
+
+      if (isOperational == typeIsOperational)
+      {
+        return normalizedName.compareTo(type.normalizedName);
+      }
+      else
+      {
+        return isOperational ? 1 : -1;
+      }
+    }
+  }
+
+
+
+  @Override
+  public boolean equals(Object o)
+  {
+    if (this == o)
+    {
+      return true;
+    }
+
+    if (o instanceof AttributeType)
+    {
+      final AttributeType other = (AttributeType) o;
+      return oid.equals(other.oid);
+    }
+
+    return false;
+  }
+
+
+
+  /**
+   * Retrieves the matching rule that should be used for approximate
+   * matching with this attribute type.
+   *
+   * @return The matching rule that should be used for approximate
+   *         matching with this attribute type.
+   */
+  public MatchingRule getApproximateMatchingRule()
+  {
+    return approximateMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the matching rule that should be used for equality
+   * matching with this attribute type.
+   *
+   * @return The matching rule that should be used for equality matching
+   *         with this attribute type.
+   */
+  public MatchingRule getEqualityMatchingRule()
+  {
+    return equalityMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the name or OID for this schema definition. If it has one
+   * or more names, then the primary name will be returned. If it does
+   * not have any names, then the OID will be returned.
+   *
+   * @return The name or OID for this schema definition.
+   */
+  public String getNameOrOID()
+  {
+    if (names.isEmpty())
+    {
+      return oid;
+    }
+    return names.get(0);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the set of user-defined names that may
+   * be used to reference this schema definition.
+   *
+   * @return Returns an iterable over the set of user-defined names that
+   *         may be used to reference this schema definition.
+   */
+  public Iterable<String> getNames()
+  {
+    return names;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this schema definition.
+   *
+   * @return The OID for this schema definition.
+   */
+  public String getOID()
+  {
+
+    return oid;
+  }
+
+
+
+  /**
+   * Retrieves the matching rule that should be used for ordering with
+   * this attribute type.
+   *
+   * @return The matching rule that should be used for ordering with
+   *         this attribute type.
+   */
+  public MatchingRule getOrderingMatchingRule()
+  {
+    return orderingMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the matching rule that should be used for substring
+   * matching with this attribute type.
+   *
+   * @return The matching rule that should be used for substring
+   *         matching with this attribute type.
+   */
+  public MatchingRule getSubstringMatchingRule()
+  {
+    return substringMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the superior type for this attribute type.
+   *
+   * @return The superior type for this attribute type, or
+   *         <CODE>null</CODE> if it does not have one.
+   */
+  public AttributeType getSuperiorType()
+  {
+    return superiorType;
+  }
+
+
+
+  /**
+   * Retrieves the syntax for this attribute type.
+   *
+   * @return The syntax for this attribute type.
+   */
+  public Syntax getSyntax()
+  {
+    return syntax;
+  }
+
+
+
+  /**
+   * Retrieves the usage indicator for this attribute type.
+   *
+   * @return The usage indicator for this attribute type.
+   */
+  public AttributeUsage getUsage()
+  {
+    return attributeUsage;
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return oid.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name.
+   *
+   * @param name
+   *          The name for which to make the determination.
+   * @return {@code true} if the specified name is assigned to this
+   *         schema definition, or {@code false} if not.
+   */
+  public boolean hasName(String name)
+  {
+    for (final String n : names)
+    {
+      if (n.equalsIgnoreCase(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name or
+   * OID.
+   *
+   * @param value
+   *          The value for which to make the determination.
+   * @return {@code true} if the provided value matches the OID or one
+   *         of the names assigned to this schema definition, or {@code
+   *         false} if not.
+   */
+  public boolean hasNameOrOID(String value)
+  {
+    return hasName(value) || getOID().equals(value);
+  }
+
+
+
+  /**
+   * Indicates whether this attribute type is declared "collective".
+   *
+   * @return {@code true} if this attribute type is declared
+   *         "collective", or {@code false} if not.
+   */
+  public boolean isCollective()
+  {
+    return isCollective;
+  }
+
+
+
+  /**
+   * Indicates whether this attribute type is declared
+   * "no-user-modification".
+   *
+   * @return {@code true} if this attribute type is declared
+   *         "no-user-modification", or {@code false} if not.
+   */
+  public boolean isNoUserModification()
+  {
+    return isNoUserModification;
+  }
+
+
+
+  /**
+   * Indicates whether or not this attribute type is the {@code
+   * objectClass} attribute type having the OID 2.5.4.0.
+   *
+   * @return {@code true} if this attribute type is the {@code
+   *         objectClass} attribute type, or {@code false} if not.
+   */
+  public boolean isObjectClass()
+  {
+    return isObjectClassType;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition is declared "obsolete".
+   *
+   * @return {@code true} if this schema definition is declared
+   *         "obsolete", or {@code false} if not.
+   */
+  public boolean isObsolete()
+  {
+    return isObsolete;
+  }
+
+
+
+  /**
+   * Indicates whether this is an operational attribute. An operational
+   * attribute is one with a usage of "directoryOperation",
+   * "distributedOperation", or "dSAOperation" (i.e., only
+   * userApplications is not operational).
+   *
+   * @return {@code true} if this is an operational attribute, or
+   *         {@code false} if not.
+   */
+  public boolean isOperational()
+  {
+    return attributeUsage.isOperational();
+  }
+
+
+
+  /**
+   * Indicates whether this attribute type is declared "single-value".
+   *
+   * @return {@code true} if this attribute type is declared
+   *         "single-value", or {@code false} if not.
+   */
+  public boolean isSingleValue()
+  {
+    return isSingleValue;
+  }
+
+
+
+  /**
+   * Indicates whether or not this attribute type is a sub-type of the
+   * provided attribute type.
+   *
+   * @param type
+   *          The attribute type for which to make the determination.
+   * @return {@code true} if this attribute type is a sub-type of the
+   *         provided attribute type, or {@code false} if not.
+   * @throws NullPointerException
+   *           If {@code type} was {@code null}.
+   */
+  public boolean isSubTypeOf(AttributeType type)
+  {
+    AttributeType tmp = this;
+    do
+    {
+      if (tmp.equals(type))
+      {
+        return true;
+      }
+      tmp = tmp.getSuperiorType();
+    } while (tmp != null);
+    return false;
+  }
+
+
+
+  /**
+   * Retrieves the string representation of this schema definition in
+   * the form specified in RFC 2252.
+   *
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  AttributeType duplicate()
+  {
+    return new AttributeType(oid, names, description, isObsolete,
+        superiorTypeOID, equalityMatchingRuleOID,
+        orderingMatchingRuleOID, substringMatchingRuleOID,
+        approximateMatchingRuleOID, syntaxOID, isSingleValue,
+        isCollective, isNoUserModification, attributeUsage,
+        extraProperties, definition);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(oid);
+
+    if (!names.isEmpty())
+    {
+      final Iterator<String> iterator = names.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NAME ( '");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append("' '");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append("' )");
+      }
+      else
+      {
+        buffer.append(" NAME '");
+        buffer.append(firstName);
+        buffer.append("'");
+      }
+    }
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+
+    if (isObsolete)
+    {
+      buffer.append(" OBSOLETE");
+    }
+
+    if (superiorTypeOID != null)
+    {
+      buffer.append(" SUP ");
+      buffer.append(superiorTypeOID);
+    }
+
+    if (equalityMatchingRuleOID != null)
+    {
+      buffer.append(" EQUALITY ");
+      buffer.append(equalityMatchingRuleOID);
+    }
+
+    if (orderingMatchingRuleOID != null)
+    {
+      buffer.append(" ORDERING ");
+      buffer.append(orderingMatchingRuleOID);
+    }
+
+    if (substringMatchingRuleOID != null)
+    {
+      buffer.append(" SUBSTR ");
+      buffer.append(substringMatchingRuleOID);
+    }
+
+    if (syntaxOID != null)
+    {
+      buffer.append(" SYNTAX ");
+      buffer.append(syntaxOID);
+    }
+
+    if (isSingleValue())
+    {
+      buffer.append(" SINGLE-VALUE");
+    }
+
+    if (isCollective())
+    {
+      buffer.append(" COLLECTIVE");
+    }
+
+    if (isNoUserModification())
+    {
+      buffer.append(" NO-USER-MODIFICATION");
+    }
+
+    if (attributeUsage != null)
+    {
+      buffer.append(" USAGE ");
+      buffer.append(attributeUsage.toString());
+    }
+
+    if (approximateMatchingRuleOID != null)
+    {
+      buffer.append(" ");
+      buffer.append(SCHEMA_PROPERTY_APPROX_RULE);
+      buffer.append(" '");
+      buffer.append(approximateMatchingRuleOID);
+      buffer.append("'");
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    if (superiorTypeOID != null)
+    {
+      try
+      {
+        superiorType = schema.getAttributeType(superiorTypeOID);
+      }
+      catch (UnknownSchemaElementException e)
+      {
+        final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUPERIOR_TYPE
+            .get(getNameOrOID(), superiorTypeOID);
+        throw new SchemaException(message);
+      }
+
+      // If there is a superior type, then it must have the same usage
+      // as the subordinate type. Also, if the superior type is
+      // collective, then so must the subordinate type be collective.
+      if (superiorType.getUsage() != getUsage())
+      {
+        final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_SUPERIOR_USAGE
+            .get(getNameOrOID(), getUsage().toString(), superiorType
+                .getNameOrOID());
+        throw new SchemaException(message);
+      }
+
+      if (superiorType.isCollective() != isCollective())
+      {
+        Message message;
+        if (isCollective())
+        {
+          message = WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_FROM_NONCOLLECTIVE
+              .get(getNameOrOID(), superiorType.getNameOrOID());
+        }
+        else
+        {
+          message = WARN_ATTR_SYNTAX_ATTRTYPE_NONCOLLECTIVE_FROM_COLLECTIVE
+              .get(getNameOrOID(), superiorType.getNameOrOID());
+        }
+        throw new SchemaException(message);
+      }
+    }
+
+    if (syntaxOID != null)
+    {
+      if (!schema.hasSyntax(syntaxOID))
+      {
+        // Try substituting a syntax from the core schema. This will
+        // never fail since the core schema is non-strict and will
+        // substitute the syntax if required.
+        syntax = Schema.getCoreSchema().getSyntax(syntaxOID);
+        final Message message = WARN_ATTR_TYPE_NOT_DEFINED.get(
+            getNameOrOID(), syntaxOID, syntax.toString());
+        warnings.add(message);
+      }
+      else
+      {
+        syntax = schema.getSyntax(syntaxOID);
+      }
+    }
+    else if (getSuperiorType() != null
+        && getSuperiorType().getSyntax() != null)
+    {
+      // Try to inherit the syntax from the superior type if possible
+      syntax = getSuperiorType().getSyntax();
+    }
+
+    if (equalityMatchingRuleOID != null)
+    {
+      // Use explicitly defined matching rule first.
+      try
+      {
+        equalityMatchingRule = schema
+            .getMatchingRule(equalityMatchingRuleOID);
+      }
+      catch (UnknownSchemaElementException e)
+      {
+        final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_EQUALITY_MR
+            .get(getNameOrOID(), equalityMatchingRuleOID);
+        throw new SchemaException(message);
+      }
+    }
+    else if (getSuperiorType() != null
+        && getSuperiorType().getEqualityMatchingRule() != null)
+    {
+      // Inherit matching rule from superior type if possible
+      equalityMatchingRule = getSuperiorType()
+          .getEqualityMatchingRule();
+    }
+    else if (getSyntax() != null
+        && getSyntax().getEqualityMatchingRule() != null)
+    {
+      // Use default for syntax
+      equalityMatchingRule = getSyntax().getEqualityMatchingRule();
+    }
+
+    if (orderingMatchingRuleOID != null)
+    {
+      // Use explicitly defined matching rule first.
+      try
+      {
+        orderingMatchingRule = schema
+            .getMatchingRule(orderingMatchingRuleOID);
+      }
+      catch (UnknownSchemaElementException e)
+      {
+        final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_ORDERING_MR
+            .get(getNameOrOID(), orderingMatchingRuleOID);
+        throw new SchemaException(message);
+      }
+    }
+    else if (getSuperiorType() != null
+        && getSuperiorType().getOrderingMatchingRule() != null)
+    {
+      // Inherit matching rule from superior type if possible
+      orderingMatchingRule = getSuperiorType()
+          .getOrderingMatchingRule();
+    }
+    else if (getSyntax() != null
+        && getSyntax().getOrderingMatchingRule() != null)
+    {
+      // Use default for syntax
+      orderingMatchingRule = getSyntax().getOrderingMatchingRule();
+    }
+
+    if (substringMatchingRuleOID != null)
+    {
+      // Use explicitly defined matching rule first.
+      try
+      {
+        substringMatchingRule = schema
+            .getMatchingRule(substringMatchingRuleOID);
+      }
+      catch (UnknownSchemaElementException e)
+      {
+        final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_SUBSTRING_MR
+            .get(getNameOrOID(), substringMatchingRuleOID);
+        throw new SchemaException(message);
+      }
+    }
+    else if (getSuperiorType() != null
+        && getSuperiorType().getSubstringMatchingRule() != null)
+    {
+      // Inherit matching rule from superior type if possible
+      substringMatchingRule = getSuperiorType()
+          .getSubstringMatchingRule();
+    }
+    else if (getSyntax() != null
+        && getSyntax().getSubstringMatchingRule() != null)
+    {
+      // Use default for syntax
+      substringMatchingRule = getSyntax().getSubstringMatchingRule();
+    }
+
+    if (approximateMatchingRuleOID != null)
+    {
+      // Use explicitly defined matching rule first.
+      try
+      {
+        approximateMatchingRule = schema
+            .getMatchingRule(approximateMatchingRuleOID);
+      }
+      catch (UnknownSchemaElementException e)
+      {
+        final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_UNKNOWN_APPROXIMATE_MR
+            .get(getNameOrOID(), approximateMatchingRuleOID);
+        throw new SchemaException(message);
+      }
+    }
+    else if (getSuperiorType() != null
+        && getSuperiorType().getApproximateMatchingRule() != null)
+    {
+      // Inherit matching rule from superior type if possible
+      approximateMatchingRule = getSuperiorType()
+          .getApproximateMatchingRule();
+    }
+    else if (getSyntax() != null
+        && getSyntax().getApproximateMatchingRule() != null)
+    {
+      // Use default for syntax
+      approximateMatchingRule = getSyntax()
+          .getApproximateMatchingRule();
+    }
+
+    // If the attribute type is COLLECTIVE, then it must have a usage of
+    // userApplications.
+    if (isCollective()
+        && getUsage() != AttributeUsage.USER_APPLICATIONS)
+    {
+      final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_COLLECTIVE_IS_OPERATIONAL
+          .get(getNameOrOID());
+      warnings.add(message);
+    }
+
+    // If the attribute type is NO-USER-MODIFICATION, then it must not
+    // have a usage of userApplications.
+    if (isNoUserModification()
+        && getUsage() == AttributeUsage.USER_APPLICATIONS)
+    {
+      final Message message = WARN_ATTR_SYNTAX_ATTRTYPE_NO_USER_MOD_NOT_OPERATIONAL
+          .get(getNameOrOID());
+      warnings.add(message);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java
new file mode 100644
index 0000000..66967d8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AttributeTypeSyntaxImpl.java
@@ -0,0 +1,277 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_ATTRIBUTE_TYPE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the attribute type description syntax, which is
+ * used to hold attribute type definitions in the server schema. The
+ * format of this syntax is defined in RFC 2252.
+ */
+final class AttributeTypeSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_ATTRIBUTE_TYPE_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the definition was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the definition. But before we start, set default
+      // values for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("sup"))
+        {
+          // This specifies the name or OID of the superior attribute
+          // type from which this attribute type should inherit its
+          // properties.
+          SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("equality"))
+        {
+          // This specifies the name or OID of the equality matching
+          // rule to use for this attribute type.
+          SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("ordering"))
+        {
+          // This specifies the name or OID of the ordering matching
+          // rule to use for this attribute type.
+          SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("substr"))
+        {
+          // This specifies the name or OID of the substring matching
+          // rule to use for this attribute type.
+          SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("syntax"))
+        {
+          // This specifies the numeric OID of the syntax for this
+          // matching rule. It may optionally be immediately followed by
+          // an open curly brace, an integer definition, and a close
+          // curly brace to suggest the minimum number of characters
+          // that should be allowed in values of that type. This
+          // implementation will ignore any such length because it does
+          // not impose any practical limit on the length of attribute
+          // values.
+          SchemaUtils.readOIDLen(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("single-definition"))
+        {
+          // This indicates that attributes of this type are allowed to
+          // have at most one definition. We do not need any more
+          // parsing for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("single-value"))
+        {
+          // This indicates that attributes of this type are allowed to
+          // have at most one value. We do not need any more parsing for
+          // this token.
+        }
+        else if (tokenName.equalsIgnoreCase("collective"))
+        {
+          // This indicates that attributes of this type are collective
+          // (i.e., have their values generated dynamically in some
+          // way). We do not need any more parsing for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("no-user-modification"))
+        {
+          // This indicates that the values of attributes of this type
+          // are not to be modified by end users. We do not need any
+          // more parsing for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("usage"))
+        {
+          // This specifies the usage string for this attribute type. It
+          // should be followed by one of the strings
+          // "userApplications", "directoryOperation",
+          // "distributedOperation", or "dSAOperation".
+          int length = 0;
+
+          reader.skipWhitespaces();
+          reader.mark();
+
+          while (reader.read() != ' ')
+          {
+            length++;
+          }
+
+          reader.reset();
+          final String usageStr = reader.read(length);
+          if (!usageStr.equalsIgnoreCase("userapplications")
+              && !usageStr.equalsIgnoreCase("directoryoperation")
+              && !usageStr.equalsIgnoreCase("distributedoperation")
+              && !usageStr.equalsIgnoreCase("dsaoperation"))
+          {
+            final Message message =
+                WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE.get(
+                    String.valueOf(oid), usageStr);
+            final DecodeException e = DecodeException.error(message);
+            StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+                "valueIsAcceptable", e);
+            throw e;
+          }
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          SchemaUtils.readExtensions(reader);
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("AttributeTypeSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AttributeUsage.java b/sdk/src/org/opends/sdk/schema/AttributeUsage.java
new file mode 100644
index 0000000..ac6e3e6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AttributeUsage.java
@@ -0,0 +1,112 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This enumeration defines the set of possible attribute usage values
+ * that may apply to an attribute type, as defined in RFC 2252.
+ */
+public enum AttributeUsage
+{
+  /**
+   * The attribute usage intended for user-defined attribute types.
+   */
+  USER_APPLICATIONS("userApplications", false),
+
+  /**
+   * The attribute usage intended for standard operational attributes.
+   */
+  DIRECTORY_OPERATION("directoryOperation", true),
+
+  /**
+   * The attribute usage intended for non-standard operational
+   * attributes shared among multiple DSAs.
+   */
+  DISTRIBUTED_OPERATION("distributedOperation", true),
+
+  /**
+   * The attribute usage intended for non-standard operational
+   * attributes used by a single DSA.
+   */
+  DSA_OPERATION("dSAOperation", true);
+
+  // The string representation of this attribute usage.
+  private final String usageString;
+
+  // Flag indicating whether or not the usage should be categorized as
+  // operational.
+  private final boolean isOperational;
+
+
+
+  /**
+   * Creates a new attribute usage with the provided string
+   * representation.
+   * 
+   * @param usageString
+   *          The string representation of this attribute usage.
+   * @param isOperational
+   *          <code>true</code> if attributes having this attribute
+   *          usage are operational, or <code>false</code> otherwise.
+   */
+  private AttributeUsage(String usageString, boolean isOperational)
+  {
+    this.usageString = usageString;
+    this.isOperational = isOperational;
+  }
+
+
+
+  /**
+   * Determine whether or not attributes having this attribute usage are
+   * operational.
+   * 
+   * @return Returns <code>true</code> if attributes having this
+   *         attribute usage are operational, or <code>false</code>
+   *         otherwise.
+   */
+  public boolean isOperational()
+  {
+    return isOperational;
+  }
+
+
+
+  /**
+   * Retrieves a string representation of this attribute usage.
+   * 
+   * @return A string representation of this attribute usage.
+   */
+  @Override
+  public String toString()
+  {
+    return usageString;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..621b334
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AuthPasswordExactEqualityMatchingRuleImpl.java
@@ -0,0 +1,62 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the authPasswordMatch matching rule defined in
+ * RFC 3112.
+ */
+final class AuthPasswordExactEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final StringBuilder[] authPWComponents =
+        AuthPasswordSyntaxImpl.decodeAuthPassword(value.toString());
+
+    final StringBuilder normalizedValue =
+        new StringBuilder(2 + authPWComponents[0].length()
+            + authPWComponents[1].length()
+            + authPWComponents[2].length());
+    normalizedValue.append(authPWComponents[0]);
+    normalizedValue.append('$');
+    normalizedValue.append(authPWComponents[1]);
+    normalizedValue.append('$');
+    normalizedValue.append(authPWComponents[2]);
+
+    return ByteString.valueOf(normalizedValue.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java
new file mode 100644
index 0000000..6c98bfa
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/AuthPasswordSyntaxImpl.java
@@ -0,0 +1,340 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_AUTH_PASSWORD_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_AUTH_PASSWORD_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the auth password attribute syntax, which is
+ * defined in RFC 3112 and is used to hold authentication information.
+ * Only equality matching will be allowed by default.
+ */
+final class AuthPasswordSyntaxImpl extends AbstractSyntaxImpl
+{
+  /**
+   * Decodes the provided authentication password value into its
+   * component parts.
+   * 
+   * @param authPasswordValue
+   *          The authentication password value to be decoded.
+   * @return A three-element array, containing the scheme, authInfo, and
+   *         authValue components of the given string, in that order.
+   * @throws DecodeException
+   *           If a problem is encountered while attempting to decode
+   *           the value.
+   */
+  static StringBuilder[] decodeAuthPassword(String authPasswordValue)
+      throws DecodeException
+  {
+    // Create placeholders for the values to return.
+    final StringBuilder scheme = new StringBuilder();
+    final StringBuilder authInfo = new StringBuilder();
+    final StringBuilder authValue = new StringBuilder();
+
+    // First, ignore any leading whitespace.
+    final int length = authPasswordValue.length();
+    int pos = 0;
+    while (pos < length && authPasswordValue.charAt(pos) == ' ')
+    {
+      pos++;
+    }
+
+    // The next set of characters will be the scheme, which must consist
+    // only of digits, uppercase alphabetic characters, dash, period,
+    // slash, and underscore characters. It must be immediately followed
+    // by one or more spaces or a dollar sign.
+    readScheme: while (pos < length)
+    {
+      final char c = authPasswordValue.charAt(pos);
+
+      switch (c)
+      {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+      case 'A':
+      case 'B':
+      case 'C':
+      case 'D':
+      case 'E':
+      case 'F':
+      case 'G':
+      case 'H':
+      case 'I':
+      case 'J':
+      case 'K':
+      case 'L':
+      case 'M':
+      case 'N':
+      case 'O':
+      case 'P':
+      case 'Q':
+      case 'R':
+      case 'S':
+      case 'T':
+      case 'U':
+      case 'V':
+      case 'W':
+      case 'X':
+      case 'Y':
+      case 'Z':
+      case '-':
+      case '.':
+      case '/':
+      case '_':
+        scheme.append(c);
+        pos++;
+        break;
+      case ' ':
+      case '$':
+        break readScheme;
+      default:
+        final Message message =
+            ERR_ATTR_SYNTAX_AUTHPW_INVALID_SCHEME_CHAR.get(pos);
+        throw DecodeException.error(message);
+      }
+    }
+
+    // The scheme must consist of at least one character.
+    if (scheme.length() == 0)
+    {
+      final Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME.get();
+      throw DecodeException.error(message);
+    }
+
+    // Ignore any spaces before the dollar sign separator. Then read the
+    // dollar sign and ignore any trailing spaces.
+    while (pos < length && authPasswordValue.charAt(pos) == ' ')
+    {
+      pos++;
+    }
+
+    if (pos < length && authPasswordValue.charAt(pos) == '$')
+    {
+      pos++;
+    }
+    else
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_AUTHPW_NO_SCHEME_SEPARATOR.get();
+      throw DecodeException.error(message);
+    }
+
+    while (pos < length && authPasswordValue.charAt(pos) == ' ')
+    {
+      pos++;
+    }
+
+    // The next component must be the authInfo element, containing only
+    // printable characters other than the dollar sign and space
+    // character.
+    readAuthInfo: while (pos < length)
+    {
+      final char c = authPasswordValue.charAt(pos);
+      if (c == ' ' || c == '$')
+      {
+        break readAuthInfo;
+      }
+      else if (PrintableStringSyntaxImpl.isPrintableCharacter(c))
+      {
+        authInfo.append(c);
+        pos++;
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_INFO_CHAR.get(pos);
+        throw DecodeException.error(message);
+      }
+    }
+
+    // The authInfo element must consist of at least one character.
+    if (scheme.length() == 0)
+    {
+      final Message message = ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO.get();
+      throw DecodeException.error(message);
+    }
+
+    // Ignore any spaces before the dollar sign separator. Then read the
+    // dollar sign and ignore any trailing spaces.
+    while (pos < length && authPasswordValue.charAt(pos) == ' ')
+    {
+      pos++;
+    }
+
+    if (pos < length && authPasswordValue.charAt(pos) == '$')
+    {
+      pos++;
+    }
+    else
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_INFO_SEPARATOR.get();
+      throw DecodeException.error(message);
+    }
+
+    while (pos < length && authPasswordValue.charAt(pos) == ' ')
+    {
+      pos++;
+    }
+
+    // The final component must be the authValue element, containing
+    // only printable characters other than the dollar sign and space
+    // character.
+    while (pos < length)
+    {
+      final char c = authPasswordValue.charAt(pos);
+      if (c == ' ' || c == '$')
+      {
+        break;
+      }
+      else if (PrintableStringSyntaxImpl.isPrintableCharacter(c))
+      {
+        authValue.append(c);
+        pos++;
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_AUTHPW_INVALID_AUTH_VALUE_CHAR.get(pos);
+        throw DecodeException.error(message);
+      }
+    }
+
+    // The authValue element must consist of at least one character.
+    if (scheme.length() == 0)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_AUTHPW_NO_AUTH_VALUE.get();
+      throw DecodeException.error(message);
+    }
+
+    // The only characters remaining must be whitespace.
+    while (pos < length)
+    {
+      final char c = authPasswordValue.charAt(pos);
+      if (c == ' ')
+      {
+        pos++;
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_AUTHPW_INVALID_TRAILING_CHAR.get(pos);
+        throw DecodeException.error(message);
+      }
+    }
+
+    // If we've gotten here, then everything must be OK.
+    return new StringBuilder[] { scheme, authInfo, authValue };
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is encoded using the auth
+   * password syntax.
+   * 
+   * @param value
+   *          The value for which to make the determination.
+   * @return <CODE>true</CODE> if the value appears to be encoded using
+   *         the auth password syntax, or <CODE>false</CODE> if not.
+   */
+  static boolean isEncoded(ByteSequence value)
+  {
+    // FIXME -- Make this more efficient, and don't use exceptions for
+    // flow control.
+
+    try
+    {
+      decodeAuthPassword(value.toString());
+      return true;
+    }
+    catch (final Exception e)
+    {
+      return false;
+    }
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_AUTH_PASSWORD_EXACT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_AUTH_PASSWORD_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    try
+    {
+      decodeAuthPassword(value.toString());
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java b/sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java
new file mode 100644
index 0000000..db1e9d3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BinarySyntaxImpl.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_BINARY_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the binary attribute syntax, which is essentially
+ * a byte array using very strict matching. Equality, ordering, and
+ * substring matching will be allowed by default.
+ */
+final class BinarySyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_BINARY_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the binary syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..36ce4b1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BitStringEqualityMatchingRuleImpl.java
@@ -0,0 +1,89 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the bitStringMatch matching rule defined in X.520
+ * and referenced in RFC 2252.
+ */
+final class BitStringEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final String valueString = value.toString().toUpperCase();
+
+    final int length = valueString.length();
+    if (length < 3)
+    {
+      final Message message =
+          WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT.get(value.toString());
+      throw DecodeException.error(message);
+    }
+
+    if (valueString.charAt(0) != '\''
+        || valueString.charAt(length - 2) != '\''
+        || valueString.charAt(length - 1) != 'B')
+    {
+      final Message message =
+          WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED.get(value.toString());
+      throw DecodeException.error(message);
+    }
+
+    for (int i = 1; i < length - 2; i++)
+    {
+      switch (valueString.charAt(i))
+      {
+      case '0':
+      case '1':
+        // These characters are fine.
+        break;
+      default:
+        final Message message =
+            WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT.get(value
+                .toString(), String.valueOf(valueString.charAt(i)));
+        throw DecodeException.error(message);
+      }
+    }
+
+    return ByteString.valueOf(valueString);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java
new file mode 100644
index 0000000..92ff2e7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BitStringSyntaxImpl.java
@@ -0,0 +1,127 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT;
+import static org.opends.sdk.schema.SchemaConstants.EMR_BIT_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_BIT_STRING_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the bit string attribute syntax, which is
+ * comprised of a string of binary digits surrounded by single quotes
+ * and followed by a capital letter "B" (e.g., '101001'B).
+ */
+final class BitStringSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_BIT_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_BIT_STRING_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String valueString = value.toString().toUpperCase();
+
+    final int length = valueString.length();
+    if (length < 3)
+    {
+      invalidReason.append(WARN_ATTR_SYNTAX_BIT_STRING_TOO_SHORT
+          .get(value.toString()));
+      return false;
+    }
+
+    if (valueString.charAt(0) != '\''
+        || valueString.charAt(length - 2) != '\''
+        || valueString.charAt(length - 1) != 'B')
+    {
+      invalidReason.append(WARN_ATTR_SYNTAX_BIT_STRING_NOT_QUOTED
+          .get(value.toString()));
+      return false;
+    }
+
+    for (int i = 1; i < length - 2; i++)
+    {
+      switch (valueString.charAt(i))
+      {
+      case '0':
+      case '1':
+        // These characters are fine.
+        break;
+      default:
+        invalidReason.append(WARN_ATTR_SYNTAX_BIT_STRING_INVALID_BIT
+            .get(value.toString(), String
+                .valueOf(valueString.charAt(i))));
+        return false;
+      }
+    }
+
+    // If we've gotten here, then everything is fine.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..0c52bcd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BooleanEqualityMatchingRuleImpl.java
@@ -0,0 +1,64 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the booleanMatch matching rule defined in X.520
+ * and referenced in RFC 4519.
+ */
+final class BooleanEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final String valueString = value.toString().toUpperCase();
+    if (valueString.equals("TRUE") || valueString.equals("YES")
+        || valueString.equals("ON") || valueString.equals("1"))
+    {
+      return SchemaConstants.TRUE_VALUE;
+    }
+    else if (valueString.equals("FALSE") || valueString.equals("NO")
+        || valueString.equals("OFF") || valueString.equals("0"))
+    {
+      return SchemaConstants.FALSE_VALUE;
+    }
+
+    throw DecodeException.error(WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN
+        .get(value.toString()));
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java
new file mode 100644
index 0000000..5d9a3cc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/BooleanSyntaxImpl.java
@@ -0,0 +1,105 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_BOOLEAN_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_BOOLEAN_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the Boolean attribute syntax, which only allows
+ * values of "TRUE" or "FALSE" (although this implementation is more
+ * flexible and will also allow "YES", "ON", or "1" instead of "TRUE",
+ * or "NO", "OFF", or "0" instead of "FALSE"). Only equality matching is
+ * allowed by default for this syntax.
+ */
+final class BooleanSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_BOOLEAN_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_BOOLEAN_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String valueString = value.toString().toUpperCase();
+
+    final boolean returnValue =
+        valueString.equals("TRUE") || valueString.equals("YES")
+            || valueString.equals("ON") || valueString.equals("1")
+            || valueString.equals("FALSE") || valueString.equals("NO")
+            || valueString.equals("OFF") || valueString.equals("0");
+
+    if (!returnValue)
+    {
+      invalidReason.append(WARN_ATTR_SYNTAX_ILLEGAL_BOOLEAN.get(value
+          .toString()));
+    }
+
+    return returnValue;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..bda2301
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactEqualityMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseExactMatch matching rule defined in X.520
+ * and referenced in RFC 4519.
+ */
+final class CaseExactEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..0df9ee9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactIA5EqualityMatchingRuleImpl.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseExactIA5Match matching rule defined in
+ * RFC 2252.
+ */
+final class CaseExactIA5EqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space and watch out
+    // for non-ASCII characters.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      final char c = buffer.charAt(pos);
+      if (c == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+      else if ((c & 0x7F) != c)
+      {
+        // This is not a valid character for an IA5 string. If strict
+        // syntax enforcement is enabled, then we'll throw an exception.
+        // Otherwise, we'll get rid of the character.
+        final Message message =
+            WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+                value.toString(), String.valueOf(c));
+        throw DecodeException.error(message);
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..a70abe7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactIA5SubstringMatchingRuleImpl.java
@@ -0,0 +1,117 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseExactIA5SubstringsMatch matching rule.
+ * This matching rule actually isn't defined in any official
+ * specification, but some directory vendors do provide an
+ * implementation using an OID from their own private namespace.
+ */
+final class CaseExactIA5SubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    return normalize(TRIM, value);
+  }
+
+
+
+  @Override
+  ByteString normalizeSubString(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    return normalize(false, value);
+  }
+
+
+
+  private ByteString normalize(boolean trim, ByteSequence value)
+      throws DecodeException
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, trim, NO_CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space and watch out
+    // for non-ASCII characters.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      final char c = buffer.charAt(pos);
+      if (c == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+      else if ((c & 0x7F) != c)
+      {
+        // This is not a valid character for an IA5 string. If strict
+        // syntax enforcement is enabled, then we'll throw an exception.
+        // Otherwise, we'll get rid of the character.
+        final Message message =
+            WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+                value.toString(), String.valueOf(c));
+        throw DecodeException.error(message);
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..f22d047
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactOrderingMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseExactOrderingMatch matching rule defined
+ * in X.520 and referenced in RFC 4519.
+ */
+final class CaseExactOrderingMatchingRuleImpl extends
+    AbstractOrderingMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..33d53c8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseExactSubstringMatchingRuleImpl.java
@@ -0,0 +1,100 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseExactSubstringsMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class CaseExactSubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return normalize(TRIM, value);
+  }
+
+
+
+  @Override
+  ByteString normalizeSubString(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    return normalize(false, value);
+  }
+
+
+
+  private ByteString normalize(boolean trim, ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, trim, NO_CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..b5b9bc8
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreEqualityMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseIgnoreMatch matching rule defined in X.520
+ * and referenced in RFC 2252.
+ */
+final class CaseIgnoreEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..c27caf9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5EqualityMatchingRuleImpl.java
@@ -0,0 +1,98 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseIgnoreIA5Match matching rule defined in
+ * RFC 2252.
+ */
+final class CaseIgnoreIA5EqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space and watch out
+    // for non-ASCII characters.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      final char c = buffer.charAt(pos);
+      if (c == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+      else if ((c & 0x7F) != c)
+      {
+        // This is not a valid character for an IA5 string. If strict
+        // syntax enforcement is enabled, then we'll throw an exception.
+        // Otherwise, we'll get rid of the character.
+        final Message message =
+            WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+                value.toString(), String.valueOf(c));
+        throw DecodeException.error(message);
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..b205035
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreIA5SubstringMatchingRuleImpl.java
@@ -0,0 +1,115 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseIgnoreIA5SubstringsMatch matching rule
+ * defined in RFC 2252.
+ */
+final class CaseIgnoreIA5SubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    return normalize(TRIM, value);
+  }
+
+
+
+  @Override
+  ByteString normalizeSubString(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    return normalize(false, value);
+  }
+
+
+
+  private ByteString normalize(boolean trim, ByteSequence value)
+      throws DecodeException
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, trim, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space and watch out
+    // for non-ASCII characters.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      final char c = buffer.charAt(pos);
+      if (c == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+      else if ((c & 0x7F) != c)
+      {
+        // This is not a valid character for an IA5 string. If strict
+        // syntax enforcement is enabled, then we'll throw an exception.
+        // Otherwise, we'll get rid of the character.
+        final Message message =
+            WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+                value.toString(), String.valueOf(c));
+        throw DecodeException.error(message);
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..e0018bd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreListEqualityMatchingRuleImpl.java
@@ -0,0 +1,96 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the caseIgnoreListMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreListEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space. Any spaces
+    // around a dollar sign will also be removed.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        final char c = buffer.charAt(pos - 1);
+        if (c == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+        else if (c == '$')
+        {
+          if (pos <= 1 || buffer.charAt(pos - 2) != '\\')
+          {
+            buffer.delete(pos, pos + 1);
+          }
+        }
+        else if (buffer.charAt(pos + 1) == '$')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..f95f1d4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreListSubstringMatchingRuleImpl.java
@@ -0,0 +1,140 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StringPrepProfile;
+
+
+
+/**
+ * This class implements the caseIgnoreListSubstringsMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreListSubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, StringPrepProfile.TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space. Any spaces
+    // around a dollar sign will also be removed.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        final char c = buffer.charAt(pos - 1);
+        if (c == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+        else if (c == '$')
+        {
+          if (pos <= 1 || buffer.charAt(pos - 2) != '\\')
+          {
+            buffer.delete(pos, pos + 1);
+          }
+        }
+        else if (buffer.charAt(pos + 1) == '$')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+
+
+
+  @Override
+  ByteString normalizeSubString(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    // In this case, the process for normalizing a substring is the same
+    // as normalizing a full value with the exception that it may
+    // include an opening or trailing space.
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, false, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return value.toByteString();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..292e17c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreOrderingMatchingRuleImpl.java
@@ -0,0 +1,83 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the caseIgnoreOrderingMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreOrderingMatchingRuleImpl extends
+    AbstractOrderingMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..0ed9ce0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CaseIgnoreSubstringMatchingRuleImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StringPrepProfile;
+
+
+
+/**
+ * This class defines the caseIgnoreSubstringsMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class CaseIgnoreSubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return normalize(TRIM, value);
+  }
+
+
+
+  @Override
+  ByteString normalizeSubString(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    return normalize(false, value);
+  }
+
+
+
+  private ByteString normalize(boolean trim, ByteSequence value)
+  {
+
+    final StringBuilder buffer = new StringBuilder();
+    StringPrepProfile.prepareUnicode(buffer, value, trim, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return value.toByteString();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java
new file mode 100644
index 0000000..6041dad
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CertificateListSyntaxImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_CERTLIST_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the certificate list attribute syntax. This
+ * should be restricted to holding only X.509 certificate lists, but we
+ * will accept any set of bytes. It will be treated much like the octet
+ * string attribute syntax.
+ */
+final class CertificateListSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_CERTLIST_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  @Override
+  public boolean isBEREncodingRequired()
+  {
+    return true;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the certificate list syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java
new file mode 100644
index 0000000..6c4ebdd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CertificatePairSyntaxImpl.java
@@ -0,0 +1,107 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_CERTPAIR_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the certificate pair attribute syntax. This
+ * should be restricted to holding only X.509 certificate pairs, but we
+ * will accept any set of bytes. It will be treated much like the octet
+ * string attribute syntax.
+ */
+final class CertificatePairSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_CERTPAIR_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  @Override
+  public boolean isBEREncodingRequired()
+  {
+    return true;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the certificate pair syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java
new file mode 100644
index 0000000..3d34b64
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CertificateSyntaxImpl.java
@@ -0,0 +1,107 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_CERTIFICATE_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the certificate attribute syntax. This should
+ * be restricted to holding only X.509 certificates, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+final class CertificateSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_CERTIFICATE_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  @Override
+  public boolean isBEREncodingRequired()
+  {
+    return true;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the certificate syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java b/sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java
new file mode 100644
index 0000000..857cfda
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ConflictingSchemaElementException.java
@@ -0,0 +1,58 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Thrown when addition of a schema element to a schema builder fails
+ * because the OID of the schema element conflicts with an existing
+ * schema element and the caller explicitly requested not to override
+ * existing schema elements.
+ */
+@SuppressWarnings("serial")
+public class ConflictingSchemaElementException extends
+    LocalizedIllegalArgumentException
+{
+  /**
+   * Creates a new conflicting schema element exception with the
+   * provided message.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   */
+  public ConflictingSchemaElementException(Message message)
+  {
+    super(message);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CoreSchema.java b/sdk/src/org/opends/sdk/schema/CoreSchema.java
new file mode 100644
index 0000000..0117a13
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CoreSchema.java
@@ -0,0 +1,2666 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * The OpenDS SDK core schema contains standard LDAP RFC schema elements. These include:
+ * <ul>
+ * <li><a href="http://tools.ietf.org/html/rfc4512">RFC 4512 -
+ * Lightweight Directory Access Protocol (LDAP): Directory Information
+ * Models </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4517">RFC 4517 -
+ * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching
+ * Rules </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4519">RFC 4519 -
+ * Lightweight Directory Access Protocol (LDAP): Schema for User
+ * Applications </a>
+ * <li><a href="http://tools.ietf.org/html/rfc4530">RFC 4530 -
+ * Lightweight Directory Access Protocol (LDAP): entryUUID Operational
+ * Attribute </a>
+ * <li><a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing
+ * Vendor Information in the LDAP Root DSE </a>
+ * <li><a href="http://tools.ietf.org/html/rfc3112">RFC 3112 - LDAP
+ * Authentication Password Schema </a>
+ * </ul>
+ * <p>
+ * The core schema is non-strict: attempts to retrieve
+ * non-existent Attribute Types will return a temporary
+ * Attribute Type having the Octet String syntax.
+ */
+public final class CoreSchema
+{
+  // Core Syntaxes
+  private static final Syntax ATTRIBUTE_TYPE_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.3");
+  private static final Syntax AUTHENTICATION_PASSWORD_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.4203.1.1.2");
+  private static final Syntax BINARY_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.5");
+  private static final Syntax BIT_STRING_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.6");
+  private static final Syntax BOOLEAN_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.7");
+  private static final Syntax CERTIFICATE_LIST_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.9");
+  private static final Syntax CERTIFICATE_PAIR_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.10");
+  private static final Syntax CERTIFICATE_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.8");
+  private static final Syntax COUNTRY_STRING_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.11");
+  private static final Syntax DELIVERY_METHOD_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.14");
+  private static final Syntax DIRECTORY_STRING_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.15");
+  private static final Syntax DIT_CONTENT_RULE_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.16");
+  private static final Syntax DIT_STRUCTURE_RULE_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.17");
+  private static final Syntax DN_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.12");
+  private static final Syntax ENHANCED_GUIDE_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.21");
+  private static final Syntax FACSIMILE_TELEPHONE_NUMBER_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.22");
+  private static final Syntax FAX_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.23");
+  private static final Syntax GENERALIZED_TIME_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.24");
+  private static final Syntax GUIDE_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.25");
+  private static final Syntax IA5_STRING_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.26");
+  private static final Syntax INTEGER_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.27");
+  private static final Syntax JPEG_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.28");
+  private static final Syntax LDAP_SYNTAX_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.54");
+  private static final Syntax MATCHING_RULE_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.30");
+  private static final Syntax MATCHING_RULE_USE_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.31");
+  private static final Syntax NAME_AND_OPTIONAL_UID_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.34");
+  private static final Syntax NAME_FORM_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.35");
+  private static final Syntax NUMERIC_STRING_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.36");
+  private static final Syntax OBJECT_CLASS_DESCRIPTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.37");
+  private static final Syntax OCTET_STRING_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.40");
+  private static final Syntax OID_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.38");
+  private static final Syntax OTHER_MAILBOX_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.39");
+  private static final Syntax POSTAL_ADDRESS_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.41");
+  private static final Syntax PRESENTATION_ADDRESS_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.43");
+  private static final Syntax PRINTABLE_STRING_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.44");
+  private static final Syntax PROTOCOL_INFORMATION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.42");
+  private static final Syntax SUBSTRING_ASSERTION_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.58");
+  private static final Syntax SUPPORTED_ALGORITHM_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.49");
+  private static final Syntax TELEPHONE_NUMBER_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.50");
+  private static final Syntax TELETEX_TERMINAL_IDENTIFIER_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.51");
+  private static final Syntax TELEX_NUMBER_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.52");
+  private static final Syntax UTC_TIME_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.4.1.1466.115.121.1.53");
+  private static final Syntax UUID_SYNTAX =
+    CoreSchemaImpl.getInstance().getSyntax("1.3.6.1.1.16.1");
+
+  // Core Matching Rules
+  private static final MatchingRule AUTH_PASSWORD_EXACT_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.4203.1.2.2");
+  private static final MatchingRule BIT_STRING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.16");
+  private static final MatchingRule BOOLEAN_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.13");
+  private static final MatchingRule CASE_EXACT_IA5_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.1466.109.114.1");
+  private static final MatchingRule CASE_EXACT_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.5");
+  private static final MatchingRule CASE_EXACT_ORDERING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.6");
+  private static final MatchingRule CASE_EXACT_SUBSTRINGS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.7");
+  private static final MatchingRule CASE_IGNORE_IA5_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.1466.109.114.2");
+  private static final MatchingRule CASE_IGNORE_IA5_SUBSTRINGS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.4.1.1466.109.114.3");
+  private static final MatchingRule CASE_IGNORE_LIST_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.11");
+  private static final MatchingRule CASE_IGNORE_LIST_SUBSTRINGS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.12");
+  private static final MatchingRule CASE_IGNORE_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.2");
+  private static final MatchingRule CASE_IGNORE_ORDERING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.3");
+  private static final MatchingRule CASE_IGNORE_SUBSTRINGS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.4");
+  private static final MatchingRule DIRECTORY_STRING_FIRST_COMPONENT_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.31");
+  private static final MatchingRule DISTINGUISHED_NAME_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.1");
+  private static final MatchingRule GENERALIZED_TIME_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.27");
+  private static final MatchingRule GENERALIZED_TIME_ORDERING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.28");
+  private static final MatchingRule INTEGER_FIRST_COMPONENT_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.29");
+  private static final MatchingRule INTEGER_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.14");
+  private static final MatchingRule INTEGER_ORDERING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.15");
+  private static final MatchingRule KEYWORD_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.33");
+  private static final MatchingRule NUMERIC_STRING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.8");
+  private static final MatchingRule NUMERIC_STRING_ORDERING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.9");
+  private static final MatchingRule NUMERIC_STRING_SUBSTRINGS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.10");
+  private static final MatchingRule OBJECT_IDENTIFIER_FIRST_COMPONENT_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.30");
+  private static final MatchingRule OBJECT_IDENTIFIER_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.0");
+  private static final MatchingRule OCTET_STRING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.17");
+  private static final MatchingRule OCTET_STRING_ORDERING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.18");
+  private static final MatchingRule OCTET_STRING_SUBSTRINGS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.19");
+  private static final MatchingRule PRESENTATION_ADDRESS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.22");
+  private static final MatchingRule PROTOCOL_INFORMATION_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.24");
+  private static final MatchingRule TELEPHONE_NUMBER_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.20");
+  private static final MatchingRule TELEPHONE_NUMBER_SUBSTRINGS_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.21");
+  private static final MatchingRule UNIQUE_MEMBER_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.23");
+  private static final MatchingRule UUID_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.1.16.2");
+  private static final MatchingRule UUID_ORDERING_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("1.3.6.1.1.16.3");
+  private static final MatchingRule WORD_MATCHING_RULE =
+    CoreSchemaImpl.getInstance().getMatchingRule("2.5.13.32");
+
+  // Core Attribute Types
+  private static final AttributeType ALIASED_OBJECT_NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.1");
+  private static final AttributeType ALT_SERVER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.6");
+  private static final AttributeType ATTRIBUTE_TYPES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.5");
+  private static final AttributeType AUTH_PASSWORD_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.4203.1.3.4");
+  private static final AttributeType BUSINESS_CATEGORY_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.15");
+  private static final AttributeType CN_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.3");
+  private static final AttributeType CREATE_TIMESTAMP_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.18.1");
+  private static final AttributeType CREATORS_NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.18.3");
+  private static final AttributeType C_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.6");
+  private static final AttributeType DC_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("0.9.2342.19200300.100.1.25");
+  private static final AttributeType DESCRIPTION_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.13");
+  private static final AttributeType DESTINATION_INDICATOR_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.27");
+  private static final AttributeType DISTINGUISHED_NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.49");
+  private static final AttributeType DIT_CONTENT_RULES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.2");
+  private static final AttributeType DIT_STRUCTURE_RULES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.1");
+  private static final AttributeType DN_QUALIFIER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.46");
+  private static final AttributeType ENHANCED_SEARCH_GUIDE_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.47");
+  private static final AttributeType ENTRY_UUID_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.1.16.4");
+  private static final AttributeType FACSIMILE_TELEPHONE_NUMBER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.23");
+  private static final AttributeType GENERATION_QUALIFIER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.44");
+  private static final AttributeType GIVEN_NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.42");
+  private static final AttributeType GOVERNING_STRUCTURE_RULE_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.10");
+  private static final AttributeType HOUSE_IDENTIFIER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.51");
+  private static final AttributeType INITIALS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.43");
+  private static final AttributeType INTERNATIONAL_ISDN_NUMBER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.25");
+  private static final AttributeType LDAP_SYNTAXES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.16");
+  private static final AttributeType L_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.7");
+  private static final AttributeType MATCHING_RULES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.4");
+  private static final AttributeType MATCHING_RULE_USE_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.8");
+  private static final AttributeType MEMBER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.31");
+  private static final AttributeType MODIFIERS_NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.18.4");
+  private static final AttributeType MODIFY_TIMESTAMP_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.18.2");
+  private static final AttributeType NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.41");
+  private static final AttributeType NAME_FORMS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.7");
+  private static final AttributeType NAMING_CONTEXTS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.5");
+  private static final AttributeType OBJECT_CLASSES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.6");
+  private static final AttributeType OBJECT_CLASS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.0");
+  private static final AttributeType OU_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.11");
+  private static final AttributeType OWNER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.32");
+  private static final AttributeType O_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.10");
+  private static final AttributeType PHYSICAL_DELIVERY_OFFICE_NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.19");
+  private static final AttributeType POSTAL_ADDRESS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.16");
+  private static final AttributeType POSTAL_CODE_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.17");
+  private static final AttributeType POST_OFFICE_BOX_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.18");
+  private static final AttributeType PREFERRED_DELIVERY_METHOD_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.28");
+  private static final AttributeType REGISTERED_ADDRESS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.26");
+  private static final AttributeType ROLE_OCCUPANT_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.33");
+  private static final AttributeType SEARCH_GUIDE_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.14");
+  private static final AttributeType SEE_ALSO_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.34");
+  private static final AttributeType SERIAL_NUMBER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.5");
+  private static final AttributeType SN_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.4");
+  private static final AttributeType STREET_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.9");
+  private static final AttributeType STRUCTURAL_OBJECT_CLASS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.21.9");
+  private static final AttributeType ST_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.8");
+  private static final AttributeType SUBSCHEMA_SUBENTRY_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.18.10");
+  private static final AttributeType SUPPORTED_AUTH_PASSWORD_SCHEMES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.4203.1.3.3");
+  private static final AttributeType SUPPORTED_CONTROL_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.13");
+  private static final AttributeType SUPPORTED_EXTENSION_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.7");
+  private static final AttributeType SUPPORTED_FEATURES_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.4203.1.3.5");
+  private static final AttributeType SUPPORTED_LDAP_VERSION_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.15");
+  private static final AttributeType SUPPORTED_SASL_MECHANISMS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.4.1.1466.101.120.14");
+  private static final AttributeType TELEPHONE_NUMBER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.20");
+  private static final AttributeType TELETEX_TERMINAL_IDENTIFIER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.22");
+  private static final AttributeType TELEX_NUMBER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.21");
+  private static final AttributeType TITLE_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.12");
+  private static final AttributeType UID_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("0.9.2342.19200300.100.1.1");
+  private static final AttributeType UNIQUE_MEMBER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.50");
+  private static final AttributeType USER_PASSWORD_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.35");
+  private static final AttributeType VENDOR_NAME_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.1.4");
+  private static final AttributeType VENDOR_VERSION_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("1.3.6.1.1.5");
+  private static final AttributeType X121_ADDRESS_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.24");
+  private static final AttributeType X500_UNIQUE_IDENTIFIER_ATTRIBUTE_TYPE =
+    CoreSchemaImpl.getInstance().getAttributeType("2.5.4.45");
+
+  // Core Object Classes
+  private static final ObjectClass ALIAS_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.1");
+  private static final ObjectClass APPLICATION_PROCESS_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.11");
+  private static final ObjectClass AUTH_PASSWORD_OBJECT_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.4.1.4203.1.4.7");
+  private static final ObjectClass COUNTRY_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.2");
+  private static final ObjectClass DC_OBJECT_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.4.1.1466.344");
+  private static final ObjectClass DEVICE_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.14");
+  private static final ObjectClass EXTENSIBLE_OBJECT_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.4.1.1466.101.120.111");
+  private static final ObjectClass GROUP_OF_NAMES_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.9");
+  private static final ObjectClass GROUP_OF_UNIQUE_NAMES_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.17");
+  private static final ObjectClass LOCALITY_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.3");
+  private static final ObjectClass ORGANIZATIONAL_PERSON_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.7");
+  private static final ObjectClass ORGANIZATIONAL_ROLE_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.8");
+  private static final ObjectClass ORGANIZATIONAL_UNIT_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.5");
+  private static final ObjectClass ORGANIZATION_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.4");
+  private static final ObjectClass PERSON_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.6");
+  private static final ObjectClass RESIDENTIAL_PERSON_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.10");
+  private static final ObjectClass SUBSCHEMA_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.20.1");
+  private static final ObjectClass TOP_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("2.5.6.0");
+  private static final ObjectClass UID_OBJECT_OBJECT_CLASS =
+    CoreSchemaImpl.getInstance().getObjectClass("1.3.6.1.1.3.1");
+
+
+
+  // Prevent instantiation
+  private CoreSchema()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * Returns a reference to the singleton core schema.
+   *
+   * @return The core schema.
+   */
+  public static Schema getInstance()
+  {
+    return CoreSchemaImpl.getInstance();
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Attribute Type Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.3}.
+   *
+   * @return A reference to the {@code Attribute Type Description Syntax}.
+   */
+  public static Syntax getAttributeTypeDescriptionSyntax()
+  {
+    return ATTRIBUTE_TYPE_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Authentication Password Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.4203.1.1.2}.
+   *
+   * @return A reference to the {@code Authentication Password Syntax}.
+   */
+  public static Syntax getAuthenticationPasswordSyntax()
+  {
+    return AUTHENTICATION_PASSWORD_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Binary Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.5}.
+   *
+   * @return A reference to the {@code Binary Syntax}.
+   */
+  public static Syntax getBinarySyntax()
+  {
+    return BINARY_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Bit String Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.6}.
+   *
+   * @return A reference to the {@code Bit String Syntax}.
+   */
+  public static Syntax getBitStringSyntax()
+  {
+    return BIT_STRING_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Boolean Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.7}.
+   *
+   * @return A reference to the {@code Boolean Syntax}.
+   */
+  public static Syntax getBooleanSyntax()
+  {
+    return BOOLEAN_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Certificate List Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.9}.
+   *
+   * @return A reference to the {@code Certificate List Syntax}.
+   */
+  public static Syntax getCertificateListSyntax()
+  {
+    return CERTIFICATE_LIST_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Certificate Pair Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.10}.
+   *
+   * @return A reference to the {@code Certificate Pair Syntax}.
+   */
+  public static Syntax getCertificatePairSyntax()
+  {
+    return CERTIFICATE_PAIR_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Certificate Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.8}.
+   *
+   * @return A reference to the {@code Certificate Syntax}.
+   */
+  public static Syntax getCertificateSyntax()
+  {
+    return CERTIFICATE_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Country String Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.11}.
+   *
+   * @return A reference to the {@code Country String Syntax}.
+   */
+  public static Syntax getCountryStringSyntax()
+  {
+    return COUNTRY_STRING_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Delivery Method Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.14}.
+   *
+   * @return A reference to the {@code Delivery Method Syntax}.
+   */
+  public static Syntax getDeliveryMethodSyntax()
+  {
+    return DELIVERY_METHOD_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Directory String Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.15}.
+   *
+   * @return A reference to the {@code Directory String Syntax}.
+   */
+  public static Syntax getDirectoryStringSyntax()
+  {
+    return DIRECTORY_STRING_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code DIT Content Rule Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.16}.
+   *
+   * @return A reference to the {@code DIT Content Rule Description Syntax}.
+   */
+  public static Syntax getDITContentRuleDescriptionSyntax()
+  {
+    return DIT_CONTENT_RULE_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code DIT Structure Rule Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.17}.
+   *
+   * @return A reference to the {@code DIT Structure Rule Description Syntax}.
+   */
+  public static Syntax getDITStructureRuleDescriptionSyntax()
+  {
+    return DIT_STRUCTURE_RULE_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code DN Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.12}.
+   *
+   * @return A reference to the {@code DN Syntax}.
+   */
+  public static Syntax getDNSyntax()
+  {
+    return DN_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Enhanced Guide Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.21}.
+   *
+   * @return A reference to the {@code Enhanced Guide Syntax}.
+   */
+  public static Syntax getEnhancedGuideSyntax()
+  {
+    return ENHANCED_GUIDE_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Facsimile Telephone Number Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.22}.
+   *
+   * @return A reference to the {@code Facsimile Telephone Number Syntax}.
+   */
+  public static Syntax getFacsimileTelephoneNumberSyntax()
+  {
+    return FACSIMILE_TELEPHONE_NUMBER_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Fax Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.23}.
+   *
+   * @return A reference to the {@code Fax Syntax}.
+   */
+  public static Syntax getFaxSyntax()
+  {
+    return FAX_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Generalized Time Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.24}.
+   *
+   * @return A reference to the {@code Generalized Time Syntax}.
+   */
+  public static Syntax getGeneralizedTimeSyntax()
+  {
+    return GENERALIZED_TIME_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Guide Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.25}.
+   *
+   * @return A reference to the {@code Guide Syntax}.
+   */
+  public static Syntax getGuideSyntax()
+  {
+    return GUIDE_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code IA5 String Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.26}.
+   *
+   * @return A reference to the {@code IA5 String Syntax}.
+   */
+  public static Syntax getIA5StringSyntax()
+  {
+    return IA5_STRING_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Integer Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.27}.
+   *
+   * @return A reference to the {@code Integer Syntax}.
+   */
+  public static Syntax getIntegerSyntax()
+  {
+    return INTEGER_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code JPEG Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.28}.
+   *
+   * @return A reference to the {@code JPEG Syntax}.
+   */
+  public static Syntax getJPEGSyntax()
+  {
+    return JPEG_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code LDAP Syntax Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.54}.
+   *
+   * @return A reference to the {@code LDAP Syntax Description Syntax}.
+   */
+  public static Syntax getLDAPSyntaxDescriptionSyntax()
+  {
+    return LDAP_SYNTAX_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Matching Rule Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.30}.
+   *
+   * @return A reference to the {@code Matching Rule Description Syntax}.
+   */
+  public static Syntax getMatchingRuleDescriptionSyntax()
+  {
+    return MATCHING_RULE_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Matching Rule Use Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.31}.
+   *
+   * @return A reference to the {@code Matching Rule Use Description Syntax}.
+   */
+  public static Syntax getMatchingRuleUseDescriptionSyntax()
+  {
+    return MATCHING_RULE_USE_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Name and Optional UID Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.34}.
+   *
+   * @return A reference to the {@code Name and Optional UID Syntax}.
+   */
+  public static Syntax getNameAndOptionalUIDSyntax()
+  {
+    return NAME_AND_OPTIONAL_UID_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Name Form Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.35}.
+   *
+   * @return A reference to the {@code Name Form Description Syntax}.
+   */
+  public static Syntax getNameFormDescriptionSyntax()
+  {
+    return NAME_FORM_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Numeric String Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.36}.
+   *
+   * @return A reference to the {@code Numeric String Syntax}.
+   */
+  public static Syntax getNumericStringSyntax()
+  {
+    return NUMERIC_STRING_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Object Class Description Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.37}.
+   *
+   * @return A reference to the {@code Object Class Description Syntax}.
+   */
+  public static Syntax getObjectClassDescriptionSyntax()
+  {
+    return OBJECT_CLASS_DESCRIPTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Octet String Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.40}.
+   *
+   * @return A reference to the {@code Octet String Syntax}.
+   */
+  public static Syntax getOctetStringSyntax()
+  {
+    return OCTET_STRING_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code OID Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.38}.
+   *
+   * @return A reference to the {@code OID Syntax}.
+   */
+  public static Syntax getOIDSyntax()
+  {
+    return OID_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Other Mailbox Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.39}.
+   *
+   * @return A reference to the {@code Other Mailbox Syntax}.
+   */
+  public static Syntax getOtherMailboxSyntax()
+  {
+    return OTHER_MAILBOX_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Postal Address Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.41}.
+   *
+   * @return A reference to the {@code Postal Address Syntax}.
+   */
+  public static Syntax getPostalAddressSyntax()
+  {
+    return POSTAL_ADDRESS_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Presentation Address Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.43}.
+   *
+   * @return A reference to the {@code Presentation Address Syntax}.
+   */
+  public static Syntax getPresentationAddressSyntax()
+  {
+    return PRESENTATION_ADDRESS_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Printable String Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.44}.
+   *
+   * @return A reference to the {@code Printable String Syntax}.
+   */
+  public static Syntax getPrintableStringSyntax()
+  {
+    return PRINTABLE_STRING_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Protocol Information Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.42}.
+   *
+   * @return A reference to the {@code Protocol Information Syntax}.
+   */
+  public static Syntax getProtocolInformationSyntax()
+  {
+    return PROTOCOL_INFORMATION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Substring Assertion Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.58}.
+   *
+   * @return A reference to the {@code Substring Assertion Syntax}.
+   */
+  public static Syntax getSubstringAssertionSyntax()
+  {
+    return SUBSTRING_ASSERTION_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Supported Algorithm Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.49}.
+   *
+   * @return A reference to the {@code Supported Algorithm Syntax}.
+   */
+  public static Syntax getSupportedAlgorithmSyntax()
+  {
+    return SUPPORTED_ALGORITHM_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Telephone Number Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.50}.
+   *
+   * @return A reference to the {@code Telephone Number Syntax}.
+   */
+  public static Syntax getTelephoneNumberSyntax()
+  {
+    return TELEPHONE_NUMBER_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Teletex Terminal Identifier Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.51}.
+   *
+   * @return A reference to the {@code Teletex Terminal Identifier Syntax}.
+   */
+  public static Syntax getTeletexTerminalIdentifierSyntax()
+  {
+    return TELETEX_TERMINAL_IDENTIFIER_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code Telex Number Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.52}.
+   *
+   * @return A reference to the {@code Telex Number Syntax}.
+   */
+  public static Syntax getTelexNumberSyntax()
+  {
+    return TELEX_NUMBER_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code UTC Time Syntax}
+   * which has the OID {@code 1.3.6.1.4.1.1466.115.121.1.53}.
+   *
+   * @return A reference to the {@code UTC Time Syntax}.
+   */
+  public static Syntax getUTCTimeSyntax()
+  {
+    return UTC_TIME_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code UUID Syntax}
+   * which has the OID {@code 1.3.6.1.1.16.1}.
+   *
+   * @return A reference to the {@code UUID Syntax}.
+   */
+  public static Syntax getUUIDSyntax()
+  {
+    return UUID_SYNTAX;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code authPasswordExactMatch} Matching Rule
+   * which has the OID {@code 1.3.6.1.4.1.4203.1.2.2}.
+   *
+   * @return A reference to the {@code authPasswordExactMatch} Matching Rule.
+   */
+  public static MatchingRule getAuthPasswordExactMatchingRule()
+  {
+    return AUTH_PASSWORD_EXACT_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code bitStringMatch} Matching Rule
+   * which has the OID {@code 2.5.13.16}.
+   *
+   * @return A reference to the {@code bitStringMatch} Matching Rule.
+   */
+  public static MatchingRule getBitStringMatchingRule()
+  {
+    return BIT_STRING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code booleanMatch} Matching Rule
+   * which has the OID {@code 2.5.13.13}.
+   *
+   * @return A reference to the {@code booleanMatch} Matching Rule.
+   */
+  public static MatchingRule getBooleanMatchingRule()
+  {
+    return BOOLEAN_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseExactIA5Match} Matching Rule
+   * which has the OID {@code 1.3.6.1.4.1.1466.109.114.1}.
+   *
+   * @return A reference to the {@code caseExactIA5Match} Matching Rule.
+   */
+  public static MatchingRule getCaseExactIA5MatchingRule()
+  {
+    return CASE_EXACT_IA5_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseExactMatch} Matching Rule
+   * which has the OID {@code 2.5.13.5}.
+   *
+   * @return A reference to the {@code caseExactMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseExactMatchingRule()
+  {
+    return CASE_EXACT_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseExactOrderingMatch} Matching Rule
+   * which has the OID {@code 2.5.13.6}.
+   *
+   * @return A reference to the {@code caseExactOrderingMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseExactOrderingMatchingRule()
+  {
+    return CASE_EXACT_ORDERING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseExactSubstringsMatch} Matching Rule
+   * which has the OID {@code 2.5.13.7}.
+   *
+   * @return A reference to the {@code caseExactSubstringsMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseExactSubstringsMatchingRule()
+  {
+    return CASE_EXACT_SUBSTRINGS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseIgnoreIA5Match} Matching Rule
+   * which has the OID {@code 1.3.6.1.4.1.1466.109.114.2}.
+   *
+   * @return A reference to the {@code caseIgnoreIA5Match} Matching Rule.
+   */
+  public static MatchingRule getCaseIgnoreIA5MatchingRule()
+  {
+    return CASE_IGNORE_IA5_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseIgnoreIA5SubstringsMatch} Matching Rule
+   * which has the OID {@code 1.3.6.1.4.1.1466.109.114.3}.
+   *
+   * @return A reference to the {@code caseIgnoreIA5SubstringsMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseIgnoreIA5SubstringsMatchingRule()
+  {
+    return CASE_IGNORE_IA5_SUBSTRINGS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseIgnoreListMatch} Matching Rule
+   * which has the OID {@code 2.5.13.11}.
+   *
+   * @return A reference to the {@code caseIgnoreListMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseIgnoreListMatchingRule()
+  {
+    return CASE_IGNORE_LIST_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseIgnoreListSubstringsMatch} Matching Rule
+   * which has the OID {@code 2.5.13.12}.
+   *
+   * @return A reference to the {@code caseIgnoreListSubstringsMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseIgnoreListSubstringsMatchingRule()
+  {
+    return CASE_IGNORE_LIST_SUBSTRINGS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseIgnoreMatch} Matching Rule
+   * which has the OID {@code 2.5.13.2}.
+   *
+   * @return A reference to the {@code caseIgnoreMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseIgnoreMatchingRule()
+  {
+    return CASE_IGNORE_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseIgnoreOrderingMatch} Matching Rule
+   * which has the OID {@code 2.5.13.3}.
+   *
+   * @return A reference to the {@code caseIgnoreOrderingMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseIgnoreOrderingMatchingRule()
+  {
+    return CASE_IGNORE_ORDERING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code caseIgnoreSubstringsMatch} Matching Rule
+   * which has the OID {@code 2.5.13.4}.
+   *
+   * @return A reference to the {@code caseIgnoreSubstringsMatch} Matching Rule.
+   */
+  public static MatchingRule getCaseIgnoreSubstringsMatchingRule()
+  {
+    return CASE_IGNORE_SUBSTRINGS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code directoryStringFirstComponentMatch} Matching Rule
+   * which has the OID {@code 2.5.13.31}.
+   *
+   * @return A reference to the {@code directoryStringFirstComponentMatch} Matching Rule.
+   */
+  public static MatchingRule getDirectoryStringFirstComponentMatchingRule()
+  {
+    return DIRECTORY_STRING_FIRST_COMPONENT_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code distinguishedNameMatch} Matching Rule
+   * which has the OID {@code 2.5.13.1}.
+   *
+   * @return A reference to the {@code distinguishedNameMatch} Matching Rule.
+   */
+  public static MatchingRule getDistinguishedNameMatchingRule()
+  {
+    return DISTINGUISHED_NAME_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code generalizedTimeMatch} Matching Rule
+   * which has the OID {@code 2.5.13.27}.
+   *
+   * @return A reference to the {@code generalizedTimeMatch} Matching Rule.
+   */
+  public static MatchingRule getGeneralizedTimeMatchingRule()
+  {
+    return GENERALIZED_TIME_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code generalizedTimeOrderingMatch} Matching Rule
+   * which has the OID {@code 2.5.13.28}.
+   *
+   * @return A reference to the {@code generalizedTimeOrderingMatch} Matching Rule.
+   */
+  public static MatchingRule getGeneralizedTimeOrderingMatchingRule()
+  {
+    return GENERALIZED_TIME_ORDERING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code integerFirstComponentMatch} Matching Rule
+   * which has the OID {@code 2.5.13.29}.
+   *
+   * @return A reference to the {@code integerFirstComponentMatch} Matching Rule.
+   */
+  public static MatchingRule getIntegerFirstComponentMatchingRule()
+  {
+    return INTEGER_FIRST_COMPONENT_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code integerMatch} Matching Rule
+   * which has the OID {@code 2.5.13.14}.
+   *
+   * @return A reference to the {@code integerMatch} Matching Rule.
+   */
+  public static MatchingRule getIntegerMatchingRule()
+  {
+    return INTEGER_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code integerOrderingMatch} Matching Rule
+   * which has the OID {@code 2.5.13.15}.
+   *
+   * @return A reference to the {@code integerOrderingMatch} Matching Rule.
+   */
+  public static MatchingRule getIntegerOrderingMatchingRule()
+  {
+    return INTEGER_ORDERING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code keywordMatch} Matching Rule
+   * which has the OID {@code 2.5.13.33}.
+   *
+   * @return A reference to the {@code keywordMatch} Matching Rule.
+   */
+  public static MatchingRule getKeywordMatchingRule()
+  {
+    return KEYWORD_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code numericStringMatch} Matching Rule
+   * which has the OID {@code 2.5.13.8}.
+   *
+   * @return A reference to the {@code numericStringMatch} Matching Rule.
+   */
+  public static MatchingRule getNumericStringMatchingRule()
+  {
+    return NUMERIC_STRING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code numericStringOrderingMatch} Matching Rule
+   * which has the OID {@code 2.5.13.9}.
+   *
+   * @return A reference to the {@code numericStringOrderingMatch} Matching Rule.
+   */
+  public static MatchingRule getNumericStringOrderingMatchingRule()
+  {
+    return NUMERIC_STRING_ORDERING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code numericStringSubstringsMatch} Matching Rule
+   * which has the OID {@code 2.5.13.10}.
+   *
+   * @return A reference to the {@code numericStringSubstringsMatch} Matching Rule.
+   */
+  public static MatchingRule getNumericStringSubstringsMatchingRule()
+  {
+    return NUMERIC_STRING_SUBSTRINGS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code objectIdentifierFirstComponentMatch} Matching Rule
+   * which has the OID {@code 2.5.13.30}.
+   *
+   * @return A reference to the {@code objectIdentifierFirstComponentMatch} Matching Rule.
+   */
+  public static MatchingRule getObjectIdentifierFirstComponentMatchingRule()
+  {
+    return OBJECT_IDENTIFIER_FIRST_COMPONENT_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code objectIdentifierMatch} Matching Rule
+   * which has the OID {@code 2.5.13.0}.
+   *
+   * @return A reference to the {@code objectIdentifierMatch} Matching Rule.
+   */
+  public static MatchingRule getObjectIdentifierMatchingRule()
+  {
+    return OBJECT_IDENTIFIER_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code octetStringMatch} Matching Rule
+   * which has the OID {@code 2.5.13.17}.
+   *
+   * @return A reference to the {@code octetStringMatch} Matching Rule.
+   */
+  public static MatchingRule getOctetStringMatchingRule()
+  {
+    return OCTET_STRING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code octetStringOrderingMatch} Matching Rule
+   * which has the OID {@code 2.5.13.18}.
+   *
+   * @return A reference to the {@code octetStringOrderingMatch} Matching Rule.
+   */
+  public static MatchingRule getOctetStringOrderingMatchingRule()
+  {
+    return OCTET_STRING_ORDERING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code octetStringSubstringsMatch} Matching Rule
+   * which has the OID {@code 2.5.13.19}.
+   *
+   * @return A reference to the {@code octetStringSubstringsMatch} Matching Rule.
+   */
+  public static MatchingRule getOctetStringSubstringsMatchingRule()
+  {
+    return OCTET_STRING_SUBSTRINGS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code presentationAddressMatch} Matching Rule
+   * which has the OID {@code 2.5.13.22}.
+   *
+   * @return A reference to the {@code presentationAddressMatch} Matching Rule.
+   */
+  public static MatchingRule getPresentationAddressMatchingRule()
+  {
+    return PRESENTATION_ADDRESS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code protocolInformationMatch} Matching Rule
+   * which has the OID {@code 2.5.13.24}.
+   *
+   * @return A reference to the {@code protocolInformationMatch} Matching Rule.
+   */
+  public static MatchingRule getProtocolInformationMatchingRule()
+  {
+    return PROTOCOL_INFORMATION_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code telephoneNumberMatch} Matching Rule
+   * which has the OID {@code 2.5.13.20}.
+   *
+   * @return A reference to the {@code telephoneNumberMatch} Matching Rule.
+   */
+  public static MatchingRule getTelephoneNumberMatchingRule()
+  {
+    return TELEPHONE_NUMBER_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code telephoneNumberSubstringsMatch} Matching Rule
+   * which has the OID {@code 2.5.13.21}.
+   *
+   * @return A reference to the {@code telephoneNumberSubstringsMatch} Matching Rule.
+   */
+  public static MatchingRule getTelephoneNumberSubstringsMatchingRule()
+  {
+    return TELEPHONE_NUMBER_SUBSTRINGS_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code uniqueMemberMatch} Matching Rule
+   * which has the OID {@code 2.5.13.23}.
+   *
+   * @return A reference to the {@code uniqueMemberMatch} Matching Rule.
+   */
+  public static MatchingRule getUniqueMemberMatchingRule()
+  {
+    return UNIQUE_MEMBER_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code uuidMatch} Matching Rule
+   * which has the OID {@code 1.3.6.1.1.16.2}.
+   *
+   * @return A reference to the {@code uuidMatch} Matching Rule.
+   */
+  public static MatchingRule getUUIDMatchingRule()
+  {
+    return UUID_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code uuidOrderingMatch} Matching Rule
+   * which has the OID {@code 1.3.6.1.1.16.3}.
+   *
+   * @return A reference to the {@code uuidOrderingMatch} Matching Rule.
+   */
+  public static MatchingRule getUUIDOrderingMatchingRule()
+  {
+    return UUID_ORDERING_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code wordMatch} Matching Rule
+   * which has the OID {@code 2.5.13.32}.
+   *
+   * @return A reference to the {@code wordMatch} Matching Rule.
+   */
+  public static MatchingRule getWordMatchingRule()
+  {
+    return WORD_MATCHING_RULE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code aliasedObjectName} Attribute Type
+   * which has the OID {@code 2.5.4.1}.
+   *
+   * @return A reference to the {@code aliasedObjectName} Attribute Type.
+   */
+  public static AttributeType getAliasedObjectNameAttributeType()
+  {
+    return ALIASED_OBJECT_NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code altServer} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.6}.
+   *
+   * @return A reference to the {@code altServer} Attribute Type.
+   */
+  public static AttributeType getAltServerAttributeType()
+  {
+    return ALT_SERVER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code attributeTypes} Attribute Type
+   * which has the OID {@code 2.5.21.5}.
+   *
+   * @return A reference to the {@code attributeTypes} Attribute Type.
+   */
+  public static AttributeType getAttributeTypesAttributeType()
+  {
+    return ATTRIBUTE_TYPES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code authPassword} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.4203.1.3.4}.
+   *
+   * @return A reference to the {@code authPassword} Attribute Type.
+   */
+  public static AttributeType getAuthPasswordAttributeType()
+  {
+    return AUTH_PASSWORD_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code businessCategory} Attribute Type
+   * which has the OID {@code 2.5.4.15}.
+   *
+   * @return A reference to the {@code businessCategory} Attribute Type.
+   */
+  public static AttributeType getBusinessCategoryAttributeType()
+  {
+    return BUSINESS_CATEGORY_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code cn} Attribute Type
+   * which has the OID {@code 2.5.4.3}.
+   *
+   * @return A reference to the {@code cn} Attribute Type.
+   */
+  public static AttributeType getCNAttributeType()
+  {
+    return CN_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code createTimestamp} Attribute Type
+   * which has the OID {@code 2.5.18.1}.
+   *
+   * @return A reference to the {@code createTimestamp} Attribute Type.
+   */
+  public static AttributeType getCreateTimestampAttributeType()
+  {
+    return CREATE_TIMESTAMP_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code creatorsName} Attribute Type
+   * which has the OID {@code 2.5.18.3}.
+   *
+   * @return A reference to the {@code creatorsName} Attribute Type.
+   */
+  public static AttributeType getCreatorsNameAttributeType()
+  {
+    return CREATORS_NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code c} Attribute Type
+   * which has the OID {@code 2.5.4.6}.
+   *
+   * @return A reference to the {@code c} Attribute Type.
+   */
+  public static AttributeType getCAttributeType()
+  {
+    return C_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code dc} Attribute Type
+   * which has the OID {@code 0.9.2342.19200300.100.1.25}.
+   *
+   * @return A reference to the {@code dc} Attribute Type.
+   */
+  public static AttributeType getDCAttributeType()
+  {
+    return DC_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code description} Attribute Type
+   * which has the OID {@code 2.5.4.13}.
+   *
+   * @return A reference to the {@code description} Attribute Type.
+   */
+  public static AttributeType getDescriptionAttributeType()
+  {
+    return DESCRIPTION_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code destinationIndicator} Attribute Type
+   * which has the OID {@code 2.5.4.27}.
+   *
+   * @return A reference to the {@code destinationIndicator} Attribute Type.
+   */
+  public static AttributeType getDestinationIndicatorAttributeType()
+  {
+    return DESTINATION_INDICATOR_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code distinguishedName} Attribute Type
+   * which has the OID {@code 2.5.4.49}.
+   *
+   * @return A reference to the {@code distinguishedName} Attribute Type.
+   */
+  public static AttributeType getDistinguishedNameAttributeType()
+  {
+    return DISTINGUISHED_NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code ditContentRules} Attribute Type
+   * which has the OID {@code 2.5.21.2}.
+   *
+   * @return A reference to the {@code ditContentRules} Attribute Type.
+   */
+  public static AttributeType getDITContentRulesAttributeType()
+  {
+    return DIT_CONTENT_RULES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code ditStructureRules} Attribute Type
+   * which has the OID {@code 2.5.21.1}.
+   *
+   * @return A reference to the {@code ditStructureRules} Attribute Type.
+   */
+  public static AttributeType getDITStructureRulesAttributeType()
+  {
+    return DIT_STRUCTURE_RULES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code dnQualifier} Attribute Type
+   * which has the OID {@code 2.5.4.46}.
+   *
+   * @return A reference to the {@code dnQualifier} Attribute Type.
+   */
+  public static AttributeType getDNQualifierAttributeType()
+  {
+    return DN_QUALIFIER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code enhancedSearchGuide} Attribute Type
+   * which has the OID {@code 2.5.4.47}.
+   *
+   * @return A reference to the {@code enhancedSearchGuide} Attribute Type.
+   */
+  public static AttributeType getEnhancedSearchGuideAttributeType()
+  {
+    return ENHANCED_SEARCH_GUIDE_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code entryUUID} Attribute Type
+   * which has the OID {@code 1.3.6.1.1.16.4}.
+   *
+   * @return A reference to the {@code entryUUID} Attribute Type.
+   */
+  public static AttributeType getEntryUUIDAttributeType()
+  {
+    return ENTRY_UUID_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code facsimileTelephoneNumber} Attribute Type
+   * which has the OID {@code 2.5.4.23}.
+   *
+   * @return A reference to the {@code facsimileTelephoneNumber} Attribute Type.
+   */
+  public static AttributeType getFacsimileTelephoneNumberAttributeType()
+  {
+    return FACSIMILE_TELEPHONE_NUMBER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code generationQualifier} Attribute Type
+   * which has the OID {@code 2.5.4.44}.
+   *
+   * @return A reference to the {@code generationQualifier} Attribute Type.
+   */
+  public static AttributeType getGenerationQualifierAttributeType()
+  {
+    return GENERATION_QUALIFIER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code givenName} Attribute Type
+   * which has the OID {@code 2.5.4.42}.
+   *
+   * @return A reference to the {@code givenName} Attribute Type.
+   */
+  public static AttributeType getGivenNameAttributeType()
+  {
+    return GIVEN_NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code governingStructureRule} Attribute Type
+   * which has the OID {@code 2.5.21.10}.
+   *
+   * @return A reference to the {@code governingStructureRule} Attribute Type.
+   */
+  public static AttributeType getGoverningStructureRuleAttributeType()
+  {
+    return GOVERNING_STRUCTURE_RULE_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code houseIdentifier} Attribute Type
+   * which has the OID {@code 2.5.4.51}.
+   *
+   * @return A reference to the {@code houseIdentifier} Attribute Type.
+   */
+  public static AttributeType getHouseIdentifierAttributeType()
+  {
+    return HOUSE_IDENTIFIER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code initials} Attribute Type
+   * which has the OID {@code 2.5.4.43}.
+   *
+   * @return A reference to the {@code initials} Attribute Type.
+   */
+  public static AttributeType getInitialsAttributeType()
+  {
+    return INITIALS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code internationalISDNNumber} Attribute Type
+   * which has the OID {@code 2.5.4.25}.
+   *
+   * @return A reference to the {@code internationalISDNNumber} Attribute Type.
+   */
+  public static AttributeType getInternationalISDNNumberAttributeType()
+  {
+    return INTERNATIONAL_ISDN_NUMBER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code ldapSyntaxes} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.16}.
+   *
+   * @return A reference to the {@code ldapSyntaxes} Attribute Type.
+   */
+  public static AttributeType getLDAPSyntaxesAttributeType()
+  {
+    return LDAP_SYNTAXES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code l} Attribute Type
+   * which has the OID {@code 2.5.4.7}.
+   *
+   * @return A reference to the {@code l} Attribute Type.
+   */
+  public static AttributeType getLAttributeType()
+  {
+    return L_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code matchingRules} Attribute Type
+   * which has the OID {@code 2.5.21.4}.
+   *
+   * @return A reference to the {@code matchingRules} Attribute Type.
+   */
+  public static AttributeType getMatchingRulesAttributeType()
+  {
+    return MATCHING_RULES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code matchingRuleUse} Attribute Type
+   * which has the OID {@code 2.5.21.8}.
+   *
+   * @return A reference to the {@code matchingRuleUse} Attribute Type.
+   */
+  public static AttributeType getMatchingRuleUseAttributeType()
+  {
+    return MATCHING_RULE_USE_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code member} Attribute Type
+   * which has the OID {@code 2.5.4.31}.
+   *
+   * @return A reference to the {@code member} Attribute Type.
+   */
+  public static AttributeType getMemberAttributeType()
+  {
+    return MEMBER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code modifiersName} Attribute Type
+   * which has the OID {@code 2.5.18.4}.
+   *
+   * @return A reference to the {@code modifiersName} Attribute Type.
+   */
+  public static AttributeType getModifiersNameAttributeType()
+  {
+    return MODIFIERS_NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code modifyTimestamp} Attribute Type
+   * which has the OID {@code 2.5.18.2}.
+   *
+   * @return A reference to the {@code modifyTimestamp} Attribute Type.
+   */
+  public static AttributeType getModifyTimestampAttributeType()
+  {
+    return MODIFY_TIMESTAMP_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code name} Attribute Type
+   * which has the OID {@code 2.5.4.41}.
+   *
+   * @return A reference to the {@code name} Attribute Type.
+   */
+  public static AttributeType getNameAttributeType()
+  {
+    return NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code nameForms} Attribute Type
+   * which has the OID {@code 2.5.21.7}.
+   *
+   * @return A reference to the {@code nameForms} Attribute Type.
+   */
+  public static AttributeType getNameFormsAttributeType()
+  {
+    return NAME_FORMS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code namingContexts} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.5}.
+   *
+   * @return A reference to the {@code namingContexts} Attribute Type.
+   */
+  public static AttributeType getNamingContextsAttributeType()
+  {
+    return NAMING_CONTEXTS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code objectClasses} Attribute Type
+   * which has the OID {@code 2.5.21.6}.
+   *
+   * @return A reference to the {@code objectClasses} Attribute Type.
+   */
+  public static AttributeType getObjectClassesAttributeType()
+  {
+    return OBJECT_CLASSES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code objectClass} Attribute Type
+   * which has the OID {@code 2.5.4.0}.
+   *
+   * @return A reference to the {@code objectClass} Attribute Type.
+   */
+  public static AttributeType getObjectClassAttributeType()
+  {
+    return OBJECT_CLASS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code ou} Attribute Type
+   * which has the OID {@code 2.5.4.11}.
+   *
+   * @return A reference to the {@code ou} Attribute Type.
+   */
+  public static AttributeType getOUAttributeType()
+  {
+    return OU_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code owner} Attribute Type
+   * which has the OID {@code 2.5.4.32}.
+   *
+   * @return A reference to the {@code owner} Attribute Type.
+   */
+  public static AttributeType getOwnerAttributeType()
+  {
+    return OWNER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code o} Attribute Type
+   * which has the OID {@code 2.5.4.10}.
+   *
+   * @return A reference to the {@code o} Attribute Type.
+   */
+  public static AttributeType getOAttributeType()
+  {
+    return O_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code physicalDeliveryOfficeName} Attribute Type
+   * which has the OID {@code 2.5.4.19}.
+   *
+   * @return A reference to the {@code physicalDeliveryOfficeName} Attribute Type.
+   */
+  public static AttributeType getPhysicalDeliveryOfficeNameAttributeType()
+  {
+    return PHYSICAL_DELIVERY_OFFICE_NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code postalAddress} Attribute Type
+   * which has the OID {@code 2.5.4.16}.
+   *
+   * @return A reference to the {@code postalAddress} Attribute Type.
+   */
+  public static AttributeType getPostalAddressAttributeType()
+  {
+    return POSTAL_ADDRESS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code postalCode} Attribute Type
+   * which has the OID {@code 2.5.4.17}.
+   *
+   * @return A reference to the {@code postalCode} Attribute Type.
+   */
+  public static AttributeType getPostalCodeAttributeType()
+  {
+    return POSTAL_CODE_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code postOfficeBox} Attribute Type
+   * which has the OID {@code 2.5.4.18}.
+   *
+   * @return A reference to the {@code postOfficeBox} Attribute Type.
+   */
+  public static AttributeType getPostOfficeBoxAttributeType()
+  {
+    return POST_OFFICE_BOX_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code preferredDeliveryMethod} Attribute Type
+   * which has the OID {@code 2.5.4.28}.
+   *
+   * @return A reference to the {@code preferredDeliveryMethod} Attribute Type.
+   */
+  public static AttributeType getPreferredDeliveryMethodAttributeType()
+  {
+    return PREFERRED_DELIVERY_METHOD_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code registeredAddress} Attribute Type
+   * which has the OID {@code 2.5.4.26}.
+   *
+   * @return A reference to the {@code registeredAddress} Attribute Type.
+   */
+  public static AttributeType getRegisteredAddressAttributeType()
+  {
+    return REGISTERED_ADDRESS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code roleOccupant} Attribute Type
+   * which has the OID {@code 2.5.4.33}.
+   *
+   * @return A reference to the {@code roleOccupant} Attribute Type.
+   */
+  public static AttributeType getRoleOccupantAttributeType()
+  {
+    return ROLE_OCCUPANT_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code searchGuide} Attribute Type
+   * which has the OID {@code 2.5.4.14}.
+   *
+   * @return A reference to the {@code searchGuide} Attribute Type.
+   */
+  public static AttributeType getSearchGuideAttributeType()
+  {
+    return SEARCH_GUIDE_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code seeAlso} Attribute Type
+   * which has the OID {@code 2.5.4.34}.
+   *
+   * @return A reference to the {@code seeAlso} Attribute Type.
+   */
+  public static AttributeType getSeeAlsoAttributeType()
+  {
+    return SEE_ALSO_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code serialNumber} Attribute Type
+   * which has the OID {@code 2.5.4.5}.
+   *
+   * @return A reference to the {@code serialNumber} Attribute Type.
+   */
+  public static AttributeType getSerialNumberAttributeType()
+  {
+    return SERIAL_NUMBER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code sn} Attribute Type
+   * which has the OID {@code 2.5.4.4}.
+   *
+   * @return A reference to the {@code sn} Attribute Type.
+   */
+  public static AttributeType getSNAttributeType()
+  {
+    return SN_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code street} Attribute Type
+   * which has the OID {@code 2.5.4.9}.
+   *
+   * @return A reference to the {@code street} Attribute Type.
+   */
+  public static AttributeType getStreetAttributeType()
+  {
+    return STREET_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code structuralObjectClass} Attribute Type
+   * which has the OID {@code 2.5.21.9}.
+   *
+   * @return A reference to the {@code structuralObjectClass} Attribute Type.
+   */
+  public static AttributeType getStructuralObjectClassAttributeType()
+  {
+    return STRUCTURAL_OBJECT_CLASS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code st} Attribute Type
+   * which has the OID {@code 2.5.4.8}.
+   *
+   * @return A reference to the {@code st} Attribute Type.
+   */
+  public static AttributeType getSTAttributeType()
+  {
+    return ST_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code subschemaSubentry} Attribute Type
+   * which has the OID {@code 2.5.18.10}.
+   *
+   * @return A reference to the {@code subschemaSubentry} Attribute Type.
+   */
+  public static AttributeType getSubschemaSubentryAttributeType()
+  {
+    return SUBSCHEMA_SUBENTRY_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code supportedAuthPasswordSchemes} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.4203.1.3.3}.
+   *
+   * @return A reference to the {@code supportedAuthPasswordSchemes} Attribute Type.
+   */
+  public static AttributeType getSupportedAuthPasswordSchemesAttributeType()
+  {
+    return SUPPORTED_AUTH_PASSWORD_SCHEMES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code supportedControl} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.13}.
+   *
+   * @return A reference to the {@code supportedControl} Attribute Type.
+   */
+  public static AttributeType getSupportedControlAttributeType()
+  {
+    return SUPPORTED_CONTROL_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code supportedExtension} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.7}.
+   *
+   * @return A reference to the {@code supportedExtension} Attribute Type.
+   */
+  public static AttributeType getSupportedExtensionAttributeType()
+  {
+    return SUPPORTED_EXTENSION_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code supportedFeatures} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.4203.1.3.5}.
+   *
+   * @return A reference to the {@code supportedFeatures} Attribute Type.
+   */
+  public static AttributeType getSupportedFeaturesAttributeType()
+  {
+    return SUPPORTED_FEATURES_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code supportedLDAPVersion} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.15}.
+   *
+   * @return A reference to the {@code supportedLDAPVersion} Attribute Type.
+   */
+  public static AttributeType getSupportedLDAPVersionAttributeType()
+  {
+    return SUPPORTED_LDAP_VERSION_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code supportedSASLMechanisms} Attribute Type
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.14}.
+   *
+   * @return A reference to the {@code supportedSASLMechanisms} Attribute Type.
+   */
+  public static AttributeType getSupportedSaslMechanismsAttributeType()
+  {
+    return SUPPORTED_SASL_MECHANISMS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code telephoneNumber} Attribute Type
+   * which has the OID {@code 2.5.4.20}.
+   *
+   * @return A reference to the {@code telephoneNumber} Attribute Type.
+   */
+  public static AttributeType getTelephoneNumberAttributeType()
+  {
+    return TELEPHONE_NUMBER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code teletexTerminalIdentifier} Attribute Type
+   * which has the OID {@code 2.5.4.22}.
+   *
+   * @return A reference to the {@code teletexTerminalIdentifier} Attribute Type.
+   */
+  public static AttributeType getTeletexTerminalIdentifierAttributeType()
+  {
+    return TELETEX_TERMINAL_IDENTIFIER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code telexNumber} Attribute Type
+   * which has the OID {@code 2.5.4.21}.
+   *
+   * @return A reference to the {@code telexNumber} Attribute Type.
+   */
+  public static AttributeType getTelexNumberAttributeType()
+  {
+    return TELEX_NUMBER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code title} Attribute Type
+   * which has the OID {@code 2.5.4.12}.
+   *
+   * @return A reference to the {@code title} Attribute Type.
+   */
+  public static AttributeType getTitleAttributeType()
+  {
+    return TITLE_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code uid} Attribute Type
+   * which has the OID {@code 0.9.2342.19200300.100.1.1}.
+   *
+   * @return A reference to the {@code uid} Attribute Type.
+   */
+  public static AttributeType getUIDAttributeType()
+  {
+    return UID_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code uniqueMember} Attribute Type
+   * which has the OID {@code 2.5.4.50}.
+   *
+   * @return A reference to the {@code uniqueMember} Attribute Type.
+   */
+  public static AttributeType getUniqueMemberAttributeType()
+  {
+    return UNIQUE_MEMBER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code userPassword} Attribute Type
+   * which has the OID {@code 2.5.4.35}.
+   *
+   * @return A reference to the {@code userPassword} Attribute Type.
+   */
+  public static AttributeType getUserPasswordAttributeType()
+  {
+    return USER_PASSWORD_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code vendorName} Attribute Type
+   * which has the OID {@code 1.3.6.1.1.4}.
+   *
+   * @return A reference to the {@code vendorName} Attribute Type.
+   */
+  public static AttributeType getVendorNameAttributeType()
+  {
+    return VENDOR_NAME_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code vendorVersion} Attribute Type
+   * which has the OID {@code 1.3.6.1.1.5}.
+   *
+   * @return A reference to the {@code vendorVersion} Attribute Type.
+   */
+  public static AttributeType getVendorVersionAttributeType()
+  {
+    return VENDOR_VERSION_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code x121Address} Attribute Type
+   * which has the OID {@code 2.5.4.24}.
+   *
+   * @return A reference to the {@code x121Address} Attribute Type.
+   */
+  public static AttributeType getX121AddressAttributeType()
+  {
+    return X121_ADDRESS_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code x500UniqueIdentifier} Attribute Type
+   * which has the OID {@code 2.5.4.45}.
+   *
+   * @return A reference to the {@code x500UniqueIdentifier} Attribute Type.
+   */
+  public static AttributeType getX500UniqueIdentifierAttributeType()
+  {
+    return X500_UNIQUE_IDENTIFIER_ATTRIBUTE_TYPE;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code alias} Object Class
+   * which has the OID {@code 2.5.6.1}.
+   *
+   * @return A reference to the {@code alias} Object Class.
+   */
+  public static ObjectClass getAliasObjectClass()
+  {
+    return ALIAS_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code applicationProcess} Object Class
+   * which has the OID {@code 2.5.6.11}.
+   *
+   * @return A reference to the {@code applicationProcess} Object Class.
+   */
+  public static ObjectClass getApplicationProcessObjectClass()
+  {
+    return APPLICATION_PROCESS_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code authPasswordObject} Object Class
+   * which has the OID {@code 1.3.6.1.4.1.4203.1.4.7}.
+   *
+   * @return A reference to the {@code authPasswordObject} Object Class.
+   */
+  public static ObjectClass getAuthPasswordObjectObjectClass()
+  {
+    return AUTH_PASSWORD_OBJECT_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code country} Object Class
+   * which has the OID {@code 2.5.6.2}.
+   *
+   * @return A reference to the {@code country} Object Class.
+   */
+  public static ObjectClass getCountryObjectClass()
+  {
+    return COUNTRY_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code dcObject} Object Class
+   * which has the OID {@code 1.3.6.1.4.1.1466.344}.
+   *
+   * @return A reference to the {@code dcObject} Object Class.
+   */
+  public static ObjectClass getDCObjectObjectClass()
+  {
+    return DC_OBJECT_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code device} Object Class
+   * which has the OID {@code 2.5.6.14}.
+   *
+   * @return A reference to the {@code device} Object Class.
+   */
+  public static ObjectClass getDeviceObjectClass()
+  {
+    return DEVICE_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code extensibleObject} Object Class
+   * which has the OID {@code 1.3.6.1.4.1.1466.101.120.111}.
+   *
+   * @return A reference to the {@code extensibleObject} Object Class.
+   */
+  public static ObjectClass getExtensibleObjectObjectClass()
+  {
+    return EXTENSIBLE_OBJECT_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code groupOfNames} Object Class
+   * which has the OID {@code 2.5.6.9}.
+   *
+   * @return A reference to the {@code groupOfNames} Object Class.
+   */
+  public static ObjectClass getGroupOfNamesObjectClass()
+  {
+    return GROUP_OF_NAMES_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code groupOfUniqueNames} Object Class
+   * which has the OID {@code 2.5.6.17}.
+   *
+   * @return A reference to the {@code groupOfUniqueNames} Object Class.
+   */
+  public static ObjectClass getGroupOfUniqueNamesObjectClass()
+  {
+    return GROUP_OF_UNIQUE_NAMES_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code locality} Object Class
+   * which has the OID {@code 2.5.6.3}.
+   *
+   * @return A reference to the {@code locality} Object Class.
+   */
+  public static ObjectClass getLocalityObjectClass()
+  {
+    return LOCALITY_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code organizationalPerson} Object Class
+   * which has the OID {@code 2.5.6.7}.
+   *
+   * @return A reference to the {@code organizationalPerson} Object Class.
+   */
+  public static ObjectClass getOrganizationalPersonObjectClass()
+  {
+    return ORGANIZATIONAL_PERSON_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code organizationalRole} Object Class
+   * which has the OID {@code 2.5.6.8}.
+   *
+   * @return A reference to the {@code organizationalRole} Object Class.
+   */
+  public static ObjectClass getOrganizationalRoleObjectClass()
+  {
+    return ORGANIZATIONAL_ROLE_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code organizationalUnit} Object Class
+   * which has the OID {@code 2.5.6.5}.
+   *
+   * @return A reference to the {@code organizationalUnit} Object Class.
+   */
+  public static ObjectClass getOrganizationalUnitObjectClass()
+  {
+    return ORGANIZATIONAL_UNIT_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code organization} Object Class
+   * which has the OID {@code 2.5.6.4}.
+   *
+   * @return A reference to the {@code organization} Object Class.
+   */
+  public static ObjectClass getOrganizationObjectClass()
+  {
+    return ORGANIZATION_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code person} Object Class
+   * which has the OID {@code 2.5.6.6}.
+   *
+   * @return A reference to the {@code person} Object Class.
+   */
+  public static ObjectClass getPersonObjectClass()
+  {
+    return PERSON_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code residentialPerson} Object Class
+   * which has the OID {@code 2.5.6.10}.
+   *
+   * @return A reference to the {@code residentialPerson} Object Class.
+   */
+  public static ObjectClass getResidentialPersonObjectClass()
+  {
+    return RESIDENTIAL_PERSON_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code subschema} Object Class
+   * which has the OID {@code 2.5.20.1}.
+   *
+   * @return A reference to the {@code subschema} Object Class.
+   */
+  public static ObjectClass getSubschemaObjectClass()
+  {
+    return SUBSCHEMA_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code top} Object Class
+   * which has the OID {@code 2.5.6.0}.
+   *
+   * @return A reference to the {@code top} Object Class.
+   */
+  public static ObjectClass getTopObjectClass()
+  {
+    return TOP_OBJECT_CLASS;
+  }
+
+
+
+  /**
+   * Returns a reference to the {@code uidObject} Object Class
+   * which has the OID {@code 1.3.6.1.1.3.1}.
+   *
+   * @return A reference to the {@code uidObject} Object Class.
+   */
+  public static ObjectClass getUIDObjectObjectClass()
+  {
+    return UID_OBJECT_OBJECT_CLASS;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java b/sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java
new file mode 100644
index 0000000..c48b5c2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CoreSchemaImpl.java
@@ -0,0 +1,1146 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+
+
+
+final class CoreSchemaImpl
+{
+  private static final Map<String, List<String>> X500_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("X.500"));
+  private static final Map<String, List<String>> RFC2252_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("RFC 2252"));
+  private static final Map<String, List<String>> RFC3045_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("RFC 3045"));
+  private static final Map<String, List<String>> RFC3112_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("RFC 3112"));
+  private static final Map<String, List<String>> RFC4512_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("RFC 4512"));
+  private static final Map<String, List<String>> RFC4517_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("RFC 4517"));
+  private static final Map<String, List<String>> RFC4519_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("RFC 4519"));
+  private static final Map<String, List<String>> RFC4530_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("RFC 4530"));
+  static final Map<String, List<String>> OPENDS_ORIGIN =
+      Collections.singletonMap(SCHEMA_PROPERTY_ORIGIN, Collections
+          .singletonList("OpenDS Directory Server"));
+  private static final String EMPTY_STRING = "".intern();
+  private static final Set<String> EMPTY_STRING_SET =
+      Collections.emptySet();
+
+  private static final Schema SINGLETON;
+
+  // Package private so that we can check for warnings in the unit
+  // tests.
+  static final List<Message> CORE_SCHEMA_WARNINGS =
+      new LinkedList<Message>();
+
+  static
+  {
+    final SchemaBuilder builder = new SchemaBuilder();
+    defaultSyntaxes(builder);
+    defaultMatchingRules(builder);
+    defaultAttributeTypes(builder);
+    defaultObjectClasses(builder);
+
+    addRFC4519(builder);
+    addRFC4530(builder);
+    addRFC3045(builder);
+    addRFC3112(builder);
+    addSunProprietary(builder);
+
+    SINGLETON = builder.toSchema(CORE_SCHEMA_WARNINGS).nonStrict();
+  }
+
+
+
+  static Schema getInstance()
+  {
+    return SINGLETON;
+  }
+
+
+
+  private static void addRFC3045(SchemaBuilder builder)
+  {
+    builder.addAttributeType("1.3.6.1.1.4", Collections
+        .singletonList("vendorName"), EMPTY_STRING, false, null,
+        EMR_CASE_EXACT_IA5_OID, null, null, null,
+        SYNTAX_DIRECTORY_STRING_OID, true, false, true,
+        AttributeUsage.DSA_OPERATION, RFC3045_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.1.5", Collections
+        .singletonList("vendorVersion"), EMPTY_STRING, false, null,
+        EMR_CASE_EXACT_IA5_OID, null, null, null,
+        SYNTAX_DIRECTORY_STRING_OID, true, false, true,
+        AttributeUsage.DSA_OPERATION, RFC3045_ORIGIN, false);
+  }
+
+
+
+  private static void addRFC3112(SchemaBuilder builder)
+  {
+    builder.addSyntax(SYNTAX_AUTH_PASSWORD_OID,
+        SYNTAX_AUTH_PASSWORD_DESCRIPTION, RFC3112_ORIGIN,
+        new AuthPasswordSyntaxImpl(), false);
+    builder.addMatchingRule(EMR_AUTH_PASSWORD_EXACT_OID, Collections
+        .singletonList(EMR_AUTH_PASSWORD_EXACT_NAME),
+        EMR_AUTH_PASSWORD_EXACT_DESCRIPTION, false,
+        SYNTAX_AUTH_PASSWORD_OID, RFC3112_ORIGIN,
+        new AuthPasswordExactEqualityMatchingRuleImpl(), false);
+    builder.addAttributeType("1.3.6.1.4.1.4203.1.3.3", Collections
+        .singletonList("supportedAuthPasswordSchemes"),
+        "supported password storage schemes", false, null,
+        EMR_CASE_EXACT_IA5_OID, null, null, null,
+        SYNTAX_IA5_STRING_OID, false, false, false,
+        AttributeUsage.DSA_OPERATION, RFC3112_ORIGIN, false);
+    builder.addAttributeType("1.3.6.1.4.1.4203.1.3.4", Collections
+        .singletonList("authPassword"),
+        "password authentication information", false, null,
+        EMR_AUTH_PASSWORD_EXACT_OID, null, null, null,
+        SYNTAX_AUTH_PASSWORD_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC3112_ORIGIN, false);
+    builder.addObjectClass("1.3.6.1.4.1.4203.1.4.7", Collections
+        .singletonList("authPasswordObject"),
+        "authentication password mix in class", false,
+        EMPTY_STRING_SET, EMPTY_STRING_SET, Collections
+            .singleton("authPassword"), ObjectClassType.AUXILIARY,
+        RFC3112_ORIGIN, false);
+  }
+
+
+
+  private static void addRFC4519(SchemaBuilder builder)
+  {
+    builder.addAttributeType("2.5.4.15", Collections
+        .singletonList("businessCategory"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.6", Arrays.asList("c",
+        "countryName"), EMPTY_STRING, false, "name", null, null, null,
+        null, SYNTAX_COUNTRY_STRING_OID, true, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.3", Arrays.asList("cn",
+        "commonName"), EMPTY_STRING, false, "name", null, null, null,
+        null, null, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("0.9.2342.19200300.100.1.25", Arrays
+        .asList("dc", "domainComponent"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_IA5_OID, null, SMR_CASE_IGNORE_IA5_OID, null,
+        SYNTAX_IA5_STRING_OID, true, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.13", Collections
+        .singletonList("description"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.27", Collections
+        .singletonList("destinationIndicator"), EMPTY_STRING, false,
+        null, EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_PRINTABLE_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.49", Collections
+        .singletonList("distinguishedName"), EMPTY_STRING, false, null,
+        EMR_DN_OID, null, null, null, SYNTAX_DN_OID, false, false,
+        false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.46", Collections
+        .singletonList("dnQualifier"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, OMR_CASE_IGNORE_OID, SMR_CASE_IGNORE_OID,
+        null, SYNTAX_PRINTABLE_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.47", Collections
+        .singletonList("enhancedSearchGuide"), EMPTY_STRING, false,
+        null, null, null, null, null, SYNTAX_ENHANCED_GUIDE_OID, false,
+        false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+        false);
+
+    builder.addAttributeType("2.5.4.23", Collections
+        .singletonList("facsimileTelephoneNumber"), EMPTY_STRING,
+        false, null, null, null, null, null, SYNTAX_FAXNUMBER_OID,
+        false, false, false, AttributeUsage.USER_APPLICATIONS,
+        RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.44", Collections
+        .singletonList("generationQualifier"), EMPTY_STRING, false,
+        "name", null, null, null, null, null, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.42", Collections
+        .singletonList("givenName"), EMPTY_STRING, false, "name", null,
+        null, null, null, null, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.51", Collections
+        .singletonList("houseIdentifier"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.43", Collections
+        .singletonList("initials"), EMPTY_STRING, false, "name", null,
+        null, null, null, null, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.25", Collections
+        .singletonList("internationalISDNNumber"), EMPTY_STRING, false,
+        null, EMR_NUMERIC_STRING_OID, null, SMR_NUMERIC_STRING_OID,
+        null, SYNTAX_NUMERIC_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.7", Arrays.asList("l",
+        "localityName"), EMPTY_STRING, false, "name", null, null, null,
+        null, null, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.31", Collections
+        .singletonList("member"), EMPTY_STRING, false,
+        "distinguishedName", null, null, null, null, null, false,
+        false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+        false);
+
+    builder.addAttributeType("2.5.4.41", Collections
+        .singletonList("name"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.10", Arrays.asList("o",
+        "organizationName"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.11", Arrays.asList("ou",
+        "organizationalUnitName"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.32", Collections
+        .singletonList("owner"), EMPTY_STRING, false,
+        "distinguishedName", null, null, null, null, null, false,
+        false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+        false);
+
+    builder.addAttributeType("2.5.4.19", Collections
+        .singletonList("physicalDeliveryOfficeName"), EMPTY_STRING,
+        false, null, EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID,
+        null, SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.16", Collections
+        .singletonList("postalAddress"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.17", Collections
+        .singletonList("postalCode"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.18", Collections
+        .singletonList("postOfficeBox"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.28", Collections
+        .singletonList("preferredDeliveryMethod"), EMPTY_STRING, false,
+        null, null, null, null, null, SYNTAX_DELIVERY_METHOD_OID, true,
+        false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+        false);
+
+    builder.addAttributeType("2.5.4.26", Collections
+        .singletonList("registeredAddress"), EMPTY_STRING, false,
+        "postalAddress", null, null, null, null,
+        SYNTAX_POSTAL_ADDRESS_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.33", Collections
+        .singletonList("roleOccupant"), EMPTY_STRING, false,
+        "distinguishedName", null, null, null, null, null, false,
+        false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+        false);
+
+    builder.addAttributeType("2.5.4.14", Collections
+        .singletonList("searchGuide"), EMPTY_STRING, false, null, null,
+        null, null, null, SYNTAX_GUIDE_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.34", Collections
+        .singletonList("seeAlso"), EMPTY_STRING, false,
+        "distinguishedName", null, null, null, null, null, false,
+        false, false, AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN,
+        false);
+
+    builder.addAttributeType("2.5.4.5", Collections
+        .singletonList("serialNumber"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_PRINTABLE_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.4", Arrays.asList("sn", "surname"),
+        EMPTY_STRING, false, "name", null, null, null, null, null,
+        false, false, false, AttributeUsage.USER_APPLICATIONS,
+        RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.8", Arrays.asList("st",
+        "stateOrProvinceName"), EMPTY_STRING, false, "name", null,
+        null, null, null, null, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.9", Arrays.asList("street",
+        "streetAddress"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.20", Collections
+        .singletonList("telephoneNumber"), EMPTY_STRING, false, null,
+        EMR_TELEPHONE_OID, null, SMR_TELEPHONE_OID, null,
+        SYNTAX_TELEPHONE_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.22", Collections
+        .singletonList("teletexTerminalIdentifier"), EMPTY_STRING,
+        false, null, null, null, null, null,
+        SYNTAX_TELETEX_TERM_ID_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.21", Collections
+        .singletonList("telexNumber"), EMPTY_STRING, false, null, null,
+        null, null, null, SYNTAX_TELEX_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.12", Collections
+        .singletonList("title"), EMPTY_STRING, false, "name", null,
+        null, null, null, null, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("0.9.2342.19200300.100.1.1", Arrays
+        .asList("uid", "userid"), EMPTY_STRING, false, null,
+        EMR_CASE_IGNORE_OID, null, SMR_CASE_IGNORE_OID, null,
+        SYNTAX_DIRECTORY_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.50", Collections
+        .singletonList("uniqueMember"), EMPTY_STRING, false, null,
+        EMR_UNIQUE_MEMBER_OID, null, null, null,
+        SYNTAX_NAME_AND_OPTIONAL_UID_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.35", Collections
+        .singletonList("userPassword"), EMPTY_STRING, false, null,
+        EMR_OCTET_STRING_OID, null, null, null,
+        SYNTAX_OCTET_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.24", Collections
+        .singletonList("x121Address"), EMPTY_STRING, false, null,
+        EMR_NUMERIC_STRING_OID, null, SMR_NUMERIC_STRING_OID, null,
+        SYNTAX_NUMERIC_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.45", Collections
+        .singletonList("x500UniqueIdentifier"), EMPTY_STRING, false,
+        null, EMR_BIT_STRING_OID, null, null, null,
+        SYNTAX_BIT_STRING_OID, false, false, false,
+        AttributeUsage.USER_APPLICATIONS, RFC4519_ORIGIN, false);
+
+    Set<String> attrs = new HashSet<String>();
+    attrs.add("seeAlso");
+    attrs.add("ou");
+    attrs.add("l");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.11", Collections
+        .singletonList("applicationProcess"), EMPTY_STRING, false,
+        Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+            .singleton("cn"), attrs, ObjectClassType.STRUCTURAL,
+        RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("searchGuide");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.2", Collections
+        .singletonList("country"), EMPTY_STRING, false, Collections
+        .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("c"),
+        attrs, ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    builder.addObjectClass("1.3.6.1.4.1.1466.344", Collections
+        .singletonList("dcObject"), EMPTY_STRING, false, Collections
+        .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("dc"),
+        EMPTY_STRING_SET, ObjectClassType.AUXILIARY, RFC4519_ORIGIN,
+        false);
+
+    attrs = new HashSet<String>();
+    attrs.add("serialNumber");
+    attrs.add("seeAlso");
+    attrs.add("owner");
+    attrs.add("ou");
+    attrs.add("o");
+    attrs.add("l");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.14", Collections
+        .singletonList("device"), EMPTY_STRING, false, Collections
+        .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("cn"),
+        attrs, ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    Set<String> must = new HashSet<String>();
+    must.add("member");
+    must.add("cn");
+
+    attrs = new HashSet<String>();
+    attrs.add("businessCategory");
+    attrs.add("seeAlso");
+    attrs.add("owner");
+    attrs.add("ou");
+    attrs.add("o");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.9", Collections
+        .singletonList("groupOfNames"), EMPTY_STRING, false,
+        Collections.singleton(TOP_OBJECTCLASS_NAME), must, attrs,
+        ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("businessCategory");
+    attrs.add("seeAlso");
+    attrs.add("owner");
+    attrs.add("ou");
+    attrs.add("o");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.17", Collections
+        .singletonList("groupOfUniqueNames"), EMPTY_STRING, false,
+        Collections.singleton(TOP_OBJECTCLASS_NAME), must, attrs,
+        ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("street");
+    attrs.add("seeAlso");
+    attrs.add("searchGuide");
+    attrs.add("st");
+    attrs.add("l");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.3", Collections
+        .singletonList("locality"), EMPTY_STRING, false, Collections
+        .singleton(TOP_OBJECTCLASS_NAME), EMPTY_STRING_SET, attrs,
+        ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("userPassword");
+    attrs.add("searchGuide");
+    attrs.add("seeAlso");
+    attrs.add("businessCategory");
+    attrs.add("x121Address");
+    attrs.add("registeredAddress");
+    attrs.add("destinationIndicator");
+    attrs.add("preferredDeliveryMethod");
+    attrs.add("telexNumber");
+    attrs.add("teletexTerminalIdentifier");
+    attrs.add("telephoneNumber");
+    attrs.add("internationalISDNNumber");
+    attrs.add("facsimileTelephoneNumber");
+    attrs.add("street");
+    attrs.add("postOfficeBox");
+    attrs.add("postalCode");
+    attrs.add("postalAddress");
+    attrs.add("physicalDeliveryOfficeName");
+    attrs.add("st");
+    attrs.add("l");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.4", Collections
+        .singletonList("organization"), EMPTY_STRING, false,
+        Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+            .singleton("o"), attrs, ObjectClassType.STRUCTURAL,
+        RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("title");
+    attrs.add("x121Address");
+    attrs.add("registeredAddress");
+    attrs.add("destinationIndicator");
+    attrs.add("preferredDeliveryMethod");
+    attrs.add("telexNumber");
+    attrs.add("teletexTerminalIdentifier");
+    attrs.add("telephoneNumber");
+    attrs.add("internationalISDNNumber");
+    attrs.add("facsimileTelephoneNumber");
+    attrs.add("street");
+    attrs.add("postOfficeBox");
+    attrs.add("postalCode");
+    attrs.add("postalAddress");
+    attrs.add("physicalDeliveryOfficeName");
+    attrs.add("ou");
+    attrs.add("st");
+    attrs.add("l");
+
+    builder.addObjectClass("2.5.6.7", Collections
+        .singletonList("organizationalPerson"), EMPTY_STRING, false,
+        Collections.singleton("person"), EMPTY_STRING_SET, attrs,
+        ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("x121Address");
+    attrs.add("registeredAddress");
+    attrs.add("destinationIndicator");
+    attrs.add("preferredDeliveryMethod");
+    attrs.add("telexNumber");
+    attrs.add("teletexTerminalIdentifier");
+    attrs.add("telephoneNumber");
+    attrs.add("internationalISDNNumber");
+    attrs.add("facsimileTelephoneNumber");
+    attrs.add("seeAlso");
+    attrs.add("roleOccupant");
+    attrs.add("preferredDeliveryMethod");
+    attrs.add("street");
+    attrs.add("postOfficeBox");
+    attrs.add("postalCode");
+    attrs.add("postalAddress");
+    attrs.add("physicalDeliveryOfficeName");
+    attrs.add("ou");
+    attrs.add("st");
+    attrs.add("l");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.8", Collections
+        .singletonList("organizationalRole"), EMPTY_STRING, false,
+        Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+            .singleton("cn"), attrs, ObjectClassType.STRUCTURAL,
+        RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("businessCategory");
+    attrs.add("description");
+    attrs.add("destinationIndicator");
+    attrs.add("facsimileTelephoneNumber");
+    attrs.add("internationalISDNNumber");
+    attrs.add("l");
+    attrs.add("physicalDeliveryOfficeName");
+    attrs.add("postalAddress");
+    attrs.add("postalCode");
+    attrs.add("postOfficeBox");
+    attrs.add("preferredDeliveryMethod");
+    attrs.add("registeredAddress");
+    attrs.add("searchGuide");
+    attrs.add("seeAlso");
+    attrs.add("st");
+    attrs.add("street");
+    attrs.add("telephoneNumber");
+    attrs.add("teletexTerminalIdentifier");
+    attrs.add("telexNumber");
+    attrs.add("userPassword");
+    attrs.add("x121Address");
+
+    builder.addObjectClass("2.5.6.5", Collections
+        .singletonList("organizationalUnit"), EMPTY_STRING, false,
+        Collections.singleton(TOP_OBJECTCLASS_NAME), Collections
+            .singleton("ou"), attrs, ObjectClassType.STRUCTURAL,
+        RFC4519_ORIGIN, false);
+
+    must = new HashSet<String>();
+    must.add("sn");
+    must.add("cn");
+
+    attrs = new HashSet<String>();
+    attrs.add("userPassword");
+    attrs.add("telephoneNumber");
+    attrs.add("destinationIndicator");
+    attrs.add("seeAlso");
+    attrs.add("description");
+
+    builder.addObjectClass("2.5.6.6", Collections
+        .singletonList("person"), EMPTY_STRING, false, Collections
+        .singleton(TOP_OBJECTCLASS_NAME), must, attrs,
+        ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    attrs = new HashSet<String>();
+    attrs.add("businessCategory");
+    attrs.add("x121Address");
+    attrs.add("registeredAddress");
+    attrs.add("destinationIndicator");
+    attrs.add("preferredDeliveryMethod");
+    attrs.add("telexNumber");
+    attrs.add("teletexTerminalIdentifier");
+    attrs.add("telephoneNumber");
+    attrs.add("internationalISDNNumber");
+    attrs.add("facsimileTelephoneNumber");
+    attrs.add("preferredDeliveryMethod");
+    attrs.add("street");
+    attrs.add("postOfficeBox");
+    attrs.add("postalCode");
+    attrs.add("postalAddress");
+    attrs.add("physicalDeliveryOfficeName");
+    attrs.add("st");
+    attrs.add("l");
+
+    builder.addObjectClass("2.5.6.10", Collections
+        .singletonList("residentialPerson"), EMPTY_STRING, false,
+        Collections.singleton("person"), Collections.singleton("l"),
+        attrs, ObjectClassType.STRUCTURAL, RFC4519_ORIGIN, false);
+
+    builder.addObjectClass("1.3.6.1.1.3.1", Collections
+        .singletonList("uidObject"), EMPTY_STRING, false, Collections
+        .singleton(TOP_OBJECTCLASS_NAME), Collections.singleton("uid"),
+        attrs, ObjectClassType.AUXILIARY, RFC4519_ORIGIN, false);
+  }
+
+
+
+  private static void addRFC4530(SchemaBuilder builder)
+  {
+    builder.addSyntax(SYNTAX_UUID_OID, SYNTAX_UUID_DESCRIPTION,
+        RFC4530_ORIGIN, new UUIDSyntaxImpl(), false);
+    builder.addMatchingRule(EMR_UUID_OID, Collections
+        .singletonList(EMR_UUID_NAME), EMPTY_STRING, false,
+        SYNTAX_UUID_OID, RFC4530_ORIGIN,
+        new UUIDEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(OMR_UUID_OID, Collections
+        .singletonList(OMR_UUID_NAME), EMPTY_STRING, false,
+        SYNTAX_UUID_OID, RFC4530_ORIGIN,
+        new UUIDOrderingMatchingRuleImpl(), false);
+    builder.addAttributeType("1.3.6.1.1.16.4", Collections
+        .singletonList("entryUUID"), "UUID of the entry", false, null,
+        EMR_UUID_OID, OMR_UUID_OID, null, null, SYNTAX_UUID_OID, true,
+        false, true, AttributeUsage.DIRECTORY_OPERATION,
+        RFC4530_ORIGIN, false);
+  }
+
+
+
+  private static void addSunProprietary(SchemaBuilder builder)
+  {
+    builder.addSyntax(SYNTAX_USER_PASSWORD_OID,
+        SYNTAX_USER_PASSWORD_DESCRIPTION, OPENDS_ORIGIN,
+        new UserPasswordSyntaxImpl(), false);
+    builder.addMatchingRule(EMR_USER_PASSWORD_EXACT_OID, Collections
+        .singletonList(EMR_USER_PASSWORD_EXACT_NAME),
+        EMR_USER_PASSWORD_EXACT_DESCRIPTION, false,
+        SYNTAX_USER_PASSWORD_OID, OPENDS_ORIGIN,
+        new UserPasswordExactEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(AMR_DOUBLE_METAPHONE_OID, Collections
+        .singletonList(AMR_DOUBLE_METAPHONE_NAME),
+        AMR_DOUBLE_METAPHONE_DESCRIPTION, false,
+        SYNTAX_DIRECTORY_STRING_OID, OPENDS_ORIGIN,
+        new DoubleMetaphoneApproximateMatchingRuleImpl(), false);
+
+  }
+
+
+
+  private static void defaultAttributeTypes(SchemaBuilder builder)
+  {
+    builder.addAttributeType("2.5.4.0", Collections
+        .singletonList("objectClass"), EMPTY_STRING, false, null,
+        EMR_OID_NAME, null, null, null, SYNTAX_OID_OID, false, false,
+        false, AttributeUsage.USER_APPLICATIONS, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.4.1", Collections
+        .singletonList("aliasedObjectName"), EMPTY_STRING, false, null,
+        EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true, false,
+        false, AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN,
+        false);
+
+    builder.addAttributeType("2.5.18.1", Collections
+        .singletonList("createTimestamp"), EMPTY_STRING, false, null,
+        EMR_GENERALIZED_TIME_NAME, OMR_GENERALIZED_TIME_NAME, null,
+        null, SYNTAX_GENERALIZED_TIME_OID, true, false, true,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.18.2", Collections
+        .singletonList("modifyTimestamp"), EMPTY_STRING, false, null,
+        EMR_GENERALIZED_TIME_NAME, OMR_GENERALIZED_TIME_NAME, null,
+        null, SYNTAX_GENERALIZED_TIME_OID, true, false, true,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder
+        .addAttributeType("2.5.18.3", Collections
+            .singletonList("creatorsName"), EMPTY_STRING, false, null,
+            EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true, false,
+            true, AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN,
+            false);
+
+    builder
+        .addAttributeType("2.5.18.4", Collections
+            .singletonList("modifiersName"), EMPTY_STRING, false, null,
+            EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true, false,
+            true, AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN,
+            false);
+
+    builder
+        .addAttributeType("2.5.18.10", Collections
+            .singletonList("subschemaSubentry"), EMPTY_STRING, false,
+            null, EMR_DN_NAME, null, null, null, SYNTAX_DN_OID, true,
+            false, true, AttributeUsage.DIRECTORY_OPERATION,
+            RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.5", Collections
+        .singletonList("attributeTypes"), EMPTY_STRING, false, null,
+        EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_ATTRIBUTE_TYPE_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.6", Collections
+        .singletonList("objectClasses"), EMPTY_STRING, false, null,
+        EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_OBJECTCLASS_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.4", Collections
+        .singletonList("matchingRules"), EMPTY_STRING, false, null,
+        EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_MATCHING_RULE_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.8", Collections
+        .singletonList("matchingRuleUse"), EMPTY_STRING, false, null,
+        EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_MATCHING_RULE_USE_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.9", Collections
+        .singletonList("structuralObjectClass"), EMPTY_STRING, false,
+        null, EMR_OID_NAME, null, null, null, SYNTAX_OID_OID, true,
+        false, true, AttributeUsage.DIRECTORY_OPERATION,
+        RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.10", Collections
+        .singletonList("governingStructureRule"), EMPTY_STRING, false,
+        null, EMR_INTEGER_NAME, null, null, null, SYNTAX_INTEGER_OID,
+        true, false, true, AttributeUsage.DIRECTORY_OPERATION,
+        RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.1466.101.120.5", Collections
+        .singletonList("namingContexts"), EMPTY_STRING, false, null,
+        null, null, null, null, SYNTAX_DN_OID, false, false, false,
+        AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.1466.101.120.6", Collections
+        .singletonList("altServer"), EMPTY_STRING, false, null, null,
+        null, null, null, SYNTAX_IA5_STRING_OID, false, false, false,
+        AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.1466.101.120.7", Collections
+        .singletonList("supportedExtension"), EMPTY_STRING, false,
+        null, null, null, null, null, SYNTAX_OID_OID, false, false,
+        false, AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.1466.101.120.13", Collections
+        .singletonList("supportedControl"), EMPTY_STRING, false, null,
+        null, null, null, null, SYNTAX_OID_OID, false, false, false,
+        AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.1466.101.120.14", Collections
+        .singletonList("supportedSASLMechanisms"), EMPTY_STRING, false,
+        null, null, null, null, null, SYNTAX_DIRECTORY_STRING_OID,
+        false, false, false, AttributeUsage.DSA_OPERATION,
+        RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.4203.1.3.5", Collections
+        .singletonList("supportedFeatures"), EMPTY_STRING, false, null,
+        EMR_OID_NAME, null, null, null, SYNTAX_OID_OID, false, false,
+        false, AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.1466.101.120.15", Collections
+        .singletonList("supportedLDAPVersion"), EMPTY_STRING, false,
+        null, null, null, null, null, SYNTAX_INTEGER_OID, false, false,
+        false, AttributeUsage.DSA_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("1.3.6.1.4.1.1466.101.120.16", Collections
+        .singletonList("ldapSyntaxes"), EMPTY_STRING, false, null,
+        EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_LDAP_SYNTAX_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.1", Collections
+        .singletonList("ditStructureRules"), EMPTY_STRING, false, null,
+        EMR_INTEGER_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_DIT_STRUCTURE_RULE_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.7", Collections
+        .singletonList("nameForms"), EMPTY_STRING, false, null,
+        EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_NAME_FORM_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+
+    builder.addAttributeType("2.5.21.2", Collections
+        .singletonList("ditContentRules"), EMPTY_STRING, false, null,
+        EMR_OID_FIRST_COMPONENT_NAME, null, null, null,
+        SYNTAX_DIT_CONTENT_RULE_OID, false, false, false,
+        AttributeUsage.DIRECTORY_OPERATION, RFC4512_ORIGIN, false);
+  }
+
+
+
+  private static void defaultMatchingRules(SchemaBuilder builder)
+  {
+    builder.addMatchingRule(EMR_BIT_STRING_OID, Collections
+        .singletonList(EMR_BIT_STRING_NAME), EMPTY_STRING, false,
+        SYNTAX_BIT_STRING_OID, RFC4512_ORIGIN,
+        new BitStringEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_BOOLEAN_OID, Collections
+        .singletonList(EMR_BOOLEAN_NAME), EMPTY_STRING, false,
+        SYNTAX_BOOLEAN_OID, RFC4512_ORIGIN,
+        new BooleanEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_CASE_EXACT_IA5_OID, Collections
+        .singletonList(EMR_CASE_EXACT_IA5_NAME), EMPTY_STRING, false,
+        SYNTAX_IA5_STRING_OID, RFC4512_ORIGIN,
+        new CaseExactIA5EqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(SMR_CASE_EXACT_IA5_OID, Collections
+        .singletonList(SMR_CASE_EXACT_IA5_NAME), EMPTY_STRING, false,
+        SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+        new CaseExactIA5SubstringMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_CASE_EXACT_OID, Collections
+        .singletonList(EMR_CASE_EXACT_NAME), EMPTY_STRING, false,
+        SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+        new CaseExactEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(OMR_CASE_EXACT_OID, Collections
+        .singletonList(OMR_CASE_EXACT_NAME), EMPTY_STRING, false,
+        SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+        new CaseExactOrderingMatchingRuleImpl(), false);
+    builder.addMatchingRule(SMR_CASE_EXACT_OID, Collections
+        .singletonList(SMR_CASE_EXACT_NAME), EMPTY_STRING, false,
+        SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+        new CaseExactSubstringMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_CASE_IGNORE_IA5_OID, Collections
+        .singletonList(EMR_CASE_IGNORE_IA5_NAME), EMPTY_STRING, false,
+        SYNTAX_IA5_STRING_OID, RFC4512_ORIGIN,
+        new CaseIgnoreIA5EqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(SMR_CASE_IGNORE_IA5_OID, Collections
+        .singletonList(SMR_CASE_IGNORE_IA5_NAME), EMPTY_STRING, false,
+        SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+        new CaseIgnoreIA5SubstringMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_CASE_IGNORE_LIST_OID, Collections
+        .singletonList(EMR_CASE_IGNORE_LIST_NAME), EMPTY_STRING, false,
+        SYNTAX_POSTAL_ADDRESS_OID, RFC4512_ORIGIN,
+        new CaseIgnoreListEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(SMR_CASE_IGNORE_LIST_OID, Collections
+        .singletonList(SMR_CASE_IGNORE_LIST_NAME), EMPTY_STRING, false,
+        SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+        new CaseIgnoreListSubstringMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_CASE_IGNORE_OID, Collections
+        .singletonList(EMR_CASE_IGNORE_NAME), EMPTY_STRING, false,
+        SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+        new CaseIgnoreEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(OMR_CASE_IGNORE_OID, Collections
+        .singletonList(OMR_CASE_IGNORE_NAME), EMPTY_STRING, false,
+        SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+        new CaseIgnoreOrderingMatchingRuleImpl(), false);
+    builder.addMatchingRule(SMR_CASE_IGNORE_OID, Collections
+        .singletonList(SMR_CASE_IGNORE_NAME), EMPTY_STRING, false,
+        SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+        new CaseIgnoreSubstringMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_DIRECTORY_STRING_FIRST_COMPONENT_OID,
+        Collections
+            .singletonList(EMR_DIRECTORY_STRING_FIRST_COMPONENT_NAME),
+        EMPTY_STRING, false, SYNTAX_DIRECTORY_STRING_OID,
+        RFC4512_ORIGIN,
+        new DirectoryStringFirstComponentEqualityMatchingRuleImpl(),
+        false);
+    builder.addMatchingRule(EMR_DN_OID, Collections
+        .singletonList(EMR_DN_NAME), EMPTY_STRING, false,
+        SYNTAX_DN_OID, RFC4512_ORIGIN,
+        new DistinguishedNameEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_GENERALIZED_TIME_OID, Collections
+        .singletonList(EMR_GENERALIZED_TIME_NAME), EMPTY_STRING, false,
+        SYNTAX_GENERALIZED_TIME_OID, RFC4512_ORIGIN,
+        new GeneralizedTimeEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(OMR_GENERALIZED_TIME_OID, Collections
+        .singletonList(OMR_GENERALIZED_TIME_NAME), EMPTY_STRING, false,
+        SYNTAX_GENERALIZED_TIME_OID, RFC4512_ORIGIN,
+        new GeneralizedTimeOrderingMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_INTEGER_FIRST_COMPONENT_OID,
+        Collections.singletonList(EMR_INTEGER_FIRST_COMPONENT_NAME),
+        EMPTY_STRING, false, SYNTAX_INTEGER_OID, RFC4512_ORIGIN,
+        new IntegerFirstComponentEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_INTEGER_OID, Collections
+        .singletonList(EMR_INTEGER_NAME), EMPTY_STRING, false,
+        SYNTAX_INTEGER_OID, RFC4512_ORIGIN,
+        new IntegerEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(OMR_INTEGER_OID, Collections
+        .singletonList(OMR_INTEGER_NAME), EMPTY_STRING, false,
+        SYNTAX_INTEGER_OID, RFC4512_ORIGIN,
+        new IntegerOrderingMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_KEYWORD_OID, Collections
+        .singletonList(EMR_KEYWORD_NAME), EMPTY_STRING, false,
+        SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+        new KeywordEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_NUMERIC_STRING_OID, Collections
+        .singletonList(EMR_NUMERIC_STRING_NAME), EMPTY_STRING, false,
+        SYNTAX_NUMERIC_STRING_OID, RFC4512_ORIGIN,
+        new NumericStringEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(OMR_NUMERIC_STRING_OID, Collections
+        .singletonList(OMR_NUMERIC_STRING_NAME), EMPTY_STRING, false,
+        SYNTAX_NUMERIC_STRING_OID, RFC4512_ORIGIN,
+        new NumericStringOrderingMatchingRuleImpl(), false);
+    builder.addMatchingRule(SMR_NUMERIC_STRING_OID, Collections
+        .singletonList(SMR_NUMERIC_STRING_NAME), EMPTY_STRING, false,
+        SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+        new NumericStringSubstringMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_OID_FIRST_COMPONENT_OID, Collections
+        .singletonList(EMR_OID_FIRST_COMPONENT_NAME), EMPTY_STRING,
+        false, SYNTAX_OID_OID, RFC4512_ORIGIN,
+        new ObjectIdentifierFirstComponentEqualityMatchingRuleImpl(),
+        false);
+    builder.addMatchingRule(EMR_OID_OID, Collections
+        .singletonList(EMR_OID_NAME), EMPTY_STRING, false,
+        SYNTAX_OID_OID, RFC4512_ORIGIN,
+        new ObjectIdentifierEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_OCTET_STRING_OID, Collections
+        .singletonList(EMR_OCTET_STRING_NAME), EMPTY_STRING, false,
+        SYNTAX_OCTET_STRING_OID, RFC4512_ORIGIN,
+        new OctetStringEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(OMR_OCTET_STRING_OID, Collections
+        .singletonList(OMR_OCTET_STRING_NAME), EMPTY_STRING, false,
+        SYNTAX_OCTET_STRING_OID, RFC4512_ORIGIN,
+        new OctetStringOrderingMatchingRuleImpl(), false);
+    // SMR octet string is not in any LDAP RFC and its from X.500
+    builder.addMatchingRule(SMR_OCTET_STRING_OID, Collections
+        .singletonList(SMR_OCTET_STRING_NAME), EMPTY_STRING, false,
+        SYNTAX_OCTET_STRING_OID, X500_ORIGIN,
+        new OctetStringSubstringMatchingRuleImpl(), false);
+    // Depreciated in RFC 4512
+    builder.addMatchingRule(EMR_PROTOCOL_INFORMATION_OID, Collections
+        .singletonList(EMR_PROTOCOL_INFORMATION_NAME), EMPTY_STRING,
+        false, SYNTAX_PROTOCOL_INFORMATION_OID, RFC2252_ORIGIN,
+        new ProtocolInformationEqualityMatchingRuleImpl(), false);
+    // Depreciated in RFC 4512
+    builder.addMatchingRule(EMR_PRESENTATION_ADDRESS_OID, Collections
+        .singletonList(EMR_PRESENTATION_ADDRESS_NAME), EMPTY_STRING,
+        false, SYNTAX_PRESENTATION_ADDRESS_OID, RFC2252_ORIGIN,
+        new PresentationAddressEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_TELEPHONE_OID, Collections
+        .singletonList(EMR_TELEPHONE_NAME), EMPTY_STRING, false,
+        SYNTAX_TELEPHONE_OID, RFC4512_ORIGIN,
+        new TelephoneNumberEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(SMR_TELEPHONE_OID, Collections
+        .singletonList(SMR_TELEPHONE_NAME), EMPTY_STRING, false,
+        SYNTAX_SUBSTRING_ASSERTION_OID, RFC4512_ORIGIN,
+        new TelephoneNumberSubstringMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_UNIQUE_MEMBER_OID, Collections
+        .singletonList(EMR_UNIQUE_MEMBER_NAME), EMPTY_STRING, false,
+        SYNTAX_NAME_AND_OPTIONAL_UID_OID, RFC4512_ORIGIN,
+        new UniqueMemberEqualityMatchingRuleImpl(), false);
+    builder.addMatchingRule(EMR_WORD_OID, Collections
+        .singletonList(EMR_WORD_NAME), EMPTY_STRING, false,
+        SYNTAX_DIRECTORY_STRING_OID, RFC4512_ORIGIN,
+        new WordEqualityMatchingRuleImpl(), false);
+  }
+
+
+
+  private static void defaultObjectClasses(SchemaBuilder builder)
+  {
+    builder.addObjectClass(TOP_OBJECTCLASS_OID, Collections
+        .singletonList(TOP_OBJECTCLASS_NAME),
+        TOP_OBJECTCLASS_DESCRIPTION, false, EMPTY_STRING_SET,
+        Collections.singleton("objectClass"), EMPTY_STRING_SET,
+        ObjectClassType.ABSTRACT, RFC4512_ORIGIN, false);
+
+    builder.addObjectClass("2.5.6.1", Collections
+        .singletonList("alias"), EMPTY_STRING, false, Collections
+        .singleton("top"), Collections.singleton("aliasedObjectName"),
+        EMPTY_STRING_SET, ObjectClassType.STRUCTURAL, RFC4512_ORIGIN,
+        false);
+
+    builder.addObjectClass(EXTENSIBLE_OBJECT_OBJECTCLASS_OID,
+        Collections.singletonList(EXTENSIBLE_OBJECT_OBJECTCLASS_NAME),
+        EMPTY_STRING, false, Collections
+            .singleton(TOP_OBJECTCLASS_NAME), EMPTY_STRING_SET,
+        EMPTY_STRING_SET, ObjectClassType.AUXILIARY, RFC4512_ORIGIN,
+        false);
+
+    final Set<String> subschemaAttrs = new HashSet<String>();
+    subschemaAttrs.add("dITStructureRules");
+    subschemaAttrs.add("nameForms");
+    subschemaAttrs.add("ditContentRules");
+    subschemaAttrs.add("objectClasses");
+    subschemaAttrs.add("attributeTypes");
+    subschemaAttrs.add("matchingRules");
+    subschemaAttrs.add("matchingRuleUse");
+
+    builder.addObjectClass("2.5.20.1", Collections
+        .singletonList("subschema"), EMPTY_STRING, false, Collections
+        .singleton(TOP_OBJECTCLASS_NAME), EMPTY_STRING_SET,
+        subschemaAttrs, ObjectClassType.AUXILIARY, RFC4512_ORIGIN,
+        false);
+  }
+
+
+
+  private static void defaultSyntaxes(SchemaBuilder builder)
+  {
+    // All RFC 4512 / 4517
+    builder.addSyntax(SYNTAX_ATTRIBUTE_TYPE_OID,
+        SYNTAX_ATTRIBUTE_TYPE_DESCRIPTION, RFC4512_ORIGIN,
+        new AttributeTypeSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_BINARY_OID, SYNTAX_BINARY_DESCRIPTION,
+        RFC4512_ORIGIN, new BinarySyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_BIT_STRING_OID,
+        SYNTAX_BIT_STRING_DESCRIPTION, RFC4512_ORIGIN,
+        new BitStringSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_BOOLEAN_OID, SYNTAX_BOOLEAN_DESCRIPTION,
+        RFC4512_ORIGIN, new BooleanSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_CERTLIST_OID, SYNTAX_CERTLIST_DESCRIPTION,
+        RFC4512_ORIGIN, new CertificateListSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_CERTPAIR_OID, SYNTAX_CERTPAIR_DESCRIPTION,
+        RFC4512_ORIGIN, new CertificatePairSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_CERTIFICATE_OID,
+        SYNTAX_CERTIFICATE_DESCRIPTION, RFC4512_ORIGIN,
+        new CertificateSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_COUNTRY_STRING_OID,
+        SYNTAX_COUNTRY_STRING_DESCRIPTION, RFC4512_ORIGIN,
+        new CountryStringSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_DELIVERY_METHOD_OID,
+        SYNTAX_DELIVERY_METHOD_DESCRIPTION, RFC4512_ORIGIN,
+        new DeliveryMethodSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_DIRECTORY_STRING_OID,
+        SYNTAX_DIRECTORY_STRING_DESCRIPTION, RFC4512_ORIGIN,
+        new DirectoryStringSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_DIT_CONTENT_RULE_OID,
+        SYNTAX_DIT_CONTENT_RULE_DESCRIPTION, RFC4512_ORIGIN,
+        new DITContentRuleSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_DIT_STRUCTURE_RULE_OID,
+        SYNTAX_DIT_STRUCTURE_RULE_DESCRIPTION, RFC4512_ORIGIN,
+        new DITStructureRuleSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_DN_OID, SYNTAX_DN_DESCRIPTION,
+        RFC4512_ORIGIN, new DistinguishedNameSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_ENHANCED_GUIDE_OID,
+        SYNTAX_ENHANCED_GUIDE_DESCRIPTION, RFC4512_ORIGIN,
+        new EnhancedGuideSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_FAXNUMBER_OID,
+        SYNTAX_FAXNUMBER_DESCRIPTION, RFC4512_ORIGIN,
+        new FacsimileNumberSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_FAX_OID, SYNTAX_FAX_DESCRIPTION,
+        RFC4512_ORIGIN, new FaxSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_GENERALIZED_TIME_OID,
+        SYNTAX_GENERALIZED_TIME_DESCRIPTION, RFC4512_ORIGIN,
+        new GeneralizedTimeSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_GUIDE_OID, SYNTAX_GUIDE_DESCRIPTION,
+        RFC4512_ORIGIN, new GuideSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_IA5_STRING_OID,
+        SYNTAX_IA5_STRING_DESCRIPTION, RFC4512_ORIGIN,
+        new IA5StringSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_INTEGER_OID, SYNTAX_INTEGER_DESCRIPTION,
+        RFC4512_ORIGIN, new IntegerSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_JPEG_OID, SYNTAX_JPEG_DESCRIPTION,
+        RFC4512_ORIGIN, new JPEGSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_MATCHING_RULE_OID,
+        SYNTAX_MATCHING_RULE_DESCRIPTION, RFC4512_ORIGIN,
+        new MatchingRuleSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_MATCHING_RULE_USE_OID,
+        SYNTAX_MATCHING_RULE_USE_DESCRIPTION, RFC4512_ORIGIN,
+        new MatchingRuleUseSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_LDAP_SYNTAX_OID,
+        SYNTAX_LDAP_SYNTAX_DESCRIPTION, RFC4512_ORIGIN,
+        new LDAPSyntaxDescriptionSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_NAME_AND_OPTIONAL_UID_OID,
+        SYNTAX_NAME_AND_OPTIONAL_UID_DESCRIPTION, RFC4517_ORIGIN,
+        new NameAndOptionalUIDSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_NAME_FORM_OID,
+        SYNTAX_NAME_FORM_DESCRIPTION, RFC4512_ORIGIN,
+        new NameFormSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_NUMERIC_STRING_OID,
+        SYNTAX_NUMERIC_STRING_DESCRIPTION, RFC4512_ORIGIN,
+        new NumericStringSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_OBJECTCLASS_OID,
+        SYNTAX_OBJECTCLASS_DESCRIPTION, RFC4512_ORIGIN,
+        new ObjectClassSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_OCTET_STRING_OID,
+        SYNTAX_OCTET_STRING_DESCRIPTION, RFC4512_ORIGIN,
+        new OctetStringSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_OID_OID, SYNTAX_OID_DESCRIPTION,
+        RFC4512_ORIGIN, new OIDSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_OTHER_MAILBOX_OID,
+        SYNTAX_OTHER_MAILBOX_DESCRIPTION, RFC4512_ORIGIN,
+        new OtherMailboxSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_POSTAL_ADDRESS_OID,
+        SYNTAX_POSTAL_ADDRESS_DESCRIPTION, RFC4512_ORIGIN,
+        new PostalAddressSyntaxImpl(), false);
+    // Depreciated in RFC 4512
+    builder.addSyntax(SYNTAX_PRESENTATION_ADDRESS_OID,
+        SYNTAX_PRESENTATION_ADDRESS_DESCRIPTION, RFC2252_ORIGIN,
+        new PresentationAddressSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_PRINTABLE_STRING_OID,
+        SYNTAX_PRINTABLE_STRING_DESCRIPTION, RFC4512_ORIGIN,
+        new PrintableStringSyntaxImpl(), false);
+    // Depreciated in RFC 4512
+    builder.addSyntax(SYNTAX_PROTOCOL_INFORMATION_OID,
+        SYNTAX_PROTOCOL_INFORMATION_DESCRIPTION, RFC2252_ORIGIN,
+        new ProtocolInformationSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_SUBSTRING_ASSERTION_OID,
+        SYNTAX_SUBSTRING_ASSERTION_DESCRIPTION, RFC4512_ORIGIN,
+        new SubstringAssertionSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_SUPPORTED_ALGORITHM_OID,
+        SYNTAX_SUPPORTED_ALGORITHM_DESCRIPTION, RFC4512_ORIGIN,
+        new SupportedAlgorithmSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_TELEPHONE_OID,
+        SYNTAX_TELEPHONE_DESCRIPTION, RFC4512_ORIGIN,
+        new TelephoneNumberSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_TELETEX_TERM_ID_OID,
+        SYNTAX_TELETEX_TERM_ID_DESCRIPTION, RFC4512_ORIGIN,
+        new TeletexTerminalIdentifierSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_TELEX_OID, SYNTAX_TELEX_DESCRIPTION,
+        RFC4512_ORIGIN, new TelexNumberSyntaxImpl(), false);
+    builder.addSyntax(SYNTAX_UTC_TIME_OID, SYNTAX_UTC_TIME_DESCRIPTION,
+        RFC4512_ORIGIN, new UTCTimeSyntaxImpl(), false);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java
new file mode 100644
index 0000000..b3cbe22
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/CountryStringSyntaxImpl.java
@@ -0,0 +1,136 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_COUNTRY_STRING_INVALID_LENGTH;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_COUNTRY_STRING_NOT_PRINTABLE;
+import static org.opends.sdk.schema.SchemaConstants.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the country string attribute syntax, which should
+ * be a two-character ISO 3166 country code. However, for
+ * maintainability, it will accept any value consisting entirely of two
+ * printable characters. In most ways, it will behave like the directory
+ * string attribute syntax.
+ */
+final class CountryStringSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_COUNTRY_STRING_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String stringValue = toLowerCase(value.toString());
+    if (stringValue.length() != 2)
+    {
+      invalidReason
+          .append(ERR_ATTR_SYNTAX_COUNTRY_STRING_INVALID_LENGTH
+              .get(stringValue));
+      return false;
+    }
+
+    if (!PrintableStringSyntaxImpl.isPrintableCharacter(stringValue
+        .charAt(0))
+        || !PrintableStringSyntaxImpl.isPrintableCharacter(stringValue
+            .charAt(1)))
+    {
+      invalidReason.append(ERR_ATTR_SYNTAX_COUNTRY_STRING_NOT_PRINTABLE
+          .get(stringValue));
+      return false;
+    }
+
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITContentRule.java b/sdk/src/org/opends/sdk/schema/DITContentRule.java
new file mode 100644
index 0000000..db5309b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITContentRule.java
@@ -0,0 +1,623 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a DIT content rule, which defines the set of
+ * allowed, required, and prohibited attributes for entries with a given
+ * structural objectclass, and also indicates which auxiliary classes
+ * that may be included in the entry.
+ */
+public final class DITContentRule extends SchemaElement
+{
+  // The structural objectclass for this DIT content rule.
+  private final String structuralClassOID;
+
+  // The set of user defined names for this definition.
+  private final List<String> names;
+
+  // Indicates whether this definition is declared "obsolete".
+  private final boolean isObsolete;
+
+  // The set of auxiliary objectclasses that entries with this content
+  // rule may contain, in a mapping between the objectclass and the
+  // user-defined name for that class.
+  private final Set<String> auxiliaryClassOIDs;
+
+  // The set of optional attribute types for this DIT content rule.
+  private final Set<String> optionalAttributeOIDs;
+
+  // The set of prohibited attribute types for this DIT content rule.
+  private final Set<String> prohibitedAttributeOIDs;
+
+  // The set of required attribute types for this DIT content rule.
+  private final Set<String> requiredAttributeOIDs;
+
+  // The definition string used to create this objectclass.
+  private final String definition;
+
+  private ObjectClass structuralClass;
+  private Set<ObjectClass> auxiliaryClasses = Collections.emptySet();
+  private Set<AttributeType> optionalAttributes =
+      Collections.emptySet();
+  private Set<AttributeType> prohibitedAttributes =
+      Collections.emptySet();
+  private Set<AttributeType> requiredAttributes =
+      Collections.emptySet();
+
+
+
+  DITContentRule(String structuralClassOID, List<String> names,
+      String description, boolean obsolete,
+      Set<String> auxiliaryClassOIDs,
+      Set<String> optionalAttributeOIDs,
+      Set<String> prohibitedAttributeOIDs,
+      Set<String> requiredAttributeOIDs,
+      Map<String, List<String>> extraProperties, String definition)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(structuralClassOID, names);
+    Validator.ensureNotNull(auxiliaryClassOIDs, optionalAttributeOIDs,
+        prohibitedAttributeOIDs, requiredAttributeOIDs);
+    this.names = names;
+    this.isObsolete = obsolete;
+    this.structuralClassOID = structuralClassOID;
+    this.auxiliaryClassOIDs = auxiliaryClassOIDs;
+    this.optionalAttributeOIDs = optionalAttributeOIDs;
+    this.prohibitedAttributeOIDs = prohibitedAttributeOIDs;
+    this.requiredAttributeOIDs = requiredAttributeOIDs;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+  }
+
+
+
+  /**
+   * Retrieves the set of auxiliary objectclasses that may be used for
+   * entries associated with this DIT content rule.
+   * 
+   * @return The set of auxiliary objectclasses that may be used for
+   *         entries associated with this DIT content rule.
+   */
+  public Iterable<ObjectClass> getAuxiliaryClasses()
+  {
+    return auxiliaryClasses;
+  }
+
+
+
+  /**
+   * Retrieves the name or structural class OID for this schema
+   * definition. If it has one or more names, then the primary name will
+   * be returned. If it does not have any names, then the OID will be
+   * returned.
+   * 
+   * @return The name or OID for this schema definition.
+   */
+  public String getNameOrOID()
+  {
+    if (names.isEmpty())
+    {
+      return structuralClassOID;
+    }
+    return names.get(0);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the set of user-defined names that may
+   * be used to reference this schema definition.
+   * 
+   * @return Returns an iterable over the set of user-defined names that
+   *         may be used to reference this schema definition.
+   */
+  public Iterable<String> getNames()
+  {
+    return names;
+  }
+
+
+
+  /**
+   * Retrieves the set of optional attributes for this DIT content rule.
+   * 
+   * @return The set of optional attributes for this DIT content rule.
+   */
+  public Iterable<AttributeType> getOptionalAttributes()
+  {
+    return optionalAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the set of prohibited attributes for this DIT content
+   * rule.
+   * 
+   * @return The set of prohibited attributes for this DIT content rule.
+   */
+  public Iterable<AttributeType> getProhibitedAttributes()
+  {
+    return prohibitedAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the set of required attributes for this DIT content rule.
+   * 
+   * @return The set of required attributes for this DIT content rule.
+   */
+  public Iterable<AttributeType> getRequiredAttributes()
+  {
+    return requiredAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the structural objectclass for this DIT content rule.
+   * 
+   * @return The structural objectclass for this DIT content rule.
+   */
+  public ObjectClass getStructuralClass()
+  {
+    return structuralClass;
+  }
+
+
+
+  /**
+   * Retrieves the structural class OID for this schema definition.
+   * 
+   * @return The structural class OID for this schema definition.
+   */
+  public String getStructuralClassOID()
+  {
+    return structuralClassOID;
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return structuralClassOID.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name.
+   * 
+   * @param name
+   *          The name for which to make the determination.
+   * @return <code>true</code> if the specified name is assigned to this
+   *         schema definition, or <code>false</code> if not.
+   */
+  public boolean hasName(String name)
+  {
+    for (final String n : names)
+    {
+      if (n.equalsIgnoreCase(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name or
+   * structural class OID.
+   * 
+   * @param value
+   *          The value for which to make the determination.
+   * @return <code>true</code> if the provided value matches the OID or
+   *         one of the names assigned to this schema definition, or
+   *         <code>false</code> if not.
+   */
+  public boolean hasNameOrOID(String value)
+  {
+    return hasName(value) || structuralClassOID.equals(value);
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition is declared "obsolete".
+   * 
+   * @return <code>true</code> if this schema definition is declared
+   *         "obsolete", or <code>false</code> if not.
+   */
+  public boolean isObsolete()
+  {
+    return isObsolete;
+  }
+
+
+
+  /**
+   * Retrieves the string representation of this schema definition in
+   * the form specified in RFC 2252.
+   * 
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  DITContentRule duplicate()
+  {
+    return new DITContentRule(structuralClassOID, names, description,
+        isObsolete, auxiliaryClassOIDs, optionalAttributeOIDs,
+        prohibitedAttributeOIDs, requiredAttributeOIDs,
+        extraProperties, definition);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(structuralClassOID);
+
+    if (!names.isEmpty())
+    {
+      final Iterator<String> iterator = names.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NAME ( '");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append("' '");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append("' )");
+      }
+      else
+      {
+        buffer.append(" NAME '");
+        buffer.append(firstName);
+        buffer.append("'");
+      }
+    }
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+
+    if (isObsolete)
+    {
+      buffer.append(" OBSOLETE");
+    }
+
+    if (!auxiliaryClassOIDs.isEmpty())
+    {
+      final Iterator<String> iterator = auxiliaryClassOIDs.iterator();
+
+      final String firstClass = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" AUX (");
+        buffer.append(firstClass);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" AUX ");
+        buffer.append(firstClass);
+      }
+    }
+
+    if (!requiredAttributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator =
+          requiredAttributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" MUST ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" MUST ");
+        buffer.append(firstName);
+      }
+    }
+
+    if (!optionalAttributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator =
+          optionalAttributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" MAY ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" MAY ");
+        buffer.append(firstName);
+      }
+    }
+
+    if (!prohibitedAttributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator =
+          prohibitedAttributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NOT ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" NOT ");
+        buffer.append(firstName);
+      }
+    }
+  }
+
+
+
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    // Get the objectclass with the specified OID. If it does not exist
+    // or is not structural, then fail.
+    if (structuralClassOID != null)
+    {
+      try
+      {
+        structuralClass = schema.getObjectClass(structuralClassOID);
+      }
+      catch (final UnknownSchemaElementException e)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DCR_UNKNOWN_STRUCTURAL_CLASS.get(
+                definition, structuralClassOID);
+        throw new SchemaException(message, e);
+      }
+      if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DCR_STRUCTURAL_CLASS_NOT_STRUCTURAL.get(
+                definition, structuralClass.getOID(), structuralClass
+                    .getNameOrOID(), structuralClass
+                    .getObjectClassType().toString());
+        warnings.add(message);
+      }
+    }
+
+    if (!auxiliaryClassOIDs.isEmpty())
+    {
+      auxiliaryClasses =
+          new HashSet<ObjectClass>(auxiliaryClassOIDs.size());
+      ObjectClass objectClass;
+      for (final String oid : auxiliaryClassOIDs)
+      {
+        try
+        {
+          objectClass = schema.getObjectClass(oid);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          // This isn't good because it is an unknown auxiliary class.
+          final Message message =
+              ERR_ATTR_SYNTAX_DCR_UNKNOWN_AUXILIARY_CLASS.get(
+                  definition, oid);
+          throw new SchemaException(message, e);
+        }
+        if (objectClass.getObjectClassType() != ObjectClassType.AUXILIARY)
+        {
+          // This isn't good because it isn't an auxiliary class.
+          final Message message =
+              ERR_ATTR_SYNTAX_DCR_AUXILIARY_CLASS_NOT_AUXILIARY.get(
+                  definition, structuralClass.getOID(), structuralClass
+                      .getObjectClassType().toString());
+          throw new SchemaException(message);
+        }
+        auxiliaryClasses.add(objectClass);
+      }
+    }
+
+    if (!requiredAttributeOIDs.isEmpty())
+    {
+      requiredAttributes =
+          new HashSet<AttributeType>(requiredAttributeOIDs.size());
+      AttributeType attributeType;
+      for (final String oid : requiredAttributeOIDs)
+      {
+        try
+        {
+          attributeType = schema.getAttributeType(oid);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          // This isn't good because it means that the DIT content rule
+          // requires an attribute type that we don't know anything
+          // about.
+          final Message message =
+              ERR_ATTR_SYNTAX_DCR_UNKNOWN_REQUIRED_ATTR.get(definition,
+                  oid);
+          throw new SchemaException(message, e);
+        }
+        requiredAttributes.add(attributeType);
+      }
+    }
+
+    if (!optionalAttributeOIDs.isEmpty())
+    {
+      optionalAttributes =
+          new HashSet<AttributeType>(optionalAttributeOIDs.size());
+      AttributeType attributeType;
+      for (final String oid : optionalAttributeOIDs)
+      {
+        try
+        {
+          attributeType = schema.getAttributeType(oid);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          // This isn't good because it means that the DIT content rule
+          // requires an attribute type that we don't know anything
+          // about.
+          final Message message =
+              ERR_ATTR_SYNTAX_DCR_UNKNOWN_OPTIONAL_ATTR.get(definition,
+                  oid);
+          throw new SchemaException(message, e);
+        }
+        optionalAttributes.add(attributeType);
+      }
+    }
+
+    if (!prohibitedAttributeOIDs.isEmpty())
+    {
+      prohibitedAttributes =
+          new HashSet<AttributeType>(prohibitedAttributeOIDs.size());
+      AttributeType attributeType;
+      for (final String oid : prohibitedAttributeOIDs)
+      {
+        try
+        {
+          attributeType = schema.getAttributeType(oid);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          // This isn't good because it means that the DIT content rule
+          // requires an attribute type that we don't know anything
+          // about.
+          final Message message =
+              ERR_ATTR_SYNTAX_DCR_UNKNOWN_PROHIBITED_ATTR.get(
+                  definition, oid);
+          throw new SchemaException(message, e);
+        }
+        prohibitedAttributes.add(attributeType);
+      }
+    }
+
+    // Make sure that none of the prohibited attributes is required by
+    // the structural or any of the auxiliary classes.
+    for (final AttributeType t : prohibitedAttributes)
+    {
+      if (structuralClass.isRequired(t))
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DCR_PROHIBITED_REQUIRED_BY_STRUCTURAL.get(
+                definition, t.getNameOrOID(), structuralClass
+                    .getNameOrOID());
+        throw new SchemaException(message);
+      }
+
+      for (final ObjectClass oc : auxiliaryClasses)
+      {
+        if (oc.isRequired(t))
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_DCR_PROHIBITED_REQUIRED_BY_AUXILIARY.get(
+                  definition, t.getNameOrOID(), oc.getNameOrOID());
+          throw new SchemaException(message);
+        }
+      }
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java
new file mode 100644
index 0000000..db62d3e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITContentRuleSyntaxImpl.java
@@ -0,0 +1,203 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DCR_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_DIT_CONTENT_RULE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the DIT content rule description syntax, which
+ * is used to hold DIT content rule definitions in the server schema.
+ * The format of this syntax is defined in RFC 2252.
+ */
+final class DITContentRuleSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_DIT_CONTENT_RULE_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll use the decodeDITContentRule method to determine if the
+    // value is acceptable.
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_DCR_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("DITConentRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("DITContentRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      SchemaUtils.readOID(reader);
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("aux"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("must"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("may"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("not"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          SchemaUtils.readExtensions(reader);
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("DITContentRuleSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITStructureRule.java b/sdk/src/org/opends/sdk/schema/DITStructureRule.java
new file mode 100644
index 0000000..851c3f3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITStructureRule.java
@@ -0,0 +1,342 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a DIT structure rule, which is used to indicate
+ * the types of children that entries may have.
+ */
+public final class DITStructureRule extends SchemaElement
+{
+  // The rule ID for this DIT structure rule.
+  private final Integer ruleID;
+
+  // The set of user defined names for this definition.
+  private final List<String> names;
+
+  // Indicates whether this definition is declared "obsolete".
+  private final boolean isObsolete;
+
+  // The name form for this DIT structure rule.
+  private final String nameFormOID;
+
+  // The set of superior DIT structure rules.
+  private final Set<Integer> superiorRuleIDs;
+
+  // The definition string used to create this objectclass.
+  private final String definition;
+
+  private NameForm nameForm;
+  private Set<DITStructureRule> superiorRules = Collections.emptySet();
+
+
+
+  DITStructureRule(Integer ruleID, List<String> names,
+      String description, boolean obsolete, String nameFormOID,
+      Set<Integer> superiorRuleIDs,
+      Map<String, List<String>> extraProperties, String definition)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(ruleID, nameFormOID, superiorRuleIDs);
+    this.ruleID = ruleID;
+    this.names = names;
+    this.isObsolete = obsolete;
+    this.nameFormOID = nameFormOID;
+    this.superiorRuleIDs = superiorRuleIDs;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+  }
+
+
+
+  /**
+   * Retrieves the name form for this DIT structure rule.
+   * 
+   * @return The name form for this DIT structure rule.
+   */
+  public NameForm getNameForm()
+  {
+    return nameForm;
+  }
+
+
+
+  /**
+   * Retrieves the name or rule ID for this schema definition. If it has
+   * one or more names, then the primary name will be returned. If it
+   * does not have any names, then the OID will be returned.
+   * 
+   * @return The name or OID for this schema definition.
+   */
+  public String getNameOrRuleID()
+  {
+    if (names.isEmpty())
+    {
+      return ruleID.toString();
+    }
+    return names.get(0);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the set of user-defined names that may
+   * be used to reference this schema definition.
+   * 
+   * @return Returns an iterable over the set of user-defined names that
+   *         may be used to reference this schema definition.
+   */
+  public Iterable<String> getNames()
+  {
+    return names;
+  }
+
+
+
+  /**
+   * Retrieves the rule ID for this DIT structure rule.
+   * 
+   * @return The rule ID for this DIT structure rule.
+   */
+  public Integer getRuleID()
+  {
+    return ruleID;
+  }
+
+
+
+  /**
+   * Retrieves the set of superior rules for this DIT structure rule.
+   * 
+   * @return The set of superior rules for this DIT structure rule.
+   */
+  public Iterable<DITStructureRule> getSuperiorRules()
+  {
+    return superiorRules;
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return ruleID.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name.
+   * 
+   * @param name
+   *          The name for which to make the determination.
+   * @return <code>true</code> if the specified name is assigned to this
+   *         schema definition, or <code>false</code> if not.
+   */
+  public boolean hasName(String name)
+  {
+    for (final String n : names)
+    {
+      if (n.equalsIgnoreCase(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition is declared "obsolete".
+   * 
+   * @return <code>true</code> if this schema definition is declared
+   *         "obsolete", or <code>false</code> if not.
+   */
+  public boolean isObsolete()
+  {
+    return isObsolete;
+  }
+
+
+
+  /**
+   * Retrieves the string representation of this schema definition in
+   * the form specified in RFC 2252.
+   * 
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  DITStructureRule duplicate()
+  {
+    return new DITStructureRule(ruleID, names, description, isObsolete,
+        nameFormOID, superiorRuleIDs, extraProperties, definition);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(ruleID);
+
+    if (!names.isEmpty())
+    {
+      final Iterator<String> iterator = names.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NAME ( '");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append("' '");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append("' )");
+      }
+      else
+      {
+        buffer.append(" NAME '");
+        buffer.append(firstName);
+        buffer.append("'");
+      }
+    }
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+
+    if (isObsolete)
+    {
+      buffer.append(" OBSOLETE");
+    }
+
+    buffer.append(" FORM ");
+    buffer.append(nameFormOID);
+
+    if (superiorRuleIDs != null && !superiorRuleIDs.isEmpty())
+    {
+      final Iterator<Integer> iterator = superiorRuleIDs.iterator();
+
+      final Integer firstRule = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" SUP ( ");
+        buffer.append(firstRule);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" SUP ");
+        buffer.append(firstRule);
+      }
+    }
+  }
+
+
+
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    try
+    {
+      nameForm = schema.getNameForm(nameFormOID);
+    }
+    catch (final UnknownSchemaElementException e)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_DSR_UNKNOWN_NAME_FORM.get(definition,
+              nameFormOID);
+      throw new SchemaException(message, e);
+    }
+
+    if (!superiorRuleIDs.isEmpty())
+    {
+      superiorRules =
+          new HashSet<DITStructureRule>(superiorRuleIDs.size());
+      DITStructureRule rule;
+      for (final Integer id : superiorRuleIDs)
+      {
+        try
+        {
+          rule = schema.getDITStructureRule(id);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_DSR_UNKNOWN_RULE_ID.get(definition, id);
+          throw new SchemaException(message, e);
+        }
+        superiorRules.add(rule);
+      }
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java
new file mode 100644
index 0000000..699fb59
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DITStructureRuleSyntaxImpl.java
@@ -0,0 +1,205 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_INTEGER_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_DIT_STRUCTURE_RULE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the DIT structure rule description syntax,
+ * which is used to hold DIT structure rule definitions in the server
+ * schema. The format of this syntax is defined in RFC 2252.
+ */
+final class DITStructureRuleSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_INTEGER_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_DIT_STRUCTURE_RULE_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll use the decodeDITStructureRule method to determine if the
+    // value is acceptable.
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      SchemaUtils.readRuleID(reader);
+
+      String nameForm = null;
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("form"))
+        {
+          nameForm = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("sup"))
+        {
+          SchemaUtils.readRuleIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          SchemaUtils.readExtensions(reader);
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+
+      if (nameForm == null)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(definition);
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("DITStructureRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java
new file mode 100644
index 0000000..1f65218
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DeliveryMethodSyntaxImpl.java
@@ -0,0 +1,172 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DELIVERY_METHOD_INVALID_ELEMENT;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DELIVERY_METHOD_NO_ELEMENTS;
+import static org.opends.sdk.schema.SchemaConstants.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.HashSet;
+import java.util.StringTokenizer;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the delivery method attribute syntax. This
+ * contains one or more of a fixed set of values. If there are multiple
+ * values, then they are separated by spaces with a dollar sign between
+ * them. The allowed values include:
+ * <UL>
+ * <LI>any</LI>
+ * <LI>mhs</LI>
+ * <LI>physical</LI>
+ * <LI>telex</LI>
+ * <LI>teletex</LI>
+ * <LI>g3fax</LI>
+ * <LI>g4fax</LI>
+ * <LI>ia5</LI>
+ * <LI>videotex</LI>
+ * <LI>telephone</LI>
+ * </UL>
+ */
+final class DeliveryMethodSyntaxImpl extends AbstractSyntaxImpl
+{
+  /**
+   * The set of values that may be used as delivery methods.
+   */
+  private static final HashSet<String> ALLOWED_VALUES =
+      new HashSet<String>();
+  {
+    ALLOWED_VALUES.add("any");
+    ALLOWED_VALUES.add("mhs");
+    ALLOWED_VALUES.add("physical");
+    ALLOWED_VALUES.add("telex");
+    ALLOWED_VALUES.add("teletex");
+    ALLOWED_VALUES.add("g3fax");
+    ALLOWED_VALUES.add("g4fax");
+    ALLOWED_VALUES.add("ia5");
+    ALLOWED_VALUES.add("videotex");
+    ALLOWED_VALUES.add("telephone");
+  }
+
+
+
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_DELIVERY_METHOD_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String stringValue = toLowerCase(value.toString());
+    final StringTokenizer tokenizer =
+        new StringTokenizer(stringValue, " $");
+    if (!tokenizer.hasMoreTokens())
+    {
+      invalidReason.append(ERR_ATTR_SYNTAX_DELIVERY_METHOD_NO_ELEMENTS
+          .get(value.toString()));
+      return false;
+    }
+
+    while (tokenizer.hasMoreTokens())
+    {
+      final String token = tokenizer.nextToken();
+      if (!ALLOWED_VALUES.contains(token))
+      {
+        invalidReason
+            .append(ERR_ATTR_SYNTAX_DELIVERY_METHOD_INVALID_ELEMENT
+                .get(value.toString(), token));
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..1814375
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DirectoryStringFirstComponentEqualityMatchingRuleImpl.java
@@ -0,0 +1,140 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the directoryStringFirstComponentMatch matching
+ * rule defined in X.520 and referenced in RFC 2252. This rule is
+ * intended for use with attributes whose values contain a set of
+ * parentheses enclosing a space-delimited set of names and/or
+ * name-value pairs (like attribute type or objectclass descriptions) in
+ * which the "first component" is the first item after the opening
+ * parenthesis.
+ */
+final class DirectoryStringFirstComponentEqualityMatchingRuleImpl
+    extends AbstractMatchingRuleImpl
+{
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return new DefaultEqualityAssertion(
+            SchemaConstants.SINGLE_SPACE_VALUE);
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return new DefaultEqualityAssertion(ByteString.empty());
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return new DefaultEqualityAssertion(ByteString.valueOf(buffer
+        .toString()));
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final String definition = value.toString();
+    final SubstringReader reader = new SubstringReader(definition);
+
+    // We'll do this a character at a time. First, skip over any leading
+    // whitespace.
+    reader.skipWhitespaces();
+
+    if (reader.remaining() <= 0)
+    {
+      // This means that the value was empty or contained only
+      // whitespace.
+      // That is illegal.
+      final Message message = ERR_ATTR_SYNTAX_EMPTY_VALUE.get();
+      throw DecodeException.error(message);
+    }
+
+    // The next character must be an open parenthesis. If it is not,
+    // then
+    // that is an error.
+    final char c = reader.read();
+    if (c != '(')
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS.get(definition,
+              (reader.pos() - 1), String.valueOf(c));
+      throw DecodeException.error(message);
+    }
+
+    // Skip over any spaces immediately following the opening
+    // parenthesis.
+    reader.skipWhitespaces();
+
+    // The next set of characters must be the OID.
+    final String string = SchemaUtils.readQuotedString(reader);
+
+    // Grab the substring between the start pos and the current pos
+    return ByteString.valueOf(string);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java
new file mode 100644
index 0000000..f53b391
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DirectoryStringSyntaxImpl.java
@@ -0,0 +1,125 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_DIRECTORYSTRING_INVALID_ZEROLENGTH_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the directory string attribute syntax, which is
+ * simply a set of UTF-8 characters. By default, they will be treated in
+ * a case-insensitive manner, and equality, ordering, substring, and
+ * approximate matching will be allowed.
+ */
+final class DirectoryStringSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_DIRECTORY_STRING_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    if (value.length() > 0
+        || schema.getSchemaCompatOptions()
+            .isZeroLengthDirectoryStringsAllowed())
+    {
+      return true;
+    }
+    else
+    {
+      invalidReason
+          .append(ERR_ATTR_SYNTAX_DIRECTORYSTRING_INVALID_ZEROLENGTH_VALUE
+              .get());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..3a3d64f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DistinguishedNameEqualityMatchingRuleImpl.java
@@ -0,0 +1,213 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.opends.sdk.*;
+import org.opends.sdk.RDN.AVA;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * This class defines the distinguishedNameMatch matching rule defined
+ * in X.520 and referenced in RFC 2252.
+ */
+final class DistinguishedNameEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  private static final Comparator<AVA> ATV_COMPARATOR = new Comparator<AVA>()
+  {
+    public int compare(AVA o1, AVA o2)
+    {
+      return o1.getAttributeType().compareTo(o2.getAttributeType());
+    }
+  };
+
+
+
+  @Override
+  public Assertion getAssertion(final Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    DN assertion;
+    try
+    {
+      assertion = DN.valueOf(value.toString(), schema);
+    }
+    catch (final LocalizedIllegalArgumentException e)
+    {
+      throw DecodeException.error(e.getMessageObject());
+    }
+
+    final DN finalAssertion = assertion;
+    return new Assertion()
+    {
+      public ConditionResult matches(ByteSequence attributeValue)
+      {
+        try
+        {
+          final DN attribute = DN.valueOf(attributeValue.toString(),
+              schema);
+          return matchDNs(finalAssertion, attribute);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          return ConditionResult.UNDEFINED;
+        }
+      }
+    };
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    try
+    {
+      return ByteString.valueOf(DN.valueOf(value.toString(), schema)
+          .toNormalizedString());
+    }
+    catch (final LocalizedIllegalArgumentException e)
+    {
+      throw DecodeException.error(e.getMessageObject());
+    }
+  }
+
+
+
+  private ConditionResult matchAVAs(AVA ava1, AVA ava2)
+  {
+    final AttributeType type = ava1.getAttributeType();
+
+    if (!type.equals(ava2.getAttributeType()))
+    {
+      return ConditionResult.FALSE;
+    }
+
+    final MatchingRule matchingRule = type.getEqualityMatchingRule();
+    if (matchingRule != null)
+    {
+      try
+      {
+        final ByteString nv1 = matchingRule
+            .normalizeAttributeValue(ava1.getAttributeValue());
+        final ByteString nv2 = matchingRule
+            .normalizeAttributeValue(ava2.getAttributeValue());
+        return nv1.equals(nv2) ? ConditionResult.TRUE
+            : ConditionResult.FALSE;
+      }
+      catch (final DecodeException de)
+      {
+        return ConditionResult.UNDEFINED;
+      }
+    }
+
+    return ConditionResult.UNDEFINED;
+  }
+
+
+
+  private ConditionResult matchDNs(DN dn1, DN dn2)
+  {
+    final int sz1 = dn1.size();
+    final int sz2 = dn2.size();
+
+    if (sz1 != sz2)
+    {
+      return ConditionResult.FALSE;
+    }
+    else
+    {
+      final RDN rdn1 = dn1.rdn();
+      final RDN rdn2 = dn2.rdn();
+      while (rdn1 != null)
+      {
+        final ConditionResult result = matchRDNs(rdn1, rdn2);
+        if (result != ConditionResult.TRUE)
+        {
+          return result;
+        }
+      }
+      return ConditionResult.TRUE;
+    }
+  }
+
+
+
+  private ConditionResult matchRDNs(RDN rdn1, RDN rdn2)
+  {
+    final int sz1 = rdn1.size();
+    final int sz2 = rdn2.size();
+
+    if (sz1 != sz2)
+    {
+      return ConditionResult.FALSE;
+    }
+    else if (sz1 == 1)
+    {
+      return matchAVAs(rdn1.getFirstAVA(), rdn2.getFirstAVA());
+    }
+    else
+    {
+      // Need to sort the AVAs before comparing.
+      final AVA[] a1 = new AVA[sz1];
+      int i = 0;
+      for (final AVA ava : rdn1)
+      {
+        a1[i++] = ava;
+      }
+      Arrays.sort(a1, ATV_COMPARATOR);
+
+      final AVA[] a2 = new AVA[sz1];
+      i = 0;
+      for (final AVA ava : rdn2)
+      {
+        a2[i++] = ava;
+      }
+      Arrays.sort(a2, ATV_COMPARATOR);
+
+      for (i = 0; i < sz1; i++)
+      {
+        final ConditionResult result = matchAVAs(a1[i], a2[i]);
+        if (result != ConditionResult.TRUE)
+        {
+          return result;
+        }
+      }
+
+      return ConditionResult.TRUE;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java
new file mode 100644
index 0000000..f068290
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DistinguishedNameSyntaxImpl.java
@@ -0,0 +1,95 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_DN_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_DN_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * This class defines the distinguished name attribute syntax, which is
+ * used for attributes that hold distinguished names (DNs). Equality and
+ * substring matching will be allowed by default.
+ */
+final class DistinguishedNameSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_DN_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_DN_NAME;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    try
+    {
+      DN.valueOf(value.toString(), schema);
+    }
+    catch (final LocalizedIllegalArgumentException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java
new file mode 100644
index 0000000..f365576
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/DoubleMetaphoneApproximateMatchingRuleImpl.java
@@ -0,0 +1,1117 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class defines an approximate matching rule based on the Double
+ * Metaphone algorithm. The Metaphone and Double Metaphone algorithms
+ * were originally devised by Lawrence Philips (published in the
+ * December 1990 issue of <I>Computer Language</I> and the <A
+ * HREF="http://www.cuj.com/documents/s=8038/cuj0006philips/">June 2000
+ * issue of <I>C/C++ Users Journal</I></A>, respectively), and this
+ * version of the algorithm is based on a version modified by Kevin
+ * Atkinson to include bugfixes and additional functionality (source is
+ * available <A HREF="http://aspell.net/metaphone/dmetaph.cpp">here</A>
+ * and additional Metaphone and Double Metaphone information is
+ * available at <A
+ * HREF="http://aspell.net/metaphone/">http://aspell.net/
+ * metaphone/</A>). This implementation is largely the same as the one
+ * provided by Kevin Atkinson, but it has been re-written for better
+ * readability, for more efficiency, to get rid of checks for conditions
+ * that can't possibly happen, and to get rid of redundant checks that
+ * aren't needed. It has also been updated to always only generate a
+ * single value rather than one or possibly two values.
+ */
+final class DoubleMetaphoneApproximateMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    String valueString = value.toString();
+    final int length = valueString.length();
+    if (length == 0)
+    {
+      // The value is empty, so it is already normalized.
+      return ByteString.empty();
+    }
+
+    final int last = length - 1;
+
+    // Pad the value to allow for checks to go past the end of the
+    // value.
+    valueString = valueString.toUpperCase() + "     ";
+
+    // The metaphone value that is being constructed.
+    final StringBuilder metaphone = new StringBuilder(4);
+
+    // Skip over GN, KN, PN, WR, and PS at the beginning of a word.
+    int pos = 0;
+    String substring = valueString.substring(0, 2);
+    if (substring.equals("GN") || substring.equals("KN")
+        || substring.equals("PN") || substring.equals("WR")
+        || substring.equals("PS"))
+    {
+      pos++;
+    }
+
+    // 'X' at the beginning of a word will sound like Z, but Z will
+    // always be mapped to S.
+    else if (valueString.charAt(0) == 'X')
+    {
+      metaphone.append("S");
+      pos++;
+    }
+
+    // Loop until we have at least four metaphone characters or have
+    // reached the end of the string.
+    while (metaphone.length() < 4 && pos < length)
+    {
+      // Check the character at the current position against various
+      // targets.
+      char posMinusFour;
+      char posMinusThree;
+      char posMinusTwo;
+      char posMinusOne;
+      char posPlusOne;
+      char posPlusTwo;
+      switch (valueString.charAt(pos))
+      {
+      case 'A':
+      case 'E':
+      case 'I':
+      case 'O':
+      case 'U':
+      case 'Y':
+        // All initial vowels map to 'A'. All others will be ignored.
+        if (pos == 0)
+        {
+          metaphone.append("A");
+        }
+
+        pos++;
+        break;
+
+      case 'B':
+        // B and BB will be mapped to P, with the exception of "MB" as
+        // in "crumb", but that will be handled elsewhere.
+        metaphone.append("P");
+
+        if (valueString.charAt(++pos) == 'B')
+        {
+          pos++;
+        }
+
+        break;
+
+      case 'C':
+        // Check for various Germanic sequences, which will be mapped to
+        // 'K'. This basically includes all occurrences of "ACH" where
+        // the preceding character is not a vowel and the following
+        // character is neither an 'E' nor an 'I' except in "BACHER" and
+        // "MACHER".
+        if (pos > 1
+            && !isVowel(posMinusTwo = valueString.charAt(pos - 2))
+            && hasSubstring(valueString, pos - 1, "ACH")
+            && (posPlusTwo = valueString.charAt(pos + 2)) != 'I'
+            && (posPlusTwo != 'E' || valueString.charAt(pos + 3) == 'R'
+                && (posMinusTwo == 'B' || posMinusTwo == 'M')))
+        {
+          metaphone.append("K");
+          pos += 2;
+          break;
+        }
+
+        // Check for a special case of "caesar", which will be maped to
+        // 'S'.
+        if (pos == 0 && hasSubstring(valueString, pos + 1, "AESAR"))
+        {
+          metaphone.append("S");
+          pos += 2;
+          break;
+        }
+
+        // CH can be treated in lots of different ways.
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+        {
+          // Check for "chia" as in "chianti" and map to 'K'.
+          if (hasSubstring(valueString, pos + 2, "IA"))
+          {
+            metaphone.append("K");
+            pos += 2;
+            break;
+          }
+
+          // Check for "chae" as in "michael" and map to 'K'.
+          if (hasSubstring(valueString, pos + 2, "AE"))
+          {
+            metaphone.append("K");
+            pos += 2;
+            break;
+          }
+
+          // Check for a Greek root at the beginning of the value like
+          // chemistry or chorus and map to 'K'.
+          if (pos == 0
+              && !hasSubstring(valueString, 2, "ORE")
+              && (hasSubstring(valueString, 2, "ARAC")
+                  || hasSubstring(valueString, 2, "ARIS")
+                  || hasSubstring(valueString, 2, "OR")
+                  || hasSubstring(valueString, 2, "YM")
+                  || hasSubstring(valueString, 2, "IA") || hasSubstring(
+                  valueString, 2, "EM")))
+          {
+            metaphone.append("K");
+            pos += 2;
+            break;
+          }
+
+          // Check for "CH" values that produce a "KH" sound that will
+          // be mapped to 'K'.
+          if (isGermanic(valueString)
+              || hasSubstring(valueString, pos - 2, "ORCHES")
+              || hasSubstring(valueString, pos - 2, "ARCHIT")
+              || hasSubstring(valueString, pos - 2, "ORCHID")
+              || (posPlusTwo = valueString.charAt(pos + 2)) == 'T'
+              || posPlusTwo == 'S'
+              || (pos == 0
+                  || (posMinusOne = valueString.charAt(pos - 1)) == 'A'
+                  || posMinusOne == 'O' || posMinusOne == 'U' || posMinusOne == 'E')
+              && (posPlusTwo == 'L' || posPlusTwo == 'R'
+                  || posPlusTwo == 'N' || posPlusTwo == 'M'
+                  || posPlusTwo == 'B' || posPlusTwo == 'H'
+                  || posPlusTwo == 'F' || posPlusTwo == 'V' || posPlusTwo == 'W'))
+          {
+            metaphone.append("K");
+            pos += 2;
+            break;
+          }
+
+          // All other "CH" values.
+          if (pos > 0)
+          {
+            if (hasSubstring(valueString, 0, "MC"))
+            {
+              metaphone.append("K");
+            }
+            else
+            {
+              metaphone.append("X");
+            }
+          }
+          else
+          {
+            metaphone.append("X");
+          }
+
+          pos += 2;
+          break;
+        }
+
+        // Check for "CZ" as in "czerny" but not "wicz" and map to 'S'.
+        if (posPlusOne == 'Z'
+            && !hasSubstring(valueString, pos - 2, "WI"))
+        {
+          metaphone.append("S");
+          pos += 2;
+          break;
+        }
+
+        // Check for "CIA" as in "focaccia" and map to 'X'.
+        if (posPlusOne == 'I' && valueString.charAt(pos + 2) == 'A')
+        {
+          metaphone.append("X");
+          pos += 3;
+          break;
+        }
+
+        // Check for a double C but not in values that start with "McC"
+        if (posPlusOne == 'C'
+            && !(pos == 1 && valueString.charAt(0) == 'M'))
+        {
+          if (((posPlusTwo = valueString.charAt(pos + 2)) == 'I'
+              || posPlusTwo == 'E' || posPlusTwo == 'H')
+              && !(posPlusTwo == 'H' && valueString.charAt(pos + 3) == 'U'))
+          {
+            if (pos == 1 && valueString.charAt(pos - 1) == 'A'
+                || hasSubstring(valueString, pos - 1, "UCCEE")
+                || hasSubstring(valueString, pos - 1, "UCCES"))
+            {
+              // Values like "accident", "accede", and "succeed".
+              metaphone.append("K");
+              pos += 2;
+              break;
+            }
+            else
+            {
+              // Values like "bacci" or "bertucci".
+              metaphone.append("X");
+              pos += 3;
+              break;
+            }
+          }
+          else
+          {
+            // This is Pierce's Rule, whatever that means.
+            metaphone.append("K");
+            pos += 2;
+            break;
+          }
+        }
+
+        // Check for CK, CG, or CQ and map to 'K'. Check for CI, CE, and
+        // CY and map to "S".
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'K'
+            || posPlusOne == 'G' || posPlusOne == 'Q')
+        {
+          metaphone.append("K");
+          pos += 2;
+          break;
+        }
+
+        // Check for CI, CE, or CY and map to 'S'.
+        if (posPlusOne == 'I' || posPlusOne == 'E' || posPlusOne == 'Y')
+        {
+          metaphone.append("S");
+          pos += 2;
+          break;
+        }
+
+        // All other cases of "C" will be mapped to 'K'. However, the
+        // number of positions that we skip ahead may vary. If there is
+        // a value that consists of two words like "mac caffrey", then
+        // skip ahead three. For the character combinations of "CK" and
+        // "CQ", then skip ahead two. For the character combinations of
+        // "CC" except "CCE" and "CCI", then skip ahead two. For all
+        // other cases, skip ahead one.
+        metaphone.append("K");
+        switch (valueString.charAt(pos + 1))
+        {
+        case ' ':
+          switch (valueString.charAt(pos + 2))
+          {
+          case 'C':
+          case 'Q':
+          case 'G':
+            pos += 3;
+            break;
+          default:
+            pos++;
+            break;
+          }
+          break;
+
+        case 'K':
+        case 'Q':
+          pos += 2;
+          break;
+
+        case 'C':
+          switch (valueString.charAt(pos + 2))
+          {
+          case 'E':
+          case 'I':
+            pos++;
+            break;
+          default:
+            pos += 2;
+            break;
+          }
+          break;
+        default:
+          pos++;
+        }
+        break;
+
+      case 'D':
+        // DG will be mapped to either 'J' (in cases like edge) or 'TK'
+        // (in cases like Edgar).
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'G')
+        {
+          if ((posPlusTwo = valueString.charAt(pos + 2)) == 'I'
+              || posPlusTwo == 'E' || posPlusTwo == 'Y')
+          {
+            metaphone.append("J");
+            pos += 3;
+            break;
+          }
+          else
+          {
+            metaphone.append("TK");
+            pos += 2;
+            break;
+          }
+        }
+
+        // DT and DD will be mapped to 'T'.
+        if (posPlusOne == 'T' || posPlusOne == 'D')
+        {
+          metaphone.append("T");
+          pos += 2;
+          break;
+        }
+
+        // All other cases will be mapped to 'T'.
+        metaphone.append("T");
+        pos++;
+        break;
+
+      case 'F':
+        // F always maps to F. If there is a double F, then skip the
+        // second one.
+        metaphone.append("F");
+        pos++;
+        if (valueString.charAt(pos) == 'F')
+        {
+          pos++;
+        }
+        break;
+
+      case 'G':
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+        {
+          // A "GH" that is not preceded by a vowel will be mapped to
+          // 'K'.
+          if (pos > 0 && !isVowel(valueString.charAt(pos - 1)))
+          {
+            metaphone.append("K");
+            pos += 2;
+            break;
+          }
+
+          if (pos == 0)
+          {
+            if (valueString.charAt(pos + 2) == 'I')
+            {
+              // Words like ghislane or ghiradelli
+              metaphone.append("J");
+            }
+            else
+            {
+              metaphone.append("K");
+            }
+
+            pos += 2;
+            break;
+          }
+
+          // A refined version of Parker's Rule.
+          if (pos > 1
+              && ((posMinusTwo = valueString.charAt(pos - 2)) == 'B'
+                  || posMinusTwo == 'H' || posMinusTwo == 'D')
+              || pos > 2
+              && ((posMinusThree = valueString.charAt(pos - 3)) == 'B'
+                  || posMinusThree == 'H' || posMinusThree == 'D')
+              || pos > 3
+              && ((posMinusFour = valueString.charAt(pos - 4)) == 'B' || posMinusFour == 'H'))
+          {
+            pos += 2;
+            break;
+          }
+          else
+          {
+            if (pos > 2
+                && valueString.charAt(pos - 1) == 'U'
+                && ((posMinusThree = valueString.charAt(pos - 3)) == 'C'
+                    || posMinusThree == 'G'
+                    || posMinusThree == 'L'
+                    || posMinusThree == 'R' || posMinusThree == 'T'))
+            {
+              // Words like laugh, McLaughlin, cough, rough are mapped
+              // to 'F'.
+              metaphone.append("F");
+            }
+            else if (pos > 0 && valueString.charAt(pos - 1) != 'I')
+            {
+              metaphone.append("K");
+            }
+
+            pos += 2;
+            break;
+          }
+        }
+
+        if (posPlusOne == 'N')
+        {
+          if (pos == 1 && isVowel(valueString.charAt(0))
+              && !isSlavoGermanic(valueString))
+          {
+            metaphone.append("KN");
+            pos += 2;
+            break;
+          }
+          else
+          {
+            if (!hasSubstring(valueString, pos + 2, "EY")
+                && !isSlavoGermanic(valueString))
+            {
+              metaphone.append("N");
+            }
+            else
+            {
+              metaphone.append("KN");
+            }
+
+            pos += 2;
+            break;
+          }
+        }
+
+        // GLI as in tagliaro will be mapped to "KL".
+        if (posPlusOne == 'L' && valueString.charAt(pos + 2) == 'I')
+        {
+          metaphone.append("KL");
+          pos += 2;
+          break;
+        }
+
+        // Forms of GY, GE, and GI at the beginning of a word will map
+        // to 'K'.
+        if (pos == 0
+            && (posPlusOne == 'Y'
+                || (substring = valueString.substring(pos + 1, pos + 3))
+                    .equals("ES") || substring.equals("EP")
+                || substring.equals("EB") || substring.equals("EL")
+                || substring.equals("EY") || substring.equals("IB")
+                || substring.equals("IL") || substring.equals("IN")
+                || substring.equals("IE") || substring.equals("EI") || substring
+                .equals("ER")))
+        {
+          metaphone.append("K");
+          pos += 2;
+          break;
+        }
+
+        // Some occurrences of GER and GY in a word will be mapped to
+        // 'K'.
+        posPlusTwo = valueString.charAt(pos + 2);
+        if ((posPlusOne == 'E' && posPlusTwo == 'R' || posPlusOne == 'Y')
+            && (posMinusOne = valueString.charAt(pos - 1)) != 'E'
+            && posMinusOne != 'I'
+            && !hasSubstring(valueString, 0, "DANGER")
+            && !hasSubstring(valueString, 0, "RANGER")
+            && !hasSubstring(valueString, 0, "MANGER")
+            && !hasSubstring(valueString, pos - 1, "RGY")
+            && !hasSubstring(valueString, pos - 1, "OGY"))
+        {
+          metaphone.append("K");
+          pos += 2;
+          break;
+        }
+
+        // Check for Italian uses like 'biaggi" and map to 'J'.
+        if (posPlusOne == 'E' || posPlusOne == 'I' || posPlusOne == 'Y'
+            || hasSubstring(valueString, pos - 1, "AGGI")
+            || hasSubstring(valueString, pos - 1, "OGGI"))
+        {
+          // Germanic uses will be mapped to 'K'.
+          if (isGermanic(valueString)
+              || hasSubstring(valueString, pos + 1, "ET"))
+          {
+            metaphone.append("K");
+          }
+          else
+          {
+            metaphone.append("J");
+          }
+
+          pos += 2;
+          break;
+        }
+
+        // All other cases will be mapped to 'K'. If there is a double
+        // G, then skip two. Otherwise, just skip one.
+        metaphone.append("K");
+        pos++;
+
+        if (posPlusOne == 'G')
+        {
+          pos++;
+        }
+
+        break;
+
+      case 'H':
+        // The letter 'H' will only be processed if it is immediately
+        // followed by a vowel and is either the start of the word or
+        // preceded by a vowel.
+        if (isVowel(valueString.charAt(pos + 1)))
+        {
+          if (pos == 0 || isVowel(valueString.charAt(pos - 1)))
+          {
+            metaphone.append("H");
+            pos++;
+          }
+        }
+
+        pos++;
+        break;
+
+      case 'J':
+        // Take care of obvious Spanish uses that should map to 'H'.
+        if (hasSubstring(valueString, 0, "SAN "))
+        {
+          metaphone.append("H");
+          pos++;
+          break;
+        }
+
+        if (hasSubstring(valueString, pos, "JOSE"))
+        {
+          if (pos == 0 && valueString.charAt(pos + 4) == ' ')
+          {
+            metaphone.append("H");
+          }
+          else
+          {
+            metaphone.append("J");
+          }
+
+          pos++;
+          break;
+        }
+
+        // All other cases will be mapped to 'J'.
+        metaphone.append("J");
+
+        if (valueString.charAt(pos + 1) == 'J')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'K':
+        // 'K' will always be mapped to 'K'. KK will be treated like K.
+        metaphone.append("K");
+
+        if (valueString.charAt(pos + 1) == 'K')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'L':
+        // 'L' will always be mapped to 'L'. LL will be treated like L,
+        // even for potential Spanish uses.
+        metaphone.append("L");
+
+        if (valueString.charAt(pos + 1) == 'L')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'M':
+        // 'M' will always be mapped to 'M'. MM will be treated like M.
+        // UMB in cases like "dumb" and "thumb" will be treated like M.
+        metaphone.append("M");
+
+        if (valueString.charAt(pos + 1) == 'M')
+        {
+          pos++;
+        }
+        else if (hasSubstring(valueString, pos - 1, "UMB"))
+        {
+          if (pos + 1 == last
+              || hasSubstring(valueString, pos + 2, "ER"))
+          {
+            pos++;
+          }
+        }
+
+        pos++;
+        break;
+
+      case 'N':
+        // 'N' will always be mapped to 'N'. NN will be treated like N.
+        metaphone.append("N");
+
+        if (valueString.charAt(pos + 1) == 'N')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'P':
+        // PH will be mapped to 'F'.
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+        {
+          metaphone.append("F");
+          pos += 2;
+          break;
+        }
+
+        // All other cases will be mapped to 'P', with PP and PB being
+        // treated like P.
+        metaphone.append("P");
+
+        if (posPlusOne == 'P' || posPlusOne == 'B')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'Q':
+        // 'Q' will always be mapped to 'K'. QQ will be treated like Q.
+        metaphone.append("K");
+
+        if (valueString.charAt(pos + 1) == 'Q')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'R':
+        // Ignore R at the end of French words.
+        if (pos == last && !isSlavoGermanic(valueString)
+            && hasSubstring(valueString, pos - 2, "IE")
+            && !hasSubstring(valueString, pos - 4, "ME")
+            && !hasSubstring(valueString, pos - 4, "MA"))
+        {
+          pos++;
+          break;
+        }
+
+        // All other cases will be mapped to 'R', with RR treated like
+        // R.
+        metaphone.append("R");
+
+        if (valueString.charAt(pos + 1) == 'R')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'S':
+        // Special cases like isle and carlysle will be silent.
+        if (hasSubstring(valueString, pos - 1, "ISL")
+            || hasSubstring(valueString, pos - 1, "YSL"))
+        {
+          pos++;
+          break;
+        }
+
+        // Special case of sugar mapped to 'X'.
+        if (hasSubstring(valueString, pos + 1, "UGAR"))
+        {
+          metaphone.append("X");
+          pos++;
+          break;
+        }
+
+        // SH is generally mapped to 'X', but not in Germanic cases.
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+        {
+          if (hasSubstring(valueString, pos + 1, "HEIM")
+              || hasSubstring(valueString, pos + 1, "HOEK")
+              || hasSubstring(valueString, pos + 1, "HOLM")
+              || hasSubstring(valueString, pos + 1, "HOLZ"))
+          {
+            metaphone.append("S");
+          }
+          else
+          {
+            metaphone.append("X");
+          }
+
+          pos += 2;
+          break;
+        }
+
+        // Italian and Armenian cases will map to "S".
+        if (hasSubstring(valueString, pos + 1, "IO")
+            || hasSubstring(valueString, pos + 1, "IA"))
+        {
+          metaphone.append("S");
+          pos += 3;
+          break;
+        }
+
+        // SZ should be mapped to 'S'.
+        if (posPlusOne == 'Z')
+        {
+          metaphone.append("S");
+          pos += 2;
+          break;
+        }
+
+        // Various combinations at the beginning of words will be mapped
+        // to 'S'.
+        if (pos == 0
+            && (posPlusOne == 'M' || posPlusOne == 'N'
+                || posPlusOne == 'L' || posPlusOne == 'W'))
+        {
+          metaphone.append("S");
+          pos++;
+          break;
+        }
+
+        // SC should be mapped to either SK, X, or S.
+        if (posPlusOne == 'C')
+        {
+          if ((posPlusTwo = valueString.charAt(pos + 2)) == 'H')
+          {
+            if (hasSubstring(valueString, pos + 3, "OO")
+                || hasSubstring(valueString, pos + 3, "UY")
+                || hasSubstring(valueString, pos + 3, "ED")
+                || hasSubstring(valueString, pos + 3, "EM"))
+            {
+              metaphone.append("SK");
+            }
+            else
+            {
+              metaphone.append("X");
+            }
+
+            pos += 3;
+            break;
+          }
+
+          if (posPlusTwo == 'I' || posPlusTwo == 'E'
+              || posPlusTwo == 'Y')
+          {
+            metaphone.append("S");
+            pos += 3;
+            break;
+          }
+
+          metaphone.append("SK");
+          pos += 3;
+          break;
+        }
+
+        // Ignore a trailing S in French words. All others will be
+        // mapped to 'S'.
+        if (!(pos == last && (hasSubstring(valueString, pos - 2, "AI") || hasSubstring(
+            valueString, pos - 2, "OI"))))
+        {
+          metaphone.append("S");
+        }
+
+        if (posPlusOne == 'S' || posPlusOne == 'Z')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'T':
+        // "TION", "TIA", and "TCH" will be mapped to 'X'.
+        if (hasSubstring(valueString, pos, "TION")
+            || hasSubstring(valueString, pos, "TIA")
+            || hasSubstring(valueString, pos, "TCH"))
+        {
+          metaphone.append("X");
+          pos += 3;
+          break;
+        }
+
+        // TH or TTH will be mapped to either T (for Germanic cases) or
+        // 0 (zero) for the rest.
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'H'
+            || posPlusOne == 'T' && valueString.charAt(pos + 2) == 'H')
+        {
+          if (isGermanic(valueString)
+              || hasSubstring(valueString, pos + 2, "OM")
+              || hasSubstring(valueString, pos + 2, "AM"))
+          {
+            metaphone.append("T");
+          }
+          else
+          {
+            metaphone.append("0");
+          }
+
+          pos += 2;
+          break;
+        }
+
+        // All other cases will map to T, with TT and TD being treated
+        // like T.
+        metaphone.append("T");
+
+        if (posPlusOne == 'T' || posPlusOne == 'D')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'V':
+        // 'V' will always be mapped to 'F', with VV treated like V.
+        metaphone.append("F");
+
+        if (valueString.charAt(pos + 1) == 'V')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'W':
+        // WR should always map to R.
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'R')
+        {
+          metaphone.append("R");
+          pos += 2;
+          break;
+        }
+
+        // W[AEIOUYH] at the beginning of the word should be mapped to
+        // A.
+        if (pos == 0 && (isVowel(posPlusOne) || posPlusOne == 'H'))
+        {
+          metaphone.append("A");
+
+          // FIXME -- This isn't in the algorithm as written. Should it
+          // be?
+          pos += 2;
+          break;
+        }
+
+        // A Polish value like WICZ or WITZ should be mapped to TS.
+        if (hasSubstring(valueString, pos + 1, "WICZ")
+            || hasSubstring(valueString, pos + 1, "WITZ"))
+        {
+          metaphone.append("TS");
+          pos += 4;
+          break;
+        }
+
+        // Otherwise, we'll just skip it.
+        pos++;
+        break;
+
+      case 'X':
+        // X maps to KS except at the end of French words.
+        if (!(pos == last && (hasSubstring(valueString, pos - 3, "IAU")
+            || hasSubstring(valueString, pos - 3, "EAU")
+            || hasSubstring(valueString, pos - 2, "AU") || hasSubstring(
+            valueString, pos - 2, "OU"))))
+        {
+          metaphone.append("KS");
+        }
+
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'C'
+            || posPlusOne == 'X')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case 'Z':
+        // Chinese usages like zhao will map to J.
+        if ((posPlusOne = valueString.charAt(pos + 1)) == 'H')
+        {
+          metaphone.append("J");
+          pos += 2;
+          break;
+        }
+
+        // All other cases map to "S". ZZ will be treated like Z.
+        metaphone.append("S");
+
+        if (posPlusOne == 'Z')
+        {
+          pos++;
+        }
+
+        pos++;
+        break;
+
+      case '\u00C7': // C with a cedilla
+        // This will always be mapped to 'S'.
+        metaphone.append("S");
+        pos++;
+        break;
+
+      case '\u00D1': // N with a tilde
+        // This will always be mapped to 'N'.
+        metaphone.append("N");
+        pos++;
+        break;
+
+      default:
+        // We don't have any special treatment for this character, so
+        // skip it.
+        pos++;
+        break;
+      }
+    }
+
+    return ByteString.valueOf(metaphone.toString());
+  }
+
+
+
+  /**
+   * Indicates whether the provided value has the given substring at the
+   * specified position.
+   * 
+   * @param value
+   *          The value containing the range for which to make the
+   *          determination.
+   * @param start
+   *          The position in the value at which to start the
+   *          comparison.
+   * @param substring
+   *          The substring to compare against the specified value
+   *          range.
+   * @return <CODE>true</CODE> if the specified portion of the value
+   *         matches the given substring, or <CODE>false</CODE> if it
+   *         does not.
+   */
+  private boolean hasSubstring(String value, int start, String substring)
+  {
+    try
+    {
+      // This can happen since a lot of the rules "look behind" and
+      // rightfully don't check if it's the first character
+      if (start < 0)
+      {
+        return false;
+      }
+
+      final int end = start + substring.length();
+
+      // value isn't big enough to do the comparison
+      if (end > value.length())
+      {
+        return false;
+      }
+
+      for (int i = 0, pos = start; pos < end; i++, pos++)
+      {
+        if (value.charAt(pos) != substring.charAt(i))
+        {
+          return false;
+        }
+      }
+
+      return true;
+    }
+    catch (final Exception e)
+    {
+      StaticUtils.DEBUG_LOG.throwing(
+          "DoubleMetaphoneApproximateMatchingRule", "hasSubstring", e);
+
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Indicates whether the provided string appears Germanic (starts with
+   * "VAN ", "VON ", or "SCH").
+   * 
+   * @param s
+   *          The string for which to make the determination.
+   * @return <CODE>true</CODE> if the provided string appears Germanic,
+   *         or <CODE>false</CODE> if not.
+   */
+  private boolean isGermanic(String s)
+  {
+    return s.startsWith("VAN ") || s.startsWith("VON ")
+        || s.startsWith("SCH");
+  }
+
+
+
+  /**
+   * Indicates whether the provided string appears to be Slavo-Germanic.
+   * 
+   * @param s
+   *          The string for which to make the determination.
+   * @return <CODE>true</CODE> if the provided string appears to be
+   *         Slavo-Germanic, or <CODE>false</CODE> if not.
+   */
+  private boolean isSlavoGermanic(String s)
+  {
+    return s.contains("W") || s.contains("K") || s.contains("CZ")
+        || s.contains("WITZ");
+  }
+
+
+
+  /**
+   * Indicates whether the provided character is a vowel (including
+   * "Y").
+   * 
+   * @param c
+   *          The character for which to make the determination.
+   * @return <CODE>true</CODE> if the provided character is a vowel, or
+   *         <CODE>false</CODE> if not.
+   */
+  private boolean isVowel(char c)
+  {
+    switch (c)
+    {
+    case 'A':
+    case 'E':
+    case 'I':
+    case 'O':
+    case 'U':
+    case 'Y':
+      return true;
+
+    default:
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java
new file mode 100644
index 0000000..4790bd1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EnhancedGuideSyntaxImpl.java
@@ -0,0 +1,184 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_ENHANCED_GUIDE_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the enhanced guide attribute syntax, which may
+ * be used to provide criteria for generating search filters for entries
+ * of a given objectclass.
+ */
+final class EnhancedGuideSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_ENHANCED_GUIDE_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Get a lowercase string version of the provided value.
+    final String valueStr = toLowerCase(value.toString());
+
+    // Find the position of the first octothorpe. It should denote the
+    // end of the objectclass.
+    final int sharpPos = valueStr.indexOf('#');
+    if (sharpPos < 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_SHARP
+          .get(valueStr));
+      return false;
+    }
+
+    // Get the objectclass and see if it is a valid name or OID.
+    final String ocName = valueStr.substring(0, sharpPos).trim();
+    final int ocLength = ocName.length();
+    if (ocLength == 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_OC
+          .get(valueStr));
+      return false;
+    }
+
+    try
+    {
+      SchemaUtils.readOID(new SubstringReader(ocName
+          .substring(ocLength)));
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+
+    // Find the last octothorpe and make sure it is followed by a valid
+    // scope.
+    final int lastSharpPos = valueStr.lastIndexOf('#');
+    if (lastSharpPos == sharpPos)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_FINAL_SHARP
+          .get(valueStr));
+      return false;
+    }
+
+    final String scopeStr = valueStr.substring(lastSharpPos + 1).trim();
+    if (!(scopeStr.equals("baseobject") || scopeStr.equals("onelevel")
+        || scopeStr.equals("wholesubtree") || scopeStr
+        .equals("subordinatesubtree")))
+    {
+      if (scopeStr.length() == 0)
+      {
+
+        invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_SCOPE
+            .get(valueStr));
+      }
+      else
+      {
+
+        invalidReason
+            .append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_INVALID_SCOPE.get(
+                valueStr, scopeStr));
+      }
+
+      return false;
+    }
+
+    // Everything between the two octothorpes must be the criteria. Make
+    // sure it is valid.
+    final String criteria =
+        valueStr.substring(sharpPos + 1, lastSharpPos).trim();
+    final int criteriaLength = criteria.length();
+    if (criteriaLength == 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_ENHANCEDGUIDE_NO_CRITERIA
+          .get(valueStr));
+      return false;
+    }
+
+    return GuideSyntaxImpl.criteriaIsValid(criteria, valueStr,
+        invalidReason);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java b/sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java
new file mode 100644
index 0000000..a82cd12
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EnumOrderingMatchingRule.java
@@ -0,0 +1,73 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE;
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class is the ordering matching rule implementation for an enum
+ * syntax implmentation. The ordering is determined by the order of the
+ * entries in the X-ENUM extension value.
+ */
+final class EnumOrderingMatchingRule extends
+    AbstractOrderingMatchingRuleImpl
+{
+  private final EnumSyntaxImpl syntax;
+
+
+
+  EnumOrderingMatchingRule(EnumSyntaxImpl syntax)
+  {
+    Validator.ensureNotNull(syntax);
+    this.syntax = syntax;
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final int index = syntax.indexOf(value);
+    if (index < 0)
+    {
+      throw DecodeException.error(
+          WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE.get(value
+              .toString(), syntax.getName()));
+    }
+    return ByteString.valueOf(index);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java
new file mode 100644
index 0000000..4687fa5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EnumSyntaxImpl.java
@@ -0,0 +1,200 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.AMR_DOUBLE_METAPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OID_GENERIC_ENUM;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides an enumeration-based mechanism where a new syntax
+ * and its corresponding matching rules can be created on-the-fly. An
+ * enum syntax is an LDAPSyntaxDescriptionSyntax with X-ENUM extension.
+ */
+final class EnumSyntaxImpl extends AbstractSyntaxImpl
+{
+  private final String oid;
+  // Set of read-only enum entries.
+  private final List<String> entries;
+
+
+
+  EnumSyntaxImpl(String oid, List<String> entries)
+  {
+    Validator.ensureNotNull(oid, entries);
+    this.oid = oid;
+    final List<String> entryStrings =
+        new ArrayList<String>(entries.size());
+
+    for (final String entry : entries)
+    {
+      final String normalized = normalize(ByteString.valueOf(entry));
+      if (!entryStrings.contains(normalized))
+      {
+        entryStrings.add(normalized);
+      }
+    }
+    this.entries = Collections.unmodifiableList(entryStrings);
+  }
+
+
+
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  public Iterable<String> getEntries()
+  {
+    return entries;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return oid;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OID_GENERIC_ENUM + "." + oid;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public int indexOf(ByteSequence value)
+  {
+    return entries.indexOf(normalize(value));
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // The value is acceptable if it belongs to the set.
+    final boolean isAllowed = entries.contains(normalize(value));
+
+    if (!isAllowed)
+    {
+      final Message message =
+          WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_INVALID_VALUE.get(value
+              .toString(), oid);
+      invalidReason.append(message);
+    }
+
+    return isAllowed;
+  }
+
+
+
+  private String normalize(ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return " ";
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return "";
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return buffer.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java
new file mode 100644
index 0000000..469ef63
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/EqualLengthApproximateMatchingRuleImpl.java
@@ -0,0 +1,71 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements an extremely simple approximate matching rule
+ * that will consider two values approximately equal only if they have
+ * the same length. It is intended purely for testing purposes.
+ */
+final class EqualLengthApproximateMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  @Override
+  public Assertion getAssertion(Schema schema, final ByteSequence value)
+      throws DecodeException
+  {
+    return new Assertion()
+    {
+      public ConditionResult matches(ByteSequence attributeValue)
+      {
+        return attributeValue.length() == value.length() ? ConditionResult.TRUE
+            : ConditionResult.FALSE;
+      }
+    };
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return value.toByteString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java
new file mode 100644
index 0000000..5112a04
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/FacsimileNumberSyntaxImpl.java
@@ -0,0 +1,240 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_EMPTY;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_END_WITH_DOLLAR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_ILLEGAL_PARAMETER;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_FAXNUMBER_NOT_PRINTABLE;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_FAXNUMBER_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.HashSet;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the facsimile telephone number attribute
+ * syntax, which contains a printable string (the number) followed by
+ * zero or more parameters. Those parameters should start with a dollar
+ * sign may be any of the following strings:
+ * <UL>
+ * <LI>twoDimensional</LI>
+ * <LI>fineResolution</LI>
+ * <LI>unlimitedLength</LI>
+ * <LI>b4Length</LI>
+ * <LI>a3Width</LI>
+ * <LI>b4Width</LI>
+ * <LI>uncompressed</LI>
+ * </UL>
+ */
+final class FacsimileNumberSyntaxImpl extends AbstractSyntaxImpl
+{
+  /**
+   * The set of allowed fax parameter values, formatted entirely in
+   * lowercase characters.
+   */
+  public static final HashSet<String> ALLOWED_FAX_PARAMETERS =
+      new HashSet<String>(7);
+
+  static
+  {
+    ALLOWED_FAX_PARAMETERS.add("twodimensional");
+    ALLOWED_FAX_PARAMETERS.add("fineresolution");
+    ALLOWED_FAX_PARAMETERS.add("unlimitedlength");
+    ALLOWED_FAX_PARAMETERS.add("b4length");
+    ALLOWED_FAX_PARAMETERS.add("a3width");
+    ALLOWED_FAX_PARAMETERS.add("b4width");
+    ALLOWED_FAX_PARAMETERS.add("uncompressed");
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_FAXNUMBER_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Get a lowercase string representation of the value and find its
+    // length.
+    final String valueString = toLowerCase(value.toString());
+    final int valueLength = valueString.length();
+
+    // The value must contain at least one character.
+    if (valueLength == 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_EMPTY.get());
+      return false;
+    }
+
+    // The first character must be a printable string character.
+    char c = valueString.charAt(0);
+    if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_NOT_PRINTABLE.get(
+          valueString, String.valueOf(c), 0));
+      return false;
+    }
+
+    // Continue reading until we find a dollar sign or the end of the
+    // string. Every intermediate character must be a printable string
+    // character.
+    int pos = 1;
+    for (; pos < valueLength; pos++)
+    {
+      c = valueString.charAt(pos);
+      if (c == '$')
+      {
+        pos++;
+        break;
+      }
+      else
+      {
+        if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+        {
+
+          invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_NOT_PRINTABLE
+              .get(valueString, String.valueOf(c), pos));
+        }
+      }
+    }
+
+    if (pos >= valueLength)
+    {
+      // We're at the end of the value, so it must be valid unless the
+      // last character was a dollar sign.
+      if (c == '$')
+      {
+
+        invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_END_WITH_DOLLAR
+            .get(valueString));
+        return false;
+      }
+      else
+      {
+        return true;
+      }
+    }
+
+    // Continue reading until we find the end of the string. Each
+    // substring must be a valid fax parameter.
+    int paramStartPos = pos;
+    while (pos < valueLength)
+    {
+      c = valueString.charAt(pos++);
+      if (c == '$')
+      {
+        final String paramStr =
+            valueString.substring(paramStartPos, pos);
+        if (!ALLOWED_FAX_PARAMETERS.contains(paramStr))
+        {
+
+          invalidReason
+              .append(ERR_ATTR_SYNTAX_FAXNUMBER_ILLEGAL_PARAMETER.get(
+                  valueString, paramStr, paramStartPos, (pos - 1)));
+          return false;
+        }
+
+        paramStartPos = pos;
+      }
+    }
+
+    // We must be at the end of the value. Read the last parameter and
+    // make sure it is valid.
+    final String paramStr = valueString.substring(paramStartPos);
+    if (!ALLOWED_FAX_PARAMETERS.contains(paramStr))
+    {
+      invalidReason.append(ERR_ATTR_SYNTAX_FAXNUMBER_ILLEGAL_PARAMETER
+          .get(valueString, paramStr, paramStartPos, (pos - 1)));
+      return false;
+    }
+
+    // If we've gotten here, then the value must be valid.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java
new file mode 100644
index 0000000..eff93f0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/FaxSyntaxImpl.java
@@ -0,0 +1,100 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_FAX_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the fax attribute syntax. This should be
+ * restricted to holding only fax message contents, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+final class FaxSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_FAX_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the fax syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..a153074
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GeneralizedTimeEqualityMatchingRuleImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the generalizedTimeMatch matching rule defined in
+ * X.520 and referenced in RFC 2252.
+ */
+final class GeneralizedTimeEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    return ByteString.valueOf(GeneralizedTimeSyntaxImpl
+        .decodeGeneralizedTimeValue(value));
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..dc7c429
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GeneralizedTimeOrderingMatchingRuleImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the generalizedTimeOrderingMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class GeneralizedTimeOrderingMatchingRuleImpl extends
+    AbstractOrderingMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    return ByteString.valueOf(GeneralizedTimeSyntaxImpl
+        .decodeGeneralizedTimeValue(value));
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java
new file mode 100644
index 0000000..9df5dde
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GeneralizedTimeSyntaxImpl.java
@@ -0,0 +1,1463 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import java.util.Calendar;
+import java.util.GregorianCalendar;
+import java.util.TimeZone;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the fax attribute syntax. This should be
+ * restricted to holding only fax message contents, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+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(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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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)
+      {
+        try
+        {
+          final GregorianCalendar calendar = new GregorianCalendar();
+          calendar.setLenient(false);
+          calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
+          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 Message message =
+              WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+                  valueString, String.valueOf(e));
+          final DecodeException de = DecodeException.error(message, e);
+          StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+              "valueIsAcceptable", de);
+          throw de;
+        }
+      }
+      else
+      {
+        final Message 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)
+      {
+        try
+        {
+          final GregorianCalendar calendar = new GregorianCalendar();
+          calendar.setLenient(false);
+          calendar.setTimeZone(getTimeZoneForOffset(valueString, 10));
+          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 Message message =
+              WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+                  valueString, String.valueOf(e));
+          final DecodeException de = DecodeException.error(message, e);
+          StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+              "valueIsAcceptable", de);
+          throw de;
+        }
+      }
+      else
+      {
+        final Message 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 Message 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 Message 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 Message 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 Message 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 Message 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)
+      {
+        try
+        {
+          final GregorianCalendar calendar = new GregorianCalendar();
+          calendar.setLenient(false);
+          calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
+          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 Message message =
+              WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+                  valueString, String.valueOf(e));
+          final DecodeException de = DecodeException.error(message, e);
+          StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+              "valueIsAcceptable", de);
+          throw de;
+        }
+      }
+      else
+      {
+        final Message 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)
+      {
+        try
+        {
+          final GregorianCalendar calendar = new GregorianCalendar();
+          calendar.setLenient(false);
+          calendar.setTimeZone(getTimeZoneForOffset(valueString, 12));
+          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 Message message =
+              WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+                  valueString, String.valueOf(e));
+          final DecodeException de = DecodeException.error(message, e);
+          StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+              "valueIsAcceptable", de);
+          throw de;
+        }
+      }
+      else
+      {
+        final Message 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 Message 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)
+      {
+        try
+        {
+          final GregorianCalendar calendar = new GregorianCalendar();
+          calendar.setLenient(false);
+          calendar.setTimeZone(TIME_ZONE_UTC_OBJ);
+          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 Message message =
+              WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+                  valueString, String.valueOf(e));
+          final DecodeException de = DecodeException.error(message, e);
+          StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+              "valueIsAcceptable", de);
+          throw de;
+        }
+      }
+      else
+      {
+        final Message 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)
+      {
+        try
+        {
+          final GregorianCalendar calendar = new GregorianCalendar();
+          calendar.setLenient(false);
+          calendar.setTimeZone(getTimeZoneForOffset(valueString, 14));
+          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 Message message =
+              WARN_ATTR_SYNTAX_GENERALIZED_TIME_ILLEGAL_TIME.get(
+                  valueString, String.valueOf(e));
+          final DecodeException de = DecodeException.error(message, e);
+          StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax",
+              "valueIsAcceptable", de);
+          throw de;
+        }
+      }
+      else
+      {
+        final Message 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 Message 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;
+    }
+  }
+
+
+
+  /**
+   * 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(String value,
+      int startPos, int year, int month, int day, int hour, int minute,
+      int second, 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 Message 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 Message 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 Message 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 Message 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 Message 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(String value,
+      int startPos) throws DecodeException
+  {
+    final String offSetStr = value.substring(startPos);
+    if (offSetStr.length() != 3 && offSetStr.length() != 5)
+    {
+      final Message 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 Message 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 Message 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 Message 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 Message 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 Message 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 Message 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;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_GENERALIZED_TIME_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_GENERALIZED_TIME_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    try
+    {
+      decodeGeneralizedTimeValue(value);
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java b/sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java
new file mode 100644
index 0000000..2928bce
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GenerateCoreSchema.java
@@ -0,0 +1,415 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import java.util.*;
+
+
+
+/**
+ * Tool for generating CoreSchema.java.
+ */
+final class GenerateCoreSchema
+{
+  private static boolean isOpenDSOID(String oid)
+  {
+    return oid.startsWith(SchemaConstants.OID_OPENDS_SERVER_BASE + ".");
+  }
+
+  private static final Set<String> ABBREVIATIONS =
+      new HashSet<String>(Arrays.asList("LDAP", "DN", "DIT", "RDN",
+          "JPEG", "OID", "UUID", "IA5", "UID", "UTC", "X500", "X121",
+          "C", "CN", "O", "OU", "L", "DC", "ISDN", "SN", "ST"));
+
+
+
+  private static String splitNameIntoWords(String name)
+  {
+    String splitName = name.replaceAll("([A-Z][a-z])", "_$1");
+    splitName = splitName.replaceAll("([a-z])([A-Z])", "$1_$2");
+
+    return splitName.toUpperCase(Locale.ENGLISH);
+  }
+
+
+
+  private static String toJavaName(String splitName)
+  {
+    StringBuilder builder = new StringBuilder();
+    for (String word : splitName.split("_"))
+    {
+      if (ABBREVIATIONS.contains(word))
+      {
+        builder.append(word);
+      }
+      else
+      {
+        builder.append(word.charAt(0));
+        if (word.length() > 1)
+        {
+          builder.append(word.substring(1).toLowerCase(Locale.ENGLISH));
+        }
+      }
+    }
+    return builder.toString();
+  }
+
+
+
+  private static void testSplitNameIntoWords()
+  {
+    String[][] values =
+        new String[][] { { "oneTwoThree", "ONE_TWO_THREE" },
+            { "oneTWOThree", "ONE_TWO_THREE" },
+            { "oneX500Three", "ONE_X500_THREE" },
+            { "oneTwoX500", "ONE_TWO_X500" },
+            { "oneTwoX500", "ONE_TWO_X500" },
+            { "x500TwoThree", "X500_TWO_THREE" }, };
+
+    for (String[] test : values)
+    {
+      String actual = splitNameIntoWords(test[0]);
+      String expected = test[1];
+      if (!actual.equals(expected))
+      {
+        System.out.println("Test Split Failure: " + test[0] + " -> "
+            + actual + " != " + expected);
+      }
+    }
+  }
+
+
+
+  /**
+   * Tool for generating CoreSchema.java.
+   *
+   * @param args
+   *          The command line arguments (none required).
+   */
+  public static void main(String[] args)
+  {
+    testSplitNameIntoWords();
+
+    Schema schema = Schema.getCoreSchema();
+
+    SortedMap<String, Syntax> syntaxes = new TreeMap<String, Syntax>();
+    for (Syntax syntax : schema.getSyntaxes())
+    {
+      if (isOpenDSOID(syntax.getOID()))
+      {
+        continue;
+      }
+
+      String name = syntax.getDescription().replaceAll(" Syntax$", "");
+      String fieldName =
+          name.replace(" ", "_").toUpperCase(Locale.ENGLISH).concat(
+              "_SYNTAX");
+      syntaxes.put(fieldName, syntax);
+    }
+
+    SortedMap<String, MatchingRule> matchingRules =
+        new TreeMap<String, MatchingRule>();
+    for (MatchingRule matchingRule : schema.getMatchingRules())
+    {
+      if (isOpenDSOID(matchingRule.getOID()))
+      {
+        continue;
+      }
+
+      String name =
+          matchingRule.getNameOrOID().replaceAll("Match$", "");
+      String fieldName =
+          splitNameIntoWords(name).concat("_MATCHING_RULE");
+      matchingRules.put(fieldName, matchingRule);
+    }
+
+    SortedMap<String, AttributeType> attributeTypes =
+        new TreeMap<String, AttributeType>();
+    for (AttributeType attributeType : schema.getAttributeTypes())
+    {
+      if (isOpenDSOID(attributeType.getOID()))
+      {
+        continue;
+      }
+      String name = attributeType.getNameOrOID();
+      String fieldName =
+          splitNameIntoWords(name).concat("_ATTRIBUTE_TYPE");
+      attributeTypes.put(fieldName, attributeType);
+    }
+
+    SortedMap<String, ObjectClass> objectClasses =
+        new TreeMap<String, ObjectClass>();
+    for (ObjectClass objectClass : schema.getObjectClasses())
+    {
+      if (isOpenDSOID(objectClass.getOID()))
+      {
+        continue;
+      }
+      String name = objectClass.getNameOrOID();
+      String fieldName =
+          splitNameIntoWords(name).concat("_OBJECT_CLASS");
+
+      objectClasses.put(fieldName, objectClass);
+    }
+
+    System.out.println();
+    System.out.println();
+    System.out.println();
+    System.out.println("package org.opends.sdk.schema;");
+    System.out.println();
+    System.out.println();
+    System.out.println();
+    System.out.println("/**");
+    System.out
+        .println(" * The OpenDS SDK core schema contains standard LDAP RFC schema elements. These include:");
+    System.out.println(" * <ul>");
+    System.out
+        .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4512\">RFC 4512 -");
+    System.out
+        .println(" * Lightweight Directory Access Protocol (LDAP): Directory Information");
+    System.out.println(" * Models </a>");
+    System.out
+        .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4517\">RFC 4517 -");
+    System.out
+        .println(" * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching");
+    System.out.println(" * Rules </a>");
+    System.out
+        .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4519\">RFC 4519 -");
+    System.out
+        .println(" * Lightweight Directory Access Protocol (LDAP): Schema for User");
+    System.out.println(" * Applications </a>");
+    System.out
+        .println(" * <li><a href=\"http://tools.ietf.org/html/rfc4530\">RFC 4530 -");
+    System.out
+        .println(" * Lightweight Directory Access Protocol (LDAP): entryUUID Operational");
+    System.out.println(" * Attribute </a>");
+    System.out
+        .println(" * <li><a href=\"http://tools.ietf.org/html/rfc3045\">RFC 3045 - Storing");
+    System.out
+        .println(" * Vendor Information in the LDAP Root DSE </a>");
+    System.out
+        .println(" * <li><a href=\"http://tools.ietf.org/html/rfc3112\">RFC 3112 - LDAP");
+    System.out.println(" * Authentication Password Schema </a>");
+    System.out.println(" * </ul>");
+    System.out.println(" * <p>");
+    System.out
+        .println(" * The core schema is non-strict: attempts to retrieve");
+    System.out
+        .println(" * non-existent Attribute Types will return a temporary");
+    System.out
+        .println(" * Attribute Type having the Octet String syntax.");
+    System.out.println(" */");
+    System.out.println("public final class CoreSchema");
+    System.out.println("{");
+
+    System.out.println("  // Core Syntaxes");
+    for (Map.Entry<String, Syntax> syntax : syntaxes.entrySet())
+    {
+      System.out.println("  private static final Syntax "
+          + syntax.getKey() + " =");
+      System.out
+          .println("    CoreSchemaImpl.getInstance().getSyntax(\""
+              + syntax.getValue().getOID() + "\");");
+    }
+
+    System.out.println();
+    System.out.println("  // Core Matching Rules");
+    for (Map.Entry<String, MatchingRule> matchingRule : matchingRules
+        .entrySet())
+    {
+      System.out.println("  private static final MatchingRule "
+          + matchingRule.getKey() + " =");
+      System.out
+          .println("    CoreSchemaImpl.getInstance().getMatchingRule(\""
+              + matchingRule.getValue().getOID() + "\");");
+    }
+
+    System.out.println();
+    System.out.println("  // Core Attribute Types");
+    for (Map.Entry<String, AttributeType> attributeType : attributeTypes
+        .entrySet())
+    {
+      System.out.println("  private static final AttributeType "
+          + attributeType.getKey() + " =");
+      System.out
+          .println("    CoreSchemaImpl.getInstance().getAttributeType(\""
+              + attributeType.getValue().getOID() + "\");");
+    }
+
+    System.out.println();
+    System.out.println("  // Core Object Classes");
+    for (Map.Entry<String, ObjectClass> objectClass : objectClasses
+        .entrySet())
+    {
+      System.out.println("  private static final ObjectClass "
+          + objectClass.getKey() + " =");
+      System.out
+          .println("    CoreSchemaImpl.getInstance().getObjectClass(\""
+              + objectClass.getValue().getOID() + "\");");
+    }
+
+    System.out.println();
+    System.out.println();
+    System.out.println();
+    System.out.println("  // Prevent instantiation");
+    System.out.println("  private CoreSchema()");
+    System.out.println("  {");
+    System.out.println("    // Nothing to do.");
+    System.out.println("  }");
+
+    System.out.println();
+    System.out.println();
+    System.out.println();
+    System.out.println("  /**");
+    System.out
+        .println("   * Returns a reference to the singleton core schema.");
+    System.out.println("   *");
+    System.out.println("   * @return The core schema.");
+    System.out.println("   */");
+    System.out.println("  public static Schema getInstance()");
+    System.out.println("  {");
+    System.out.println("    return CoreSchemaImpl.getInstance();");
+    System.out.println("  }");
+
+    for (Map.Entry<String, Syntax> syntax : syntaxes.entrySet())
+    {
+      System.out.println();
+      System.out.println();
+      System.out.println();
+
+      String description =
+          toCodeJavaDoc(syntax.getValue().getDescription().replaceAll(
+              " Syntax$", "")
+              + " Syntax");
+      System.out.println("  /**");
+      System.out.println("   * Returns a reference to the "
+          + description);
+      System.out.println("   * which has the OID "
+          + toCodeJavaDoc(syntax.getValue().getOID()) + ".");
+      System.out.println("   *");
+      System.out.println("   * @return A reference to the "
+          + description + ".");
+
+      System.out.println("   */");
+      System.out.println("  public static Syntax get"
+          + toJavaName(syntax.getKey()) + "()");
+      System.out.println("  {");
+      System.out.println("    return " + syntax.getKey() + ";");
+      System.out.println("  }");
+    }
+
+    for (Map.Entry<String, MatchingRule> matchingRule : matchingRules
+        .entrySet())
+    {
+      System.out.println();
+      System.out.println();
+      System.out.println();
+
+      String description =
+          toCodeJavaDoc(matchingRule.getValue().getNameOrOID());
+      System.out.println("  /**");
+      System.out.println("   * Returns a reference to the "
+          + description + " Matching Rule");
+      System.out.println("   * which has the OID "
+          + toCodeJavaDoc(matchingRule.getValue().getOID()) + ".");
+      System.out.println("   *");
+      System.out.println("   * @return A reference to the "
+          + description + " Matching Rule.");
+
+      System.out.println("   */");
+      System.out.println("  public static MatchingRule get"
+          + toJavaName(matchingRule.getKey()) + "()");
+      System.out.println("  {");
+      System.out.println("    return " + matchingRule.getKey() + ";");
+      System.out.println("  }");
+    }
+
+    for (Map.Entry<String, AttributeType> attributeType : attributeTypes
+        .entrySet())
+    {
+      System.out.println();
+      System.out.println();
+      System.out.println();
+
+      String description =
+          toCodeJavaDoc(attributeType.getValue().getNameOrOID());
+      System.out.println("  /**");
+      System.out.println("   * Returns a reference to the "
+          + description + " Attribute Type");
+      System.out.println("   * which has the OID "
+          + toCodeJavaDoc(attributeType.getValue().getOID()) + ".");
+      System.out.println("   *");
+      System.out.println("   * @return A reference to the "
+          + description + " Attribute Type.");
+
+      System.out.println("   */");
+      System.out.println("  public static AttributeType get"
+          + toJavaName(attributeType.getKey()) + "()");
+      System.out.println("  {");
+      System.out.println("    return " + attributeType.getKey() + ";");
+      System.out.println("  }");
+    }
+
+    for (Map.Entry<String, ObjectClass> objectClass : objectClasses
+        .entrySet())
+    {
+      System.out.println();
+      System.out.println();
+      System.out.println();
+
+      String description =
+          toCodeJavaDoc(objectClass.getValue().getNameOrOID());
+      System.out.println("  /**");
+      System.out.println("   * Returns a reference to the "
+          + description + " Object Class");
+      System.out.println("   * which has the OID "
+          + toCodeJavaDoc(objectClass.getValue().getOID()) + ".");
+      System.out.println("   *");
+      System.out.println("   * @return A reference to the "
+          + description + " Object Class.");
+
+      System.out.println("   */");
+      System.out.println("  public static ObjectClass get"
+          + toJavaName(objectClass.getKey()) + "()");
+      System.out.println("  {");
+      System.out.println("    return " + objectClass.getKey() + ";");
+      System.out.println("  }");
+    }
+
+    System.out.println("}");
+  }
+
+
+
+  private static String toCodeJavaDoc(String text)
+  {
+    return String.format("{@code %s}", text);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java
new file mode 100644
index 0000000..497e4a2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/GuideSyntaxImpl.java
@@ -0,0 +1,430 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_GUIDE_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the guide attribute syntax, which may be used
+ * to provide criteria for generating search filters for entries,
+ * optionally tied to a specified objectclass.
+ */
+final class GuideSyntaxImpl extends AbstractSyntaxImpl
+{
+  /**
+   * Determines whether the provided string represents a valid criteria
+   * according to the guide syntax.
+   * 
+   * @param criteria
+   *          The portion of the criteria for which to make the
+   *          determination.
+   * @param valueStr
+   *          The complete guide value provided by the client.
+   * @param invalidReason
+   *          The buffer to which to append the reason that the criteria
+   *          is invalid if a problem is found.
+   * @return <CODE>true</CODE> if the provided string does contain a
+   *         valid criteria, or <CODE>false</CODE> if not.
+   */
+  static boolean criteriaIsValid(String criteria, String valueStr,
+      MessageBuilder invalidReason)
+  {
+    // See if the criteria starts with a '!'. If so, then just evaluate
+    // everything after that as a criteria.
+    char c = criteria.charAt(0);
+    if (c == '!')
+    {
+      return criteriaIsValid(criteria.substring(1), valueStr,
+          invalidReason);
+    }
+
+    // See if the criteria starts with a '('. If so, then find the
+    // corresponding ')' and parse what's in between as a criteria.
+    if (c == '(')
+    {
+      final int length = criteria.length();
+      int depth = 1;
+
+      for (int i = 1; i < length; i++)
+      {
+        c = criteria.charAt(i);
+        if (c == ')')
+        {
+          depth--;
+          if (depth == 0)
+          {
+            final String subCriteria = criteria.substring(1, i);
+            if (!criteriaIsValid(subCriteria, valueStr, invalidReason))
+            {
+              return false;
+            }
+
+            // If we are at the end of the value, then it was valid.
+            // Otherwise, the next character must be a pipe or an
+            // ampersand followed by another set of criteria.
+            if (i == length - 1)
+            {
+              return true;
+            }
+            else
+            {
+              c = criteria.charAt(i + 1);
+              if (c == '|' || c == '&')
+              {
+                return criteriaIsValid(criteria.substring(i + 2),
+                    valueStr, invalidReason);
+              }
+              else
+              {
+
+                invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR
+                    .get(valueStr, criteria, c, (i + 1)));
+                return false;
+              }
+            }
+          }
+        }
+        else if (c == '(')
+        {
+          depth++;
+        }
+      }
+
+      // If we've gotten here, then we went through the entire value
+      // without finding the appropriate closing parenthesis.
+
+      invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_MISSING_CLOSE_PAREN
+          .get(valueStr, criteria));
+      return false;
+    }
+
+    // See if the criteria starts with a '?'. If so, then it must be
+    // either "?true" or "?false".
+    if (c == '?')
+    {
+      if (criteria.startsWith("?true"))
+      {
+        if (criteria.length() == 5)
+        {
+          return true;
+        }
+        else
+        {
+          // The only characters allowed next are a pipe or an
+          // ampersand.
+          c = criteria.charAt(5);
+          if (c == '|' || c == '&')
+          {
+            return criteriaIsValid(criteria.substring(6), valueStr,
+                invalidReason);
+          }
+          else
+          {
+            invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR
+                .get(valueStr, criteria, c, 5));
+            return false;
+          }
+        }
+      }
+      else if (criteria.startsWith("?false"))
+      {
+        if (criteria.length() == 6)
+        {
+          return true;
+        }
+        else
+        {
+          // The only characters allowed next are a pipe or an
+          // ampersand.
+          c = criteria.charAt(6);
+          if (c == '|' || c == '&')
+          {
+            return criteriaIsValid(criteria.substring(7), valueStr,
+                invalidReason);
+          }
+          else
+          {
+            invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR
+                .get(valueStr, criteria, c, 6));
+            return false;
+          }
+        }
+      }
+      else
+      {
+        invalidReason
+            .append(ERR_ATTR_SYNTAX_GUIDE_INVALID_QUESTION_MARK.get(
+                valueStr, criteria));
+        return false;
+      }
+    }
+
+    // See if the criteria is either "true" or "false". If so, then it
+    // is valid.
+    if (criteria.equals("true") || criteria.equals("false"))
+    {
+      return true;
+    }
+
+    // The only thing that will be allowed is an attribute type name or
+    // OID followed by a dollar sign and a match type. Find the dollar
+    // sign and verify whether the value before it is a valid attribute
+    // type name or OID.
+    final int dollarPos = criteria.indexOf('$');
+    if (dollarPos < 0)
+    {
+      invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_DOLLAR.get(
+          valueStr, criteria));
+      return false;
+    }
+    else if (dollarPos == 0)
+    {
+      invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_ATTR.get(valueStr,
+          criteria));
+      return false;
+    }
+    else if (dollarPos == criteria.length() - 1)
+    {
+      invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_MATCH_TYPE.get(
+          valueStr, criteria));
+      return false;
+    }
+    else
+    {
+      try
+      {
+        SchemaUtils.readOID(new SubstringReader(criteria.substring(0,
+            dollarPos)));
+      }
+      catch (final DecodeException de)
+      {
+        invalidReason.append(de.getMessageObject());
+        return false;
+      }
+    }
+
+    // The substring immediately after the dollar sign must be one of
+    // "eq", "substr", "ge", "le", or "approx". It may be followed by
+    // the end of the value, a pipe, or an ampersand.
+    int endPos;
+    c = criteria.charAt(dollarPos + 1);
+    switch (c)
+    {
+    case 'e':
+      if (criteria.startsWith("eq", dollarPos + 1))
+      {
+        endPos = dollarPos + 3;
+        break;
+      }
+      else
+      {
+        invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+            .get(valueStr, criteria, dollarPos + 1));
+        return false;
+      }
+
+    case 's':
+      if (criteria.startsWith("substr", dollarPos + 1))
+      {
+        endPos = dollarPos + 7;
+        break;
+      }
+      else
+      {
+        invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+            .get(valueStr, criteria, dollarPos + 1));
+        return false;
+      }
+
+    case 'g':
+      if (criteria.startsWith("ge", dollarPos + 1))
+      {
+        endPos = dollarPos + 3;
+        break;
+      }
+      else
+      {
+        invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+            .get(valueStr, criteria, dollarPos + 1));
+        return false;
+      }
+
+    case 'l':
+      if (criteria.startsWith("le", dollarPos + 1))
+      {
+        endPos = dollarPos + 3;
+        break;
+      }
+      else
+      {
+        invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+            .get(valueStr, criteria, dollarPos + 1));
+        return false;
+      }
+
+    case 'a':
+      if (criteria.startsWith("approx", dollarPos + 1))
+      {
+        endPos = dollarPos + 7;
+        break;
+      }
+      else
+      {
+        invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+            .get(valueStr, criteria, dollarPos + 1));
+        return false;
+      }
+
+    default:
+      invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_INVALID_MATCH_TYPE
+          .get(valueStr, criteria, dollarPos + 1));
+      return false;
+    }
+
+    // See if we are at the end of the value. If so, then it is valid.
+    // Otherwise, the next character must be a pipe or an ampersand.
+    if (endPos >= criteria.length())
+    {
+      return true;
+    }
+    else
+    {
+      c = criteria.charAt(endPos);
+      if (c == '|' || c == '&')
+      {
+        return criteriaIsValid(criteria.substring(endPos + 1),
+            valueStr, invalidReason);
+      }
+      else
+      {
+        invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_ILLEGAL_CHAR.get(
+            valueStr, criteria, c, endPos));
+        return false;
+      }
+    }
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_GUIDE_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Get a lowercase string version of the provided value.
+    final String valueStr = toLowerCase(value.toString());
+
+    // Find the position of the octothorpe. If there isn't one, then the
+    // entire value should be the criteria.
+    final int sharpPos = valueStr.indexOf('#');
+    if (sharpPos < 0)
+    {
+      return criteriaIsValid(valueStr, valueStr, invalidReason);
+    }
+
+    // Get the objectclass and see if it is a valid name or OID.
+    final String ocName = valueStr.substring(0, sharpPos).trim();
+    final int ocLength = ocName.length();
+    if (ocLength == 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_GUIDE_NO_OC.get(valueStr));
+      return false;
+    }
+
+    try
+    {
+      SchemaUtils.readOID(new SubstringReader(ocName.substring(0,
+          ocLength)));
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+
+    // The rest of the value must be the criteria.
+    return criteriaIsValid(valueStr.substring(sharpPos + 1), valueStr,
+        invalidReason);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java
new file mode 100644
index 0000000..e0086f0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IA5StringSyntaxImpl.java
@@ -0,0 +1,131 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the IA5 string attribute syntax, which is
+ * simply a set of ASCII characters. By default, they will be treated in
+ * a case-insensitive manner, and equality, ordering, substring, and
+ * approximate matching will be allowed.
+ */
+final class IA5StringSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_IA5_STRING_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We will allow any value that does not contain any non-ASCII
+    // characters. Empty values are acceptable as well.
+    byte b;
+    for (int i = 0; i < value.length(); i++)
+    {
+      b = value.byteAt(i);
+      if ((b & 0x7F) != b)
+      {
+
+        final Message message =
+            WARN_ATTR_SYNTAX_IA5_ILLEGAL_CHARACTER.get(
+                value.toString(), String.valueOf(b));
+        invalidReason.append(message);
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..f408ddc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerEqualityMatchingRuleImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_INTEGER;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class defines the integerMatch matching rule defined in X.520
+ * and referenced in RFC 2252.
+ */
+final class IntegerEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    try
+    {
+      return ByteString.valueOf(Integer.parseInt(value.toString()));
+    }
+    catch (final Exception e)
+    {
+      StaticUtils.DEBUG_LOG.throwing("IntegerEqualityMatchingRule",
+          "normalizeAttributeValue", e);
+
+      final Message message =
+          WARN_ATTR_SYNTAX_ILLEGAL_INTEGER.get(value.toString());
+      throw DecodeException.error(message);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..de646b0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerFirstComponentEqualityMatchingRuleImpl.java
@@ -0,0 +1,131 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_EMR_INTFIRSTCOMP_FIRST_COMPONENT_NOT_INT;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the integerFirstComponentMatch matching rule
+ * defined in X.520 and referenced in RFC 2252. This rule is intended
+ * for use with attributes whose values contain a set of parentheses
+ * enclosing a space-delimited set of names and/or name-value pairs
+ * (like attribute type or objectclass descriptions) in which the
+ * "first component" is the first item after the opening parenthesis.
+ */
+final class IntegerFirstComponentEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+      final int intValue = SchemaUtils.readRuleID(reader);
+
+      return new Assertion()
+      {
+        public ConditionResult matches(ByteSequence attributeValue)
+        {
+          final int actualIntValue =
+              attributeValue.toByteString().toInt();
+          return intValue == actualIntValue ? ConditionResult.TRUE
+              : ConditionResult.FALSE;
+        }
+      };
+    }
+    catch (final Exception e)
+    {
+      StaticUtils.DEBUG_LOG.throwing(
+          "IntegerFirstComponentEqualityMatchingRule", "getAssertion",
+          e);
+
+      final Message message =
+          ERR_EMR_INTFIRSTCOMP_FIRST_COMPONENT_NOT_INT.get(value
+              .toString());
+      throw DecodeException.error(message);
+    }
+
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final String definition = value.toString();
+    final SubstringReader reader = new SubstringReader(definition);
+
+    // We'll do this a character at a time. First, skip over any leading
+    // whitespace.
+    reader.skipWhitespaces();
+
+    if (reader.remaining() <= 0)
+    {
+      // This means that the value was empty or contained only
+      // whitespace. That is illegal.
+      final Message message = ERR_ATTR_SYNTAX_EMPTY_VALUE.get();
+      throw DecodeException.error(message);
+    }
+
+    // The next character must be an open parenthesis. If it is not,
+    // then that is an error.
+    final char c = reader.read();
+    if (c != '(')
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS.get(definition,
+              (reader.pos() - 1), String.valueOf(c));
+      throw DecodeException.error(message);
+    }
+
+    // Skip over any spaces immediately following the opening
+    // parenthesis.
+    reader.skipWhitespaces();
+
+    // The next set of characters must be the OID.
+    return ByteString.valueOf(SchemaUtils.readRuleID(reader));
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..9f630f0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerOrderingMatchingRuleImpl.java
@@ -0,0 +1,66 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_ILLEGAL_INTEGER;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class defines the integerOrderingMatch matching rule defined in
+ * X.520 and referenced in RFC 4519.
+ */
+final class IntegerOrderingMatchingRuleImpl extends
+    AbstractOrderingMatchingRuleImpl
+{
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    try
+    {
+      return ByteString.valueOf(Integer.parseInt(value.toString()));
+    }
+    catch (final Exception e)
+    {
+      StaticUtils.DEBUG_LOG.throwing("IntegerOrderingMatchingRule",
+          "normalizeAttributeValue", e);
+
+      final Message message =
+          WARN_ATTR_SYNTAX_ILLEGAL_INTEGER.get(value.toString());
+      throw DecodeException.error(message);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java
new file mode 100644
index 0000000..e95441d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/IntegerSyntaxImpl.java
@@ -0,0 +1,228 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_DASH_NEEDS_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_INITIAL_ZERO;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER;
+import static org.opends.sdk.schema.SchemaConstants.EMR_INTEGER_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_INTEGER_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_INTEGER_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the integer attribute syntax, which holds an
+ * arbitrarily-long integer value. Equality, ordering, and substring
+ * matching will be allowed by default.
+ */
+final class IntegerSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_INTEGER_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_INTEGER_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_INTEGER_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_EXACT_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String valueString = value.toString();
+    final int length = valueString.length();
+
+    if (length == 0)
+    {
+      invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_EMPTY_VALUE
+          .get(valueString));
+      return false;
+    }
+    else if (length == 1)
+    {
+      switch (valueString.charAt(0))
+      {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+        return true;
+      case '-':
+        invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_DASH_NEEDS_VALUE
+            .get(valueString));
+        return false;
+      default:
+        invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER
+            .get(valueString, valueString.charAt(0), 0));
+        return false;
+      }
+    }
+    else
+    {
+      boolean negative = false;
+
+      switch (valueString.charAt(0))
+      {
+      case '0':
+        invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INITIAL_ZERO
+            .get(valueString));
+        return false;
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+        // These are all fine.
+        break;
+      case '-':
+        // This is fine too.
+        negative = true;
+        break;
+      default:
+        invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER
+            .get(valueString, valueString.charAt(0), 0));
+        return false;
+      }
+
+      switch (valueString.charAt(1))
+      {
+      case '0':
+        // This is fine as long as the value isn't negative.
+        if (negative)
+        {
+          invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INITIAL_ZERO
+              .get(valueString));
+          return false;
+        }
+        break;
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+      case '6':
+      case '7':
+      case '8':
+      case '9':
+        // These are all fine.
+        break;
+      default:
+        invalidReason.append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER
+            .get(valueString, valueString.charAt(0), 0));
+        return false;
+      }
+
+      for (int i = 2; i < length; i++)
+      {
+        switch (valueString.charAt(i))
+        {
+        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:
+          invalidReason
+              .append(WARN_ATTR_SYNTAX_INTEGER_INVALID_CHARACTER.get(
+                  valueString, valueString.charAt(0), 0));
+          return false;
+        }
+      }
+
+      return true;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java
new file mode 100644
index 0000000..3b07283
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/JPEGSyntaxImpl.java
@@ -0,0 +1,99 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_JPEG_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the JPEG attribute syntax. This should be
+ * restricted to holding only JPEG image contents, but we will accept
+ * any set of bytes. It will be treated much like the octet string
+ * attribute syntax.
+ */
+final class JPEGSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_JPEG_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the fax syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..1b4b4e3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/KeywordEqualityMatchingRuleImpl.java
@@ -0,0 +1,184 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the keywordMatch matching rule defined in
+ * X.520. That document defines "keyword" as implementation-specific,
+ * but in this case we will consider it a match if the assertion value
+ * is contained within the attribute value and is bounded by the edge of
+ * the value or any of the following characters: <BR>
+ * <UL>
+ * <LI>A space</LI>
+ * <LI>A period</LI>
+ * <LI>A comma</LI>
+ * <LI>A slash</LI>
+ * <LI>A dollar sign</LI>
+ * <LI>A plus sign</LI>
+ * <LI>A dash</LI>
+ * <LI>An underscore</LI>
+ * <LI>An octothorpe</LI>
+ * <LI>An equal sign</LI>
+ * </UL>
+ */
+final class KeywordEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    final String normalStr = normalize(value);
+
+    return new Assertion()
+    {
+      public ConditionResult matches(ByteSequence attributeValue)
+      {
+        // See if the assertion value is contained in the attribute
+        // value. If not, then it isn't a match.
+        final String valueStr1 = attributeValue.toString();
+
+        final int pos = valueStr1.indexOf(normalStr);
+        if (pos < 0)
+        {
+          return ConditionResult.FALSE;
+        }
+
+        if (pos > 0)
+        {
+          final char c = valueStr1.charAt(pos - 1);
+          switch (c)
+          {
+          case ' ':
+          case '.':
+          case ',':
+          case '/':
+          case '$':
+          case '+':
+          case '-':
+          case '_':
+          case '#':
+          case '=':
+            // These are all acceptable.
+            break;
+
+          default:
+            // Anything else is not.
+            return ConditionResult.FALSE;
+          }
+        }
+
+        if (valueStr1.length() > pos + normalStr.length())
+        {
+          final char c = valueStr1.charAt(pos + normalStr.length());
+          switch (c)
+          {
+          case ' ':
+          case '.':
+          case ',':
+          case '/':
+          case '$':
+          case '+':
+          case '-':
+          case '_':
+          case '#':
+          case '=':
+            // These are all acceptable.
+            break;
+
+          default:
+            // Anything else is not.
+            return ConditionResult.FALSE;
+          }
+        }
+
+        // If we've gotten here, then we can assume it is a match.
+        return ConditionResult.TRUE;
+      }
+    };
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return ByteString.valueOf(normalize(value));
+  }
+
+
+
+  private String normalize(ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return " ".intern();
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return "".intern();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return buffer.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java
new file mode 100644
index 0000000..bfe652c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/LDAPSyntaxDescriptionSyntaxImpl.java
@@ -0,0 +1,236 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_LDAP_SYNTAX_NAME;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the LDAP syntax description syntax, which is used
+ * to hold attribute syntax definitions in the schema. The format of
+ * this syntax is defined in RFC 2252.
+ */
+final class LDAPSyntaxDescriptionSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_LDAP_SYNTAX_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll use the decodeNameForm method to determine if the value is
+    // acceptable.
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRSYNTAX_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("LDAPSyntaxDescriptionSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("LDAPSyntaxDescriptionSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the syntax. It is an
+          // arbitrary string of characters enclosed in single quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("LDAPSyntaxDescriptionSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+
+      for (final Map.Entry<String, List<String>> property : extraProperties
+          .entrySet())
+      {
+        if (property.getKey().equalsIgnoreCase("x-pattern"))
+        {
+          final Iterator<String> values =
+              property.getValue().iterator();
+          if (values.hasNext())
+          {
+            final String pattern = values.next();
+            try
+            {
+              Pattern.compile(values.next());
+            }
+            catch (final Exception e)
+            {
+              final Message message =
+                  WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN
+                      .get(oid, pattern);
+              final DecodeException de =
+                  DecodeException.error(message, e);
+              StaticUtils.DEBUG_LOG.throwing(
+                  "LDAPSyntaxDescriptionSyntax", "valueIsAcceptable",
+                  de);
+              throw de;
+            }
+            break;
+          }
+        }
+        else if (property.getKey().equalsIgnoreCase("x-enum"))
+        {
+          final List<String> values = property.getValue();
+          for (int i = 0; i < values.size() - 1; i++)
+          {
+            final String entry = values.get(i);
+            for (int j = i + 1; j < values.size(); j++)
+            {
+              if (entry.equals(values.get(j)))
+              {
+                final Message message =
+                    WARN_ATTR_SYNTAX_LDAPSYNTAX_ENUM_DUPLICATE_VALUE
+                        .get(oid, entry, j);
+                final DecodeException e = DecodeException.error(message);
+                StaticUtils.DEBUG_LOG.throwing(
+                    "LDAPSyntaxDescriptionSyntax", "valueIsAcceptable",
+                    e);
+                throw e;
+              }
+            }
+          }
+        }
+      }
+
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRule.java b/sdk/src/org/opends/sdk/schema/MatchingRule.java
new file mode 100644
index 0000000..1cedd1c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRule.java
@@ -0,0 +1,457 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_UNKNOWN_SYNTAX;
+import static org.opends.messages.SchemaMessages.WARN_MATCHING_RULE_NOT_IMPLEMENTED;
+
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * matching rules, which are used by servers to compare attribute values
+ * against assertion values when performing Search and Compare
+ * operations. They are also used to identify the value to be added or
+ * deleted when modifying entries, and are used when comparing a
+ * purported distinguished name with the name of an entry.
+ * <p>
+ * Matching rule implementations must extend the
+ * <code>MatchingRuleImplementation</code> class so they can be used by
+ * OpenDS.
+ * <p>
+ * Where ordered sets of names, or extra properties are provided, the
+ * ordering will be preserved when the associated fields are accessed
+ * via their getters or via the {@link #toString()} methods.
+ */
+public final class MatchingRule extends SchemaElement
+{
+  private final String oid;
+  private final List<String> names;
+  private final boolean isObsolete;
+  private final String syntaxOID;
+  private final String definition;
+  private MatchingRuleImpl impl;
+  private Syntax syntax;
+  private Schema schema;
+
+
+
+  MatchingRule(String oid, List<String> names, String description,
+      boolean obsolete, String syntax,
+      Map<String, List<String>> extraProperties, String definition,
+      MatchingRuleImpl implementation)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(oid, names, description, syntax);
+    Validator.ensureNotNull(extraProperties);
+    this.oid = oid;
+    this.names = names;
+    this.isObsolete = obsolete;
+    this.syntaxOID = syntax;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+    this.impl = implementation;
+  }
+
+
+
+  /**
+   * Get a comparator that can be used to compare the attribute values
+   * normalized by this matching rule.
+   *
+   * @return A comparator that can be used to compare the attribute
+   *         values normalized by this matching rule.
+   */
+  public Comparator<ByteSequence> comparator()
+  {
+    return impl.comparator(schema);
+  }
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion value,
+   * which is best suite for efficiently performing matching operations
+   * on that value. The assertion value is guarenteed to be valid
+   * against this matching rule's assertion syntax.
+   *
+   * @param value
+   *          The syntax checked assertion value to be normalized.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if the syntax of the value is not valid.
+   */
+  public Assertion getAssertion(ByteSequence value)
+      throws DecodeException
+  {
+    return impl.getAssertion(schema, value);
+  }
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion substring
+   * values, which is best suite for efficiently performing matching
+   * operations on that value.
+   *
+   * @param subInitial
+   *          The normalized substring value fragment that should appear
+   *          at the beginning of the target value.
+   * @param subAnyElements
+   *          The normalized substring value fragments that should
+   *          appear in the middle of the target value.
+   * @param subFinal
+   *          The normalized substring value fragment that should appear
+   *          at the end of the target value.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if the syntax of the value is not valid.
+   */
+  public Assertion getAssertion(ByteSequence subInitial,
+      List<ByteSequence> subAnyElements, ByteSequence subFinal)
+      throws DecodeException
+  {
+    return impl.getAssertion(schema, subInitial, subAnyElements,
+        subFinal);
+  }
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion value,
+   * which is best suite for efficiently performing greater than or
+   * equal ordering matching operations on that value. The assertion
+   * value is guarenteed to be valid against this matching rule's
+   * assertion syntax.
+   *
+   * @param value
+   *          The syntax checked assertion value to be normalized.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if the syntax of the value is not valid.
+   */
+  public Assertion getGreaterOrEqualAssertion(ByteSequence value)
+      throws DecodeException
+  {
+    return impl.getGreaterOrEqualAssertion(schema, value);
+  }
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion value,
+   * which is best suite for efficiently performing greater than or
+   * equal ordering matching operations on that value. The assertion
+   * value is guarenteed to be valid against this matching rule's
+   * assertion syntax.
+   *
+   * @param value
+   *          The syntax checked assertion value to be normalized.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if the syntax of the value is not valid.
+   */
+  public Assertion getLessOrEqualAssertion(ByteSequence value)
+      throws DecodeException
+  {
+    return impl.getLessOrEqualAssertion(schema, value);
+  }
+
+
+
+  /**
+   * Retrieves the name or OID for this schema definition. If it has one
+   * or more names, then the primary name will be returned. If it does
+   * not have any names, then the OID will be returned.
+   *
+   * @return The name or OID for this schema definition.
+   */
+  public String getNameOrOID()
+  {
+    if (names.isEmpty())
+    {
+      return oid;
+    }
+    return names.get(0);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the set of user-defined names that may
+   * be used to reference this schema definition.
+   *
+   * @return Returns an iterable over the set of user-defined names that
+   *         may be used to reference this schema definition.
+   */
+  public Iterable<String> getNames()
+  {
+    return names;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this schema definition.
+   *
+   * @return The OID for this schema definition.
+   */
+  public String getOID()
+  {
+
+    return oid;
+  }
+
+
+
+  /**
+   * Retrieves the OID of the assertion value syntax with which this
+   * matching rule is associated.
+   *
+   * @return The OID of the assertion value syntax with which this
+   *         matching rule is associated.
+   */
+  public Syntax getSyntax()
+  {
+    return syntax;
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return oid.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name.
+   *
+   * @param name
+   *          The name for which to make the determination.
+   * @return <code>true</code> if the specified name is assigned to this
+   *         schema definition, or <code>false</code> if not.
+   */
+  public boolean hasName(String name)
+  {
+    for (final String n : names)
+    {
+      if (n.equalsIgnoreCase(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name or
+   * OID.
+   *
+   * @param value
+   *          The value for which to make the determination.
+   * @return <code>true</code> if the provided value matches the OID or
+   *         one of the names assigned to this schema definition, or
+   *         <code>false</code> if not.
+   */
+  public boolean hasNameOrOID(String value)
+  {
+    return hasName(value) || getOID().equals(value);
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition is declared "obsolete".
+   *
+   * @return <code>true</code> if this schema definition is declared
+   *         "obsolete", or <code>false</code> if not.
+   */
+  public boolean isObsolete()
+  {
+    return isObsolete;
+  }
+
+
+
+  /**
+   * Retrieves the normalized form of the provided attribute value,
+   * which is best suite for efficiently performing matching operations
+   * on that value.
+   *
+   * @param value
+   *          The attribute value to be normalized.
+   * @return The normalized version of the provided attribute value.
+   * @throws DecodeException
+   *           if the syntax of the value is not valid.
+   */
+  public ByteString normalizeAttributeValue(ByteSequence value)
+      throws DecodeException
+  {
+    return impl.normalizeAttributeValue(schema, value);
+  }
+
+
+
+  /**
+   * Retrieves the string representation of this schema definition in
+   * the form specified in RFC 2252.
+   *
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  MatchingRule duplicate()
+  {
+    return new MatchingRule(oid, names, description, isObsolete,
+        syntaxOID, extraProperties, definition, impl);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(oid);
+
+    if (!names.isEmpty())
+    {
+      final Iterator<String> iterator = names.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NAME ( '");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append("' '");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append("' )");
+      }
+      else
+      {
+        buffer.append(" NAME '");
+        buffer.append(firstName);
+        buffer.append("'");
+      }
+    }
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+
+    if (isObsolete)
+    {
+      buffer.append(" OBSOLETE");
+    }
+
+    buffer.append(" SYNTAX ");
+    buffer.append(syntaxOID);
+  }
+
+
+
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    // Try finding an implementation in the core schema
+    if (impl == null && Schema.getDefaultSchema().hasMatchingRule(oid))
+    {
+      impl = Schema.getDefaultSchema().getMatchingRule(oid).impl;
+    }
+    if (impl == null && Schema.getCoreSchema().hasMatchingRule(oid))
+    {
+      impl = Schema.getCoreSchema().getMatchingRule(oid).impl;
+    }
+
+    if (impl == null)
+    {
+      impl = Schema.getDefaultMatchingRule().impl;
+      final Message message =
+          WARN_MATCHING_RULE_NOT_IMPLEMENTED.get(oid, Schema
+              .getDefaultMatchingRule().getOID());
+      warnings.add(message);
+    }
+
+    try
+    {
+      // Make sure the specifiec syntax is defined in this schema.
+      syntax = schema.getSyntax(syntaxOID);
+    }
+    catch (final UnknownSchemaElementException e)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_MR_UNKNOWN_SYNTAX.get(getNameOrOID(),
+              syntaxOID);
+      throw new SchemaException(message, e);
+    }
+
+    this.schema = schema;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java
new file mode 100644
index 0000000..04e6e12
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleImpl.java
@@ -0,0 +1,160 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import java.util.Comparator;
+import java.util.List;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This interface defines the set of methods that must be implemented to
+ * define a new matching rule.
+ */
+public interface MatchingRuleImpl
+{
+  /**
+   * Get a comparator that can be used to compare the attribute values
+   * normalized by this matching rule.
+   * 
+   * @param schema
+   *          The schema in which this matching rule is defined.
+   * @return A comparator that can be used to compare the attribute
+   *         values normalized by this matching rule.
+   */
+  public Comparator<ByteSequence> comparator(Schema schema);
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion value,
+   * which is best suite for efficiently performing matching operations
+   * on that value. The assertion value is guarenteed to be valid
+   * against this matching rule's assertion syntax.
+   * 
+   * @param schema
+   *          The schema in which this matching rule is defined.
+   * @param value
+   *          The syntax checked assertion value to be normalized.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if an syntax error occured while parsing the value.
+   */
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException;
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion substring
+   * values, which is best suite for efficiently performing matching
+   * operations on that value.
+   * 
+   * @param schema
+   *          The schema in which this matching rule is defined.
+   * @param subInitial
+   *          The normalized substring value fragment that should appear
+   *          at the beginning of the target value.
+   * @param subAnyElements
+   *          The normalized substring value fragments that should
+   *          appear in the middle of the target value.
+   * @param subFinal
+   *          The normalized substring value fragment that should appear
+   *          at the end of the target value.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if an syntax error occured while parsing the value.
+   */
+  public Assertion getAssertion(Schema schema, ByteSequence subInitial,
+      List<ByteSequence> subAnyElements, ByteSequence subFinal)
+      throws DecodeException;
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion value,
+   * which is best suite for efficiently performing greater than or
+   * equal matching operations on that value. The assertion value is
+   * guarenteed to be valid against this matching rule's assertion
+   * syntax.
+   * 
+   * @param schema
+   *          The schema in which this matching rule is defined.
+   * @param value
+   *          The syntax checked assertion value to be normalized.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if an syntax error occured while parsing the value.
+   */
+  public Assertion getGreaterOrEqualAssertion(Schema schema,
+      ByteSequence value) throws DecodeException;
+
+
+
+  /**
+   * Retrieves the normalized form of the provided assertion value,
+   * which is best suite for efficiently performing greater than or
+   * equal matching operations on that value. The assertion value is
+   * guarenteed to be valid against this matching rule's assertion
+   * syntax.
+   * 
+   * @param schema
+   *          The schema in which this matching rule is defined.
+   * @param value
+   *          The syntax checked assertion value to be normalized.
+   * @return The normalized version of the provided assertion value.
+   * @throws DecodeException
+   *           if an syntax error occured while parsing the value.
+   */
+  public Assertion getLessOrEqualAssertion(Schema schema,
+      ByteSequence value) throws DecodeException;
+
+
+
+  /**
+   * Retrieves the normalized form of the provided attribute value,
+   * which is best suite for efficiently performing matching operations
+   * on that value.
+   * 
+   * @param schema
+   *          The schema in which this matching rule is defined.
+   * @param value
+   *          The attribute value to be normalized.
+   * @return The normalized version of the provided attribute value.
+   * @throws DecodeException
+   *           if an syntax error occured while parsing the value.
+   */
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException;
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java
new file mode 100644
index 0000000..9657d53
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleSyntaxImpl.java
@@ -0,0 +1,215 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MR_NO_SYNTAX;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_MATCHING_RULE_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the matching rule description syntax, which is
+ * used to hold matching rule definitions in the server schema. The
+ * format of this syntax is defined in RFC 2252.
+ */
+final class MatchingRuleSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_MATCHING_RULE_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll use the decodeMatchingRule method to determine if the value
+    // is acceptable.
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_MR_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      SchemaUtils.readOID(reader);
+      String syntax = null;
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the matching rule. It is
+          // an arbitrary string of characters enclosed in single
+          // quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the matching rule should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("syntax"))
+        {
+          syntax = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          SchemaUtils.readExtensions(reader);
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+
+      // Make sure that a syntax was specified.
+      if (syntax == null)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MR_NO_SYNTAX.get(definition);
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("MatchingRuleSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleUse.java b/sdk/src/org/opends/sdk/schema/MatchingRuleUse.java
new file mode 100644
index 0000000..11a6a62
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleUse.java
@@ -0,0 +1,371 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_ATTR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_MATCHING_RULE;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * a matching rule use definition, which may be used to restrict the set
+ * of attribute types that may be used for a given matching rule.
+ */
+public final class MatchingRuleUse extends SchemaElement
+{
+  // The OID of the matching rule associated with this matching rule
+  // use definition.
+  private final String oid;
+
+  // The set of user defined names for this definition.
+  private final List<String> names;
+
+  // Indicates whether this definition is declared "obsolete".
+  private final boolean isObsolete;
+
+  // The set of attribute types with which this matching rule use is
+  // associated.
+  private final Set<String> attributeOIDs;
+
+  // The definition string used to create this objectclass.
+  private final String definition;
+
+  private MatchingRule matchingRule;
+  private Set<AttributeType> attributes = Collections.emptySet();
+
+
+
+  MatchingRuleUse(String oid, List<String> names, String description,
+      boolean obsolete, Set<String> attributeOIDs,
+      Map<String, List<String>> extraProperties, String definition)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(oid, names, attributeOIDs);
+    this.oid = oid;
+    this.names = names;
+    this.isObsolete = obsolete;
+    this.attributeOIDs = attributeOIDs;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+  }
+
+
+
+  /**
+   * Retrieves the set of attributes associated with this matching rule
+   * use.
+   * 
+   * @return The set of attributes associated with this matching rule
+   *         use.
+   */
+  public Iterable<AttributeType> getAttributes()
+  {
+    return attributes;
+  }
+
+
+
+  /**
+   * Retrieves the matching rule for this matching rule use.
+   * 
+   * @return The matching rule for this matching rule use.
+   */
+  public MatchingRule getMatchingRule()
+  {
+    return matchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the matching rule OID for this schema definition.
+   * 
+   * @return The OID for this schema definition.
+   */
+  public String getMatchingRuleOID()
+  {
+    return oid;
+  }
+
+
+
+  /**
+   * Retrieves the name or matching rule OID for this schema definition.
+   * If it has one or more names, then the primary name will be
+   * returned. If it does not have any names, then the OID will be
+   * returned.
+   * 
+   * @return The name or OID for this schema definition.
+   */
+  public String getNameOrOID()
+  {
+    if (names.isEmpty())
+    {
+      return oid;
+    }
+    return names.get(0);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the set of user-defined names that may
+   * be used to reference this schema definition.
+   * 
+   * @return Returns an iterable over the set of user-defined names that
+   *         may be used to reference this schema definition.
+   */
+  public Iterable<String> getNames()
+  {
+    return names;
+  }
+
+
+
+  /**
+   * Indicates whether the provided attribute type is referenced by this
+   * matching rule use.
+   * 
+   * @param attributeType
+   *          The attribute type for which to make the determination.
+   * @return {@code true} if the provided attribute type is referenced
+   *         by this matching rule use, or {@code false} if it is not.
+   */
+  public boolean hasAttribute(AttributeType attributeType)
+  {
+    return attributes.contains(attributeType);
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return oid.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name.
+   * 
+   * @param name
+   *          The name for which to make the determination.
+   * @return <code>true</code> if the specified name is assigned to this
+   *         schema definition, or <code>false</code> if not.
+   */
+  public boolean hasName(String name)
+  {
+    for (final String n : names)
+    {
+      if (n.equalsIgnoreCase(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name or
+   * matching rule OID.
+   * 
+   * @param value
+   *          The value for which to make the determination.
+   * @return <code>true</code> if the provided value matches the OID or
+   *         one of the names assigned to this schema definition, or
+   *         <code>false</code> if not.
+   */
+  public boolean hasNameOrOID(String value)
+  {
+    return hasName(value) || oid.equals(value);
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition is declared "obsolete".
+   * 
+   * @return <code>true</code> if this schema definition is declared
+   *         "obsolete", or <code>false</code> if not.
+   */
+  public boolean isObsolete()
+  {
+    return isObsolete;
+  }
+
+
+
+  /**
+   * Retrieves the string representation of this schema definition in
+   * the form specified in RFC 2252.
+   * 
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  MatchingRuleUse duplicate()
+  {
+    return new MatchingRuleUse(oid, names, description, isObsolete,
+        attributeOIDs, extraProperties, definition);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(oid);
+
+    if (!names.isEmpty())
+    {
+      final Iterator<String> iterator = names.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NAME ( '");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append("' '");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append("' )");
+      }
+      else
+      {
+        buffer.append(" NAME '");
+        buffer.append(firstName);
+        buffer.append("'");
+      }
+    }
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+
+    if (isObsolete)
+    {
+      buffer.append(" OBSOLETE");
+    }
+
+    if (!attributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator = attributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" APPLIES ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" APPLIES ");
+        buffer.append(firstName);
+      }
+    }
+  }
+
+
+
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    try
+    {
+      matchingRule = schema.getMatchingRule(oid);
+    }
+    catch (final UnknownSchemaElementException e)
+    {
+      // This is bad because the matching rule use is associated with a
+      // matching rule that we don't know anything about.
+      final Message message =
+          ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_MATCHING_RULE.get(definition,
+              oid);
+      throw new SchemaException(message, e);
+    }
+
+    attributes = new HashSet<AttributeType>(attributeOIDs.size());
+    AttributeType attributeType;
+    for (final String attribute : attributeOIDs)
+    {
+      try
+      {
+        attributeType = schema.getAttributeType(attribute);
+      }
+      catch (final UnknownSchemaElementException e)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MRUSE_UNKNOWN_ATTR.get(oid, attribute);
+        throw new SchemaException(message, e);
+      }
+      attributes.add(attributeType);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java
new file mode 100644
index 0000000..086055a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/MatchingRuleUseSyntaxImpl.java
@@ -0,0 +1,217 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_MRUSE_NO_ATTR;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_MATCHING_RULE_USE_NAME;
+
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the matching rule use description syntax, which
+ * is used to hold matching rule use definitions in the server schema.
+ * The format of this syntax is defined in RFC 2252.
+ */
+final class MatchingRuleUseSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_MATCHING_RULE_USE_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll use the decodeAttributeType method to determine if the
+    // value is acceptable.
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_MRUSE_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      SchemaUtils.readOID(reader);
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      Set<String> attributes = null;
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("applies"))
+        {
+          attributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          SchemaUtils.readExtensions(reader);
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+
+      // Make sure that the set of attributes was defined.
+      if (attributes == null || attributes.size() == 0)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MRUSE_NO_ATTR.get(definition);
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("MatchingRuleUseSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java
new file mode 100644
index 0000000..acc8ad7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NameAndOptionalUIDSyntaxImpl.java
@@ -0,0 +1,152 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAMEANDUID_ILLEGAL_BINARY_DIGIT;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAMEANDUID_INVALID_DN;
+import static org.opends.sdk.schema.SchemaConstants.EMR_UNIQUE_MEMBER_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_NAME_AND_OPTIONAL_UID_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DN;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * This class implements the name and optional UID attribute syntax,
+ * which holds values consisting of a DN, optionally followed by an
+ * octothorpe (#) and a bit string value.
+ */
+final class NameAndOptionalUIDSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_UNIQUE_MEMBER_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_NAME_AND_OPTIONAL_UID_NAME;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String valueString = value.toString().trim();
+    final int valueLength = valueString.length();
+
+    // See if the value contains the "optional uid" portion. If we think
+    // it does, then mark its location.
+    int dnEndPos = valueLength;
+    int sharpPos = -1;
+    if (valueString.endsWith("'B") || valueString.endsWith("'b"))
+    {
+      sharpPos = valueString.lastIndexOf("#'");
+      if (sharpPos > 0)
+      {
+        dnEndPos = sharpPos;
+      }
+    }
+
+    // Take the DN portion of the string and try to normalize it.
+    try
+    {
+      DN.valueOf(valueString.substring(0, dnEndPos), schema);
+    }
+    catch (final LocalizedIllegalArgumentException e)
+    {
+      // We couldn't normalize the DN for some reason. The value cannot
+      // be acceptable.
+      invalidReason.append(ERR_ATTR_SYNTAX_NAMEANDUID_INVALID_DN.get(
+          valueString, e.getMessageObject()));
+      return false;
+    }
+
+    // If there is an "optional uid", then normalize it and make sure it
+    // only contains valid binary digits.
+    if (sharpPos > 0)
+    {
+      final int endPos = valueLength - 2;
+      for (int i = sharpPos + 2; i < endPos; i++)
+      {
+        final char c = valueString.charAt(i);
+        if (!(c == '0' || c == '1'))
+        {
+
+          invalidReason
+              .append(ERR_ATTR_SYNTAX_NAMEANDUID_ILLEGAL_BINARY_DIGIT
+                  .get(valueString, String.valueOf(c), i));
+          return false;
+        }
+      }
+    }
+
+    // If we've gotten here, then the value is acceptable.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NameForm.java b/sdk/src/org/opends/sdk/schema/NameForm.java
new file mode 100644
index 0000000..eb0b398
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NameForm.java
@@ -0,0 +1,452 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * a name form, which defines the attribute type(s) that must and/or may
+ * be used in the RDN of an entry with a given structural objectclass.
+ */
+public final class NameForm extends SchemaElement
+{
+  // The OID that may be used to reference this definition.
+  private final String oid;
+
+  // The set of user defined names for this definition.
+  private final List<String> names;
+
+  // Indicates whether this definition is declared "obsolete".
+  private final boolean isObsolete;
+
+  // The reference to the structural objectclass for this name form.
+  private final String structuralClassOID;
+
+  // The set of optional attribute types for this name form.
+  private final Set<String> optionalAttributeOIDs;
+
+  // The set of required attribute types for this name form.
+  private final Set<String> requiredAttributeOIDs;
+
+  // The definition string used to create this objectclass.
+  private final String definition;
+
+  private ObjectClass structuralClass;
+  private Set<AttributeType> optionalAttributes =
+      Collections.emptySet();
+  private Set<AttributeType> requiredAttributes =
+      Collections.emptySet();
+
+
+
+  NameForm(String oid, List<String> names, String description,
+      boolean obsolete, String structuralClassOID,
+      Set<String> requiredAttributeOIDs,
+      Set<String> optionalAttributeOIDs,
+      Map<String, List<String>> extraProperties, String definition)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(oid, names);
+    Validator.ensureNotNull(structuralClassOID, requiredAttributeOIDs,
+        optionalAttributeOIDs);
+    Validator.ensureTrue(requiredAttributeOIDs.size() > 0,
+        "required attribute is empty");
+    this.oid = oid;
+    this.names = names;
+    this.isObsolete = obsolete;
+    this.structuralClassOID = structuralClassOID;
+    this.requiredAttributeOIDs = requiredAttributeOIDs;
+    this.optionalAttributeOIDs = optionalAttributeOIDs;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+  }
+
+
+
+  /**
+   * Retrieves the name or OID for this schema definition. If it has one
+   * or more names, then the primary name will be returned. If it does
+   * not have any names, then the OID will be returned.
+   * 
+   * @return The name or OID for this schema definition.
+   */
+  public String getNameOrOID()
+  {
+    if (names.isEmpty())
+    {
+      return oid;
+    }
+    return names.get(0);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the set of user-defined names that may
+   * be used to reference this schema definition.
+   * 
+   * @return Returns an iterable over the set of user-defined names that
+   *         may be used to reference this schema definition.
+   */
+  public Iterable<String> getNames()
+  {
+    return names;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this schema definition.
+   * 
+   * @return The OID for this schema definition.
+   */
+  public String getOID()
+  {
+
+    return oid;
+  }
+
+
+
+  /**
+   * Retrieves the set of optional attributes for this name form.
+   * 
+   * @return The set of optional attributes for this name form.
+   */
+  public Iterable<AttributeType> getOptionalAttributes()
+  {
+    return optionalAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the set of required attributes for this name form.
+   * 
+   * @return The set of required attributes for this name form.
+   */
+  public Iterable<AttributeType> getRequiredAttributes()
+  {
+    return requiredAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the reference to the structural objectclass for this name
+   * form.
+   * 
+   * @return The reference to the structural objectclass for this name
+   *         form.
+   */
+  public ObjectClass getStructuralClass()
+  {
+    return structuralClass;
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return oid.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name.
+   * 
+   * @param name
+   *          The name for which to make the determination.
+   * @return <code>true</code> if the specified name is assigned to this
+   *         schema definition, or <code>false</code> if not.
+   */
+  public boolean hasName(String name)
+  {
+    for (final String n : names)
+    {
+      if (n.equalsIgnoreCase(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name or
+   * OID.
+   * 
+   * @param value
+   *          The value for which to make the determination.
+   * @return <code>true</code> if the provided value matches the OID or
+   *         one of the names assigned to this schema definition, or
+   *         <code>false</code> if not.
+   */
+  public boolean hasNameOrOID(String value)
+  {
+    return hasName(value) || getOID().equals(value);
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition is declared "obsolete".
+   * 
+   * @return <code>true</code> if this schema definition is declared
+   *         "obsolete", or <code>false</code> if not.
+   */
+  public boolean isObsolete()
+  {
+    return isObsolete;
+  }
+
+
+
+  /**
+   * Retrieves the string representation of this schema definition in
+   * the form specified in RFC 2252.
+   * 
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  NameForm duplicate()
+  {
+    return new NameForm(oid, names, description, isObsolete,
+        structuralClassOID, requiredAttributeOIDs,
+        optionalAttributeOIDs, extraProperties, definition);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(oid);
+
+    if (!names.isEmpty())
+    {
+      final Iterator<String> iterator = names.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NAME ( '");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append("' '");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append("' )");
+      }
+      else
+      {
+        buffer.append(" NAME '");
+        buffer.append(firstName);
+        buffer.append("'");
+      }
+    }
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+
+    if (isObsolete)
+    {
+      buffer.append(" OBSOLETE");
+    }
+
+    buffer.append(" OC ");
+    buffer.append(structuralClassOID);
+
+    if (!requiredAttributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator =
+          requiredAttributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" MUST ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" MUST ");
+        buffer.append(firstName);
+      }
+    }
+
+    if (!optionalAttributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator =
+          optionalAttributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" MAY ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" MAY ");
+        buffer.append(firstName);
+      }
+    }
+  }
+
+
+
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    try
+    {
+      structuralClass = schema.getObjectClass(structuralClassOID);
+    }
+    catch (final UnknownSchemaElementException e)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS.get(oid,
+              structuralClassOID);
+      throw new SchemaException(message, e);
+    }
+    if (structuralClass.getObjectClassType() != ObjectClassType.STRUCTURAL)
+    {
+      // This is bad because the associated structural class type is not
+      // structural.
+      final Message message =
+          ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL
+              .get(oid, structuralClass.getOID(), structuralClass
+                  .getNameOrOID(), String.valueOf(structuralClass
+                  .getObjectClassType()));
+      throw new SchemaException(message);
+    }
+
+    requiredAttributes =
+        new HashSet<AttributeType>(requiredAttributeOIDs.size());
+    AttributeType attributeType;
+    for (final String oid : requiredAttributeOIDs)
+    {
+      try
+      {
+        attributeType = schema.getAttributeType(oid);
+      }
+      catch (final UnknownSchemaElementException e)
+      {
+        // This isn't good because it means that the name form requires
+        // an attribute type that we don't know anything about.
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR.get(
+                this.oid, oid);
+        throw new SchemaException(message, e);
+      }
+      requiredAttributes.add(attributeType);
+    }
+
+    if (!optionalAttributeOIDs.isEmpty())
+    {
+      optionalAttributes =
+          new HashSet<AttributeType>(optionalAttributeOIDs.size());
+      for (final String oid : optionalAttributeOIDs)
+      {
+        try
+        {
+          attributeType = schema.getAttributeType(oid);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          // This isn't good because it means that the name form
+          // requires an attribute type that we don't know anything
+          // about.
+          final Message message =
+              ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR.get(
+                  this.oid, oid);
+          throw new SchemaException(message, e);
+        }
+        optionalAttributes.add(attributeType);
+      }
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java
new file mode 100644
index 0000000..e03bbc0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NameFormSyntaxImpl.java
@@ -0,0 +1,223 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_NAME_FORM_NAME;
+
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the name form description syntax, which is used
+ * to hold name form definitions in the server schema. The format of
+ * this syntax is defined in RFC 2252.
+ */
+final class NameFormSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_NAME_FORM_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll use the decodeNameForm method to determine if the value is
+    // acceptable.
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), c);
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      SchemaUtils.readOID(reader);
+
+      String structuralClass = null;
+      Set<String> requiredAttributes = null;
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("oc"))
+        {
+          structuralClass = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("must"))
+        {
+          requiredAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("may"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          SchemaUtils.readExtensions(reader);
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+
+      // Make sure that a structural class was specified. If not, then
+      // it cannot be valid.
+      if (structuralClass == null)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS
+                .get(definition);
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      if (requiredAttributes == null || requiredAttributes.size() == 0)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_NO_REQUIRED_ATTR.get(definition);
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("NameFormSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..e803010
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringEqualityMatchingRuleImpl.java
@@ -0,0 +1,60 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the numericStringMatch matching rule defined in
+ * X.520 and referenced in RFC 2252. It allows for values with numeric
+ * digits and spaces, but ignores spaces when performing matching.
+ */
+final class NumericStringEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+    if (buffer.length() == 0)
+    {
+      return ByteString.empty();
+    }
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..0b342fc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringOrderingMatchingRuleImpl.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This implements defines the numericStringOrderingMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class NumericStringOrderingMatchingRuleImpl extends
+    AbstractOrderingMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+    if (buffer.length() == 0)
+    {
+      return ByteString.empty();
+    }
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..216aaef
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringSubstringMatchingRuleImpl.java
@@ -0,0 +1,59 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.NO_CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the numericStringSubstringsMatch matching rule
+ * defined in X.520 and referenced in RFC 2252.
+ */
+final class NumericStringSubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, NO_CASE_FOLD);
+
+    if (buffer.length() == 0)
+    {
+      return ByteString.empty();
+    }
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java
new file mode 100644
index 0000000..960ead7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/NumericStringSyntaxImpl.java
@@ -0,0 +1,137 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_NUMERIC_STRING_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_NUMERIC_STRING_ILLEGAL_CHAR;
+import static org.opends.sdk.schema.SchemaConstants.EMR_NUMERIC_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_NUMERIC_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_NUMERIC_STRING_NAME;
+import static org.opends.sdk.util.StaticUtils.isDigit;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the numeric string attribute syntax, which may
+ * be hold one or more numeric digits and/or spaces. Equality, ordering,
+ * and substring matching will be allowed by default.
+ */
+final class NumericStringSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_NUMERIC_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_NUMERIC_STRING_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_NUMERIC_STRING_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_EXACT_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String valueString = value.toString();
+    final int length = valueString.length();
+
+    // It must have at least one digit or space.
+    if (length == 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_NUMERIC_STRING_EMPTY_VALUE
+          .get());
+      return false;
+    }
+
+    // Iterate through the characters and make sure they are all digits
+    // or spaces.
+    for (int i = 0; i < length; i++)
+    {
+      final char c = valueString.charAt(i);
+      if (!(isDigit(c) || c == ' '))
+      {
+
+        invalidReason
+            .append(WARN_ATTR_SYNTAX_NUMERIC_STRING_ILLEGAL_CHAR.get(
+                valueString, String.valueOf(c), i));
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java
new file mode 100644
index 0000000..3ca71ae
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OIDSyntaxImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OID_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the OID syntax, which holds either an identifier
+ * name or a numeric OID. Equality and substring matching will be
+ * allowed by default.
+ */
+final class OIDSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_OID_NAME;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    try
+    {
+      SchemaUtils.readOID(new SubstringReader(value.toString()));
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectClass.java b/sdk/src/org/opends/sdk/schema/ObjectClass.java
new file mode 100644
index 0000000..81844ee
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectClass.java
@@ -0,0 +1,810 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_NAME;
+import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
+import static org.opends.sdk.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * an objectclass, which contains a collection of attributes that must
+ * and/or may be present in an entry with that objectclass.
+ * <p>
+ * Where ordered sets of names, attribute types, or extra properties are
+ * provided, the ordering will be preserved when the associated fields
+ * are accessed via their getters or via the {@link #toString()}
+ * methods.
+ */
+public final class ObjectClass extends SchemaElement
+{
+  // The OID that may be used to reference this definition.
+  private final String oid;
+
+  // The set of user defined names for this definition.
+  private final List<String> names;
+
+  // Indicates whether this definition is declared "obsolete".
+  private final boolean isObsolete;
+
+  // The reference to the superior objectclasses.
+  private final Set<String> superiorClassOIDs;
+
+  // The objectclass type for this objectclass.
+  private final ObjectClassType objectClassType;
+
+  // The set of required attribute types for this objectclass.
+  private final Set<String> requiredAttributeOIDs;
+
+  // The set of optional attribute types for this objectclass.
+  private final Set<String> optionalAttributeOIDs;
+
+  // The definition string used to create this objectclass.
+  private final String definition;
+
+  private Set<ObjectClass> superiorClasses = Collections.emptySet();
+  private Set<AttributeType> declaredRequiredAttributes =
+      Collections.emptySet();
+  private Set<AttributeType> requiredAttributes =
+      Collections.emptySet();
+  private Set<AttributeType> declaredOptionalAttributes =
+      Collections.emptySet();
+  private Set<AttributeType> optionalAttributes =
+      Collections.emptySet();
+  private boolean validated = false;
+
+
+
+  ObjectClass(String oid, List<String> names, String description,
+      boolean obsolete, Set<String> superiorClassOIDs,
+      Set<String> requiredAttributeOIDs,
+      Set<String> optionalAttributeOIDs,
+      ObjectClassType objectClassType,
+      Map<String, List<String>> extraProperties, String definition)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(oid, names);
+    Validator.ensureNotNull(superiorClassOIDs, requiredAttributeOIDs,
+        optionalAttributeOIDs, objectClassType);
+    this.oid = oid;
+    this.names = names;
+    this.isObsolete = obsolete;
+    this.superiorClassOIDs = superiorClassOIDs;
+    this.objectClassType = objectClassType;
+    this.requiredAttributeOIDs = requiredAttributeOIDs;
+    this.optionalAttributeOIDs = optionalAttributeOIDs;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+  }
+
+
+
+  /**
+   * Construct a extensibleObject object class where the set of allowed
+   * attribute types of this object class is implicitly the set of all
+   * attribute types of userApplications usage.
+   *
+   * @param description
+   *          The description for this schema definition
+   * @param extraProperties
+   *          The map of "extra" properties for this schema definition
+   */
+  ObjectClass(String description,
+      Map<String, List<String>> extraProperties)
+  {
+    super(description, extraProperties);
+    this.oid = EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
+    this.names =
+        Collections.singletonList(EXTENSIBLE_OBJECT_OBJECTCLASS_NAME);
+    this.isObsolete = false;
+    this.superiorClassOIDs =
+        Collections.singleton(TOP_OBJECTCLASS_NAME);
+    this.objectClassType = ObjectClassType.AUXILIARY;
+    this.requiredAttributeOIDs = Collections.emptySet();
+    this.optionalAttributeOIDs = Collections.emptySet();
+
+    this.definition = buildDefinition();
+  }
+
+
+
+  @Override
+  public boolean equals(Object o)
+  {
+    if (this == o)
+    {
+      return true;
+    }
+
+    if (o instanceof ObjectClass)
+    {
+      final ObjectClass other = (ObjectClass) o;
+      return oid.equals(other.oid);
+    }
+
+    return false;
+  }
+
+
+
+  /**
+   * Retrieves the list of optional attributes for this objectclass.
+   * Note that this set will not automatically include any optional
+   * attributes for superior objectclasses.
+   *
+   * @return Returns the list of optional attributes for this
+   *         objectclass.
+   */
+  public Iterable<AttributeType> getDeclaredOptionalAttributes()
+  {
+    return declaredOptionalAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the list of required attributes for this objectclass.
+   * Note that this set will not automatically include any required
+   * attributes for superior objectclasses.
+   *
+   * @return Returns the list of required attributes for this
+   *         objectclass.
+   */
+  public Iterable<AttributeType> getDeclaredRequiredAttributes()
+  {
+    return declaredRequiredAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the name or OID for this schema definition. If it has one
+   * or more names, then the primary name will be returned. If it does
+   * not have any names, then the OID will be returned.
+   *
+   * @return The name or OID for this schema definition.
+   */
+  public String getNameOrOID()
+  {
+    if (names.isEmpty())
+    {
+      return oid;
+    }
+    return names.get(0);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the set of user-defined names that may
+   * be used to reference this schema definition.
+   *
+   * @return Returns an iterable over the set of user-defined names that
+   *         may be used to reference this schema definition.
+   */
+  public Iterable<String> getNames()
+  {
+    return names;
+  }
+
+
+
+  /**
+   * Retrieves the objectclass type for this objectclass.
+   *
+   * @return The objectclass type for this objectclass.
+   */
+  public ObjectClassType getObjectClassType()
+  {
+
+    return objectClassType;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this schema definition.
+   *
+   * @return The OID for this schema definition.
+   */
+  public String getOID()
+  {
+
+    return oid;
+  }
+
+
+
+  /**
+   * Retrieves the list of all optional attributes for this objectclass
+   * and any superior objectclasses that it might have.
+   *
+   * @return Returns the list of all optional attributes for this
+   *         objectclass and any superior objectclasses that it might
+   *         have.
+   */
+  public Iterable<AttributeType> getOptionalAttributes()
+  {
+    return optionalAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the list of all required attributes for this objectclass
+   * and any superior objectclasses that it might have.
+   *
+   * @return Returns the list of all required attributes for this
+   *         objectclass and any superior objectclasses that it might
+   *         have.
+   */
+  public Iterable<AttributeType> getRequiredAttributes()
+  {
+    return requiredAttributes;
+  }
+
+
+
+  /**
+   * Retrieves the reference to the superior classes for this
+   * objectclass.
+   *
+   * @return The list of superior classes for this objectlass.
+   */
+  public Iterable<ObjectClass> getSuperiorClasses()
+  {
+    return superiorClasses;
+  }
+
+
+
+  @Override
+  public int hashCode()
+  {
+    return oid.hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name.
+   *
+   * @param name
+   *          The name for which to make the determination.
+   * @return <code>true</code> if the specified name is assigned to this
+   *         schema definition, or <code>false</code> if not.
+   */
+  public boolean hasName(String name)
+  {
+    for (final String n : names)
+    {
+      if (n.equalsIgnoreCase(name))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition has the specified name or
+   * OID.
+   *
+   * @param value
+   *          The value for which to make the determination.
+   * @return <code>true</code> if the provided value matches the OID or
+   *         one of the names assigned to this schema definition, or
+   *         <code>false</code> if not.
+   */
+  public boolean hasNameOrOID(String value)
+  {
+    return hasName(value) || getOID().equals(value);
+  }
+
+
+
+  /**
+   * Indicates whether this objectclass is a descendant of the provided
+   * class.
+   *
+   * @param objectClass
+   *          The objectClass for which to make the determination.
+   * @return <code>true</code> if this objectclass is a descendant of
+   *         the provided class, or <code>false</code> if not.
+   */
+  public boolean isDescendantOf(ObjectClass objectClass)
+  {
+    for (final ObjectClass sup : superiorClasses)
+    {
+      if (sup.equals(objectClass) || sup.isDescendantOf(objectClass))
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether this schema definition is declared "obsolete".
+   *
+   * @return <code>true</code> if this schema definition is declared
+   *         "obsolete", or <code>false</code> if not.
+   */
+  public boolean isObsolete()
+  {
+    return isObsolete;
+  }
+
+
+
+  /**
+   * Indicates whether the provided attribute type is included in the
+   * optional attribute list for this or any of its superior
+   * objectclasses.
+   *
+   * @param attributeType
+   *          The attribute type for which to make the determination.
+   * @return <code>true</code> if the provided attribute type is
+   *         optional for this objectclass or any of its superior
+   *         classes, or <code>false</code> if not.
+   */
+  public boolean isOptional(AttributeType attributeType)
+  {
+    return optionalAttributes.contains(attributeType);
+  }
+
+
+
+  /**
+   * Indicates whether the provided attribute type is included in the
+   * required attribute list for this or any of its superior
+   * objectclasses.
+   *
+   * @param attributeType
+   *          The attribute type for which to make the determination.
+   * @return <code>true</code> if the provided attribute type is
+   *         required by this objectclass or any of its superior
+   *         classes, or <code>false</code> if not.
+   */
+  public boolean isRequired(AttributeType attributeType)
+  {
+    return requiredAttributes.contains(attributeType);
+  }
+
+
+
+  /**
+   * Indicates whether the provided attribute type is in the list of
+   * required or optional attributes for this objectclass or any of its
+   * superior classes.
+   *
+   * @param attributeType
+   *          The attribute type for which to make the determination.
+   * @return <code>true</code> if the provided attribute type is
+   *         required or allowed for this objectclass or any of its
+   *         superior classes, or <code>false</code> if it is not.
+   */
+  public boolean isRequiredOrOptional(AttributeType attributeType)
+  {
+    return isRequired(attributeType) || isOptional(attributeType);
+  }
+
+
+
+  /**
+   * Retrieves the string representation of this schema definition in
+   * the form specified in RFC 2252.
+   *
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  ObjectClass duplicate()
+  {
+    return new ObjectClass(oid, names, description, isObsolete,
+        superiorClassOIDs, requiredAttributeOIDs,
+        optionalAttributeOIDs, objectClassType, extraProperties,
+        definition);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(oid);
+
+    if (!names.isEmpty())
+    {
+      final Iterator<String> iterator = names.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" NAME ( '");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append("' '");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append("' )");
+      }
+      else
+      {
+        buffer.append(" NAME '");
+        buffer.append(firstName);
+        buffer.append("'");
+      }
+    }
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+
+    if (isObsolete)
+    {
+      buffer.append(" OBSOLETE");
+    }
+
+    if (!superiorClassOIDs.isEmpty())
+    {
+      final Iterator<String> iterator = superiorClassOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" SUP ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" SUP ");
+        buffer.append(firstName);
+      }
+    }
+
+    if (objectClassType != null)
+    {
+      buffer.append(" ");
+      buffer.append(objectClassType.toString());
+    }
+
+    if (!requiredAttributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator =
+          requiredAttributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" MUST ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" MUST ");
+        buffer.append(firstName);
+      }
+    }
+
+    if (!optionalAttributeOIDs.isEmpty())
+    {
+      final Iterator<String> iterator =
+          optionalAttributeOIDs.iterator();
+
+      final String firstName = iterator.next();
+      if (iterator.hasNext())
+      {
+        buffer.append(" MAY ( ");
+        buffer.append(firstName);
+
+        while (iterator.hasNext())
+        {
+          buffer.append(" $ ");
+          buffer.append(iterator.next());
+        }
+
+        buffer.append(" )");
+      }
+      else
+      {
+        buffer.append(" MAY ");
+        buffer.append(firstName);
+      }
+    }
+  }
+
+
+
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    if (validated)
+    {
+      return;
+    }
+    validated = true;
+
+    // Init a flag to check to inheritance from top (only needed for
+    // structural object classes) per RFC 4512
+    boolean derivesTop = objectClassType != ObjectClassType.STRUCTURAL;
+
+    if (!superiorClassOIDs.isEmpty())
+    {
+      superiorClasses =
+          new HashSet<ObjectClass>(superiorClassOIDs.size());
+      ObjectClass superiorClass;
+      for (final String superClassOid : superiorClassOIDs)
+      {
+        try
+        {
+          superiorClass = schema.getObjectClass(superClassOid);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          final Message message =
+              WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_SUPERIOR_CLASS.get(
+                  oid, superClassOid);
+          throw new SchemaException(message, e);
+        }
+
+        // Make sure that the inheritance configuration is acceptable.
+        final ObjectClassType superiorType =
+            superiorClass.getObjectClassType();
+        switch (objectClassType)
+        {
+        case ABSTRACT:
+          // Abstract classes may only inherit from other abstract
+          // classes.
+          if (superiorType != ObjectClassType.ABSTRACT)
+          {
+            final Message message =
+                WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE.get(
+                    oid, objectClassType.toString(), superiorType
+                        .toString(), superiorClass.getNameOrOID());
+            throw new SchemaException(message);
+          }
+          break;
+
+        case AUXILIARY:
+          // Auxiliary classes may only inherit from abstract classes or
+          // other auxiliary classes.
+          if (superiorType != ObjectClassType.ABSTRACT
+              && superiorType != ObjectClassType.AUXILIARY)
+          {
+            final Message message =
+                WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE.get(
+                    oid, objectClassType.toString(), superiorType
+                        .toString(), superiorClass.getNameOrOID());
+            throw new SchemaException(message);
+          }
+          break;
+
+        case STRUCTURAL:
+          // Structural classes may only inherit from abstract classes
+          // or other structural classes.
+          if (superiorType != ObjectClassType.ABSTRACT
+              && superiorType != ObjectClassType.STRUCTURAL)
+          {
+            final Message message =
+                WARN_ATTR_SYNTAX_OBJECTCLASS_INVALID_SUPERIOR_TYPE.get(
+                    oid, objectClassType.toString(), superiorType
+                        .toString(), superiorClass.getNameOrOID());
+            throw new SchemaException(message);
+          }
+          break;
+        }
+
+        // All existing structural object classes defined in this schema
+        // are implicitly guaranteed to inherit from top
+        if (!derivesTop && superiorType == ObjectClassType.STRUCTURAL)
+        {
+          derivesTop = true;
+        }
+
+        // Validate superior object class so we can inherit its
+        // attributes.
+        superiorClass.validate(warnings, schema);
+
+        // Inherit all required attributes from superior class.
+        Iterator<AttributeType> i =
+            superiorClass.getRequiredAttributes().iterator();
+        if (i.hasNext() && requiredAttributes == Collections.EMPTY_SET)
+        {
+          requiredAttributes = new HashSet<AttributeType>();
+        }
+        while (i.hasNext())
+        {
+          requiredAttributes.add(i.next());
+        }
+
+        // Inherit all optional attributes from superior class.
+        i = superiorClass.getRequiredAttributes().iterator();
+        if (i.hasNext() && requiredAttributes == Collections.EMPTY_SET)
+        {
+          requiredAttributes = new HashSet<AttributeType>();
+        }
+        while (i.hasNext())
+        {
+          requiredAttributes.add(i.next());
+        }
+
+        superiorClasses.add(superiorClass);
+      }
+    }
+
+    if (!derivesTop)
+    {
+      derivesTop = isDescendantOf(schema.getObjectClass("2.5.6.0"));
+    }
+
+    // Structural classes must have the "top" objectclass somewhere
+    // in the superior chain.
+    if (!derivesTop)
+    {
+      final Message message =
+          WARN_ATTR_SYNTAX_OBJECTCLASS_STRUCTURAL_SUPERIOR_NOT_TOP
+              .get(oid);
+      throw new SchemaException(message);
+    }
+
+    if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID))
+    {
+      declaredOptionalAttributes =
+          new HashSet<AttributeType>(requiredAttributeOIDs.size());
+      for (final AttributeType attributeType : schema
+          .getAttributeTypes())
+      {
+        if (attributeType.getUsage() == AttributeUsage.USER_APPLICATIONS)
+        {
+          declaredOptionalAttributes.add(attributeType);
+        }
+      }
+      optionalAttributes = declaredRequiredAttributes;
+      return;
+    }
+
+    if (!requiredAttributeOIDs.isEmpty())
+    {
+      declaredRequiredAttributes =
+          new HashSet<AttributeType>(requiredAttributeOIDs.size());
+      AttributeType attributeType;
+      for (final String requiredAttribute : requiredAttributeOIDs)
+      {
+        try
+        {
+          attributeType = schema.getAttributeType(requiredAttribute);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          // This isn't good because it means that the objectclass
+          // requires an attribute type that we don't know anything
+          // about.
+          final Message message =
+              WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_REQUIRED_ATTR.get(
+                  oid, requiredAttribute);
+          throw new SchemaException(message, e);
+        }
+        declaredRequiredAttributes.add(attributeType);
+      }
+      if (requiredAttributes == Collections.EMPTY_SET)
+      {
+        requiredAttributes = declaredRequiredAttributes;
+      }
+      else
+      {
+        requiredAttributes.addAll(declaredRequiredAttributes);
+      }
+    }
+
+    if (!optionalAttributeOIDs.isEmpty())
+    {
+      declaredOptionalAttributes =
+          new HashSet<AttributeType>(optionalAttributeOIDs.size());
+      AttributeType attributeType;
+      for (final String optionalAttribute : optionalAttributeOIDs)
+      {
+        try
+        {
+          attributeType = schema.getAttributeType(optionalAttribute);
+        }
+        catch (final UnknownSchemaElementException e)
+        {
+          // This isn't good because it means that the objectclass
+          // requires an attribute type that we don't know anything
+          // about.
+          final Message message =
+              WARN_ATTR_SYNTAX_OBJECTCLASS_UNKNOWN_OPTIONAL_ATTR.get(
+                  oid, optionalAttribute);
+          throw new SchemaException(message, e);
+        }
+        declaredOptionalAttributes.add(attributeType);
+      }
+      if (optionalAttributes == Collections.EMPTY_SET)
+      {
+        optionalAttributes = declaredOptionalAttributes;
+      }
+      else
+      {
+        optionalAttributes.addAll(declaredOptionalAttributes);
+      }
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java
new file mode 100644
index 0000000..23a294a
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectClassSyntaxImpl.java
@@ -0,0 +1,214 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_ILLEGAL_TOKEN;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS;
+import static org.opends.sdk.schema.SchemaConstants.EMR_OID_FIRST_COMPONENT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OBJECTCLASS_NAME;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the object class description syntax, which is
+ * used to hold objectclass definitions in the server schema. The format
+ * of this syntax is defined in RFC 2252.
+ */
+final class ObjectClassSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OID_FIRST_COMPONENT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_OBJECTCLASS_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll use the decodeObjectClass method to determine if the value
+    // is acceptable.
+    try
+    {
+      final String definition = value.toString();
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE.get();
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("ObjectClassSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        final DecodeException e = DecodeException.error(message);
+        StaticUtils.DEBUG_LOG.throwing("ObjectClassSyntax",
+            "valueIsAcceptable", e);
+        throw e;
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      SchemaUtils.readOID(reader);
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("sup"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("abstract"))
+        {
+          // This indicates that entries must not include this
+          // objectclass unless they also include a non-abstract
+          // objectclass that inherits from this class. We do not need
+          // any more parsing for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("structural"))
+        {
+          // This indicates that this is a structural objectclass.
+          // We do not need any more parsing for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("auxiliary"))
+        {
+          // This indicates that this is an auxiliary objectclass.
+          // We do not need any more parsing for this token.
+        }
+        else if (tokenName.equalsIgnoreCase("must"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("may"))
+        {
+          SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          SchemaUtils.readExtensions(reader);
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          final DecodeException e = DecodeException.error(message);
+          StaticUtils.DEBUG_LOG.throwing("ObjectClassSyntax",
+              "valueIsAcceptable", e);
+          throw e;
+        }
+      }
+      return true;
+    }
+    catch (final DecodeException de)
+    {
+      invalidReason.append(de.getMessageObject());
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectClassType.java b/sdk/src/org/opends/sdk/schema/ObjectClassType.java
new file mode 100644
index 0000000..4affff3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectClassType.java
@@ -0,0 +1,82 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This enumeration defines the set of possible objectclass types that
+ * may be used, as defined in RFC 2252.
+ */
+public enum ObjectClassType
+{
+  /**
+   * The objectclass type that to use for classes declared "abstract".
+   */
+  ABSTRACT("ABSTRACT"),
+
+  /**
+   * The objectclass type that to use for classes declared "structural".
+   */
+  STRUCTURAL("STRUCTURAL"),
+
+  /**
+   * The objectclass type that to use for classes declared "auxiliary".
+   */
+  AUXILIARY("AUXILIARY");
+
+  // The string representation of this objectclass type.
+  private final String typeString;
+
+
+
+  /**
+   * Creates a new objectclass type with the provided string
+   * representation.
+   * 
+   * @param typeString
+   *          The string representation for this objectclass type.
+   */
+  private ObjectClassType(String typeString)
+  {
+    this.typeString = typeString;
+  }
+
+
+
+  /**
+   * Retrieves a string representation of this objectclass type.
+   * 
+   * @return A string representation of this objectclass type.
+   */
+  @Override
+  public String toString()
+  {
+    return typeString;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..b006242
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectIdentifierEqualityMatchingRuleImpl.java
@@ -0,0 +1,188 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class defines the objectIdentifierMatch matching rule defined in
+ * X.520 and referenced in RFC 2252. This expects to work on OIDs and
+ * will match either an attribute/objectclass name or a numeric OID.
+ * NOTE: This matching rule requires a schema to lookup object
+ * identifiers in the descriptor form.
+ */
+final class ObjectIdentifierEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  static class OIDAssertion implements Assertion
+  {
+    private final String oid;
+
+
+
+    OIDAssertion(String oid)
+    {
+      this.oid = oid;
+    }
+
+
+
+    public ConditionResult matches(ByteSequence attributeValue)
+    {
+      final String attrStr = attributeValue.toString();
+
+      // We should have normalized all values to OIDs. If not, we know
+      // the descriptor form is not valid in the schema.
+      if (attrStr.length() == 0
+          || !StaticUtils.isDigit(attrStr.charAt(0)))
+      {
+        return ConditionResult.UNDEFINED;
+      }
+      if (oid.length() == 0 || !StaticUtils.isDigit(oid.charAt(0)))
+      {
+        return ConditionResult.UNDEFINED;
+      }
+
+      return attrStr.equals(oid) ? ConditionResult.TRUE
+          : ConditionResult.FALSE;
+    }
+  }
+
+
+
+  static String resolveNames(Schema schema, String oid)
+  {
+    if (!StaticUtils.isDigit(oid.charAt(0)))
+    {
+      // Do an best effort attempt to normalize names to OIDs.
+
+      String schemaName = null;
+
+      if (schema.hasAttributeType(oid))
+      {
+        schemaName = schema.getAttributeType(oid).getOID();
+      }
+
+      if (schemaName == null)
+      {
+        if (schema.hasDITContentRule(oid))
+        {
+          schemaName =
+              schema.getDITContentRule(oid).getStructuralClass()
+                  .getOID();
+        }
+      }
+
+      if (schemaName == null)
+      {
+        if (schema.hasSyntax(oid))
+        {
+          schemaName = schema.getSyntax(oid).getOID();
+        }
+      }
+
+      if (schemaName == null)
+      {
+        if (schema.hasObjectClass(oid))
+        {
+          schemaName = schema.getObjectClass(oid).getOID();
+        }
+      }
+
+      if (schemaName == null)
+      {
+        if (schema.hasMatchingRule(oid))
+        {
+          schemaName = schema.getMatchingRule(oid).getOID();
+        }
+      }
+
+      if (schemaName == null)
+      {
+        if (schema.hasMatchingRuleUse(oid))
+        {
+          schemaName =
+              schema.getMatchingRuleUse(oid).getMatchingRule().getOID();
+        }
+      }
+
+      if (schemaName == null)
+      {
+        if (schema.hasNameForm(oid))
+        {
+          schemaName = schema.getNameForm(oid).getOID();
+        }
+      }
+
+      if (schemaName != null)
+      {
+        return schemaName;
+      }
+      else
+      {
+        return StaticUtils.toLowerCase(oid);
+      }
+    }
+    return oid;
+  }
+
+
+
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    final String definition = value.toString();
+    final SubstringReader reader = new SubstringReader(definition);
+    final String normalized =
+        resolveNames(schema, SchemaUtils.readOID(reader));
+
+    return new OIDAssertion(normalized);
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final String definition = value.toString();
+    final SubstringReader reader = new SubstringReader(definition);
+    final String normalized =
+        resolveNames(schema, SchemaUtils.readOID(reader));
+    return ByteString.valueOf(normalized);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..a743223
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ObjectIdentifierFirstComponentEqualityMatchingRuleImpl.java
@@ -0,0 +1,110 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Assertion;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * This class implements the objectIdentifierFirstComponentMatch
+ * matching rule defined in X.520 and referenced in RFC 2252. This rule
+ * is intended for use with attributes whose values contain a set of
+ * parentheses enclosing a space-delimited set of names and/or
+ * name-value pairs (like attribute type or objectclass descriptions) in
+ * which the "first component" is the first item after the opening
+ * parenthesis.
+ */
+final class ObjectIdentifierFirstComponentEqualityMatchingRuleImpl
+    extends AbstractMatchingRuleImpl
+{
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    final String definition = value.toString();
+    final SubstringReader reader = new SubstringReader(definition);
+    final String normalized =
+        ObjectIdentifierEqualityMatchingRuleImpl.resolveNames(schema,
+            SchemaUtils.readOID(reader));
+
+    return new ObjectIdentifierEqualityMatchingRuleImpl.OIDAssertion(
+        normalized);
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    final String definition = value.toString();
+    final SubstringReader reader = new SubstringReader(definition);
+
+    // We'll do this a character at a time. First, skip over any leading
+    // whitespace.
+    reader.skipWhitespaces();
+
+    if (reader.remaining() <= 0)
+    {
+      // This means that the value was empty or contained only
+      // whitespace. That is illegal.
+      final Message message = ERR_ATTR_SYNTAX_EMPTY_VALUE.get();
+      throw DecodeException.error(message);
+    }
+
+    // The next character must be an open parenthesis. If it is not,
+    // then that is an error.
+    final char c = reader.read();
+    if (c != '(')
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_EXPECTED_OPEN_PARENTHESIS.get(definition,
+              (reader.pos() - 1), String.valueOf(c));
+      throw DecodeException.error(message);
+    }
+
+    // Skip over any spaces immediately following the opening
+    // parenthesis.
+    reader.skipWhitespaces();
+
+    // The next set of characters must be the OID.
+    final String normalized =
+        ObjectIdentifierEqualityMatchingRuleImpl.resolveNames(schema,
+            SchemaUtils.readOID(reader));
+    return ByteString.valueOf(normalized);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..83ab9e2
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringEqualityMatchingRuleImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the octetStringMatch matching rule defined in
+ * X.520. It will be used as the default equality matching rule for the
+ * binary and octet string syntaxes.
+ */
+final class OctetStringEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return value.toByteString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..1c08743
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringOrderingMatchingRuleImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the octetStringOrderingMatch matching rule defined
+ * in X.520. This will be the default ordering matching rule for the
+ * binary and octet string syntaxes.
+ */
+final class OctetStringOrderingMatchingRuleImpl extends
+    AbstractOrderingMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return value.toByteString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..57d7354
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringSubstringMatchingRuleImpl.java
@@ -0,0 +1,49 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the octetStringSubstringsMatch matching rule
+ * defined in X.520. It will be used as the default substring matching
+ * rule for the binary and octet string syntaxes.
+ */
+final class OctetStringSubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return value.toByteString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java
new file mode 100644
index 0000000..ccc4977
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OctetStringSyntaxImpl.java
@@ -0,0 +1,99 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OCTET_STRING_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the octet string attribute syntax, which is
+ * equivalent to the binary syntax and should be considered a
+ * replacement for it. Equality, ordering, and substring matching will
+ * be allowed by default.
+ */
+final class OctetStringSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_OCTET_STRING_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the octet string syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java
new file mode 100644
index 0000000..3956aea
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/OtherMailboxSyntaxImpl.java
@@ -0,0 +1,177 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_LIST_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_LIST_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_OTHER_MAILBOX_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the other mailbox attribute syntax, which
+ * consists of a printable string component (the mailbox type) followed
+ * by a dollar sign and an IA5 string component (the mailbox). Equality
+ * and substring matching will be allowed by default.
+ */
+final class OtherMailboxSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_LIST_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_OTHER_MAILBOX_NAME;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_LIST_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Check to see if the provided value was null. If so, then that's
+    // not acceptable.
+    if (value == null)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_EMPTY_VALUE
+          .get());
+      return false;
+    }
+
+    // Get the value as a string and determine its length. If it is
+    // empty, then that's not acceptable.
+    final String valueString = value.toString();
+    final int valueLength = valueString.length();
+    if (valueLength == 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_EMPTY_VALUE
+          .get());
+      return false;
+    }
+
+    // Iterate through the characters in the vale until we find a dollar
+    // sign. Every character up to that point must be a printable string
+    // character.
+    int pos = 0;
+    for (; pos < valueLength; pos++)
+    {
+      final char c = valueString.charAt(pos);
+      if (c == '$')
+      {
+        if (pos == 0)
+        {
+
+          invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_NO_MBTYPE
+              .get(valueString));
+          return false;
+        }
+
+        pos++;
+        break;
+      }
+      else if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+      {
+
+        invalidReason
+            .append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_ILLEGAL_MBTYPE_CHAR
+                .get(valueString, String.valueOf(c), pos));
+        return false;
+      }
+    }
+
+    // Make sure there is at least one character left for the mailbox.
+    if (pos >= valueLength)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_NO_MAILBOX
+          .get(valueString));
+      return false;
+    }
+
+    // The remaining characters in the value must be IA5 (ASCII)
+    // characters.
+    for (; pos < valueLength; pos++)
+    {
+      final char c = valueString.charAt(pos);
+      if (c != (c & 0x7F))
+      {
+
+        invalidReason
+            .append(ERR_ATTR_SYNTAX_OTHER_MAILBOX_ILLEGAL_MB_CHAR.get(
+                valueString, String.valueOf(c), pos));
+        return false;
+      }
+    }
+
+    // If we've gotten here, then the value is OK.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java
new file mode 100644
index 0000000..bb90087
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PostalAddressSyntaxImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_POSTAL_ADDRESS_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the postal address attribute syntax, which is a
+ * list of UCS (Universal Character Set, as defined in the ISO 10646
+ * specification and includes UTF-8 and UTF-16) strings separated by
+ * dollar signs. By default, they will be treated in a case-insensitive
+ * manner, and equality and substring matching will be allowed.
+ */
+final class PostalAddressSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_POSTAL_ADDRESS_NAME;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We'll allow any value.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..5c80c28
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PresentationAddressEqualityMatchingRuleImpl.java
@@ -0,0 +1,85 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the presentationAddressMatch matching rule
+ * defined in X.520 and referenced in RFC 2252. However, since this
+ * matching rule and the associated syntax have been deprecated, this
+ * matching rule behaves exactly like the caseIgnoreMatch rule.
+ */
+final class PresentationAddressEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java
new file mode 100644
index 0000000..ad4531d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PresentationAddressSyntaxImpl.java
@@ -0,0 +1,113 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the presentation address attribute syntax,
+ * which is defined in RFC 1278. However, because this LDAP syntax is
+ * being deprecated, this implementation behaves exactly like the
+ * directory string syntax.
+ */
+final class PresentationAddressSyntaxImpl extends AbstractSyntaxImpl
+{
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_PRESENTATION_ADDRESS_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We will accept any value for this syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java
new file mode 100644
index 0000000..06b2c20
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/PrintableStringSyntaxImpl.java
@@ -0,0 +1,250 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_PRINTABLE_STRING_EMPTY_VALUE;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_PRINTABLE_STRING_ILLEGAL_CHARACTER;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the printable string attribute syntax, which is
+ * simply a string of characters from a limited ASCII character set
+ * (uppercase and lowercase letters, numeric digits, the space, and a
+ * set of various symbols). By default, they will be treated in a
+ * case-insensitive manner, and equality, ordering, substring, and
+ * approximate matching will be allowed.
+ */
+final class PrintableStringSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  /**
+   * Indicates whether the provided character is a valid printable
+   * character.
+   * 
+   * @param c
+   *          The character for which to make the determination.
+   * @return <CODE>true</CODE> if the provided character is a printable
+   *         character, or <CODE>false</CODE> if not.
+   */
+  static boolean isPrintableCharacter(char c)
+  {
+    switch (c)
+    {
+    case 'a':
+    case 'b':
+    case 'c':
+    case 'd':
+    case 'e':
+    case 'f':
+    case 'g':
+    case 'h':
+    case 'i':
+    case 'j':
+    case 'k':
+    case 'l':
+    case 'm':
+    case 'n':
+    case 'o':
+    case 'p':
+    case 'q':
+    case 'r':
+    case 's':
+    case 't':
+    case 'u':
+    case 'v':
+    case 'w':
+    case 'x':
+    case 'y':
+    case 'z':
+    case 'A':
+    case 'B':
+    case 'C':
+    case 'D':
+    case 'E':
+    case 'F':
+    case 'G':
+    case 'H':
+    case 'I':
+    case 'J':
+    case 'K':
+    case 'L':
+    case 'M':
+    case 'N':
+    case 'O':
+    case 'P':
+    case 'Q':
+    case 'R':
+    case 'S':
+    case 'T':
+    case 'U':
+    case 'V':
+    case 'W':
+    case 'X':
+    case 'Y':
+    case 'Z':
+    case '0':
+    case '1':
+    case '2':
+    case '3':
+    case '4':
+    case '5':
+    case '6':
+    case '7':
+    case '8':
+    case '9':
+    case '\'':
+    case '(':
+    case ')':
+    case '+':
+    case ',':
+    case '-':
+    case '.':
+    case '=':
+    case '/':
+    case ':':
+    case '?':
+    case ' ':
+      return true;
+    default:
+      return false;
+    }
+  }
+
+
+
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_PRINTABLE_STRING_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Check to see if the provided value was null. If so, then that's
+    // not acceptable.
+    if (value == null)
+    {
+
+      invalidReason
+          .append(WARN_ATTR_SYNTAX_PRINTABLE_STRING_EMPTY_VALUE.get());
+      return false;
+    }
+
+    // Get the value as a string and determine its length. If it is
+    // empty, then that's not acceptable.
+    final String valueString = value.toString();
+    final int valueLength = valueString.length();
+    if (valueLength == 0)
+    {
+
+      invalidReason
+          .append(WARN_ATTR_SYNTAX_PRINTABLE_STRING_EMPTY_VALUE.get());
+      return false;
+    }
+
+    // Iterate through all the characters and see if they are
+    // acceptable.
+    for (int i = 0; i < valueLength; i++)
+    {
+      final char c = valueString.charAt(i);
+      if (!isPrintableCharacter(c))
+      {
+
+        invalidReason
+            .append(WARN_ATTR_SYNTAX_PRINTABLE_STRING_ILLEGAL_CHARACTER
+                .get(valueString, String.valueOf(c), i));
+        return false;
+      }
+    }
+
+    // If we've gotten here, then the value is OK.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..c21b29e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ProtocolInformationEqualityMatchingRuleImpl.java
@@ -0,0 +1,85 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the protocolInformationMatch matching rule
+ * defined in X.520 and referenced in RFC 2252. However, since this
+ * matching rule and the associated syntax have been deprecated, this
+ * matching rule behaves exactly like the caseIgnoreMatch rule.
+ */
+final class ProtocolInformationEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return SchemaConstants.SINGLE_SPACE_VALUE;
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return ByteString.empty();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java
new file mode 100644
index 0000000..fb9c8de
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/ProtocolInformationSyntaxImpl.java
@@ -0,0 +1,113 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the protocol information attribute syntax,
+ * which is being deprecated. As such, this implementation behaves
+ * exactly like the directory string syntax.
+ */
+final class ProtocolInformationSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_PROTOCOL_INFORMATION_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We will accept any value for this syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java
new file mode 100644
index 0000000..45ff406
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/RegexSyntaxImpl.java
@@ -0,0 +1,127 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.AMR_DOUBLE_METAPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class provides a regex mechanism where a new syntax and its
+ * corresponding matching rules can be created on-the-fly. A regex
+ * syntax is an LDAPSyntaxDescriptionSyntax with X-PATTERN extension.
+ */
+final class RegexSyntaxImpl extends AbstractSyntaxImpl
+{
+  // The Pattern associated with the regex.
+  private final Pattern pattern;
+
+
+
+  RegexSyntaxImpl(Pattern pattern)
+  {
+    Validator.ensureNotNull(pattern);
+    this.pattern = pattern;
+  }
+
+
+
+  @Override
+  public String getApproximateMatchingRule()
+  {
+    return AMR_DOUBLE_METAPHONE_OID;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return "Regex(" + pattern.toString() + ")";
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    final String strValue = value.toString();
+    final boolean matches = pattern.matcher(strValue).matches();
+    if (!matches)
+    {
+      final Message message =
+          WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_VALUE.get(strValue,
+              pattern.pattern());
+      invalidReason.append(message);
+    }
+    return matches;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/Schema.java b/sdk/src/org/opends/sdk/schema/Schema.java
new file mode 100644
index 0000000..227c522
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/Schema.java
@@ -0,0 +1,2531 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure that holds information about the
+ * components of the LDAP schema. It includes the following kinds of
+ * elements:
+ * <UL>
+ * <LI>Attribute type definitions</LI>
+ * <LI>Object class definitions</LI>
+ * <LI>Attribute syntax definitions</LI>
+ * <LI>Matching rule definitions</LI>
+ * <LI>Matching rule use definitions</LI>
+ * <LI>DIT content rule definitions</LI>
+ * <LI>DIT structure rule definitions</LI>
+ * <LI>Name form definitions</LI>
+ * </UL>
+ */
+public final class Schema
+{
+  private static class EmptyImpl implements Impl
+  {
+    private final Map<SchemaLocal<?>, Object> attachments;
+
+    private final SchemaCompatOptions options;
+
+
+
+    private EmptyImpl()
+    {
+      this.options = SchemaCompatOptions.defaultOptions();
+      this.attachments = new WeakHashMap<SchemaLocal<?>, Object>();
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    public <T> T getAttachment(SchemaLocal<T> attachment)
+    {
+      T o;
+      synchronized (attachments)
+      {
+        o = (T) attachments.get(attachment);
+        if (o == null)
+        {
+          o = attachment.initialValue();
+          if (o != null)
+          {
+            attachments.put(attachment, o);
+          }
+        }
+      }
+      return o;
+    }
+
+
+
+    public AttributeType getAttributeType(String name)
+    {
+      // Construct an placeholder attribute type with the given name,
+      // the default matching rule, and the default syntax. The OID of
+      // the attribute will be the normalized OID alias with "-oid"
+      // appended to the given name.
+      final StringBuilder builder = new StringBuilder(name.length() + 4);
+      StaticUtils.toLowerCase(name, builder);
+      builder.append("-oid");
+      final String noid = builder.toString();
+
+      return new AttributeType(noid, Collections.singletonList(name),
+          "", Schema.getDefaultMatchingRule(), Schema
+              .getDefaultSyntax());
+    }
+
+
+
+    public Collection<AttributeType> getAttributeTypes()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public List<AttributeType> getAttributeTypesByName(String name)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public DITContentRule getDITContentRule(String name)
+        throws UnknownSchemaElementException
+    {
+      throw new UnknownSchemaElementException(WARN_DCR_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<DITContentRule> getDITContentRules()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<DITContentRule> getDITContentRulesByName(
+        String name)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public DITStructureRule getDITStructureRule(int ruleID)
+        throws UnknownSchemaElementException
+    {
+      throw new UnknownSchemaElementException(WARN_DSR_UNKNOWN
+          .get(String.valueOf(ruleID)));
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStructureRulesByName(
+        String name)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+        NameForm nameForm)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStuctureRules()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public MatchingRule getMatchingRule(String name)
+        throws UnknownSchemaElementException
+    {
+      throw new UnknownSchemaElementException(WARN_MR_UNKNOWN.get(name));
+    }
+
+
+
+    public Collection<MatchingRule> getMatchingRules()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<MatchingRule> getMatchingRulesByName(String name)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+        throws UnknownSchemaElementException
+    {
+      return getMatchingRuleUse(matchingRule.getOID());
+    }
+
+
+
+    public MatchingRuleUse getMatchingRuleUse(String name)
+        throws UnknownSchemaElementException
+    {
+      throw new UnknownSchemaElementException(WARN_MRU_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<MatchingRuleUse> getMatchingRuleUses()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+        String name)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public NameForm getNameForm(String name)
+        throws UnknownSchemaElementException
+    {
+      throw new UnknownSchemaElementException(WARN_NAMEFORM_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<NameForm> getNameFormByObjectClass(
+        ObjectClass structuralClass)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<NameForm> getNameForms()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<NameForm> getNameFormsByName(String name)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public ObjectClass getObjectClass(String name)
+        throws UnknownSchemaElementException
+    {
+      throw new UnknownSchemaElementException(WARN_OBJECTCLASS_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<ObjectClass> getObjectClasses()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public Collection<ObjectClass> getObjectClassesByName(String name)
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public SchemaCompatOptions getSchemaCompatOptions()
+    {
+      return options;
+    }
+
+
+
+    public Syntax getSyntax(String numericOID)
+    {
+      // Fake up a syntax substituted by the default syntax.
+      return new Syntax(numericOID);
+    }
+
+
+
+    public Collection<Syntax> getSyntaxes()
+    {
+      return Collections.emptyList();
+    }
+
+
+
+    public boolean hasAttributeType(String name)
+    {
+      // In theory a non-strict schema always contains the requested
+      // attribute type, so we could always return true. However, we
+      // should provide a way for callers to differentiate between a
+      // real attribute type and a faked up attribute type.
+      return false;
+    }
+
+
+
+    public boolean hasDITContentRule(String name)
+    {
+      return false;
+    }
+
+
+
+    public boolean hasDITStructureRule(int ruleID)
+    {
+      return false;
+    }
+
+
+
+    public boolean hasMatchingRule(String name)
+    {
+      return false;
+    }
+
+
+
+    public boolean hasMatchingRuleUse(String name)
+    {
+      return false;
+    }
+
+
+
+    public boolean hasNameForm(String name)
+    {
+      return false;
+    }
+
+
+
+    public boolean hasObjectClass(String name)
+    {
+      return false;
+    }
+
+
+
+    public boolean hasSyntax(String numericOID)
+    {
+      return false;
+    }
+
+
+
+    public boolean isStrict()
+    {
+      return false;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    public <T> T removeAttachment(SchemaLocal<T> attachment)
+    {
+      T o;
+      synchronized (attachments)
+      {
+        o = (T) attachments.remove(attachment);
+      }
+      return o;
+    }
+
+
+
+    public <T> void setAttachment(SchemaLocal<T> attachment, T value)
+    {
+      synchronized (attachments)
+      {
+        attachments.put(attachment, value);
+      }
+    }
+  }
+
+
+
+  private static interface Impl
+  {
+    <T> T getAttachment(SchemaLocal<T> attachment);
+
+
+
+    AttributeType getAttributeType(String name)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<AttributeType> getAttributeTypes();
+
+
+
+    List<AttributeType> getAttributeTypesByName(String name);
+
+
+
+    DITContentRule getDITContentRule(String name)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<DITContentRule> getDITContentRules();
+
+
+
+    Collection<DITContentRule> getDITContentRulesByName(String name);
+
+
+
+    DITStructureRule getDITStructureRule(int ruleID)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<DITStructureRule> getDITStructureRulesByName(String name);
+
+
+
+    Collection<DITStructureRule> getDITStructureRulesByNameForm(
+        NameForm nameForm);
+
+
+
+    Collection<DITStructureRule> getDITStuctureRules();
+
+
+
+    MatchingRule getMatchingRule(String name)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<MatchingRule> getMatchingRules();
+
+
+
+    Collection<MatchingRule> getMatchingRulesByName(String name);
+
+
+
+    MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+        throws UnknownSchemaElementException;
+
+
+
+    MatchingRuleUse getMatchingRuleUse(String name)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<MatchingRuleUse> getMatchingRuleUses();
+
+
+
+    Collection<MatchingRuleUse> getMatchingRuleUsesByName(String name);
+
+
+
+    NameForm getNameForm(String name)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<NameForm> getNameFormByObjectClass(
+        ObjectClass structuralClass);
+
+
+
+    Collection<NameForm> getNameForms();
+
+
+
+    Collection<NameForm> getNameFormsByName(String name);
+
+
+
+    ObjectClass getObjectClass(String name)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<ObjectClass> getObjectClasses();
+
+
+
+    Collection<ObjectClass> getObjectClassesByName(String name);
+
+
+
+    SchemaCompatOptions getSchemaCompatOptions();
+
+
+
+    Syntax getSyntax(String numericOID)
+        throws UnknownSchemaElementException;
+
+
+
+    Collection<Syntax> getSyntaxes();
+
+
+
+    boolean hasAttributeType(String name);
+
+
+
+    boolean hasDITContentRule(String name);
+
+
+
+    boolean hasDITStructureRule(int ruleID);
+
+
+
+    boolean hasMatchingRule(String name);
+
+
+
+    boolean hasMatchingRuleUse(String name);
+
+
+
+    boolean hasNameForm(String name);
+
+
+
+    boolean hasObjectClass(String name);
+
+
+
+    boolean hasSyntax(String numericOID);
+
+
+
+    boolean isStrict();
+
+
+
+    <T> T removeAttachment(SchemaLocal<T> attachment);
+
+
+
+    <T> void setAttachment(SchemaLocal<T> attachment, T value);
+  }
+
+
+
+  private static class NonStrictImpl implements Impl
+  {
+    private final Impl strictImpl;
+
+
+
+    private NonStrictImpl(Impl strictImpl)
+    {
+      this.strictImpl = strictImpl;
+    }
+
+
+
+    public <T> T getAttachment(SchemaLocal<T> attachment)
+    {
+      return strictImpl.getAttachment(attachment);
+    }
+
+
+
+    public AttributeType getAttributeType(String name)
+        throws UnknownSchemaElementException
+    {
+      if (!strictImpl.hasAttributeType(name))
+      {
+        // Construct an placeholder attribute type with the given name,
+        // the default matching rule, and the default syntax. The OID of
+        // the attribute will be the normalized OID alias with "-oid"
+        // appended to the given name.
+        final StringBuilder builder = new StringBuilder(
+            name.length() + 4);
+        StaticUtils.toLowerCase(name, builder);
+        builder.append("-oid");
+        final String noid = builder.toString();
+
+        return new AttributeType(noid, Collections.singletonList(name),
+            "", Schema.getDefaultMatchingRule(), Schema
+                .getDefaultSyntax());
+      }
+      return strictImpl.getAttributeType(name);
+    }
+
+
+
+    public Collection<AttributeType> getAttributeTypes()
+    {
+      return strictImpl.getAttributeTypes();
+    }
+
+
+
+    public List<AttributeType> getAttributeTypesByName(String name)
+    {
+      return strictImpl.getAttributeTypesByName(name);
+    }
+
+
+
+    public DITContentRule getDITContentRule(String name)
+        throws UnknownSchemaElementException
+    {
+      return strictImpl.getDITContentRule(name);
+    }
+
+
+
+    public Collection<DITContentRule> getDITContentRules()
+    {
+      return strictImpl.getDITContentRules();
+    }
+
+
+
+    public Collection<DITContentRule> getDITContentRulesByName(
+        String name)
+    {
+      return strictImpl.getDITContentRulesByName(name);
+    }
+
+
+
+    public DITStructureRule getDITStructureRule(int ruleID)
+        throws UnknownSchemaElementException
+    {
+      return strictImpl.getDITStructureRule(ruleID);
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStructureRulesByName(
+        String name)
+    {
+      return strictImpl.getDITStructureRulesByName(name);
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+        NameForm nameForm)
+    {
+      return strictImpl.getDITStructureRulesByNameForm(nameForm);
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStuctureRules()
+    {
+      return strictImpl.getDITStuctureRules();
+    }
+
+
+
+    public MatchingRule getMatchingRule(String name)
+        throws UnknownSchemaElementException
+    {
+      return strictImpl.getMatchingRule(name);
+    }
+
+
+
+    public Collection<MatchingRule> getMatchingRules()
+    {
+      return strictImpl.getMatchingRules();
+    }
+
+
+
+    public Collection<MatchingRule> getMatchingRulesByName(String name)
+    {
+      return strictImpl.getMatchingRulesByName(name);
+    }
+
+
+
+    public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+        throws UnknownSchemaElementException
+    {
+      return strictImpl.getMatchingRuleUse(matchingRule);
+    }
+
+
+
+    public MatchingRuleUse getMatchingRuleUse(String name)
+        throws UnknownSchemaElementException
+    {
+      return strictImpl.getMatchingRuleUse(name);
+    }
+
+
+
+    public Collection<MatchingRuleUse> getMatchingRuleUses()
+    {
+      return strictImpl.getMatchingRuleUses();
+    }
+
+
+
+    public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+        String name)
+    {
+      return strictImpl.getMatchingRuleUsesByName(name);
+    }
+
+
+
+    public NameForm getNameForm(String name)
+        throws UnknownSchemaElementException
+    {
+      return strictImpl.getNameForm(name);
+    }
+
+
+
+    public Collection<NameForm> getNameFormByObjectClass(
+        ObjectClass structuralClass)
+    {
+      return strictImpl.getNameFormByObjectClass(structuralClass);
+    }
+
+
+
+    public Collection<NameForm> getNameForms()
+    {
+      return strictImpl.getNameForms();
+    }
+
+
+
+    public Collection<NameForm> getNameFormsByName(String name)
+    {
+      return strictImpl.getNameFormsByName(name);
+    }
+
+
+
+    public ObjectClass getObjectClass(String name)
+        throws UnknownSchemaElementException
+    {
+      return strictImpl.getObjectClass(name);
+    }
+
+
+
+    public Collection<ObjectClass> getObjectClasses()
+    {
+      return strictImpl.getObjectClasses();
+    }
+
+
+
+    public Collection<ObjectClass> getObjectClassesByName(String name)
+    {
+      return strictImpl.getObjectClassesByName(name);
+    }
+
+
+
+    public SchemaCompatOptions getSchemaCompatOptions()
+    {
+      return strictImpl.getSchemaCompatOptions();
+    }
+
+
+
+    public Syntax getSyntax(String numericOID)
+    {
+      if (!strictImpl.hasSyntax(numericOID))
+      {
+        return new Syntax(numericOID);
+      }
+      return strictImpl.getSyntax(numericOID);
+    }
+
+
+
+    public Collection<Syntax> getSyntaxes()
+    {
+      return strictImpl.getSyntaxes();
+    }
+
+
+
+    public boolean hasAttributeType(String name)
+    {
+      // In theory a non-strict schema always contains the requested
+      // attribute type, so we could always return true. However, we
+      // should provide a way for callers to differentiate between a
+      // real attribute type and a faked up attribute type.
+      return strictImpl.hasAttributeType(name);
+    }
+
+
+
+    public boolean hasDITContentRule(String name)
+    {
+      return strictImpl.hasDITContentRule(name);
+    }
+
+
+
+    public boolean hasDITStructureRule(int ruleID)
+    {
+      return strictImpl.hasDITStructureRule(ruleID);
+    }
+
+
+
+    public boolean hasMatchingRule(String name)
+    {
+      return strictImpl.hasMatchingRule(name);
+    }
+
+
+
+    public boolean hasMatchingRuleUse(String name)
+    {
+      return strictImpl.hasMatchingRuleUse(name);
+    }
+
+
+
+    public boolean hasNameForm(String name)
+    {
+      return strictImpl.hasNameForm(name);
+    }
+
+
+
+    public boolean hasObjectClass(String name)
+    {
+      return strictImpl.hasObjectClass(name);
+    }
+
+
+
+    public boolean hasSyntax(String numericOID)
+    {
+      return strictImpl.hasSyntax(numericOID);
+    }
+
+
+
+    public boolean isStrict()
+    {
+      return false;
+    }
+
+
+
+    public <T> T removeAttachment(SchemaLocal<T> attachment)
+    {
+      return strictImpl.removeAttachment(attachment);
+    }
+
+
+
+    public <T> void setAttachment(SchemaLocal<T> attachment, T value)
+    {
+      strictImpl.setAttachment(attachment, value);
+    }
+  }
+
+
+
+  private static class StrictImpl implements Impl
+  {
+    private final Map<SchemaLocal<?>, Object> attachments;
+
+    private final Map<Integer, DITStructureRule> id2StructureRules;
+
+    private final Map<String, List<AttributeType>> name2AttributeTypes;
+
+    private final Map<String, List<DITContentRule>> name2ContentRules;
+
+    private final Map<String, List<MatchingRule>> name2MatchingRules;
+
+    private final Map<String, List<MatchingRuleUse>> name2MatchingRuleUses;
+
+    private final Map<String, List<NameForm>> name2NameForms;
+
+    private final Map<String, List<ObjectClass>> name2ObjectClasses;
+
+    private final Map<String, List<DITStructureRule>> name2StructureRules;
+
+    private final Map<String, List<DITStructureRule>> nameForm2StructureRules;
+
+    private final Map<String, AttributeType> numericOID2AttributeTypes;
+
+    private final Map<String, DITContentRule> numericOID2ContentRules;
+
+    private final Map<String, MatchingRule> numericOID2MatchingRules;
+
+    private final Map<String, MatchingRuleUse> numericOID2MatchingRuleUses;
+
+    private final Map<String, NameForm> numericOID2NameForms;
+
+    private final Map<String, ObjectClass> numericOID2ObjectClasses;
+
+    private final Map<String, Syntax> numericOID2Syntaxes;
+
+    private final Map<String, List<NameForm>> objectClass2NameForms;
+
+    private final SchemaCompatOptions options;
+
+
+
+    private StrictImpl(Map<String, Syntax> numericOID2Syntaxes,
+        Map<String, MatchingRule> numericOID2MatchingRules,
+        Map<String, MatchingRuleUse> numericOID2MatchingRuleUses,
+        Map<String, AttributeType> numericOID2AttributeTypes,
+        Map<String, ObjectClass> numericOID2ObjectClasses,
+        Map<String, NameForm> numericOID2NameForms,
+        Map<String, DITContentRule> numericOID2ContentRules,
+        Map<Integer, DITStructureRule> id2StructureRules,
+        Map<String, List<MatchingRule>> name2MatchingRules,
+        Map<String, List<MatchingRuleUse>> name2MatchingRuleUses,
+        Map<String, List<AttributeType>> name2AttributeTypes,
+        Map<String, List<ObjectClass>> name2ObjectClasses,
+        Map<String, List<NameForm>> name2NameForms,
+        Map<String, List<DITContentRule>> name2ContentRules,
+        Map<String, List<DITStructureRule>> name2StructureRules,
+        Map<String, List<NameForm>> objectClass2NameForms,
+        Map<String, List<DITStructureRule>> nameForm2StructureRules,
+        SchemaCompatOptions options)
+    {
+      this.numericOID2Syntaxes = Collections
+          .unmodifiableMap(numericOID2Syntaxes);
+      this.numericOID2MatchingRules = Collections
+          .unmodifiableMap(numericOID2MatchingRules);
+      this.numericOID2MatchingRuleUses = Collections
+          .unmodifiableMap(numericOID2MatchingRuleUses);
+      this.numericOID2AttributeTypes = Collections
+          .unmodifiableMap(numericOID2AttributeTypes);
+      this.numericOID2ObjectClasses = Collections
+          .unmodifiableMap(numericOID2ObjectClasses);
+      this.numericOID2NameForms = Collections
+          .unmodifiableMap(numericOID2NameForms);
+      this.numericOID2ContentRules = Collections
+          .unmodifiableMap(numericOID2ContentRules);
+      this.id2StructureRules = Collections
+          .unmodifiableMap(id2StructureRules);
+      this.name2MatchingRules = Collections
+          .unmodifiableMap(name2MatchingRules);
+      this.name2MatchingRuleUses = Collections
+          .unmodifiableMap(name2MatchingRuleUses);
+      this.name2AttributeTypes = Collections
+          .unmodifiableMap(name2AttributeTypes);
+      this.name2ObjectClasses = Collections
+          .unmodifiableMap(name2ObjectClasses);
+      this.name2NameForms = Collections.unmodifiableMap(name2NameForms);
+      this.name2ContentRules = Collections
+          .unmodifiableMap(name2ContentRules);
+      this.name2StructureRules = Collections
+          .unmodifiableMap(name2StructureRules);
+      this.objectClass2NameForms = Collections
+          .unmodifiableMap(objectClass2NameForms);
+      this.nameForm2StructureRules = Collections
+          .unmodifiableMap(nameForm2StructureRules);
+      this.options = options;
+      attachments = new WeakHashMap<SchemaLocal<?>, Object>();
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    public <T> T getAttachment(SchemaLocal<T> attachment)
+    {
+      T o;
+      synchronized (attachments)
+      {
+        o = (T) attachments.get(attachment);
+        if (o == null)
+        {
+          o = attachment.initialValue();
+          if (o != null)
+          {
+            attachments.put(attachment, o);
+          }
+        }
+      }
+      return o;
+    }
+
+
+
+    public AttributeType getAttributeType(String name)
+        throws UnknownSchemaElementException
+    {
+      final AttributeType type = numericOID2AttributeTypes.get(name);
+      if (type != null)
+      {
+        return type;
+      }
+      final List<AttributeType> attributes = name2AttributeTypes
+          .get(StaticUtils.toLowerCase(name));
+      if (attributes != null)
+      {
+        if (attributes.size() == 1)
+        {
+          return attributes.get(0);
+        }
+        throw new UnknownSchemaElementException(
+            WARN_ATTR_TYPE_AMBIGIOUS.get(name));
+      }
+      throw new UnknownSchemaElementException(WARN_ATTR_TYPE_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<AttributeType> getAttributeTypes()
+    {
+      return numericOID2AttributeTypes.values();
+    }
+
+
+
+    public List<AttributeType> getAttributeTypesByName(String name)
+    {
+      final List<AttributeType> attributes = name2AttributeTypes
+          .get(StaticUtils.toLowerCase(name));
+      if (attributes == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return attributes;
+      }
+    }
+
+
+
+    public DITContentRule getDITContentRule(String name)
+        throws UnknownSchemaElementException
+    {
+      final DITContentRule rule = numericOID2ContentRules.get(name);
+      if (rule != null)
+      {
+        return rule;
+      }
+      final List<DITContentRule> rules = name2ContentRules
+          .get(StaticUtils.toLowerCase(name));
+      if (rules != null)
+      {
+        if (rules.size() == 1)
+        {
+          return rules.get(0);
+        }
+        throw new UnknownSchemaElementException(WARN_DCR_AMBIGIOUS
+            .get(name));
+      }
+      throw new UnknownSchemaElementException(WARN_DCR_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<DITContentRule> getDITContentRules()
+    {
+      return numericOID2ContentRules.values();
+    }
+
+
+
+    public Collection<DITContentRule> getDITContentRulesByName(
+        String name)
+    {
+      final List<DITContentRule> rules = name2ContentRules
+          .get(StaticUtils.toLowerCase(name));
+      if (rules == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return rules;
+      }
+    }
+
+
+
+    public DITStructureRule getDITStructureRule(int ruleID)
+        throws UnknownSchemaElementException
+    {
+      final DITStructureRule rule = id2StructureRules.get(ruleID);
+      if (rule == null)
+      {
+        throw new UnknownSchemaElementException(WARN_DSR_UNKNOWN
+            .get(String.valueOf(ruleID)));
+      }
+      return rule;
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStructureRulesByName(
+        String name)
+    {
+      final List<DITStructureRule> rules = name2StructureRules
+          .get(StaticUtils.toLowerCase(name));
+      if (rules == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return rules;
+      }
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+        NameForm nameForm)
+    {
+      final List<DITStructureRule> rules = nameForm2StructureRules
+          .get(nameForm.getOID());
+      if (rules == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return rules;
+      }
+    }
+
+
+
+    public Collection<DITStructureRule> getDITStuctureRules()
+    {
+      return id2StructureRules.values();
+    }
+
+
+
+    public MatchingRule getMatchingRule(String name)
+        throws UnknownSchemaElementException
+    {
+      final MatchingRule rule = numericOID2MatchingRules.get(name);
+      if (rule != null)
+      {
+        return rule;
+      }
+      final List<MatchingRule> rules = name2MatchingRules
+          .get(StaticUtils.toLowerCase(name));
+      if (rules != null)
+      {
+        if (rules.size() == 1)
+        {
+          return rules.get(0);
+        }
+        throw new UnknownSchemaElementException(WARN_MR_AMBIGIOUS
+            .get(name));
+      }
+      throw new UnknownSchemaElementException(WARN_MR_UNKNOWN.get(name));
+    }
+
+
+
+    public Collection<MatchingRule> getMatchingRules()
+    {
+      return numericOID2MatchingRules.values();
+    }
+
+
+
+    public Collection<MatchingRule> getMatchingRulesByName(String name)
+    {
+      final List<MatchingRule> rules = name2MatchingRules
+          .get(StaticUtils.toLowerCase(name));
+      if (rules == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return rules;
+      }
+    }
+
+
+
+    public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+        throws UnknownSchemaElementException
+    {
+      return getMatchingRuleUse(matchingRule.getOID());
+    }
+
+
+
+    public MatchingRuleUse getMatchingRuleUse(String name)
+        throws UnknownSchemaElementException
+    {
+      final MatchingRuleUse rule = numericOID2MatchingRuleUses
+          .get(name);
+      if (rule != null)
+      {
+        return rule;
+      }
+      final List<MatchingRuleUse> uses = name2MatchingRuleUses
+          .get(StaticUtils.toLowerCase(name));
+      if (uses != null)
+      {
+        if (uses.size() == 1)
+        {
+          return uses.get(0);
+        }
+        throw new UnknownSchemaElementException(WARN_MRU_AMBIGIOUS
+            .get(name));
+      }
+      throw new UnknownSchemaElementException(WARN_MRU_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<MatchingRuleUse> getMatchingRuleUses()
+    {
+      return numericOID2MatchingRuleUses.values();
+    }
+
+
+
+    public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+        String name)
+    {
+      final List<MatchingRuleUse> rules = name2MatchingRuleUses
+          .get(StaticUtils.toLowerCase(name));
+      if (rules == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return rules;
+      }
+    }
+
+
+
+    public NameForm getNameForm(String name)
+        throws UnknownSchemaElementException
+    {
+      final NameForm form = numericOID2NameForms.get(name);
+      if (form != null)
+      {
+        return form;
+      }
+      final List<NameForm> forms = name2NameForms.get(StaticUtils
+          .toLowerCase(name));
+      if (forms != null)
+      {
+        if (forms.size() == 1)
+        {
+          return forms.get(0);
+        }
+        throw new UnknownSchemaElementException(WARN_NAMEFORM_AMBIGIOUS
+            .get(name));
+      }
+      throw new UnknownSchemaElementException(WARN_NAMEFORM_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<NameForm> getNameFormByObjectClass(
+        ObjectClass structuralClass)
+    {
+      final List<NameForm> forms = objectClass2NameForms
+          .get(structuralClass.getOID());
+      if (forms == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return forms;
+      }
+    }
+
+
+
+    public Collection<NameForm> getNameForms()
+    {
+      return numericOID2NameForms.values();
+    }
+
+
+
+    public Collection<NameForm> getNameFormsByName(String name)
+    {
+      final List<NameForm> forms = name2NameForms.get(StaticUtils
+          .toLowerCase(name));
+      if (forms == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return forms;
+      }
+    }
+
+
+
+    public ObjectClass getObjectClass(String name)
+        throws UnknownSchemaElementException
+    {
+      final ObjectClass oc = numericOID2ObjectClasses.get(name);
+      if (oc != null)
+      {
+        return oc;
+      }
+      final List<ObjectClass> classes = name2ObjectClasses
+          .get(StaticUtils.toLowerCase(name));
+      if (classes != null)
+      {
+        if (classes.size() == 1)
+        {
+          return classes.get(0);
+        }
+        throw new UnknownSchemaElementException(
+            WARN_OBJECTCLASS_AMBIGIOUS.get(name));
+      }
+      throw new UnknownSchemaElementException(WARN_OBJECTCLASS_UNKNOWN
+          .get(name));
+    }
+
+
+
+    public Collection<ObjectClass> getObjectClasses()
+    {
+      return numericOID2ObjectClasses.values();
+    }
+
+
+
+    public Collection<ObjectClass> getObjectClassesByName(String name)
+    {
+      final List<ObjectClass> classes = name2ObjectClasses
+          .get(StaticUtils.toLowerCase(name));
+      if (classes == null)
+      {
+        return Collections.emptyList();
+      }
+      else
+      {
+        return classes;
+      }
+    }
+
+
+
+    public SchemaCompatOptions getSchemaCompatOptions()
+    {
+      return options;
+    }
+
+
+
+    public Syntax getSyntax(String numericOID)
+        throws UnknownSchemaElementException
+    {
+      final Syntax syntax = numericOID2Syntaxes.get(numericOID);
+      if (syntax == null)
+      {
+        throw new UnknownSchemaElementException(WARN_SYNTAX_UNKNOWN
+            .get(numericOID));
+      }
+      return syntax;
+    }
+
+
+
+    public Collection<Syntax> getSyntaxes()
+    {
+      return numericOID2Syntaxes.values();
+    }
+
+
+
+    public boolean hasAttributeType(String name)
+    {
+      if (numericOID2AttributeTypes.containsKey(name))
+      {
+        return true;
+      }
+      final List<AttributeType> attributes = name2AttributeTypes
+          .get(StaticUtils.toLowerCase(name));
+      return attributes != null && attributes.size() == 1;
+    }
+
+
+
+    public boolean hasDITContentRule(String name)
+    {
+      if (numericOID2ContentRules.containsKey(name))
+      {
+        return true;
+      }
+      final List<DITContentRule> rules = name2ContentRules
+          .get(StaticUtils.toLowerCase(name));
+      return rules != null && rules.size() == 1;
+    }
+
+
+
+    public boolean hasDITStructureRule(int ruleID)
+    {
+      return id2StructureRules.containsKey(ruleID);
+    }
+
+
+
+    public boolean hasMatchingRule(String name)
+    {
+      if (numericOID2MatchingRules.containsKey(name))
+      {
+        return true;
+      }
+      final List<MatchingRule> rules = name2MatchingRules
+          .get(StaticUtils.toLowerCase(name));
+      return rules != null && rules.size() == 1;
+    }
+
+
+
+    public boolean hasMatchingRuleUse(String name)
+    {
+      if (numericOID2MatchingRuleUses.containsKey(name))
+      {
+        return true;
+      }
+      final List<MatchingRuleUse> uses = name2MatchingRuleUses
+          .get(StaticUtils.toLowerCase(name));
+      return uses != null && uses.size() == 1;
+    }
+
+
+
+    public boolean hasNameForm(String name)
+    {
+      if (numericOID2NameForms.containsKey(name))
+      {
+        return true;
+      }
+      final List<NameForm> forms = name2NameForms.get(StaticUtils
+          .toLowerCase(name));
+      return forms != null && forms.size() == 1;
+    }
+
+
+
+    public boolean hasObjectClass(String name)
+    {
+      if (numericOID2ObjectClasses.containsKey(name))
+      {
+        return true;
+      }
+      final List<ObjectClass> classes = name2ObjectClasses
+          .get(StaticUtils.toLowerCase(name));
+      return classes != null && classes.size() == 1;
+    }
+
+
+
+    public boolean hasSyntax(String numericOID)
+    {
+      return numericOID2Syntaxes.containsKey(numericOID);
+    }
+
+
+
+    public boolean isStrict()
+    {
+      return true;
+    }
+
+
+
+    @SuppressWarnings("unchecked")
+    public <T> T removeAttachment(SchemaLocal<T> attachment)
+    {
+      T o;
+      synchronized (attachments)
+      {
+        o = (T) attachments.remove(attachment);
+      }
+      return o;
+    }
+
+
+
+    public <T> void setAttachment(SchemaLocal<T> attachment, T value)
+    {
+      synchronized (attachments)
+      {
+        attachments.put(attachment, value);
+      }
+    }
+  }
+
+
+
+  private static final Schema CORE_SCHEMA = CoreSchemaImpl
+      .getInstance();
+
+  private static final Schema EMPTY_SCHEMA = new Schema(new EmptyImpl());
+
+  private static volatile Schema DEFAULT_SCHEMA = CoreSchemaImpl
+      .getInstance();
+
+  private static final AttributeDescription ATTR_ATTRIBUTE_TYPES = AttributeDescription
+      .valueOf("attributeTypes");
+
+  private static final AttributeDescription ATTR_DIT_CONTENT_RULES = AttributeDescription
+      .valueOf("dITContentRules");
+
+  private static final AttributeDescription ATTR_DIT_STRUCTURE_RULES = AttributeDescription
+      .valueOf("dITStructureRules");
+
+  private static final AttributeDescription ATTR_LDAP_SYNTAXES = AttributeDescription
+      .valueOf("ldapSyntaxes");
+
+  private static final AttributeDescription ATTR_MATCHING_RULE_USE = AttributeDescription
+      .valueOf("matchingRuleUse");
+
+  private static final AttributeDescription ATTR_MATCHING_RULES = AttributeDescription
+      .valueOf("matchingRules");
+
+  private static final AttributeDescription ATTR_NAME_FORMS = AttributeDescription
+      .valueOf("nameForms");
+
+  private static final AttributeDescription ATTR_OBJECT_CLASSES = AttributeDescription
+      .valueOf("objectClasses");
+
+  private static final AttributeDescription ATTR_SUBSCHEMA_SUBENTRY = AttributeDescription
+      .valueOf("subschemaSubentry");
+
+  private static final String[] SUBSCHEMA_ATTRS = new String[] {
+      ATTR_LDAP_SYNTAXES.toString(), ATTR_ATTRIBUTE_TYPES.toString(),
+      ATTR_DIT_CONTENT_RULES.toString(),
+      ATTR_DIT_STRUCTURE_RULES.toString(),
+      ATTR_MATCHING_RULE_USE.toString(),
+      ATTR_MATCHING_RULES.toString(), ATTR_NAME_FORMS.toString(),
+      ATTR_OBJECT_CLASSES.toString() };
+
+
+
+  /**
+   * Returns the core schema. The core schema is non-strict and contains
+   * the following standard LDAP schema elements:
+   * <ul>
+   * <li><a href="http://tools.ietf.org/html/rfc4512">RFC 4512 -
+   * Lightweight Directory Access Protocol (LDAP): Directory Information
+   * Models </a>
+   * <li><a href="http://tools.ietf.org/html/rfc4517">RFC 4517 -
+   * Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching
+   * Rules </a>
+   * <li><a href="http://tools.ietf.org/html/rfc4519">RFC 4519 -
+   * Lightweight Directory Access Protocol (LDAP): Schema for User
+   * Applications </a>
+   * <li><a href="http://tools.ietf.org/html/rfc4530">RFC 4530 -
+   * Lightweight Directory Access Protocol (LDAP): entryUUID Operational
+   * Attribute </a>
+   * <li><a href="http://tools.ietf.org/html/rfc3045">RFC 3045 - Storing
+   * Vendor Information in the LDAP root DSE </a>
+   * <li><a href="http://tools.ietf.org/html/rfc3112">RFC 3112 - LDAP
+   * Authentication Password Schema </a>
+   * </ul>
+   *
+   * @return The core schema.
+   */
+  public static Schema getCoreSchema()
+  {
+    return CORE_SCHEMA;
+  }
+
+
+
+  /**
+   * Returns the default schema which should be used by this
+   * application. The default schema is initially set to the core
+   * schema.
+   *
+   * @return The default schema which should be used by this
+   *         application.
+   */
+  public static Schema getDefaultSchema()
+  {
+    return DEFAULT_SCHEMA;
+  }
+
+
+
+  /**
+   * Returns the empty schema. The empty schema is non-strict and does
+   * not contain any schema elements.
+   *
+   * @return The empty schema.
+   */
+  public static Schema getEmptySchema()
+  {
+    return EMPTY_SCHEMA;
+  }
+
+
+
+  /**
+   * Reads the schema associated with the provided entry from an LDAP
+   * Directory Server.
+   *
+   * @param connection
+   *          The connection to the Directory Server.
+   * @param dn
+   *          The name of the entry whose schema is to be retrieved.
+   * @param warnings
+   *          A list to which any warning messages encountered while
+   *          decoding the schema should be appended.
+   * @return The schema.
+   * @throws LocalizedIllegalArgumentException
+   *           If {@code dn} could not be decoded using the default
+   *           schema.
+   * @throws ErrorResultException
+   *           If the server returned an error result code.
+   * @throws InterruptedException
+   *           If the current thread was interrupted while waiting.
+   * @throws SchemaNotFoundException
+   *           If the requested schema was not found in the Directory
+   *           Server.
+   */
+  public static Schema getSchema(Connection connection, String dn,
+      List<Message> warnings) throws ErrorResultException,
+      InterruptedException, LocalizedIllegalArgumentException,
+      SchemaNotFoundException
+  {
+    Validator.ensureNotNull(connection, dn, warnings);
+    SearchResultEntry result = connection.readEntry(dn,
+        ATTR_SUBSCHEMA_SUBENTRY.toString());
+    Attribute subentryAttr;
+    if ((subentryAttr = result.getAttribute(ATTR_SUBSCHEMA_SUBENTRY)) == null
+        || subentryAttr.isEmpty())
+    {
+      throw new SchemaNotFoundException(ERR_NO_SUBSCHEMA_SUBENTRY_ATTR
+          .get(dn));
+    }
+
+    String subschemaDNString = subentryAttr.iterator().next()
+        .toString();
+    DN subschemaDN;
+    try
+    {
+      subschemaDN = DN.valueOf(subschemaDNString);
+    }
+    catch (LocalizedIllegalArgumentException e)
+    {
+      throw new SchemaNotFoundException(
+          ERR_INVALID_SUBSCHEMA_SUBENTRY_ATTR.get(dn,
+              subschemaDNString, e.getMessageObject()));
+    }
+    result = connection.readEntry(subschemaDN, SUBSCHEMA_ATTRS);
+
+    final SchemaBuilder builder = new SchemaBuilder();
+    Attribute attr = result.getAttribute(ATTR_LDAP_SYNTAXES);
+
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addSyntax(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    attr = result.getAttribute(ATTR_ATTRIBUTE_TYPES);
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addAttributeType(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    attr = result.getAttribute(ATTR_OBJECT_CLASSES);
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addObjectClass(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    attr = result.getAttribute(ATTR_MATCHING_RULE_USE);
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addMatchingRuleUse(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    attr = result.getAttribute(ATTR_MATCHING_RULES);
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addMatchingRule(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    attr = result.getAttribute(ATTR_DIT_CONTENT_RULES);
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addDITContentRule(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    attr = result.getAttribute(ATTR_DIT_STRUCTURE_RULES);
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addDITStructureRule(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    attr = result.getAttribute(ATTR_NAME_FORMS);
+    if (attr != null)
+    {
+      for (final ByteString def : attr)
+      {
+        try
+        {
+          builder.addNameForm(def.toString(), true);
+        }
+        catch (final LocalizedIllegalArgumentException e)
+        {
+          warnings.add(e.getMessageObject());
+        }
+      }
+    }
+
+    return builder.toSchema(warnings);
+  }
+
+
+
+  /**
+   * Sets the default schema which should be used by this application.
+   * The default schema is initially set to the core schema.
+   *
+   * @param schema
+   *          The default schema which should be used by this
+   *          application.
+   */
+  public static void setDefaultSchema(Schema schema)
+  {
+    DEFAULT_SCHEMA = schema;
+  }
+
+
+
+  static MatchingRule getDefaultMatchingRule()
+  {
+    return CoreSchema.getOctetStringMatchingRule();
+  }
+
+
+
+  static Syntax getDefaultSyntax()
+  {
+    return CoreSchema.getOctetStringSyntax();
+  }
+
+
+
+  private final Impl impl;
+
+
+
+  Schema(Map<String, Syntax> numericOID2Syntaxes,
+      Map<String, MatchingRule> numericOID2MatchingRules,
+      Map<String, MatchingRuleUse> numericOID2MatchingRuleUses,
+      Map<String, AttributeType> numericOID2AttributeTypes,
+      Map<String, ObjectClass> numericOID2ObjectClasses,
+      Map<String, NameForm> numericOID2NameForms,
+      Map<String, DITContentRule> numericOID2ContentRules,
+      Map<Integer, DITStructureRule> id2StructureRules,
+      Map<String, List<MatchingRule>> name2MatchingRules,
+      Map<String, List<MatchingRuleUse>> name2MatchingRuleUses,
+      Map<String, List<AttributeType>> name2AttributeTypes,
+      Map<String, List<ObjectClass>> name2ObjectClasses,
+      Map<String, List<NameForm>> name2NameForms,
+      Map<String, List<DITContentRule>> name2ContentRules,
+      Map<String, List<DITStructureRule>> name2StructureRules,
+      Map<String, List<NameForm>> objectClass2NameForms,
+      Map<String, List<DITStructureRule>> nameForm2StructureRules,
+      SchemaCompatOptions options)
+  {
+    impl = new StrictImpl(numericOID2Syntaxes,
+        numericOID2MatchingRules, numericOID2MatchingRuleUses,
+        numericOID2AttributeTypes, numericOID2ObjectClasses,
+        numericOID2NameForms, numericOID2ContentRules,
+        id2StructureRules, name2MatchingRules, name2MatchingRuleUses,
+        name2AttributeTypes, name2ObjectClasses, name2NameForms,
+        name2ContentRules, name2StructureRules, objectClass2NameForms,
+        nameForm2StructureRules, options);
+  }
+
+
+
+  private Schema(Impl impl)
+  {
+    this.impl = impl;
+  }
+
+
+
+  /**
+   * Returns the attribute type with the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the attribute type to retrieve.
+   * @return The requested attribute type.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested attribute
+   *           type was not found or if the provided name is ambiguous.
+   */
+  public AttributeType getAttributeType(String name)
+      throws UnknownSchemaElementException
+  {
+    return impl.getAttributeType(name);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the attribute
+   * types contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the attribute
+   *         types contained in this schema.
+   */
+  public Collection<AttributeType> getAttributeTypes()
+  {
+    return impl.getAttributeTypes();
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the attribute
+   * types having the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the attribute types to retrieve.
+   * @return An unmodifiable collection containing all of the attribute
+   *         types having the specified name or numeric OID.
+   */
+  public List<AttributeType> getAttributeTypesByName(String name)
+  {
+    return impl.getAttributeTypesByName(name);
+  }
+
+
+
+  /**
+   * Returns the DIT content rule with the specified name or numeric
+   * OID.
+   *
+   * @param name
+   *          The name or OID of the DIT content rule to retrieve.
+   * @return The requested DIT content rule.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested DIT content
+   *           rule was not found or if the provided name is ambiguous.
+   */
+  public DITContentRule getDITContentRule(String name)
+      throws UnknownSchemaElementException
+  {
+    return impl.getDITContentRule(name);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the DIT
+   * content rules contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the DIT
+   *         content rules contained in this schema.
+   */
+  public Collection<DITContentRule> getDITContentRules()
+  {
+    return impl.getDITContentRules();
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the DIT
+   * content rules having the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the DIT content rules to retrieve.
+   * @return An unmodifiable collection containing all of the DIT
+   *         content rules having the specified name or numeric OID.
+   */
+  public Collection<DITContentRule> getDITContentRulesByName(String name)
+  {
+    return impl.getDITContentRulesByName(name);
+  }
+
+
+
+  /**
+   * Returns the DIT structure rule with the specified name or numeric
+   * OID.
+   *
+   * @param ruleID
+   *          The ID of the DIT structure rule to retrieve.
+   * @return The requested DIT structure rule.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested DIT
+   *           structure rule was not found.
+   */
+  public DITStructureRule getDITStructureRule(int ruleID)
+      throws UnknownSchemaElementException
+  {
+    return impl.getDITStructureRule(ruleID);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the DIT
+   * structure rules having the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the DIT structure rules to retrieve.
+   * @return An unmodifiable collection containing all of the DIT
+   *         structure rules having the specified name or numeric OID.
+   */
+  public Collection<DITStructureRule> getDITStructureRulesByName(
+      String name)
+  {
+    return impl.getDITStructureRulesByName(name);
+  }
+
+
+
+  /**
+   * Retrieves the DIT structure rules for the provided name form.
+   *
+   * @param nameForm
+   *          The name form.
+   * @return The requested DIT structure rules.
+   */
+  public Collection<DITStructureRule> getDITStructureRulesByNameForm(
+      NameForm nameForm)
+  {
+    return impl.getDITStructureRulesByNameForm(nameForm);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the DIT
+   * structure rules contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the DIT
+   *         structure rules contained in this schema.
+   */
+  public Collection<DITStructureRule> getDITStuctureRules()
+  {
+    return impl.getDITStuctureRules();
+  }
+
+
+
+  /**
+   * Returns the matching rule with the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the matching rule to retrieve.
+   * @return The requested matching rule.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested matching
+   *           rule was not found or if the provided name is ambiguous.
+   */
+  public MatchingRule getMatchingRule(String name)
+      throws UnknownSchemaElementException
+  {
+    return impl.getMatchingRule(name);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the matching
+   * rules contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the matching
+   *         rules contained in this schema.
+   */
+  public Collection<MatchingRule> getMatchingRules()
+  {
+    return impl.getMatchingRules();
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the matching
+   * rules having the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the matching rules to retrieve.
+   * @return An unmodifiable collection containing all of the matching
+   *         rules having the specified name or numeric OID.
+   */
+  public Collection<MatchingRule> getMatchingRulesByName(String name)
+  {
+    return impl.getMatchingRulesByName(name);
+  }
+
+
+
+  /**
+   * Returns the matching rule use associated with the provided matching
+   * rule.
+   *
+   * @param matchingRule
+   *          The matching rule whose matching rule use is to be
+   *          retrieved.
+   * @return The requested matching rule use.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested matching
+   *           rule use was not found or if the provided name is
+   *           ambiguous.
+   */
+  public MatchingRuleUse getMatchingRuleUse(MatchingRule matchingRule)
+      throws UnknownSchemaElementException
+  {
+    return getMatchingRuleUse(matchingRule.getOID());
+  }
+
+
+
+  /**
+   * Returns the matching rule use with the specified name or numeric
+   * OID.
+   *
+   * @param name
+   *          The name or OID of the matching rule use to retrieve.
+   * @return The requested matching rule use.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested matching
+   *           rule use was not found or if the provided name is
+   *           ambiguous.
+   */
+  public MatchingRuleUse getMatchingRuleUse(String name)
+      throws UnknownSchemaElementException
+  {
+    return impl.getMatchingRuleUse(name);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the matching
+   * rule uses contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the matching
+   *         rule uses contained in this schema.
+   */
+  public Collection<MatchingRuleUse> getMatchingRuleUses()
+  {
+    return impl.getMatchingRuleUses();
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the matching
+   * rule uses having the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the matching rule uses to retrieve.
+   * @return An unmodifiable collection containing all of the matching
+   *         rule uses having the specified name or numeric OID.
+   */
+  public Collection<MatchingRuleUse> getMatchingRuleUsesByName(
+      String name)
+  {
+    return impl.getMatchingRuleUsesByName(name);
+  }
+
+
+
+  /**
+   * Returns the name form with the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the name form to retrieve.
+   * @return The requested name form.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested name form
+   *           was not found or if the provided name is ambiguous.
+   */
+  public NameForm getNameForm(String name)
+      throws UnknownSchemaElementException
+  {
+    return impl.getNameForm(name);
+  }
+
+
+
+  /**
+   * Retrieves the name forms for the specified structural objectclass.
+   *
+   * @param structuralClass
+   *          The structural objectclass for the name form to retrieve.
+   * @return The requested name forms
+   */
+  public Collection<NameForm> getNameFormByObjectClass(
+      ObjectClass structuralClass)
+  {
+    return impl.getNameFormByObjectClass(structuralClass);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the name forms
+   * contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the name forms
+   *         contained in this schema.
+   */
+  public Collection<NameForm> getNameForms()
+  {
+    return impl.getNameForms();
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the name forms
+   * having the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the name forms to retrieve.
+   * @return An unmodifiable collection containing all of the name forms
+   *         having the specified name or numeric OID.
+   */
+  public Collection<NameForm> getNameFormsByName(String name)
+  {
+    return impl.getNameFormsByName(name);
+  }
+
+
+
+  /**
+   * Returns the object class with the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the object class to retrieve.
+   * @return The requested object class.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested object class
+   *           was not found or if the provided name is ambiguous.
+   */
+  public ObjectClass getObjectClass(String name)
+      throws UnknownSchemaElementException
+  {
+    return impl.getObjectClass(name);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the object
+   * classes contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the object
+   *         classes contained in this schema.
+   */
+  public Collection<ObjectClass> getObjectClasses()
+  {
+    return impl.getObjectClasses();
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the object
+   * classes having the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the object classes to retrieve.
+   * @return An unmodifiable collection containing all of the object
+   *         classes having the specified name or numeric OID.
+   */
+  public Collection<ObjectClass> getObjectClassesByName(String name)
+  {
+    return impl.getObjectClassesByName(name);
+  }
+
+
+
+  /**
+   * Returns the syntax with the specified numeric OID.
+   *
+   * @param numericOID
+   *          The OID of the syntax to retrieve.
+   * @return The requested syntax.
+   * @throws UnknownSchemaElementException
+   *           If this is a strict schema and the requested syntax was
+   *           not found or if the provided name is ambiguous.
+   */
+  public Syntax getSyntax(String numericOID)
+      throws UnknownSchemaElementException
+  {
+    return impl.getSyntax(numericOID);
+  }
+
+
+
+  /**
+   * Returns an unmodifiable collection containing all of the syntaxes
+   * contained in this schema.
+   *
+   * @return An unmodifiable collection containing all of the syntaxes
+   *         contained in this schema.
+   */
+  public Collection<Syntax> getSyntaxes()
+  {
+    return impl.getSyntaxes();
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains an attribute type
+   * with the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the attribute type.
+   * @return {@code true} if this schema contains an attribute type with
+   *         the specified name or numeric OID, otherwise {@code false}.
+   */
+  public boolean hasAttributeType(String name)
+  {
+    return impl.hasAttributeType(name);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains a DIT content rule
+   * with the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the DIT content rule.
+   * @return {@code true} if this schema contains a DIT content rule
+   *         with the specified name or numeric OID, otherwise {@code
+   *         false}.
+   */
+  public boolean hasDITContentRule(String name)
+  {
+    return impl.hasDITContentRule(name);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains a DIT structure rule
+   * with the specified rule ID.
+   *
+   * @param ruleID
+   *          The ID of the DIT structure rule.
+   * @return {@code true} if this schema contains a DIT structure rule
+   *         with the specified rule ID, otherwise {@code false}.
+   */
+  public boolean hasDITStructureRule(int ruleID)
+  {
+    return impl.hasDITStructureRule(ruleID);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains a matching rule with
+   * the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the matching rule.
+   * @return {@code true} if this schema contains a matching rule with
+   *         the specified name or numeric OID, otherwise {@code false}.
+   */
+  public boolean hasMatchingRule(String name)
+  {
+    return impl.hasMatchingRule(name);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains a matching rule use
+   * with the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the matching rule use.
+   * @return {@code true} if this schema contains a matching rule use
+   *         with the specified name or numeric OID, otherwise {@code
+   *         false}.
+   */
+  public boolean hasMatchingRuleUse(String name)
+  {
+    return impl.hasMatchingRuleUse(name);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains a name form with the
+   * specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the name form.
+   * @return {@code true} if this schema contains a name form with the
+   *         specified name or numeric OID, otherwise {@code false}.
+   */
+  public boolean hasNameForm(String name)
+  {
+    return impl.hasNameForm(name);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains an object class with
+   * the specified name or numeric OID.
+   *
+   * @param name
+   *          The name or OID of the object class.
+   * @return {@code true} if this schema contains an object class with
+   *         the specified name or numeric OID, otherwise {@code false}.
+   */
+  public boolean hasObjectClass(String name)
+  {
+    return impl.hasObjectClass(name);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema contains a syntax with the
+   * specified numeric OID.
+   *
+   * @param numericOID
+   *          The OID of the syntax.
+   * @return {@code true} if this schema contains a syntax with the
+   *         specified numeric OID, otherwise {@code false}.
+   */
+  public boolean hasSyntax(String numericOID)
+  {
+    return impl.hasSyntax(numericOID);
+  }
+
+
+
+  /**
+   * Indicates whether or not this schema is strict. Attribute type
+   * queries in non-strict schema always succeed: if the requested
+   * attribute type is not found then a temporary attribute type is
+   * created automatically having the Octet String syntax and associated
+   * matching rules. Strict schema, on the other hand, throw an
+   * {@link UnknownSchemaElementException} whenever an attempt is made
+   * to retrieve a non-existent attribute type.
+   *
+   * @return {@code true} if this schema is strict.
+   */
+  public boolean isStrict()
+  {
+    return impl.isStrict();
+  }
+
+
+
+  /**
+   * Returns a non-strict view of this schema. Attribute type queries in
+   * non-strict schema always succeed: if the requested attribute type
+   * is not found then a temporary attribute type is created
+   * automatically having the Octet String syntax and associated
+   * matching rules. Strict schema, on the other hand, throw an
+   * {@link UnknownSchemaElementException} whenever an attempt is made
+   * to retrieve a non-existent attribute type.
+   *
+   * @return A non-strict view of this schema.
+   */
+  public Schema nonStrict()
+  {
+    if (impl.isStrict())
+    {
+      return new Schema(new NonStrictImpl(impl));
+    }
+    else
+    {
+      return this;
+    }
+  }
+
+
+
+  <T> T getAttachment(SchemaLocal<T> attachment)
+  {
+    return impl.getAttachment(attachment);
+  }
+
+
+
+  SchemaCompatOptions getSchemaCompatOptions()
+  {
+    return impl.getSchemaCompatOptions();
+  }
+
+
+
+  <T> T removeAttachment(SchemaLocal<T> attachment)
+  {
+    return impl.removeAttachment(attachment);
+  }
+
+
+
+  <T> void setAttachment(SchemaLocal<T> attachment, T value)
+  {
+    impl.setAttachment(attachment, value);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaBuilder.java b/sdk/src/org/opends/sdk/schema/SchemaBuilder.java
new file mode 100644
index 0000000..f8f1c9e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaBuilder.java
@@ -0,0 +1,3007 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.CoreMessages.*;
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EXTENSIBLE_OBJECT_OBJECTCLASS_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_GENERIC_ENUM_NAME;
+import static org.opends.sdk.schema.SchemaConstants.SCHEMA_PROPERTY_APPROX_RULE;
+import static org.opends.sdk.schema.SchemaConstants.TOP_OBJECTCLASS_NAME;
+
+import java.util.*;
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.SubstringReader;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * Schema builders should be used for incremental construction of new
+ * schemas.
+ */
+public final class SchemaBuilder
+{
+
+  private Map<Integer, DITStructureRule> id2StructureRules;
+  private Map<String, List<AttributeType>> name2AttributeTypes;
+  private Map<String, List<DITContentRule>> name2ContentRules;
+  private Map<String, List<MatchingRule>> name2MatchingRules;
+  private Map<String, List<MatchingRuleUse>> name2MatchingRuleUses;
+  private Map<String, List<NameForm>> name2NameForms;
+  private Map<String, List<ObjectClass>> name2ObjectClasses;
+  private Map<String, List<DITStructureRule>> name2StructureRules;
+  private Map<String, List<DITStructureRule>> nameForm2StructureRules;
+  private Map<String, AttributeType> numericOID2AttributeTypes;
+  private Map<String, DITContentRule> numericOID2ContentRules;
+  private Map<String, MatchingRule> numericOID2MatchingRules;
+  private Map<String, MatchingRuleUse> numericOID2MatchingRuleUses;
+  private Map<String, NameForm> numericOID2NameForms;
+  private Map<String, ObjectClass> numericOID2ObjectClasses;
+  private Map<String, Syntax> numericOID2Syntaxes;
+  private Map<String, List<NameForm>> objectClass2NameForms;
+  private SchemaCompatOptions options;
+  private Schema schema;
+
+
+
+  /**
+   * Creates a new schema builder with no schema elements and default
+   * compatibility options.
+   */
+  public SchemaBuilder()
+  {
+    initBuilder();
+  }
+
+
+
+  /**
+   * Creates a new schema builder containing all of the schema elements
+   * from the provided schema and its compatibility options.
+   *
+   * @param schema
+   *          The initial contents of the schema builder.
+   * @throws NullPointerException
+   *           If {@code schema} was {@code null}.
+   */
+  public SchemaBuilder(Schema schema) throws NullPointerException
+  {
+    Validator.ensureNotNull(schema);
+    initBuilder();
+    setSchemaCompatOptions(schema.getSchemaCompatOptions());
+    addSchema(schema, true);
+  }
+
+
+
+  /**
+   * Adds the provided attribute type definition to this schema builder.
+   *
+   * @param definition
+   *          The attribute type definition.
+   * @param overwrite
+   *          {@code true} if any existing attribute type with the same
+   *          OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided attribute type definition could not be
+   *           parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addAttributeType(String definition,
+      boolean overwrite) throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the definition was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRTYPE_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRTYPE_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      List<String> names = Collections.emptyList();
+      String description = "".intern();
+      boolean isObsolete = false;
+      String superiorType = null;
+      String equalityMatchingRule = null;
+      String orderingMatchingRule = null;
+      String substringMatchingRule = null;
+      String approximateMatchingRule = null;
+      String syntax = null;
+      boolean isSingleValue = false;
+      boolean isCollective = false;
+      boolean isNoUserModification = false;
+      AttributeUsage attributeUsage = AttributeUsage.USER_APPLICATIONS;
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the definition. But before we start, set default
+      // values for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          names = SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+          isObsolete = true;
+        }
+        else if (tokenName.equalsIgnoreCase("sup"))
+        {
+          // This specifies the name or OID of the superior attribute
+          // type from which this attribute type should inherit its
+          // properties.
+          superiorType = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("equality"))
+        {
+          // This specifies the name or OID of the equality matching
+          // rule to use for this attribute type.
+          equalityMatchingRule = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("ordering"))
+        {
+          // This specifies the name or OID of the ordering matching
+          // rule to use for this attribute type.
+          orderingMatchingRule = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("substr"))
+        {
+          // This specifies the name or OID of the substring matching
+          // rule to use for this attribute type.
+          substringMatchingRule = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("syntax"))
+        {
+          // This specifies the numeric OID of the syntax for this
+          // matching rule. It may optionally be immediately followed by
+          // an open curly brace, an integer definition, and a close
+          // curly brace to suggest the minimum number of characters
+          // that should be allowed in values of that type. This
+          // implementation will ignore any such length because it does
+          // not impose any practical limit on the length of attribute
+          // values.
+          syntax = SchemaUtils.readOIDLen(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("single-definition"))
+        {
+          // This indicates that attributes of this type are allowed to
+          // have at most one definition. We do not need any more
+          // parsing for this token.
+          isSingleValue = true;
+        }
+        else if (tokenName.equalsIgnoreCase("single-value"))
+        {
+          // This indicates that attributes of this type are allowed to
+          // have at most one value. We do not need any more parsing for
+          // this token.
+          isSingleValue = true;
+        }
+        else if (tokenName.equalsIgnoreCase("collective"))
+        {
+          // This indicates that attributes of this type are collective
+          // (i.e., have their values generated dynamically in some
+          // way). We do not need any more parsing for this token.
+          isCollective = true;
+        }
+        else if (tokenName.equalsIgnoreCase("no-user-modification"))
+        {
+          // This indicates that the values of attributes of this type
+          // are not to be modified by end users. We do not need any
+          // more parsing for this token.
+          isNoUserModification = true;
+        }
+        else if (tokenName.equalsIgnoreCase("usage"))
+        {
+          // This specifies the usage string for this attribute type. It
+          // should be followed by one of the strings
+          // "userApplications", "directoryOperation",
+          // "distributedOperation", or "dSAOperation".
+          int length = 0;
+
+          reader.skipWhitespaces();
+          reader.mark();
+
+          while (reader.read() != ' ')
+          {
+            length++;
+          }
+
+          reader.reset();
+          final String usageStr = reader.read(length);
+          if (usageStr.equalsIgnoreCase("userapplications"))
+          {
+            attributeUsage = AttributeUsage.USER_APPLICATIONS;
+          }
+          else if (usageStr.equalsIgnoreCase("directoryoperation"))
+          {
+            attributeUsage = AttributeUsage.DIRECTORY_OPERATION;
+          }
+          else if (usageStr.equalsIgnoreCase("distributedoperation"))
+          {
+            attributeUsage = AttributeUsage.DISTRIBUTED_OPERATION;
+          }
+          else if (usageStr.equalsIgnoreCase("dsaoperation"))
+          {
+            attributeUsage = AttributeUsage.DSA_OPERATION;
+          }
+          else
+          {
+            final Message message =
+                WARN_ATTR_SYNTAX_ATTRTYPE_INVALID_ATTRIBUTE_USAGE.get(
+                    String.valueOf(oid), usageStr);
+            throw new LocalizedIllegalArgumentException(message);
+          }
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      final List<String> approxRules =
+          extraProperties.get(SCHEMA_PROPERTY_APPROX_RULE);
+      if (approxRules != null && !approxRules.isEmpty())
+      {
+        approximateMatchingRule = approxRules.get(0);
+      }
+
+      final AttributeType attrType =
+          new AttributeType(oid, names, description, isObsolete,
+              superiorType, equalityMatchingRule, orderingMatchingRule,
+              substringMatchingRule, approximateMatchingRule, syntax,
+              isSingleValue, isCollective, isNoUserModification,
+              attributeUsage, extraProperties, definition);
+
+      addAttributeType(attrType, overwrite);
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided attribute type definition to this schema builder.
+   *
+   * @param oid
+   *          The OID of the attribute type definition.
+   * @param names
+   *          The user-friendly names of the attribute type definition.
+   * @param description
+   *          The description of the attribute type definition.
+   * @param obsolete
+   *          {@code true} if the attribute type definition is obsolete,
+   *          otherwise {@code false}.
+   * @param superiorType
+   *          The OID of the superior attribute type definition.
+   * @param equalityMatchingRule
+   *          The OID of the equality matching rule, which may be
+   *          {@code null} indicating that the superior attribute type's
+   *          matching rule should be used or, if none is defined, the
+   *          default matching rule associated with the syntax.
+   * @param orderingMatchingRule
+   *          The OID of the ordering matching rule, which may be
+   *          {@code null} indicating that the superior attribute type's
+   *          matching rule should be used or, if none is defined, the
+   *          default matching rule associated with the syntax.
+   * @param substringMatchingRule
+   *          The OID of the substring matching rule, which may be
+   *          {@code null} indicating that the superior attribute type's
+   *          matching rule should be used or, if none is defined, the
+   *          default matching rule associated with the syntax.
+   * @param approximateMatchingRule
+   *          The OID of the approximate matching rule, which may be
+   *          {@code null} indicating that the superior attribute type's
+   *          matching rule should be used or, if none is defined, the
+   *          default matching rule associated with the syntax.
+   * @param syntax
+   *          The OID of the syntax definition.
+   * @param singleValue
+   *          {@code true} if the attribute type definition is
+   *          single-valued, otherwise {@code false}.
+   * @param collective
+   *          {@code true} if the attribute type definition is a
+   *          collective attribute, otherwise {@code false}.
+   * @param noUserModification
+   *          {@code true} if the attribute type definition is
+   *          read-only, otherwise {@code false}.
+   * @param attributeUsage
+   *          The intended use of the attribute type definition.
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          attribute type definition.
+   * @param overwrite
+   *          {@code true} if any existing attribute type with the same
+   *          OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addAttributeType(String oid, List<String> names,
+      String description, boolean obsolete, String superiorType,
+      String equalityMatchingRule, String orderingMatchingRule,
+      String substringMatchingRule, String approximateMatchingRule,
+      String syntax, boolean singleValue, boolean collective,
+      boolean noUserModification, AttributeUsage attributeUsage,
+      Map<String, List<String>> extraProperties, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    final AttributeType attrType =
+        new AttributeType(oid, names, description, obsolete,
+            superiorType, equalityMatchingRule, orderingMatchingRule,
+            substringMatchingRule, approximateMatchingRule, syntax,
+            singleValue, collective, noUserModification,
+            attributeUsage, extraProperties, null);
+    addAttributeType(attrType, overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided DIT content rule definition to this schema
+   * builder.
+   *
+   * @param definition
+   *          The DIT content rule definition.
+   * @param overwrite
+   *          {@code true} if any existing DIT content rule with the
+   *          same OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided DIT content rule definition could not be
+   *           parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addDITContentRule(String definition,
+      boolean overwrite) throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_DCR_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DCR_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String structuralClass = SchemaUtils.readOID(reader);
+
+      List<String> names = Collections.emptyList();
+      String description = "".intern();
+      boolean isObsolete = false;
+      Set<String> auxiliaryClasses = Collections.emptySet();
+      Set<String> optionalAttributes = Collections.emptySet();
+      Set<String> prohibitedAttributes = Collections.emptySet();
+      Set<String> requiredAttributes = Collections.emptySet();
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          names = SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+          isObsolete = true;
+        }
+        else if (tokenName.equalsIgnoreCase("aux"))
+        {
+          auxiliaryClasses = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("must"))
+        {
+          requiredAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("may"))
+        {
+          optionalAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("not"))
+        {
+          prohibitedAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      final DITContentRule rule =
+          new DITContentRule(structuralClass, names, description,
+              isObsolete, auxiliaryClasses, optionalAttributes,
+              prohibitedAttributes, requiredAttributes,
+              extraProperties, definition);
+      addDITContentRule(rule, overwrite);
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided DIT content rule definition to this schema
+   * builder.
+   *
+   * @param structuralClass
+   *          The name of the structural object class to which the DIT
+   *          content rule applies.
+   * @param names
+   *          The user-friendly names of the DIT content rule
+   *          definition.
+   * @param description
+   *          The description of the DIT content rule definition.
+   * @param obsolete
+   *          {@code true} if the DIT content rule definition is
+   *          obsolete, otherwise {@code false}.
+   * @param auxiliaryClasses
+   *          A list of auxiliary object classes that entries subject to
+   *          the DIT content rule may belong to.
+   * @param optionalAttributes
+   *          A list of attribute types that entries subject to the DIT
+   *          content rule may contain.
+   * @param prohibitedAttributes
+   *          A list of attribute types that entries subject to the DIT
+   *          content rule must not contain.
+   * @param requiredAttributes
+   *          A list of attribute types that entries subject to the DIT
+   *          content rule must contain.
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          DIT content rule definition.
+   * @param overwrite
+   *          {@code true} if any existing DIT content rule with the
+   *          same OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addDITContentRule(String structuralClass,
+      List<String> names, String description, boolean obsolete,
+      Set<String> auxiliaryClasses, Set<String> optionalAttributes,
+      Set<String> prohibitedAttributes, Set<String> requiredAttributes,
+      Map<String, List<String>> extraProperties, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    final DITContentRule rule =
+        new DITContentRule(structuralClass, names, description,
+            obsolete, auxiliaryClasses, optionalAttributes,
+            prohibitedAttributes, requiredAttributes, extraProperties,
+            null);
+    addDITContentRule(rule, overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided DIT structure rule definition to this schema
+   * builder.
+   *
+   * @param ruleID
+   *          The rule identifier of the DIT structure rule.
+   * @param names
+   *          The user-friendly names of the DIT structure rule
+   *          definition.
+   * @param description
+   *          The description of the DIT structure rule definition.
+   * @param obsolete
+   *          {@code true} if the DIT structure rule definition is
+   *          obsolete, otherwise {@code false}.
+   * @param nameForm
+   *          The name form associated with the DIT structure rule.
+   * @param superiorRules
+   *          A list of superior rules (by rule id).
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          DIT structure rule definition.
+   * @param overwrite
+   *          {@code true} if any existing DIT structure rule with the
+   *          same OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addDITStructureRule(Integer ruleID,
+      List<String> names, String description, boolean obsolete,
+      String nameForm, Set<Integer> superiorRules,
+      Map<String, List<String>> extraProperties, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    final DITStructureRule rule =
+        new DITStructureRule(ruleID, names, description, obsolete,
+            nameForm, superiorRules, extraProperties, null);
+    addDITStructureRule(rule, overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided DIT structure rule definition to this schema
+   * builder.
+   *
+   * @param definition
+   *          The DIT structure rule definition.
+   * @param overwrite
+   *          {@code true} if any existing DIT structure rule with the
+   *          same OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided DIT structure rule definition could not
+   *           be parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addDITStructureRule(String definition,
+      boolean overwrite) throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_DSR_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DSR_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final Integer ruleID = SchemaUtils.readRuleID(reader);
+
+      List<String> names = Collections.emptyList();
+      String description = "".intern();
+      boolean isObsolete = false;
+      String nameForm = null;
+      Set<Integer> superiorRules = Collections.emptySet();
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          names = SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+          isObsolete = true;
+        }
+        else if (tokenName.equalsIgnoreCase("form"))
+        {
+          nameForm = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("sup"))
+        {
+          superiorRules = SchemaUtils.readRuleIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      if (nameForm == null)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_DSR_NO_NAME_FORM.get(definition);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      final DITStructureRule rule =
+          new DITStructureRule(ruleID, names, description, isObsolete,
+              nameForm, superiorRules, extraProperties, definition);
+      addDITStructureRule(rule, overwrite);
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided enumeration syntax definition to this schema
+   * builder.
+   *
+   * @param oid
+   *          The OID of the enumeration syntax definition.
+   * @param description
+   *          The description of the enumeration syntax definition.
+   * @param overwrite
+   *          {@code true} if any existing syntax with the same OID
+   *          should be overwritten.
+   * @param enumerations
+   *          The range of values which attribute values must match in
+   *          order to be valid.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addEnumerationSyntax(String oid,
+      String description, boolean overwrite, String... enumerations)
+      throws ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull((Object) enumerations);
+
+    final EnumSyntaxImpl enumImpl =
+        new EnumSyntaxImpl(oid, Arrays.asList(enumerations));
+    final Syntax enumSyntax =
+        new Syntax(oid, description, Collections.singletonMap("X-ENUM",
+            Arrays.asList(enumerations)), null, enumImpl);
+    final MatchingRule enumOMR =
+        new MatchingRule(enumImpl.getOrderingMatchingRule(),
+            Collections.singletonList(OMR_GENERIC_ENUM_NAME + oid), "",
+            false, oid, CoreSchemaImpl.OPENDS_ORIGIN, null,
+            new EnumOrderingMatchingRule(enumImpl));
+
+    addSyntax(enumSyntax, overwrite);
+    try
+    {
+      addMatchingRule(enumOMR, overwrite);
+    }
+    catch (final ConflictingSchemaElementException e)
+    {
+      removeSyntax(oid);
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided matching rule definition to this schema builder.
+   *
+   * @param definition
+   *          The matching rule definition.
+   * @param overwrite
+   *          {@code true} if any existing matching rule with the same
+   *          OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided matching rule definition could not be
+   *           parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addMatchingRule(String definition,
+      boolean overwrite) throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_MR_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MR_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      List<String> names = Collections.emptyList();
+      String description = "".intern();
+      boolean isObsolete = false;
+      String syntax = null;
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          names = SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the matching rule. It is
+          // an arbitrary string of characters enclosed in single
+          // quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the matching rule should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+          isObsolete = true;
+        }
+        else if (tokenName.equalsIgnoreCase("syntax"))
+        {
+          syntax = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      // Make sure that a syntax was specified.
+      if (syntax == null)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MR_NO_SYNTAX.get(definition);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      addMatchingRule(new MatchingRule(oid, names, description,
+          isObsolete, syntax, extraProperties, definition, null),
+          overwrite);
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided matching rule definition to this schema builder.
+   *
+   * @param oid
+   *          The OID of the matching rule definition.
+   * @param names
+   *          The user-friendly names of the matching rule definition.
+   * @param description
+   *          The description of the matching rule definition.
+   * @param obsolete
+   *          {@code true} if the matching rule definition is obsolete,
+   *          otherwise {@code false}.
+   * @param assertionSyntax
+   *          The OID of the assertion syntax definition.
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          matching rule definition.
+   * @param implementation
+   *          The implementation of the matching rule.
+   * @param overwrite
+   *          {@code true} if any existing matching rule with the same
+   *          OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addMatchingRule(String oid, List<String> names,
+      String description, boolean obsolete, String assertionSyntax,
+      Map<String, List<String>> extraProperties,
+      MatchingRuleImpl implementation, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(implementation);
+    final MatchingRule matchingRule =
+        new MatchingRule(oid, names, description, obsolete,
+            assertionSyntax, extraProperties, null, implementation);
+    addMatchingRule(matchingRule, overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided matching rule use definition to this schema
+   * builder.
+   *
+   * @param definition
+   *          The matching rule use definition.
+   * @param overwrite
+   *          {@code true} if any existing matching rule use with the
+   *          same OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided matching rule use definition could not be
+   *           parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addMatchingRuleUse(String definition,
+      boolean overwrite) throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message = ERR_ATTR_SYNTAX_MRUSE_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MRUSE_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      List<String> names = Collections.emptyList();
+      String description = "".intern();
+      boolean isObsolete = false;
+      Set<String> attributes = null;
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          names = SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+          isObsolete = true;
+        }
+        else if (tokenName.equalsIgnoreCase("applies"))
+        {
+          attributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      // Make sure that the set of attributes was defined.
+      if (attributes == null || attributes.size() == 0)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_MRUSE_NO_ATTR.get(definition);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      final MatchingRuleUse use =
+          new MatchingRuleUse(oid, names, description, isObsolete,
+              attributes, extraProperties, definition);
+      addMatchingRuleUse(use, overwrite);
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided matching rule use definition to this schema
+   * builder.
+   *
+   * @param oid
+   *          The OID of the matching rule use definition.
+   * @param names
+   *          The user-friendly names of the matching rule use
+   *          definition.
+   * @param description
+   *          The description of the matching rule use definition.
+   * @param obsolete
+   *          {@code true} if the matching rule use definition is
+   *          obsolete, otherwise {@code false}.
+   * @param attributeOIDs
+   *          The list of attribute types the matching rule applies to.
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          matching rule use definition.
+   * @param overwrite
+   *          {@code true} if any existing matching rule use with the
+   *          same OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addMatchingRuleUse(String oid,
+      List<String> names, String description, boolean obsolete,
+      Set<String> attributeOIDs,
+      Map<String, List<String>> extraProperties, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    final MatchingRuleUse use =
+        new MatchingRuleUse(oid, names, description, obsolete,
+            attributeOIDs, extraProperties, null);
+    addMatchingRuleUse(use, overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided name form definition to this schema builder.
+   *
+   * @param definition
+   *          The name form definition.
+   * @param overwrite
+   *          {@code true} if any existing name form with the same OID
+   *          should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided name form definition could not be parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addNameForm(String definition, boolean overwrite)
+      throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), c);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      List<String> names = Collections.emptyList();
+      String description = "".intern();
+      boolean isObsolete = false;
+      String structuralClass = null;
+      Set<String> optionalAttributes = Collections.emptySet();
+      Set<String> requiredAttributes = null;
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          names = SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+          isObsolete = true;
+        }
+        else if (tokenName.equalsIgnoreCase("oc"))
+        {
+          structuralClass = SchemaUtils.readOID(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("must"))
+        {
+          requiredAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("may"))
+        {
+          optionalAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      // Make sure that a structural class was specified. If not, then
+      // it cannot be valid.
+      if (structuralClass == null)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS
+                .get(definition);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      if (requiredAttributes == null || requiredAttributes.size() == 0)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_NAME_FORM_NO_REQUIRED_ATTR.get(definition);
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      final NameForm nameForm =
+          new NameForm(oid, names, description, isObsolete,
+              structuralClass, requiredAttributes, optionalAttributes,
+              extraProperties, definition);
+      addNameForm(nameForm, overwrite);
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided name form definition to this schema builder.
+   *
+   * @param oid
+   *          The OID of the name form definition.
+   * @param names
+   *          The user-friendly names of the name form definition.
+   * @param description
+   *          The description of the name form definition.
+   * @param obsolete
+   *          {@code true} if the name form definition is obsolete,
+   *          otherwise {@code false}.
+   * @param structuralClass
+   *          The structural object class this rule applies to.
+   * @param requiredAttributes
+   *          A list of naming attribute types that entries subject to
+   *          the name form must contain.
+   * @param optionalAttributes
+   *          A list of naming attribute types that entries subject to
+   *          the name form may contain.
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          name form definition.
+   * @param overwrite
+   *          {@code true} if any existing name form use with the same
+   *          OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addNameForm(String oid, List<String> names,
+      String description, boolean obsolete, String structuralClass,
+      Set<String> requiredAttributes, Set<String> optionalAttributes,
+      Map<String, List<String>> extraProperties, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    final NameForm nameForm =
+        new NameForm(oid, names, description, obsolete,
+            structuralClass, requiredAttributes, optionalAttributes,
+            extraProperties, null);
+    addNameForm(nameForm, overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided object class definition to this schema builder.
+   *
+   * @param definition
+   *          The object class definition.
+   * @param overwrite
+   *          {@code true} if any existing object class with the same
+   *          OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided object class definition could not be
+   *           parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addObjectClass(String definition,
+      boolean overwrite) throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_OBJECTCLASS_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_OBJECTCLASS_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      List<String> names = Collections.emptyList();
+      String description = "".intern();
+      boolean isObsolete = false;
+      Set<String> superiorClasses = Collections.emptySet();
+      Set<String> requiredAttributes = Collections.emptySet();
+      Set<String> optionalAttributes = Collections.emptySet();
+      ObjectClassType objectClassType = ObjectClassType.STRUCTURAL;
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("name"))
+        {
+          names = SchemaUtils.readNameDescriptors(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the attribute type. It
+          // is an arbitrary string of characters enclosed in single
+          // quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("obsolete"))
+        {
+          // This indicates whether the attribute type should be
+          // considered obsolete. We do not need to do any more parsing
+          // for this token.
+          isObsolete = true;
+        }
+        else if (tokenName.equalsIgnoreCase("sup"))
+        {
+          superiorClasses = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("abstract"))
+        {
+          // This indicates that entries must not include this
+          // objectclass unless they also include a non-abstract
+          // objectclass that inherits from this class. We do not need
+          // any more parsing for this token.
+          objectClassType = ObjectClassType.ABSTRACT;
+        }
+        else if (tokenName.equalsIgnoreCase("structural"))
+        {
+          // This indicates that this is a structural objectclass. We do
+          // not need any more parsing for this token.
+          objectClassType = ObjectClassType.STRUCTURAL;
+        }
+        else if (tokenName.equalsIgnoreCase("auxiliary"))
+        {
+          // This indicates that this is an auxiliary objectclass. We do
+          // not need any more parsing for this token.
+          objectClassType = ObjectClassType.AUXILIARY;
+        }
+        else if (tokenName.equalsIgnoreCase("must"))
+        {
+          requiredAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.equalsIgnoreCase("may"))
+        {
+          optionalAttributes = SchemaUtils.readOIDs(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID))
+      {
+        addObjectClass(new ObjectClass(description, extraProperties),
+            overwrite);
+      }
+      else
+      {
+        if (objectClassType == ObjectClassType.STRUCTURAL
+            && superiorClasses.isEmpty())
+        {
+          superiorClasses = Collections.singleton(TOP_OBJECTCLASS_NAME);
+        }
+
+        addObjectClass(new ObjectClass(oid, names, description,
+            isObsolete, superiorClasses, requiredAttributes,
+            optionalAttributes, objectClassType, extraProperties,
+            definition), overwrite);
+      }
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided object class definition to this schema builder.
+   *
+   * @param oid
+   *          The OID of the object class definition.
+   * @param names
+   *          The user-friendly names of the object class definition.
+   * @param description
+   *          The description of the object class definition.
+   * @param obsolete
+   *          {@code true} if the object class definition is obsolete,
+   *          otherwise {@code false}.
+   * @param superiorClassOIDs
+   *          A list of direct superclasses of the object class.
+   * @param requiredAttributeOIDs
+   *          A list of attribute types that entries must contain.
+   * @param optionalAttributeOIDs
+   *          A list of attribute types that entries may contain.
+   * @param objectClassType
+   *          The type of the object class.
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          object class definition.
+   * @param overwrite
+   *          {@code true} if any existing object class with the same
+   *          OID should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addObjectClass(String oid, List<String> names,
+      String description, boolean obsolete,
+      Set<String> superiorClassOIDs, Set<String> requiredAttributeOIDs,
+      Set<String> optionalAttributeOIDs,
+      ObjectClassType objectClassType,
+      Map<String, List<String>> extraProperties, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    if (oid.equals(EXTENSIBLE_OBJECT_OBJECTCLASS_OID))
+    {
+      addObjectClass(new ObjectClass(description, extraProperties),
+          overwrite);
+    }
+    else
+    {
+      if (objectClassType == ObjectClassType.STRUCTURAL
+          && superiorClassOIDs.isEmpty())
+      {
+        superiorClassOIDs = Collections.singleton(TOP_OBJECTCLASS_NAME);
+      }
+
+      addObjectClass(
+          new ObjectClass(oid, names, description, obsolete,
+              superiorClassOIDs, requiredAttributeOIDs,
+              optionalAttributeOIDs, objectClassType, extraProperties,
+              null), overwrite);
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided pattern syntax definition to this schema builder.
+   *
+   * @param oid
+   *          The OID of the pattern syntax definition.
+   * @param description
+   *          The description of the pattern syntax definition.
+   * @param pattern
+   *          The regular expression pattern which attribute values must
+   *          match in order to be valid.
+   * @param overwrite
+   *          {@code true} if any existing syntax with the same OID
+   *          should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addPatternSyntax(String oid, String description,
+      Pattern pattern, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(pattern);
+
+    addSyntax(new Syntax(oid, description, Collections.singletonMap(
+        "X-PATTERN", Collections.singletonList(pattern.toString())),
+        null, null), overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds all of the schema elements in the provided schema to this
+   * schema builder.
+   *
+   * @param schema
+   *          The schema to be copied into this schema builder.
+   * @param overwrite
+   *          {@code true} if existing schema elements with the same
+   *          conflicting OIDs should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and conflicting
+   *           schema elements were found.
+   * @throws NullPointerException
+   *           If {@code schema} was {@code null}.
+   */
+  public SchemaBuilder addSchema(Schema schema, boolean overwrite)
+      throws ConflictingSchemaElementException, NullPointerException
+  {
+    Validator.ensureNotNull(schema);
+    for (final Syntax syntax : schema.getSyntaxes())
+    {
+      addSyntax(syntax.duplicate(), overwrite);
+    }
+
+    for (final MatchingRule matchingRule : schema.getMatchingRules())
+    {
+      addMatchingRule(matchingRule.duplicate(), overwrite);
+    }
+
+    for (final MatchingRuleUse matchingRuleUse : schema
+        .getMatchingRuleUses())
+    {
+      addMatchingRuleUse(matchingRuleUse.duplicate(), overwrite);
+    }
+
+    for (final AttributeType attributeType : schema.getAttributeTypes())
+    {
+      addAttributeType(attributeType.duplicate(), overwrite);
+    }
+
+    for (final ObjectClass objectClass : schema.getObjectClasses())
+    {
+      addObjectClass(objectClass.duplicate(), overwrite);
+    }
+
+    for (final NameForm nameForm : schema.getNameForms())
+    {
+      addNameForm(nameForm.duplicate(), overwrite);
+    }
+
+    for (final DITContentRule contentRule : schema.getDITContentRules())
+    {
+      addDITContentRule(contentRule.duplicate(), overwrite);
+    }
+
+    for (final DITStructureRule structureRule : schema
+        .getDITStuctureRules())
+    {
+      addDITStructureRule(structureRule.duplicate(), overwrite);
+    }
+
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided substitution syntax definition to this schema
+   * builder.
+   *
+   * @param oid
+   *          The OID of the substitution syntax definition.
+   * @param description
+   *          The description of the substitution syntax definition.
+   * @param substituteSyntax
+   *          The OID of the syntax whose implementation should be
+   *          substituted.
+   * @param overwrite
+   *          {@code true} if any existing syntax with the same OID
+   *          should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   */
+  public SchemaBuilder addSubstitutionSyntax(String oid,
+      String description, String substituteSyntax, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(substituteSyntax);
+
+    addSyntax(new Syntax(oid, description, Collections.singletonMap(
+        "X-SUBST", Collections.singletonList(substituteSyntax)), null,
+        null), overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided syntax definition to this schema builder.
+   *
+   * @param definition
+   *          The syntax definition.
+   * @param overwrite
+   *          {@code true} if any existing syntax with the same OID
+   *          should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws LocalizedIllegalArgumentException
+   *           If the provided syntax definition could not be parsed.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addSyntax(String definition, boolean overwrite)
+      throws LocalizedIllegalArgumentException,
+      ConflictingSchemaElementException
+  {
+    Validator.ensureNotNull(definition);
+    try
+    {
+      final SubstringReader reader = new SubstringReader(definition);
+
+      // We'll do this a character at a time. First, skip over any
+      // leading whitespace.
+      reader.skipWhitespaces();
+
+      if (reader.remaining() <= 0)
+      {
+        // This means that the value was empty or contained only
+        // whitespace. That is illegal.
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRSYNTAX_EMPTY_VALUE.get();
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // The next character must be an open parenthesis. If it is not,
+      // then that is an error.
+      final char c = reader.read();
+      if (c != '(')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_ATTRSYNTAX_EXPECTED_OPEN_PARENTHESIS.get(
+                definition, (reader.pos() - 1), String.valueOf(c));
+        throw new LocalizedIllegalArgumentException(message);
+      }
+
+      // Skip over any spaces immediately following the opening
+      // parenthesis.
+      reader.skipWhitespaces();
+
+      // The next set of characters must be the OID.
+      final String oid = SchemaUtils.readOID(reader);
+
+      String description = "".intern();
+      Map<String, List<String>> extraProperties =
+          Collections.emptyMap();
+
+      // At this point, we should have a pretty specific syntax that
+      // describes what may come next, but some of the components are
+      // optional and it would be pretty easy to put something in the
+      // wrong order, so we will be very flexible about what we can
+      // accept. Just look at the next token, figure out what it is and
+      // how to treat what comes after it, then repeat until we get to
+      // the end of the value. But before we start, set default values
+      // for everything else we might need to know.
+      while (true)
+      {
+        final String tokenName = SchemaUtils.readTokenName(reader);
+
+        if (tokenName == null)
+        {
+          // No more tokens.
+          break;
+        }
+        else if (tokenName.equalsIgnoreCase("desc"))
+        {
+          // This specifies the description for the syntax. It is an
+          // arbitrary string of characters enclosed in single quotes.
+          description = SchemaUtils.readQuotedString(reader);
+        }
+        else if (tokenName.matches("^X-[A-Za-z_-]+$"))
+        {
+          // This must be a non-standard property and it must be
+          // followed by either a single definition in single quotes or
+          // an open parenthesis followed by one or more values in
+          // single quotes separated by spaces followed by a close
+          // parenthesis.
+          if (extraProperties.isEmpty())
+          {
+            extraProperties = new HashMap<String, List<String>>();
+          }
+          extraProperties.put(tokenName, SchemaUtils
+              .readExtensions(reader));
+        }
+        else
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_TOKEN.get(tokenName);
+          throw new LocalizedIllegalArgumentException(message);
+        }
+      }
+
+      // See if it is a enum syntax
+      for (final Map.Entry<String, List<String>> property : extraProperties
+          .entrySet())
+      {
+        if (property.getKey().equalsIgnoreCase("x-enum"))
+        {
+          final EnumSyntaxImpl enumImpl =
+              new EnumSyntaxImpl(oid, property.getValue());
+          final Syntax enumSyntax =
+              new Syntax(oid, description, extraProperties, definition,
+                  enumImpl);
+          final MatchingRule enumOMR =
+              new MatchingRule(enumImpl.getOrderingMatchingRule(),
+                  Collections
+                      .singletonList(OMR_GENERIC_ENUM_NAME + oid), "",
+                  false, oid, CoreSchemaImpl.OPENDS_ORIGIN, null,
+                  new EnumOrderingMatchingRule(enumImpl));
+
+          addSyntax(enumSyntax, overwrite);
+          addMatchingRule(enumOMR, overwrite);
+          return this;
+        }
+      }
+
+      addSyntax(new Syntax(oid, description, extraProperties,
+          definition, null), overwrite);
+    }
+    catch (final DecodeException e)
+    {
+      throw new LocalizedIllegalArgumentException(e.getMessageObject(),
+          e.getCause());
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Adds the provided syntax definition to this schema builder.
+   *
+   * @param oid
+   *          The OID of the syntax definition.
+   * @param description
+   *          The description of the syntax definition.
+   * @param extraProperties
+   *          A map containing additional properties associated with the
+   *          syntax definition.
+   * @param implementation
+   *          The implementation of the syntax.
+   * @param overwrite
+   *          {@code true} if any existing syntax with the same OID
+   *          should be overwritten.
+   * @return A reference to this schema builder.
+   * @throws ConflictingSchemaElementException
+   *           If {@code overwrite} was {@code false} and a conflicting
+   *           schema element was found.
+   * @throws NullPointerException
+   *           If {@code definition} was {@code null}.
+   */
+  public SchemaBuilder addSyntax(String oid, String description,
+      Map<String, List<String>> extraProperties,
+      SyntaxImpl implementation, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    addSyntax(new Syntax(oid, description, extraProperties, null,
+        implementation), overwrite);
+    return this;
+  }
+
+
+
+  /**
+   * Removes the named attribute type from this schema builder.
+   *
+   * @param name
+   *          The name or OID of the attribute type to be removed.
+   * @return {@code true} if the attribute type was found.
+   */
+  public boolean removeAttributeType(String name)
+  {
+    if (schema.hasAttributeType(name))
+    {
+      removeAttributeType(schema.getAttributeType(name));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Removes the named DIT content rule from this schema builder.
+   *
+   * @param name
+   *          The name or OID of the DIT content rule to be removed.
+   * @return {@code true} if the DIT content rule was found.
+   */
+  public boolean removeDITContentRule(String name)
+  {
+    if (schema.hasDITContentRule(name))
+    {
+      removeDITContentRule(schema.getDITContentRule(name));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Removes the specified DIT structure rule from this schema builder.
+   *
+   * @param ruleID
+   *          The ID of the DIT structure rule to be removed.
+   * @return {@code true} if the DIT structure rule was found.
+   */
+  public boolean removeDITStructureRule(Integer ruleID)
+  {
+    if (schema.hasDITStructureRule(ruleID))
+    {
+      removeDITStructureRule(schema.getDITStructureRule(ruleID));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Removes the named matching rule from this schema builder.
+   *
+   * @param name
+   *          The name or OID of the matching rule to be removed.
+   * @return {@code true} if the matching rule was found.
+   */
+  public boolean removeMatchingRule(String name)
+  {
+    if (schema.hasMatchingRule(name))
+    {
+      removeMatchingRule(schema.getMatchingRule(name));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Removes the named matching rule use from this schema builder.
+   *
+   * @param name
+   *          The name or OID of the matching rule use to be removed.
+   * @return {@code true} if the matching rule use was found.
+   */
+  public boolean removeMatchingRuleUse(String name)
+  {
+    if (schema.hasMatchingRuleUse(name))
+    {
+      removeMatchingRuleUse(schema.getMatchingRuleUse(name));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Removes the named name form from this schema builder.
+   *
+   * @param name
+   *          The name or OID of the name form to be removed.
+   * @return {@code true} if the name form was found.
+   */
+  public boolean removeNameForm(String name)
+  {
+    if (schema.hasNameForm(name))
+    {
+      removeNameForm(schema.getNameForm(name));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Removes the named object class from this schema builder.
+   *
+   * @param name
+   *          The name or OID of the object class to be removed.
+   * @return {@code true} if the object class was found.
+   */
+  public boolean removeObjectClass(String name)
+  {
+    if (schema.hasObjectClass(name))
+    {
+      removeObjectClass(schema.getObjectClass(name));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Removes the named syntax from this schema builder.
+   *
+   * @param numericOID
+   *          The name of the syntax to be removed.
+   * @return {@code true} if the syntax was found.
+   */
+  public boolean removeSyntax(String numericOID)
+  {
+    if (schema.hasSyntax(numericOID))
+    {
+      removeSyntax(schema.getSyntax(numericOID));
+      return true;
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Sets the schema compatibility options for this schema builder. The
+   * schema builder maintains its own set of compatibility options, so
+   * subsequent changes to the provided set of options will not impact
+   * this schema builder.
+   *
+   * @param options
+   *          The set of schema compatibility options that this schema
+   *          builder should use.
+   * @return A reference to this schema builder.
+   * @throws NullPointerException
+   *           If {@code options} was {@code null}.
+   */
+  public SchemaBuilder setSchemaCompatOptions(
+      SchemaCompatOptions options) throws NullPointerException
+  {
+    Validator.ensureNotNull(options);
+    this.options.assign(options);
+    return this;
+  }
+
+
+
+  /**
+   * Returns a {@code Schema} containing all of the schema elements
+   * contained in this schema builder as well as the same set of schema
+   * compatibility options.
+   * <p>
+   * Any errors that were detected while validating the schema will be
+   * ignored.
+   * <p>
+   * When this method returns this schema builder is empty and contains
+   * a default set of compatibility options.
+   *
+   * @return A {@code Schema} containing all of the schema elements
+   *         contained in this schema builder as well as the same set of
+   *         schema compatibility options
+   */
+  public Schema toSchema()
+  {
+    return toSchema(null);
+  }
+
+
+
+  /**
+   * Returns a {@code Schema} containing all of the schema elements
+   * contained in this schema builder as well as the same set of schema
+   * compatibility options.
+   * <p>
+   * When this method returns this schema builder is empty and contains
+   * a default set of compatibility options.
+   *
+   * @param errorMessages
+   *          A list into which any errors that were detected while
+   *          validating the schema will be placed, may be {@code null}
+   *          in which case any errors will be ignored.
+   * @return A {@code Schema} containing all of the schema elements
+   *         contained in this schema builder as well as the same set of
+   *         schema compatibility options
+   */
+  public Schema toSchema(List<Message> errorMessages)
+  {
+    if (errorMessages == null)
+    {
+      errorMessages = new LinkedList<Message>();
+    }
+
+    validate(errorMessages);
+    final Schema builtSchema = schema;
+    initBuilder();
+    return builtSchema;
+  }
+
+
+
+  private synchronized void addAttributeType(AttributeType attribute,
+      boolean overwrite) throws ConflictingSchemaElementException
+  {
+    AttributeType conflictingAttribute;
+    if (numericOID2AttributeTypes.containsKey(attribute.getOID()))
+    {
+      conflictingAttribute =
+          numericOID2AttributeTypes.get(attribute.getOID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_ATTRIBUTE_OID.get(attribute
+                .getNameOrOID(), attribute.getOID(),
+                conflictingAttribute.getNameOrOID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeAttributeType(conflictingAttribute);
+    }
+
+    numericOID2AttributeTypes.put(attribute.getOID(), attribute);
+    for (final String name : attribute.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      List<AttributeType> attrs;
+      if ((attrs = name2AttributeTypes.get(lowerName)) == null)
+      {
+        name2AttributeTypes.put(lowerName, Collections
+            .singletonList(attribute));
+      }
+      else if (attrs.size() == 1)
+      {
+        attrs = new ArrayList<AttributeType>(attrs);
+        attrs.add(attribute);
+        name2AttributeTypes.put(lowerName, attrs);
+      }
+      else
+      {
+        attrs.add(attribute);
+      }
+    }
+  }
+
+
+
+  private synchronized void addDITContentRule(DITContentRule rule,
+      boolean overwrite) throws ConflictingSchemaElementException
+  {
+    DITContentRule conflictingRule;
+    if (numericOID2ContentRules.containsKey(rule
+        .getStructuralClassOID()))
+    {
+      conflictingRule =
+          numericOID2ContentRules.get(rule.getStructuralClassOID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_DIT_CONTENT_RULE.get(rule
+                .getNameOrOID(), rule.getStructuralClassOID(),
+                conflictingRule.getNameOrOID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeDITContentRule(conflictingRule);
+    }
+
+    numericOID2ContentRules.put(rule.getStructuralClassOID(), rule);
+    for (final String name : rule.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      List<DITContentRule> rules;
+      if ((rules = name2ContentRules.get(lowerName)) == null)
+      {
+        name2ContentRules.put(lowerName, Collections
+            .singletonList(rule));
+      }
+      else if (rules.size() == 1)
+      {
+        rules = new ArrayList<DITContentRule>(rules);
+        rules.add(rule);
+        name2ContentRules.put(lowerName, rules);
+      }
+      else
+      {
+        rules.add(rule);
+      }
+    }
+  }
+
+
+
+  private synchronized void addDITStructureRule(DITStructureRule rule,
+      boolean overwrite) throws ConflictingSchemaElementException
+  {
+    DITStructureRule conflictingRule;
+    if (id2StructureRules.containsKey(rule.getRuleID()))
+    {
+      conflictingRule = id2StructureRules.get(rule.getRuleID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_DIT_STRUCTURE_RULE_ID.get(rule
+                .getNameOrRuleID(), rule.getRuleID(), conflictingRule
+                .getNameOrRuleID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeDITStructureRule(conflictingRule);
+    }
+
+    id2StructureRules.put(rule.getRuleID(), rule);
+    for (final String name : rule.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      List<DITStructureRule> rules;
+      if ((rules = name2StructureRules.get(lowerName)) == null)
+      {
+        name2StructureRules.put(lowerName, Collections
+            .singletonList(rule));
+      }
+      else if (rules.size() == 1)
+      {
+        rules = new ArrayList<DITStructureRule>(rules);
+        rules.add(rule);
+        name2StructureRules.put(lowerName, rules);
+      }
+      else
+      {
+        rules.add(rule);
+      }
+    }
+  }
+
+
+
+  private synchronized void addMatchingRule(MatchingRule rule,
+      boolean overwrite) throws ConflictingSchemaElementException
+  {
+    MatchingRule conflictingRule;
+    if (numericOID2MatchingRules.containsKey(rule.getOID()))
+    {
+      conflictingRule = numericOID2MatchingRules.get(rule.getOID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_MR_OID.get(rule.getNameOrOID(), rule
+                .getOID(), conflictingRule.getNameOrOID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeMatchingRule(conflictingRule);
+    }
+
+    numericOID2MatchingRules.put(rule.getOID(), rule);
+    for (final String name : rule.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      List<MatchingRule> rules;
+      if ((rules = name2MatchingRules.get(lowerName)) == null)
+      {
+        name2MatchingRules.put(lowerName, Collections
+            .singletonList(rule));
+      }
+      else if (rules.size() == 1)
+      {
+        rules = new ArrayList<MatchingRule>(rules);
+        rules.add(rule);
+        name2MatchingRules.put(lowerName, rules);
+      }
+      else
+      {
+        rules.add(rule);
+      }
+    }
+  }
+
+
+
+  private synchronized void addMatchingRuleUse(MatchingRuleUse use,
+      boolean overwrite) throws ConflictingSchemaElementException
+  {
+    MatchingRuleUse conflictingUse;
+    if (numericOID2MatchingRuleUses.containsKey(use
+        .getMatchingRuleOID()))
+    {
+      conflictingUse =
+          numericOID2MatchingRuleUses.get(use.getMatchingRuleOID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_MATCHING_RULE_USE.get(use
+                .getNameOrOID(), use.getMatchingRuleOID(),
+                conflictingUse.getNameOrOID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeMatchingRuleUse(conflictingUse);
+    }
+
+    numericOID2MatchingRuleUses.put(use.getMatchingRuleOID(), use);
+    for (final String name : use.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      List<MatchingRuleUse> uses;
+      if ((uses = name2MatchingRuleUses.get(lowerName)) == null)
+      {
+        name2MatchingRuleUses.put(lowerName, Collections
+            .singletonList(use));
+      }
+      else if (uses.size() == 1)
+      {
+        uses = new ArrayList<MatchingRuleUse>(uses);
+        uses.add(use);
+        name2MatchingRuleUses.put(lowerName, uses);
+      }
+      else
+      {
+        uses.add(use);
+      }
+    }
+  }
+
+
+
+  private synchronized void addNameForm(NameForm form, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    NameForm conflictingForm;
+    if (numericOID2NameForms.containsKey(form.getOID()))
+    {
+      conflictingForm = numericOID2NameForms.get(form.getOID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_NAME_FORM_OID.get(form
+                .getNameOrOID(), form.getOID(), conflictingForm
+                .getNameOrOID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeNameForm(conflictingForm);
+    }
+
+    numericOID2NameForms.put(form.getOID(), form);
+    for (final String name : form.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      List<NameForm> forms;
+      if ((forms = name2NameForms.get(lowerName)) == null)
+      {
+        name2NameForms.put(lowerName, Collections.singletonList(form));
+      }
+      else if (forms.size() == 1)
+      {
+        forms = new ArrayList<NameForm>(forms);
+        forms.add(form);
+        name2NameForms.put(lowerName, forms);
+      }
+      else
+      {
+        forms.add(form);
+      }
+    }
+  }
+
+
+
+  private synchronized void addObjectClass(ObjectClass oc,
+      boolean overwrite) throws ConflictingSchemaElementException
+  {
+    ObjectClass conflictingOC;
+    if (numericOID2ObjectClasses.containsKey(oc.getOID()))
+    {
+      conflictingOC = numericOID2ObjectClasses.get(oc.getOID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_OBJECTCLASS_OID.get(oc
+                .getNameOrOID(), oc.getOID(), conflictingOC
+                .getNameOrOID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeObjectClass(conflictingOC);
+    }
+
+    numericOID2ObjectClasses.put(oc.getOID(), oc);
+    for (final String name : oc.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      List<ObjectClass> classes;
+      if ((classes = name2ObjectClasses.get(lowerName)) == null)
+      {
+        name2ObjectClasses
+            .put(lowerName, Collections.singletonList(oc));
+      }
+      else if (classes.size() == 1)
+      {
+        classes = new ArrayList<ObjectClass>(classes);
+        classes.add(oc);
+        name2ObjectClasses.put(lowerName, classes);
+      }
+      else
+      {
+        classes.add(oc);
+      }
+    }
+  }
+
+
+
+  private synchronized void addSyntax(Syntax syntax, boolean overwrite)
+      throws ConflictingSchemaElementException
+  {
+    Syntax conflictingSyntax;
+    if (numericOID2Syntaxes.containsKey(syntax.getOID()))
+    {
+      conflictingSyntax = numericOID2Syntaxes.get(syntax.getOID());
+      if (!overwrite)
+      {
+        final Message message =
+            ERR_SCHEMA_CONFLICTING_SYNTAX_OID.get(syntax.toString(),
+                syntax.getOID(), conflictingSyntax.getOID());
+        throw new ConflictingSchemaElementException(message);
+      }
+      removeSyntax(conflictingSyntax);
+    }
+    numericOID2Syntaxes.put(syntax.getOID(), syntax);
+  }
+
+
+
+  private void initBuilder()
+  {
+    numericOID2Syntaxes = new HashMap<String, Syntax>();
+    numericOID2MatchingRules = new HashMap<String, MatchingRule>();
+    numericOID2MatchingRuleUses =
+        new HashMap<String, MatchingRuleUse>();
+    numericOID2AttributeTypes = new HashMap<String, AttributeType>();
+    numericOID2ObjectClasses = new HashMap<String, ObjectClass>();
+    numericOID2NameForms = new HashMap<String, NameForm>();
+    numericOID2ContentRules = new HashMap<String, DITContentRule>();
+    id2StructureRules = new HashMap<Integer, DITStructureRule>();
+
+    name2MatchingRules = new HashMap<String, List<MatchingRule>>();
+    name2MatchingRuleUses =
+        new HashMap<String, List<MatchingRuleUse>>();
+    name2AttributeTypes = new HashMap<String, List<AttributeType>>();
+    name2ObjectClasses = new HashMap<String, List<ObjectClass>>();
+    name2NameForms = new HashMap<String, List<NameForm>>();
+    name2ContentRules = new HashMap<String, List<DITContentRule>>();
+    name2StructureRules = new HashMap<String, List<DITStructureRule>>();
+
+    objectClass2NameForms = new HashMap<String, List<NameForm>>();
+    nameForm2StructureRules =
+        new HashMap<String, List<DITStructureRule>>();
+    options = SchemaCompatOptions.defaultOptions();
+    schema =
+        new Schema(numericOID2Syntaxes, numericOID2MatchingRules,
+            numericOID2MatchingRuleUses, numericOID2AttributeTypes,
+            numericOID2ObjectClasses, numericOID2NameForms,
+            numericOID2ContentRules, id2StructureRules,
+            name2MatchingRules, name2MatchingRuleUses,
+            name2AttributeTypes, name2ObjectClasses, name2NameForms,
+            name2ContentRules, name2StructureRules,
+            objectClass2NameForms, nameForm2StructureRules, options);
+  }
+
+
+
+  private synchronized void removeAttributeType(
+      AttributeType attributeType)
+  {
+    numericOID2AttributeTypes.remove(attributeType.getOID());
+    for (final String name : attributeType.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      final List<AttributeType> attributes =
+          name2AttributeTypes.get(lowerName);
+      if (attributes != null && attributes.contains(attributeType))
+      {
+        if (attributes.size() <= 1)
+        {
+          name2AttributeTypes.remove(lowerName);
+        }
+        else
+        {
+          attributes.remove(attributeType);
+        }
+      }
+    }
+  }
+
+
+
+  private synchronized void removeDITContentRule(DITContentRule rule)
+  {
+    numericOID2ContentRules.remove(rule.getStructuralClassOID());
+    for (final String name : rule.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      final List<DITContentRule> rules =
+          name2ContentRules.get(lowerName);
+      if (rules != null && rules.contains(rule))
+      {
+        if (rules.size() <= 1)
+        {
+          name2AttributeTypes.remove(lowerName);
+        }
+        else
+        {
+          rules.remove(rule);
+        }
+      }
+    }
+  }
+
+
+
+  private synchronized void removeDITStructureRule(DITStructureRule rule)
+  {
+    id2StructureRules.remove(rule.getRuleID());
+    for (final String name : rule.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      final List<DITStructureRule> rules =
+          name2StructureRules.get(lowerName);
+      if (rules != null && rules.contains(rule))
+      {
+        if (rules.size() <= 1)
+        {
+          name2StructureRules.remove(lowerName);
+        }
+        else
+        {
+          rules.remove(rule);
+        }
+      }
+    }
+  }
+
+
+
+  private synchronized void removeMatchingRule(MatchingRule rule)
+  {
+    numericOID2MatchingRules.remove(rule.getOID());
+    for (final String name : rule.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      final List<MatchingRule> rules =
+          name2MatchingRules.get(lowerName);
+      if (rules != null && rules.contains(rule))
+      {
+        if (rules.size() <= 1)
+        {
+          name2MatchingRules.remove(lowerName);
+        }
+        else
+        {
+          rules.remove(rule);
+        }
+      }
+    }
+  }
+
+
+
+  private synchronized void removeMatchingRuleUse(MatchingRuleUse use)
+  {
+    numericOID2MatchingRuleUses.remove(use.getMatchingRuleOID());
+    for (final String name : use.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      final List<MatchingRuleUse> uses =
+          name2MatchingRuleUses.get(lowerName);
+      if (uses != null && uses.contains(use))
+      {
+        if (uses.size() <= 1)
+        {
+          name2MatchingRuleUses.remove(lowerName);
+        }
+        else
+        {
+          uses.remove(use);
+        }
+      }
+    }
+  }
+
+
+
+  private synchronized void removeNameForm(NameForm form)
+  {
+    numericOID2NameForms.remove(form.getOID());
+    name2NameForms.remove(form.getOID());
+    for (final String name : form.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      final List<NameForm> forms = name2NameForms.get(lowerName);
+      if (forms != null && forms.contains(form))
+      {
+        if (forms.size() <= 1)
+        {
+          name2NameForms.remove(lowerName);
+        }
+        else
+        {
+          forms.remove(form);
+        }
+      }
+    }
+  }
+
+
+
+  private synchronized void removeObjectClass(ObjectClass oc)
+  {
+    numericOID2ObjectClasses.remove(oc.getOID());
+    name2ObjectClasses.remove(oc.getOID());
+    for (final String name : oc.getNames())
+    {
+      final String lowerName = StaticUtils.toLowerCase(name);
+      final List<ObjectClass> classes =
+          name2ObjectClasses.get(lowerName);
+      if (classes != null && classes.contains(oc))
+      {
+        if (classes.size() <= 1)
+        {
+          name2ObjectClasses.remove(lowerName);
+        }
+        else
+        {
+          classes.remove(oc);
+        }
+      }
+    }
+  }
+
+
+
+  private synchronized void removeSyntax(Syntax syntax)
+  {
+    numericOID2Syntaxes.remove(syntax.getOID());
+  }
+
+
+
+  private synchronized void validate(List<Message> warnings)
+  {
+    // Verify all references in all elements
+    for (final Syntax syntax : numericOID2Syntaxes.values().toArray(
+        new Syntax[numericOID2Syntaxes.values().size()]))
+    {
+      try
+      {
+        syntax.validate(warnings, schema);
+      }
+      catch (final SchemaException e)
+      {
+        removeSyntax(syntax);
+        warnings.add(ERR_SYNTAX_VALIDATION_FAIL.get(syntax.toString(),
+            e.toString()));
+      }
+    }
+
+    for (final MatchingRule rule : numericOID2MatchingRules.values()
+        .toArray(
+            new MatchingRule[numericOID2MatchingRules.values().size()]))
+    {
+      try
+      {
+        rule.validate(warnings, schema);
+      }
+      catch (final SchemaException e)
+      {
+        removeMatchingRule(rule);
+        warnings.add(ERR_MR_VALIDATION_FAIL.get(rule.toString(), e
+            .toString()));
+      }
+    }
+
+    for (final AttributeType attribute : numericOID2AttributeTypes
+        .values()
+        .toArray(
+            new AttributeType[numericOID2AttributeTypes.values().size()]))
+    {
+      try
+      {
+        attribute.validate(warnings, schema);
+      }
+      catch (final SchemaException e)
+      {
+        removeAttributeType(attribute);
+        warnings.add(ERR_ATTR_TYPE_VALIDATION_FAIL.get(attribute
+            .toString(), e.toString()));
+      }
+    }
+
+    for (final ObjectClass oc : numericOID2ObjectClasses.values()
+        .toArray(
+            new ObjectClass[numericOID2ObjectClasses.values().size()]))
+    {
+      try
+      {
+        oc.validate(warnings, schema);
+      }
+      catch (final SchemaException e)
+      {
+        removeObjectClass(oc);
+        warnings.add(ERR_OC_VALIDATION_FAIL.get(oc.toString(), e
+            .toString()));
+      }
+    }
+
+    for (final MatchingRuleUse use : numericOID2MatchingRuleUses
+        .values().toArray(
+            new MatchingRuleUse[numericOID2MatchingRuleUses.values()
+                .size()]))
+    {
+      try
+      {
+        use.validate(warnings, schema);
+      }
+      catch (final SchemaException e)
+      {
+        removeMatchingRuleUse(use);
+        warnings.add(ERR_MRU_VALIDATION_FAIL.get(use.toString(), e
+            .toString()));
+      }
+    }
+
+    for (final NameForm form : numericOID2NameForms.values().toArray(
+        new NameForm[numericOID2NameForms.values().size()]))
+    {
+      try
+      {
+        form.validate(warnings, schema);
+
+        // build the objectClass2NameForms map
+        List<NameForm> forms;
+        final String ocOID = form.getStructuralClass().getOID();
+        if ((forms = objectClass2NameForms.get(ocOID)) == null)
+        {
+          objectClass2NameForms.put(ocOID, Collections
+              .singletonList(form));
+        }
+        else if (forms.size() == 1)
+        {
+          forms = new ArrayList<NameForm>(forms);
+          forms.add(form);
+          objectClass2NameForms.put(ocOID, forms);
+        }
+        else
+        {
+          forms.add(form);
+        }
+      }
+      catch (final SchemaException e)
+      {
+        removeNameForm(form);
+        warnings.add(ERR_NAMEFORM_VALIDATION_FAIL.get(form.toString(),
+            e.toString()));
+      }
+    }
+
+    for (final DITContentRule rule : numericOID2ContentRules
+        .values()
+        .toArray(
+            new DITContentRule[numericOID2ContentRules.values().size()]))
+    {
+      try
+      {
+        rule.validate(warnings, schema);
+      }
+      catch (final SchemaException e)
+      {
+        removeDITContentRule(rule);
+        warnings.add(ERR_DCR_VALIDATION_FAIL.get(rule.toString(), e
+            .toString()));
+      }
+    }
+
+    for (final DITStructureRule rule : id2StructureRules.values()
+        .toArray(
+            new DITStructureRule[id2StructureRules.values().size()]))
+    {
+      try
+      {
+        rule.validate(warnings, schema);
+
+        // build the nameForm2StructureRules map
+        List<DITStructureRule> rules;
+        final String ocOID = rule.getNameForm().getOID();
+        if ((rules = nameForm2StructureRules.get(ocOID)) == null)
+        {
+          nameForm2StructureRules.put(ocOID, Collections
+              .singletonList(rule));
+        }
+        else if (rules.size() == 1)
+        {
+          rules = new ArrayList<DITStructureRule>(rules);
+          rules.add(rule);
+          nameForm2StructureRules.put(ocOID, rules);
+        }
+        else
+        {
+          rules.add(rule);
+        }
+      }
+      catch (final SchemaException e)
+      {
+        removeDITStructureRule(rule);
+        warnings.add(ERR_DSR_VALIDATION_FAIL.get(rule.toString(), e
+            .toString()));
+      }
+    }
+
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java b/sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java
new file mode 100644
index 0000000..e4bfc0e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaCompatOptions.java
@@ -0,0 +1,164 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This class provides various schema compatibility options which may be
+ * used to facilitate interoperability with legacy LDAP applications.
+ */
+public final class SchemaCompatOptions
+{
+  /**
+   * Creates a copy of the provided schema compatibility options.
+   * 
+   * @param options
+   *          The options to be copied.
+   * @return The copy of the provided schema compatibility options.
+   */
+  public static SchemaCompatOptions copyOf(SchemaCompatOptions options)
+  {
+    return defaultOptions().assign(options);
+  }
+
+
+
+  /**
+   * Creates a new set of schema compatibility options with default
+   * settings.
+   * 
+   * @return The new schema compatibility options.
+   */
+  public static SchemaCompatOptions defaultOptions()
+  {
+    return new SchemaCompatOptions();
+  }
+
+  private boolean isTelephoneNumberSyntaxStrict = false;
+
+  private boolean isZeroLengthDirectoryStringsAllowed = false;
+
+
+
+  // Prevent direct instantiation.
+  private SchemaCompatOptions()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * Indicates whether or not the Telephone Number syntax should ensure
+   * that all values conform to the E.123 international telephone number
+   * format. By default this compatibility option is set to {@code
+   * false}.
+   * 
+   * @return {@code true} if the Telephone Number syntax should ensure
+   *         that all values conform to the E.123 international
+   *         telephone number format, or {@code false} if not.
+   */
+  public boolean isTelephoneNumberSyntaxStrict()
+  {
+    return isTelephoneNumberSyntaxStrict;
+  }
+
+
+
+  /**
+   * Indicates whether or not zero-length values will be allowed by the
+   * Directory String syntax. This is technically forbidden by the LDAP
+   * specification, but it was allowed in earlier versions of the
+   * server, and the discussion of the directory string syntax in RFC
+   * 2252 does not explicitly state that they are not allowed. By
+   * default this compatibility option is set to {@code false}.
+   * 
+   * @return {@code true} if zero-length values will be allowed by the
+   *         Directory String syntax, or {@code false} if not.
+   */
+  public boolean isZeroLengthDirectoryStringsAllowed()
+  {
+    return isZeroLengthDirectoryStringsAllowed;
+  }
+
+
+
+  /**
+   * Indicates whether or not the Telephone Number syntax should ensure
+   * that all values conform to the E.123 international telephone number
+   * format. By default this compatibility option is set to {@code
+   * false}.
+   * 
+   * @param isStrict
+   *          {@code true} if the Telephone Number syntax should ensure
+   *          that all values conform to the E.123 international
+   *          telephone number format, or {@code false} if not.
+   * @return A reference to this {@code SchemaCompat}.
+   */
+  public SchemaCompatOptions setTelephoneNumberSyntaxStrict(
+      boolean isStrict)
+  {
+    this.isTelephoneNumberSyntaxStrict = isStrict;
+    return this;
+  }
+
+
+
+  /**
+   * Specifies whether or not zero-length values will be allowed by the
+   * Directory String syntax. This is technically forbidden by the LDAP
+   * specification, but it was allowed in earlier versions of the
+   * server, and the discussion of the directory string syntax in RFC
+   * 2252 does not explicitly state that they are not allowed. By
+   * default this compatibility option is set to {@code false}.
+   * 
+   * @param isAllowed
+   *          {@code true} if zero-length values will be allowed by the
+   *          Directory String syntax, or {@code false} if not.
+   * @return A reference to this {@code SchemaCompat}.
+   */
+  public SchemaCompatOptions setZeroLengthDirectoryStringsAllowed(
+      boolean isAllowed)
+  {
+    this.isZeroLengthDirectoryStringsAllowed = isAllowed;
+    return this;
+  }
+
+
+
+  // Assigns the provided options to this set of options.
+  SchemaCompatOptions assign(SchemaCompatOptions options)
+  {
+    return setTelephoneNumberSyntaxStrict(
+        options.isTelephoneNumberSyntaxStrict)
+        .setZeroLengthDirectoryStringsAllowed(
+            options.isZeroLengthDirectoryStringsAllowed);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaConstants.java b/sdk/src/org/opends/sdk/schema/SchemaConstants.java
new file mode 100644
index 0000000..e97549b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaConstants.java
@@ -0,0 +1,1623 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines a number of constants used by Directory Server
+ * schema elements, like matching rules, syntaxes, attribute types, and
+ * objectclasses.
+ */
+class SchemaConstants
+{
+  /**
+   * The IANA-assigned base OID for all things under the OpenDS
+   * umbrella.
+   */
+  public static final String OID_OPENDS_BASE = "1.3.6.1.4.1.26027";
+
+  /**
+   * The base OID that will be used for the OpenDS Directory Server
+   * project.
+   */
+  public static final String OID_OPENDS_SERVER_BASE =
+      OID_OPENDS_BASE + ".1";
+
+  /**
+   * The base OID that will be used for OpenDS Directory Server
+   * attribute type definitions.
+   */
+  public static final String OID_OPENDS_SERVER_ATTRIBUTE_TYPE_BASE =
+      OID_OPENDS_SERVER_BASE + ".1";
+
+  /**
+   * The base OID that will be used for OpenDS Directory Server object
+   * class definitions.
+   */
+  public static final String OID_OPENDS_SERVER_OBJECT_CLASS_BASE =
+      OID_OPENDS_SERVER_BASE + ".2";
+
+  /**
+   * The base OID that will be used for OpenDS Directory Server
+   * attribute syntax definitions.
+   */
+  public static final String OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE =
+      OID_OPENDS_SERVER_BASE + ".3";
+
+  /**
+   * The base OID that will be used for OpenDS Directory Server matching
+   * rule definitions.
+   */
+  public static final String OID_OPENDS_SERVER_MATCHING_RULE_BASE =
+      OID_OPENDS_SERVER_BASE + ".4";
+
+  /**
+   * The base OID that will be used for OpenDS Directory Server control
+   * definitions.
+   */
+  public static final String OID_OPENDS_SERVER_CONTROL_BASE =
+      OID_OPENDS_SERVER_BASE + ".5";
+
+  /**
+   * The base OID that will be used for OpenDS Directory Server extended
+   * operation definitions.
+   */
+  public static final String OID_OPENDS_SERVER_EXTENDED_OPERATION_BASE =
+      OID_OPENDS_SERVER_BASE + ".6";
+
+  /**
+   * The base OID that will be used for general-purpose (i.e., "other")
+   * types of OIDs that need to be allocated for the OpenDS Directory
+   * Server.
+   */
+  public static final String OID_OPENDS_SERVER_GENERAL_USE_BASE =
+      OID_OPENDS_SERVER_BASE + ".9";
+
+  /**
+   * The base OID that will be used for temporary or experimental OIDs
+   * within the OpenDS Directory Server.
+   */
+  public static final String OID_OPENDS_SERVER_EXPERIMENTAL_BASE =
+      OID_OPENDS_SERVER_BASE + ".999";
+
+  /**
+   * The description for the doubleMetaphoneApproximateMatch approximate
+   * matching rule.
+   */
+  public static final String AMR_DOUBLE_METAPHONE_DESCRIPTION =
+      "Double Metaphone Approximate Match";
+
+  /**
+   * The name for the doubleMetaphoneApproximateMatch approximate
+   * matching rule.
+   */
+  public static final String AMR_DOUBLE_METAPHONE_NAME =
+      "ds-mr-double-metaphone-approx";
+
+  /**
+   * The OID for the doubleMetaphoneApproximateMatch approximate
+   * matching rule.
+   */
+  public static final String AMR_DOUBLE_METAPHONE_OID =
+      OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".1";
+
+  /**
+   * The description for the authPasswordExactMatch matching rule.
+   */
+  public static final String EMR_AUTH_PASSWORD_EXACT_DESCRIPTION =
+      "authentication password exact matching rule";
+
+  /**
+   * The name for the authPasswordExactMatch equality matching rule.
+   */
+  public static final String EMR_AUTH_PASSWORD_EXACT_NAME =
+      "authPasswordExactMatch";
+
+  /**
+   * The OID for the authPasswordExactMatch equality matching rule.
+   */
+  public static final String EMR_AUTH_PASSWORD_EXACT_OID =
+      "1.3.6.1.4.1.4203.1.2.2";
+
+  /**
+   * The description for the authPasswordMatch matching rule.
+   */
+  public static final String EMR_AUTH_PASSWORD_DESCRIPTION =
+      "authentication password matching rule";
+
+  /**
+   * The name for the authPasswordMatch equality matching rule.
+   */
+  public static final String EMR_AUTH_PASSWORD_NAME =
+      "authPasswordMatch";
+
+  /**
+   * The OID for the authPasswordMatch equality matching rule.
+   */
+  public static final String EMR_AUTH_PASSWORD_OID =
+      "1.3.6.1.4.1.4203.1.2.3";
+
+  /**
+   * The name for the bitStringMatch equality matching rule.
+   */
+  public static final String EMR_BIT_STRING_NAME = "bitStringMatch";
+
+  /**
+   * The OID for the bitStringMatch equality matching rule.
+   */
+  public static final String EMR_BIT_STRING_OID = "2.5.13.16";
+
+  /**
+   * The name for the booleanMatch equality matching rule.
+   */
+  public static final String EMR_BOOLEAN_NAME = "booleanMatch";
+
+  /**
+   * The OID for the booleanMatch equality matching rule.
+   */
+  public static final String EMR_BOOLEAN_OID = "2.5.13.13";
+
+  /**
+   * The name for the caseExactMatch equality matching rule.
+   */
+  public static final String EMR_CASE_EXACT_NAME = "caseExactMatch";
+
+  /**
+   * The OID for the caseExactMatch equality matching rule.
+   */
+  public static final String EMR_CASE_EXACT_OID = "2.5.13.5";
+
+  /**
+   * The name for the caseExactIA5Match equality matching rule.
+   */
+  public static final String EMR_CASE_EXACT_IA5_NAME =
+      "caseExactIA5Match";
+
+  /**
+   * The OID for the caseExactIA5Match equality matching rule.
+   */
+  public static final String EMR_CASE_EXACT_IA5_OID =
+      "1.3.6.1.4.1.1466.109.114.1";
+
+  /**
+   * The name for the caseIgnoreMatch equality matching rule.
+   */
+  public static final String EMR_CASE_IGNORE_NAME = "caseIgnoreMatch";
+
+  /**
+   * The OID for the caseIgnoreMatch equality matching rule.
+   */
+  public static final String EMR_CASE_IGNORE_OID = "2.5.13.2";
+
+  /**
+   * The name for the caseIgnoreIA5Match equality matching rule.
+   */
+  public static final String EMR_CASE_IGNORE_IA5_NAME =
+      "caseIgnoreIA5Match";
+
+  /**
+   * The OID for the caseIgnoreIA5Match equality matching rule.
+   */
+  public static final String EMR_CASE_IGNORE_IA5_OID =
+      "1.3.6.1.4.1.1466.109.114.2";
+
+  /**
+   * The name for the caseIgnoreListMatch equality matching rule.
+   */
+  public static final String EMR_CASE_IGNORE_LIST_NAME =
+      "caseIgnoreListMatch";
+
+  /**
+   * The OID for the caseIgnoreListMatch equality matching rule.
+   */
+  public static final String EMR_CASE_IGNORE_LIST_OID = "2.5.13.11";
+
+  /**
+   * The name for the directoryStringFirstComponentMatch equality
+   * matching rule.
+   */
+  public static final String EMR_DIRECTORY_STRING_FIRST_COMPONENT_NAME =
+      "directoryStringFirstComponentMatch";
+
+  /**
+   * The OID for the directoryStringFirstComponentMatch equality
+   * matching rule.
+   */
+  public static final String EMR_DIRECTORY_STRING_FIRST_COMPONENT_OID =
+      "2.5.13.31";
+
+  /**
+   * The name for the distinguishedNameMatch equality matching rule.
+   */
+  public static final String EMR_DN_NAME = "distinguishedNameMatch";
+
+  /**
+   * The OID for the distinguishedNameMatch equality matching rule.
+   */
+  public static final String EMR_DN_OID = "2.5.13.1";
+
+  /**
+   * The name for the generalizedTimeMatch equality matching rule.
+   */
+  public static final String EMR_GENERALIZED_TIME_NAME =
+      "generalizedTimeMatch";
+
+  /**
+   * The OID for the generalizedTimeMatch equality matching rule.
+   */
+  public static final String EMR_GENERALIZED_TIME_OID = "2.5.13.27";
+
+  /**
+   * The name for the integerMatch equality matching rule.
+   */
+  public static final String EMR_INTEGER_NAME = "integerMatch";
+
+  /**
+   * The OID for the integerMatch equality matching rule.
+   */
+  public static final String EMR_INTEGER_OID = "2.5.13.14";
+
+  /**
+   * The name for the integerFirstComponentMatch equality matching rule.
+   */
+  public static final String EMR_INTEGER_FIRST_COMPONENT_NAME =
+      "integerFirstComponentMatch";
+
+  /**
+   * The OID for the integerFirstComponentMatch equality matching rule.
+   */
+  public static final String EMR_INTEGER_FIRST_COMPONENT_OID =
+      "2.5.13.29";
+
+  /**
+   * The name for the keywordMatch equality matching rule.
+   */
+  public static final String EMR_KEYWORD_NAME = "keywordMatch";
+
+  /**
+   * The OID for the keywordMatch equality matching rule.
+   */
+  public static final String EMR_KEYWORD_OID = "2.5.13.33";
+
+  /**
+   * The name for the numericStringMatch equality matching rule.
+   */
+  public static final String EMR_NUMERIC_STRING_NAME =
+      "numericStringMatch";
+
+  /**
+   * The OID for the numericStringMatch equality matching rule.
+   */
+  public static final String EMR_NUMERIC_STRING_OID = "2.5.13.8";
+
+  /**
+   * The name for the octetStringMatch equality matching rule.
+   */
+  public static final String EMR_OCTET_STRING_NAME = "octetStringMatch";
+
+  /**
+   * The OID for the octetStringMatch equality matching rule.
+   */
+  public static final String EMR_OCTET_STRING_OID = "2.5.13.17";
+
+  /**
+   * The name for the objectIdentifierMatch equality matching rule.
+   */
+  public static final String EMR_OID_NAME = "objectIdentifierMatch";
+
+  /**
+   * The OID for the objectIdentifierMatch equality matching rule.
+   */
+  public static final String EMR_OID_OID = "2.5.13.0";
+
+  /**
+   * The name for the objectIdentifierFirstComponentMatch equality
+   * matching rule.
+   */
+  public static final String EMR_OID_FIRST_COMPONENT_NAME =
+      "objectIdentifierFirstComponentMatch";
+
+  /**
+   * The OID for the objectIdentifierFirstComponentMatch equality
+   * matching rule.
+   */
+  public static final String EMR_OID_FIRST_COMPONENT_OID = "2.5.13.30";
+
+  /**
+   * The name for the presentationAddressMatch equality matching rule.
+   */
+  public static final String EMR_PRESENTATION_ADDRESS_NAME =
+      "presentationAddressMatch";
+
+  /**
+   * The OID for the presentationAddressMatch equality matching rule.
+   */
+  public static final String EMR_PRESENTATION_ADDRESS_OID = "2.5.13.22";
+
+  /**
+   * The name for the protocolInformationMatch equality matching rule.
+   */
+  public static final String EMR_PROTOCOL_INFORMATION_NAME =
+      "protocolInformationMatch";
+
+  /**
+   * The OID for the protocolInformationMatch equality matching rule.
+   */
+  public static final String EMR_PROTOCOL_INFORMATION_OID = "2.5.13.24";
+
+  /**
+   * The name for the telephoneNumberMatch equality matching rule.
+   */
+  public static final String EMR_TELEPHONE_NAME =
+      "telephoneNumberMatch";
+
+  /**
+   * The OID for the telephoneNumberMatch equality matching rule.
+   */
+  public static final String EMR_TELEPHONE_OID = "2.5.13.20";
+
+  /**
+   * The name for the uniqueMemberMatch equality matching rule.
+   */
+  public static final String EMR_UNIQUE_MEMBER_NAME =
+      "uniqueMemberMatch";
+
+  /**
+   * The OID for the uniqueMemberMatch equality matching rule.
+   */
+  public static final String EMR_UNIQUE_MEMBER_OID = "2.5.13.23";
+
+  /**
+   * The description for the userPasswordExactMatch matching rule.
+   */
+  public static final String EMR_USER_PASSWORD_EXACT_DESCRIPTION =
+      "user password exact matching rule";
+
+  /**
+   * The name for the userPasswordExactMatch equality matching rule.
+   */
+  public static final String EMR_USER_PASSWORD_EXACT_NAME =
+      "ds-mr-user-password-exact";
+
+  /**
+   * The OID for the userPasswordExactMatch equality matching rule.
+   */
+  public static final String EMR_USER_PASSWORD_EXACT_OID =
+      OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".2";
+
+  /**
+   * The description for the userPasswordMatch matching rule.
+   */
+  public static final String EMR_USER_PASSWORD_DESCRIPTION =
+      "user password matching rule";
+
+  /**
+   * The name for the userPasswordMatch equality matching rule.
+   */
+  public static final String EMR_USER_PASSWORD_NAME =
+      "ds-mr-user-password-equality";
+
+  /**
+   * The OID for the userPasswordMatch equality matching rule.
+   */
+  public static final String EMR_USER_PASSWORD_OID =
+      OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".3";
+
+  /**
+   * The name for the uuidMatch equality matching rule.
+   */
+  public static final String EMR_UUID_NAME = "uuidMatch";
+
+  /**
+   * The OID for the uuidMatch equality matching rule.
+   */
+  public static final String EMR_UUID_OID = "1.3.6.1.1.16.2";
+
+  /**
+   * The name for the wordMatch equality matching rule.
+   */
+  public static final String EMR_WORD_NAME = "wordMatch";
+
+  /**
+   * The OID for the wordMatch equality matching rule.
+   */
+  public static final String EMR_WORD_OID = "2.5.13.32";
+
+  /**
+   * The name for the caseExactOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_CASE_EXACT_NAME =
+      "caseExactOrderingMatch";
+
+  /**
+   * The OID for the caseExactOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_CASE_EXACT_OID = "2.5.13.6";
+
+  /**
+   * The name for the caseIgnoreOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_CASE_IGNORE_NAME =
+      "caseIgnoreOrderingMatch";
+
+  /**
+   * The OID for the caseIgnoreOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_CASE_IGNORE_OID = "2.5.13.3";
+
+  /**
+   * The name for the generalizedTimeOrderingMatch ordering matching
+   * rule.
+   */
+  public static final String OMR_GENERALIZED_TIME_NAME =
+      "generalizedTimeOrderingMatch";
+
+  /**
+   * The OID for the generalizedTimeOrderingMatch ordering matching
+   * rule.
+   */
+  public static final String OMR_GENERALIZED_TIME_OID = "2.5.13.28";
+
+  /**
+   * The name for the integerOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_INTEGER_NAME = "integerOrderingMatch";
+
+  /**
+   * The OID for the integerOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_INTEGER_OID = "2.5.13.15";
+
+  /**
+   * The name for the numericStringOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_NUMERIC_STRING_NAME =
+      "numericStringOrderingMatch";
+
+  /**
+   * The OID for the numericStringOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_NUMERIC_STRING_OID = "2.5.13.9";
+
+  /**
+   * The name for the octetStringOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_OCTET_STRING_NAME =
+      "octetStringOrderingMatch";
+
+  /**
+   * The OID for the octetStringOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_OCTET_STRING_OID = "2.5.13.18";
+
+  /**
+   * The name for the uuidOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_UUID_NAME = "uuidOrderingMatch";
+
+  /**
+   * The OID for the uuidOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_UUID_OID = "1.3.6.1.1.16.3";
+
+  /**
+   * The name for the enumOrderingMatch ordering matching rule.
+   */
+  public static final String OMR_GENERIC_ENUM_NAME =
+      "enumOrderingMatch";
+
+  /**
+   * The oid for the generic enum syntax ordering matching rule.
+   */
+  public static final String OMR_OID_GENERIC_ENUM =
+      "1.3.6.1.4.1.26027.1.4.8";
+
+  /**
+   * The name for the caseExactSubstringsMatch substring matching rule.
+   */
+  public static final String SMR_CASE_EXACT_NAME =
+      "caseExactSubstringsMatch";
+
+  /**
+   * The OID for the caseExactSubstringsMatch substring matching rule.
+   */
+  public static final String SMR_CASE_EXACT_OID = "2.5.13.7";
+
+  /**
+   * The name for the caseExactIA5SubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_CASE_EXACT_IA5_NAME =
+      "caseExactIA5SubstringsMatch";
+
+  /**
+   * The OID for the caseExactIA5SubstringsMatch substring matching
+   * rule. // FIXME -- This needs to be updated once a real OID is
+   * assigned.
+   */
+  public static final String SMR_CASE_EXACT_IA5_OID =
+      OID_OPENDS_SERVER_MATCHING_RULE_BASE + ".902";
+
+  /**
+   * The name for the caseIgnoreSubstringsMatch substring matching rule.
+   */
+  public static final String SMR_CASE_IGNORE_NAME =
+      "caseIgnoreSubstringsMatch";
+
+  /**
+   * The OID for the caseIgnoreSubstringsMatch substring matching rule.
+   */
+  public static final String SMR_CASE_IGNORE_OID = "2.5.13.4";
+
+  /**
+   * The name for the caseIgnoreIA5SubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_CASE_IGNORE_IA5_NAME =
+      "caseIgnoreIA5SubstringsMatch";
+
+  /**
+   * The OID for the caseIgnoreIA5SubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_CASE_IGNORE_IA5_OID =
+      "1.3.6.1.4.1.1466.109.114.3";
+
+  /**
+   * The name for the caseIgnoreListSubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_CASE_IGNORE_LIST_NAME =
+      "caseIgnoreListSubstringsMatch";
+
+  /**
+   * The OID for the caseIgnoreListSubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_CASE_IGNORE_LIST_OID = "2.5.13.12";
+
+  /**
+   * The name for the numericStringSubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_NUMERIC_STRING_NAME =
+      "numericStringSubstringsMatch";
+
+  /**
+   * The OID for the numericStringSubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_NUMERIC_STRING_OID = "2.5.13.10";
+
+  /**
+   * The name for the octetStringSubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_OCTET_STRING_NAME =
+      "octetStringSubstringsMatch";
+
+  /**
+   * The OID for the octetStringSubstringsMatch substring matching rule.
+   */
+  public static final String SMR_OCTET_STRING_OID = "2.5.13.19";
+
+  /**
+   * The name for the telephoneNumberSubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_TELEPHONE_NAME =
+      "telephoneNumberSubstringsMatch";
+
+  /**
+   * The OID for the telephoneNumberSubstringsMatch substring matching
+   * rule.
+   */
+  public static final String SMR_TELEPHONE_OID = "2.5.13.21";
+
+  /**
+   * The OID for the absolute subtree specification attribute syntax.
+   */
+  public static final String SYNTAX_ABSOLUTE_SUBTREE_SPECIFICATION_OID =
+      OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".3";
+
+  /**
+   * The description for the absolute subtree specification attribute
+   * syntax.
+   */
+  public static final String SYNTAX_ABSOLUTE_SUBTREE_SPECIFICATION_DESCRIPTION =
+      "Absolute Subtree Specification";
+
+  /**
+   * The name for the absolute subtree specification attribute syntax.
+   */
+  public static final String SYNTAX_ABSOLUTE_SUBTREE_SPECIFICATION_NAME =
+      "ds-absolute-subtree-specification";
+
+  /**
+   * The OID for the aci attribute syntax.
+   */
+  public static final String SYNTAX_ACI_OID =
+      OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".4";
+
+  /**
+   * The description for aci attribute syntax.
+   */
+  public static final String SYNTAX_ACI_DESCRIPTION =
+      "Sun-defined Access Control Information";
+
+  /**
+   * The name for the aci attribute syntax.
+   */
+  public static final String SYNTAX_ACI_NAME =
+      "ds-syntax-dseecompat-aci";
+
+  /**
+   * The description for the attribute type description attribute
+   * syntax.
+   */
+  public static final String SYNTAX_ATTRIBUTE_TYPE_DESCRIPTION =
+      "Attribute Type Description";
+
+  /**
+   * The name for the attribute type description attribute syntax.
+   */
+  public static final String SYNTAX_ATTRIBUTE_TYPE_NAME =
+      "AttributeTypeDescription";
+
+  /**
+   * The OID for the attribute type description attribute syntax.
+   */
+  public static final String SYNTAX_ATTRIBUTE_TYPE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.3";
+
+  /**
+   * The description for the auth password attribute syntax.
+   */
+  public static final String SYNTAX_AUTH_PASSWORD_DESCRIPTION =
+      "Authentication Password Syntax";
+
+  /**
+   * The name for the auth password attribute syntax.
+   */
+  public static final String SYNTAX_AUTH_PASSWORD_NAME =
+      "AuthenticationPasswordSyntax";
+
+  /**
+   * The OID for the auth password attribute syntax.
+   */
+  public static final String SYNTAX_AUTH_PASSWORD_OID =
+      "1.3.6.1.4.1.4203.1.1.2";
+
+  /**
+   * The description for the binary attribute syntax.
+   */
+  public static final String SYNTAX_BINARY_DESCRIPTION = "Binary";
+
+  /**
+   * The name for the binary attribute syntax.
+   */
+  public static final String SYNTAX_BINARY_NAME = "Binary";
+
+  /**
+   * The OID for the binary attribute syntax.
+   */
+  public static final String SYNTAX_BINARY_OID =
+      "1.3.6.1.4.1.1466.115.121.1.5";
+
+  /**
+   * The description for the bit string attribute syntax.
+   */
+  public static final String SYNTAX_BIT_STRING_DESCRIPTION =
+      "Bit String";
+
+  /**
+   * The name for the bit string attribute syntax.
+   */
+  public static final String SYNTAX_BIT_STRING_NAME = "BitString";
+
+  /**
+   * The OID for the bit string attribute syntax.
+   */
+  public static final String SYNTAX_BIT_STRING_OID =
+      "1.3.6.1.4.1.1466.115.121.1.6";
+
+  /**
+   * The description for the Boolean attribute syntax.
+   */
+  public static final String SYNTAX_BOOLEAN_DESCRIPTION = "Boolean";
+
+  /**
+   * The name for the Boolean attribute syntax.
+   */
+  public static final String SYNTAX_BOOLEAN_NAME = "Boolean";
+
+  /**
+   * The OID for the Boolean attribute syntax.
+   */
+  public static final String SYNTAX_BOOLEAN_OID =
+      "1.3.6.1.4.1.1466.115.121.1.7";
+
+  /**
+   * The description for the certificate attribute syntax.
+   */
+  public static final String SYNTAX_CERTIFICATE_DESCRIPTION =
+      "Certificate";
+
+  /**
+   * The name for the certificate attribute syntax.
+   */
+  public static final String SYNTAX_CERTIFICATE_NAME = "Certificate";
+
+  /**
+   * The OID for the certificate attribute syntax.
+   */
+  public static final String SYNTAX_CERTIFICATE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.8";
+
+  /**
+   * The description for the certificate list attribute syntax.
+   */
+  public static final String SYNTAX_CERTLIST_DESCRIPTION =
+      "Certificate List";
+
+  /**
+   * The name for the certificate list attribute syntax.
+   */
+  public static final String SYNTAX_CERTLIST_NAME = "CertificateList";
+
+  /**
+   * The OID for the certificate list attribute syntax.
+   */
+  public static final String SYNTAX_CERTLIST_OID =
+      "1.3.6.1.4.1.1466.115.121.1.9";
+
+  /**
+   * The description for the certificate pair attribute syntax.
+   */
+  public static final String SYNTAX_CERTPAIR_DESCRIPTION =
+      "Certificate Pair";
+
+  /**
+   * The name for the certificate pair attribute syntax.
+   */
+  public static final String SYNTAX_CERTPAIR_NAME = "CertificatePair";
+
+  /**
+   * The OID for the certificate pair attribute syntax.
+   */
+  public static final String SYNTAX_CERTPAIR_OID =
+      "1.3.6.1.4.1.1466.115.121.1.10";
+
+  /**
+   * The description for the country string attribute syntax.
+   */
+  public static final String SYNTAX_COUNTRY_STRING_DESCRIPTION =
+      "Country String";
+
+  /**
+   * The name for the country string attribute syntax.
+   */
+  public static final String SYNTAX_COUNTRY_STRING_NAME =
+      "CountryString";
+
+  /**
+   * The OID for the country string attribute syntax.
+   */
+  public static final String SYNTAX_COUNTRY_STRING_OID =
+      "1.3.6.1.4.1.1466.115.121.1.11";
+
+  /**
+   * The description for the delivery method attribute syntax.
+   */
+  public static final String SYNTAX_DELIVERY_METHOD_DESCRIPTION =
+      "Delivery Method";
+
+  /**
+   * The name for the delivery method attribute syntax.
+   */
+  public static final String SYNTAX_DELIVERY_METHOD_NAME =
+      "DeliveryMethod";
+
+  /**
+   * The OID for the delivery method attribute syntax.
+   */
+  public static final String SYNTAX_DELIVERY_METHOD_OID =
+      "1.3.6.1.4.1.1466.115.121.1.14";
+
+  /**
+   * The description for the Directory String attribute syntax.
+   */
+  public static final String SYNTAX_DIRECTORY_STRING_DESCRIPTION =
+      "Directory String";
+
+  /**
+   * The name for the Directory String attribute syntax.
+   */
+  public static final String SYNTAX_DIRECTORY_STRING_NAME =
+      "DirectoryString";
+
+  /**
+   * The OID for the Directory String attribute syntax.
+   */
+  public static final String SYNTAX_DIRECTORY_STRING_OID =
+      "1.3.6.1.4.1.1466.115.121.1.15";
+
+  /**
+   * The description for the DIT content rule description attribute
+   * syntax.
+   */
+  public static final String SYNTAX_DIT_CONTENT_RULE_DESCRIPTION =
+      "DIT Content Rule Description";
+
+  /**
+   * The name for the DIT content rule description attribute syntax.
+   */
+  public static final String SYNTAX_DIT_CONTENT_RULE_NAME =
+      "DITContentRuleDescription";
+
+  /**
+   * The OID for the DIT content rule description attribute syntax.
+   */
+  public static final String SYNTAX_DIT_CONTENT_RULE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.16";
+
+  /**
+   * The description for the DIT structure rule description attribute
+   * syntax.
+   */
+  public static final String SYNTAX_DIT_STRUCTURE_RULE_DESCRIPTION =
+      "DIT Structure Rule Description";
+
+  /**
+   * The name for the DIT structure rule description attribute syntax.
+   */
+  public static final String SYNTAX_DIT_STRUCTURE_RULE_NAME =
+      "DITStructureRuleDescription";
+
+  /**
+   * The OID for the DIT structure rule description attribute syntax.
+   */
+  public static final String SYNTAX_DIT_STRUCTURE_RULE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.17";
+
+  /**
+   * The description for the distinguished name attribute syntax.
+   */
+  public static final String SYNTAX_DN_DESCRIPTION = "DN";
+
+  /**
+   * The name for the distinguished name attribute syntax.
+   */
+  public static final String SYNTAX_DN_NAME = "DN";
+
+  /**
+   * The OID for the distinguished name attribute syntax.
+   */
+  public static final String SYNTAX_DN_OID =
+      "1.3.6.1.4.1.1466.115.121.1.12";
+
+  /**
+   * The description for the enhanced guide attribute syntax.
+   */
+  public static final String SYNTAX_ENHANCED_GUIDE_DESCRIPTION =
+      "Enhanced Guide";
+
+  /**
+   * The name for the enhanced guide attribute syntax.
+   */
+  public static final String SYNTAX_ENHANCED_GUIDE_NAME =
+      "EnhancedGuide";
+
+  /**
+   * The OID for the enhanced guide attribute syntax.
+   */
+  public static final String SYNTAX_ENHANCED_GUIDE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.21";
+
+  /**
+   * The description for the facsimile telephone number attribute
+   * syntax.
+   */
+  public static final String SYNTAX_FAXNUMBER_DESCRIPTION =
+      "Facsimile Telephone Number";
+
+  /**
+   * The name for the facsimile telephone number attribute syntax.
+   */
+  public static final String SYNTAX_FAXNUMBER_NAME =
+      "FacsimileTelephoneNumber";
+
+  /**
+   * The OID for the facsimile telephone number attribute syntax.
+   */
+  public static final String SYNTAX_FAXNUMBER_OID =
+      "1.3.6.1.4.1.1466.115.121.1.22";
+
+  /**
+   * The description for the fax attribute syntax.
+   */
+  public static final String SYNTAX_FAX_DESCRIPTION = "Fax";
+
+  /**
+   * The name for the fax attribute syntax.
+   */
+  public static final String SYNTAX_FAX_NAME = "Fax";
+
+  /**
+   * The OID for the fax attribute syntax.
+   */
+  public static final String SYNTAX_FAX_OID =
+      "1.3.6.1.4.1.1466.115.121.1.23";
+
+  /**
+   * The description for the generalized time attribute syntax.
+   */
+  public static final String SYNTAX_GENERALIZED_TIME_DESCRIPTION =
+      "Generalized Time";
+
+  /**
+   * The name for the generalized time attribute syntax.
+   */
+  public static final String SYNTAX_GENERALIZED_TIME_NAME =
+      "GeneralizedTime";
+
+  /**
+   * The OID for the generalized time attribute syntax.
+   */
+  public static final String SYNTAX_GENERALIZED_TIME_OID =
+      "1.3.6.1.4.1.1466.115.121.1.24";
+
+  /**
+   * The description for the guide attribute syntax.
+   */
+  public static final String SYNTAX_GUIDE_DESCRIPTION = "Guide";
+
+  /**
+   * The name for the guide attribute syntax.
+   */
+  public static final String SYNTAX_GUIDE_NAME = "Guide";
+
+  /**
+   * The OID for the guide attribute syntax.
+   */
+  public static final String SYNTAX_GUIDE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.25";
+
+  /**
+   * The description for the IA5 string attribute syntax.
+   */
+  public static final String SYNTAX_IA5_STRING_DESCRIPTION =
+      "IA5 String";
+
+  /**
+   * The name for the IA5 string attribute syntax.
+   */
+  public static final String SYNTAX_IA5_STRING_NAME = "IA5String";
+
+  /**
+   * The OID for the IA5 string attribute syntax.
+   */
+  public static final String SYNTAX_IA5_STRING_OID =
+      "1.3.6.1.4.1.1466.115.121.1.26";
+
+  /**
+   * The description for the integer attribute syntax.
+   */
+  public static final String SYNTAX_INTEGER_DESCRIPTION = "Integer";
+
+  /**
+   * The name for the integer attribute syntax.
+   */
+  public static final String SYNTAX_INTEGER_NAME = "Integer";
+
+  /**
+   * The OID for the integer attribute syntax.
+   */
+  public static final String SYNTAX_INTEGER_OID =
+      "1.3.6.1.4.1.1466.115.121.1.27";
+
+  /**
+   * The description for the JPEG attribute syntax.
+   */
+  public static final String SYNTAX_JPEG_DESCRIPTION = "JPEG";
+
+  /**
+   * The name for the JPEG attribute syntax.
+   */
+  public static final String SYNTAX_JPEG_NAME = "JPEG";
+
+  /**
+   * The OID for the JPEG attribute syntax.
+   */
+  public static final String SYNTAX_JPEG_OID =
+      "1.3.6.1.4.1.1466.115.121.1.28";
+
+  /**
+   * The description for the LDAP syntax description attribute syntax.
+   */
+  public static final String SYNTAX_LDAP_SYNTAX_DESCRIPTION =
+      "LDAP Syntax Description";
+
+  /**
+   * The name for the LDAP syntax description attribute syntax.
+   */
+  public static final String SYNTAX_LDAP_SYNTAX_NAME =
+      "LDAPSyntaxDescription";
+
+  /**
+   * The OID for the LDAP syntax description attribute syntax.
+   */
+  public static final String SYNTAX_LDAP_SYNTAX_OID =
+      "1.3.6.1.4.1.1466.115.121.1.54";
+
+  /**
+   * The description for the matching rule description attribute syntax.
+   */
+  public static final String SYNTAX_MATCHING_RULE_DESCRIPTION =
+      "Matching Rule Description";
+
+  /**
+   * The name for the matching rule description attribute syntax.
+   */
+  public static final String SYNTAX_MATCHING_RULE_NAME =
+      "MatchingRuleDescription";
+
+  /**
+   * The OID for the matching rule description attribute syntax.
+   */
+  public static final String SYNTAX_MATCHING_RULE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.30";
+
+  /**
+   * The description for the matching rule use description attribute
+   * syntax.
+   */
+  public static final String SYNTAX_MATCHING_RULE_USE_DESCRIPTION =
+      "Matching Rule Use Description";
+
+  /**
+   * The name for the matching rule use description attribute syntax.
+   */
+  public static final String SYNTAX_MATCHING_RULE_USE_NAME =
+      "MatchingRuleUseDescription";
+
+  /**
+   * The OID for the matching rule use description attribute syntax.
+   */
+  public static final String SYNTAX_MATCHING_RULE_USE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.31";
+
+  /**
+   * The description for the name and optional uid attribute syntax.
+   */
+  public static final String SYNTAX_NAME_AND_OPTIONAL_UID_DESCRIPTION =
+      "Name and Optional UID";
+
+  /**
+   * The name for the name and optional uid attribute syntax.
+   */
+  public static final String SYNTAX_NAME_AND_OPTIONAL_UID_NAME =
+      "NameAndOptionalUID";
+
+  /**
+   * The OID for the name and optional uid attribute syntax.
+   */
+  public static final String SYNTAX_NAME_AND_OPTIONAL_UID_OID =
+      "1.3.6.1.4.1.1466.115.121.1.34";
+
+  /**
+   * The description for the name form description attribute syntax.
+   */
+  public static final String SYNTAX_NAME_FORM_DESCRIPTION =
+      "Name Form Description";
+
+  /**
+   * The name for the name form description attribute syntax.
+   */
+  public static final String SYNTAX_NAME_FORM_NAME =
+      "NameFormDescription";
+
+  /**
+   * The OID for the name form description attribute syntax.
+   */
+  public static final String SYNTAX_NAME_FORM_OID =
+      "1.3.6.1.4.1.1466.115.121.1.35";
+
+  /**
+   * The description for the numeric string attribute syntax.
+   */
+  public static final String SYNTAX_NUMERIC_STRING_DESCRIPTION =
+      "Numeric String";
+
+  /**
+   * The name for the numeric string attribute syntax.
+   */
+  public static final String SYNTAX_NUMERIC_STRING_NAME =
+      "NumericString";
+
+  /**
+   * The OID for the numeric string attribute syntax.
+   */
+  public static final String SYNTAX_NUMERIC_STRING_OID =
+      "1.3.6.1.4.1.1466.115.121.1.36";
+
+  /**
+   * The description for the object class description attribute syntax.
+   */
+  public static final String SYNTAX_OBJECTCLASS_DESCRIPTION =
+      "Object Class Description";
+
+  /**
+   * The name for the object class description attribute syntax.
+   */
+  public static final String SYNTAX_OBJECTCLASS_NAME =
+      "ObjectClassDescription";
+
+  /**
+   * The OID for the object class description attribute syntax.
+   */
+  public static final String SYNTAX_OBJECTCLASS_OID =
+      "1.3.6.1.4.1.1466.115.121.1.37";
+
+  /**
+   * The description for the octet string attribute syntax.
+   */
+  public static final String SYNTAX_OCTET_STRING_DESCRIPTION =
+      "Octet String";
+
+  /**
+   * The name for the octet string attribute syntax.
+   */
+  public static final String SYNTAX_OCTET_STRING_NAME = "OctetString";
+
+  /**
+   * The OID for the octet string attribute syntax.
+   */
+  public static final String SYNTAX_OCTET_STRING_OID =
+      "1.3.6.1.4.1.1466.115.121.1.40";
+
+  /**
+   * The description for the object identifier attribute syntax.
+   */
+  public static final String SYNTAX_OID_DESCRIPTION = "OID";
+
+  /**
+   * The name for the object identifier attribute syntax.
+   */
+  public static final String SYNTAX_OID_NAME = "OID";
+
+  /**
+   * The OID for the object identifier attribute syntax.
+   */
+  public static final String SYNTAX_OID_OID =
+      "1.3.6.1.4.1.1466.115.121.1.38";
+
+  /**
+   * The description for the other mailbox attribute syntax.
+   */
+  public static final String SYNTAX_OTHER_MAILBOX_DESCRIPTION =
+      "Other Mailbox";
+
+  /**
+   * The name for the other mailbox attribute syntax.
+   */
+  public static final String SYNTAX_OTHER_MAILBOX_NAME = "OtherMailbox";
+
+  /**
+   * The OID for the other mailbox attribute syntax.
+   */
+  public static final String SYNTAX_OTHER_MAILBOX_OID =
+      "1.3.6.1.4.1.1466.115.121.1.39";
+
+  /**
+   * The description for the postal address attribute syntax.
+   */
+  public static final String SYNTAX_POSTAL_ADDRESS_DESCRIPTION =
+      "Postal Address";
+
+  /**
+   * The name for the postal address attribute syntax.
+   */
+  public static final String SYNTAX_POSTAL_ADDRESS_NAME =
+      "PostalAddress";
+
+  /**
+   * The OID for the postal address attribute syntax.
+   */
+  public static final String SYNTAX_POSTAL_ADDRESS_OID =
+      "1.3.6.1.4.1.1466.115.121.1.41";
+
+  /**
+   * The description for the presentation address attribute syntax.
+   */
+  public static final String SYNTAX_PRESENTATION_ADDRESS_DESCRIPTION =
+      "Presentation Address";
+
+  /**
+   * The name for the presentation address attribute syntax.
+   */
+  public static final String SYNTAX_PRESENTATION_ADDRESS_NAME =
+      "PresentationAddress";
+
+  /**
+   * The OID for the presentation address attribute syntax.
+   */
+  public static final String SYNTAX_PRESENTATION_ADDRESS_OID =
+      "1.3.6.1.4.1.1466.115.121.1.43";
+
+  /**
+   * The description for the printable string attribute syntax.
+   */
+  public static final String SYNTAX_PRINTABLE_STRING_DESCRIPTION =
+      "Printable String";
+
+  /**
+   * The name for the printable string attribute syntax.
+   */
+  public static final String SYNTAX_PRINTABLE_STRING_NAME =
+      "PrintableString";
+
+  /**
+   * The OID for the printable string attribute syntax.
+   */
+  public static final String SYNTAX_PRINTABLE_STRING_OID =
+      "1.3.6.1.4.1.1466.115.121.1.44";
+
+  /**
+   * The description for the protocol information attribute syntax.
+   */
+  public static final String SYNTAX_PROTOCOL_INFORMATION_DESCRIPTION =
+      "Protocol Information";
+
+  /**
+   * The name for the protocol information attribute syntax.
+   */
+  public static final String SYNTAX_PROTOCOL_INFORMATION_NAME =
+      "ProtocolInformation";
+
+  /**
+   * The OID for the protocol information attribute syntax.
+   */
+  public static final String SYNTAX_PROTOCOL_INFORMATION_OID =
+      "1.3.6.1.4.1.1466.115.121.1.42";
+
+  /**
+   * The OID for the relative subtree specification attribute syntax.
+   */
+  public static final String SYNTAX_RELATIVE_SUBTREE_SPECIFICATION_OID =
+      OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".2";
+
+  /**
+   * The description for the relative subtree specification attribute
+   * syntax.
+   */
+  public static final String SYNTAX_RELATIVE_SUBTREE_SPECIFICATION_DESCRIPTION =
+      "Relative Subtree Specification";
+
+  /**
+   * The name for the relative subtree specification attribute syntax.
+   */
+  public static final String SYNTAX_RELATIVE_SUBTREE_SPECIFICATION_NAME =
+      "ds-relative-subtree-specification";
+
+  /**
+   * The OID for the RFC3672 subtree specification attribute syntax.
+   */
+  public static final String SYNTAX_RFC3672_SUBTREE_SPECIFICATION_OID =
+      "1.3.6.1.4.1.1466.115.121.1.45";
+
+  /**
+   * The description for the RFC3672 subtree specification attribute
+   * syntax.
+   */
+  public static final String SYNTAX_RFC3672_SUBTREE_SPECIFICATION_DESCRIPTION =
+      "RFC3672 Subtree Specification";
+
+  /**
+   * The name for the RFC3672 subtree specification attribute syntax.
+   */
+  public static final String SYNTAX_RFC3672_SUBTREE_SPECIFICATION_NAME =
+      "SubtreeSpecification";
+
+  /**
+   * The description for the substring assertion attribute syntax.
+   */
+  public static final String SYNTAX_SUBSTRING_ASSERTION_DESCRIPTION =
+      "Substring Assertion";
+
+  /**
+   * The name for the substring assertion attribute syntax.
+   */
+  public static final String SYNTAX_SUBSTRING_ASSERTION_NAME =
+      "SubstringAssertion";
+
+  /**
+   * The OID for the Substring Assertion syntax used for assertion
+   * values in extensible match filters.
+   */
+  public static final String SYNTAX_SUBSTRING_ASSERTION_OID =
+      "1.3.6.1.4.1.1466.115.121.1.58";
+
+  /**
+   * The description for the supported algorithm attribute syntax.
+   */
+  public static final String SYNTAX_SUPPORTED_ALGORITHM_DESCRIPTION =
+      "Supported Algorithm";
+
+  /**
+   * The name for the supported algorithm attribute syntax.
+   */
+  public static final String SYNTAX_SUPPORTED_ALGORITHM_NAME =
+      "SupportedAlgorithm";
+
+  /**
+   * The OID for the Substring Assertion syntax used for assertion
+   * values in extensible match filters.
+   */
+  public static final String SYNTAX_SUPPORTED_ALGORITHM_OID =
+      "1.3.6.1.4.1.1466.115.121.1.49";
+
+  /**
+   * The description for the telephone number attribute syntax.
+   */
+  public static final String SYNTAX_TELEPHONE_DESCRIPTION =
+      "Telephone Number";
+
+  /**
+   * The name for the telephone number attribute syntax.
+   */
+  public static final String SYNTAX_TELEPHONE_NAME = "TelephoneNumber";
+
+  /**
+   * The OID for the telephone number attribute syntax.
+   */
+  public static final String SYNTAX_TELEPHONE_OID =
+      "1.3.6.1.4.1.1466.115.121.1.50";
+
+  /**
+   * The description for the teletex terminal identifier attribute
+   * syntax.
+   */
+  public static final String SYNTAX_TELETEX_TERM_ID_DESCRIPTION =
+      "Teletex Terminal Identifier";
+
+  /**
+   * The name for the teletex terminal identifier attribute syntax.
+   */
+  public static final String SYNTAX_TELETEX_TERM_ID_NAME =
+      "TeletexTerminalIdentifier";
+
+  /**
+   * The OID for the teletex terminal identifier attribute syntax.
+   */
+  public static final String SYNTAX_TELETEX_TERM_ID_OID =
+      "1.3.6.1.4.1.1466.115.121.1.51";
+
+  /**
+   * The description for the telex number attribute syntax.
+   */
+  public static final String SYNTAX_TELEX_DESCRIPTION = "Telex Number";
+
+  /**
+   * The name for the telex number attribute syntax.
+   */
+  public static final String SYNTAX_TELEX_NAME = "TelexNumber";
+
+  /**
+   * The OID for the telex number attribute syntax.
+   */
+  public static final String SYNTAX_TELEX_OID =
+      "1.3.6.1.4.1.1466.115.121.1.52";
+
+  /**
+   * The description for the user password attribute syntax.
+   */
+  public static final String SYNTAX_USER_PASSWORD_DESCRIPTION =
+      "User Password";
+
+  /**
+   * The name for the user password attribute syntax.
+   */
+  public static final String SYNTAX_USER_PASSWORD_NAME =
+      "ds-syntax-user-password";
+
+  /**
+   * The OID for the user password attribute syntax.
+   */
+  public static final String SYNTAX_USER_PASSWORD_OID =
+      OID_OPENDS_SERVER_ATTRIBUTE_SYNTAX_BASE + ".1";
+
+  /**
+   * The description for the UTC time attribute syntax.
+   */
+  public static final String SYNTAX_UTC_TIME_DESCRIPTION = "UTC Time";
+
+  /**
+   * The name for the UTC time attribute syntax.
+   */
+  public static final String SYNTAX_UTC_TIME_NAME = "UTCTime";
+
+  /**
+   * The OID for the UTC time attribute syntax.
+   */
+  public static final String SYNTAX_UTC_TIME_OID =
+      "1.3.6.1.4.1.1466.115.121.1.53";
+
+  /**
+   * The description for the UUID attribute syntax.
+   */
+  public static final String SYNTAX_UUID_DESCRIPTION = "UUID";
+
+  /**
+   * The name for the UUID attribute syntax.
+   */
+  public static final String SYNTAX_UUID_NAME = "UUID";
+
+  /**
+   * The OID for the UUID attribute syntax.
+   */
+  public static final String SYNTAX_UUID_OID = "1.3.6.1.1.16.1";
+
+  /**
+   * The description for the "top" objectclass.
+   */
+  public static final String TOP_OBJECTCLASS_DESCRIPTION =
+      "Topmost ObjectClass";
+
+  /**
+   * The name of the "top" objectclass.
+   */
+  public static final String TOP_OBJECTCLASS_NAME = "top";
+
+  /**
+   * The OID for the "top" objectclass.
+   */
+  public static final String TOP_OBJECTCLASS_OID = "2.5.6.0";
+
+  /**
+   * The name for the relative time greater-than extensible ordering
+   * matching rule.
+   */
+  public static final String EXT_OMR_RELATIVE_TIME_GT_NAME =
+      "relativeTimeGTOrderingMatch";
+
+  /**
+   * The alternative name for the relative time greater-than extensible
+   * ordering matching rule.
+   */
+  public static final String EXT_OMR_RELATIVE_TIME_GT_ALT_NAME =
+      "relativeTimeOrderingMatch.gt";
+
+  /**
+   * The OID for the relative time greater-than extensible ordering
+   * matching rule.
+   */
+  public static final String EXT_OMR_RELATIVE_TIME_GT_OID =
+      "1.3.6.1.4.1.26027.1.4.5";
+
+  /**
+   * The name for the relative time less-than extensible ordering
+   * matching rule.
+   */
+  public static final String EXT_OMR_RELATIVE_TIME_LT_NAME =
+      "relativeTimeLTOrderingMatch";
+
+  /**
+   * The alternative name for the relative time less-than extensible
+   * ordering matching rule.
+   */
+  public static final String EXT_OMR_RELATIVE_TIME_LT_ALT_NAME =
+      "relativeTimeOrderingMatch.lt";
+
+  /**
+   * The OID for the relative time less-than extensible ordering
+   * matching rule.
+   */
+  public static final String EXT_OMR_RELATIVE_TIME_LT_OID =
+      "1.3.6.1.4.1.26027.1.4.6";
+
+  /**
+   * The OID for the partial date and time extensible matching rule.
+   */
+  public static final String EXT_PARTIAL_DATE_TIME_OID =
+      "1.3.6.1.4.1.26027.1.4.7";
+
+  /**
+   * The name for the partial date and time extensible rule.
+   */
+  public static final String EXT_PARTIAL_DATE_TIME_NAME =
+      "partialDateAndTimeMatchingRule";
+
+  /**
+   * The name of the schema extension that will be used to specify the
+   * approximate matching rule that should be used for a given attribute
+   * type.
+   */
+  public static final String SCHEMA_PROPERTY_APPROX_RULE = "X-APPROX";
+
+  /**
+   * The name of the schema property that will be used to specify the
+   * origin of a schema element.
+   */
+  public static final String SCHEMA_PROPERTY_ORIGIN = "X-ORIGIN";
+
+  /**
+   * The OID for the extensibleObject objectclass.
+   */
+  public static final String EXTENSIBLE_OBJECT_OBJECTCLASS_OID =
+      "1.3.6.1.4.1.1466.101.120.111";
+
+  /**
+   * The name for the extensibleObject objectclass.
+   */
+  public static final String EXTENSIBLE_OBJECT_OBJECTCLASS_NAME =
+      "extensibleObject";
+
+  /**
+   * The value representing just one space character.
+   */
+  public static final ByteString SINGLE_SPACE_VALUE =
+      ByteString.valueOf(" ");
+
+  /**
+   * The normalized true value.
+   */
+  public static final ByteString TRUE_VALUE =
+      ByteString.valueOf("TRUE");
+
+  /**
+   * The normalized false value.
+   */
+  public static final ByteString FALSE_VALUE =
+      ByteString.valueOf("FALSE");
+
+  /**
+   * The name of the time zone for universal coordinated time (UTC).
+   */
+  public static final String TIME_ZONE_UTC = "UTC";
+
+  /**
+   * The date format string that will be used to construct and parse
+   * dates represented using generalized time with a two-digit year. It
+   * is assumed that the provided date formatter will be set to UTC.
+   */
+  public static final String DATE_FORMAT_UTC_TIME = "yyMMddHHmmss'Z'";
+
+
+
+  // Prevent instantiation.
+  private SchemaConstants()
+  {
+    // Nothing to do.
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaElement.java b/sdk/src/org/opends/sdk/schema/SchemaElement.java
new file mode 100644
index 0000000..59cdab7
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaElement.java
@@ -0,0 +1,185 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import java.util.List;
+import java.util.Map;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * An abstract base class for LDAP schema definitions which contain an
+ * description, and an optional set of extra properties.
+ * <p>
+ * This class defines common properties and behaviour of the various
+ * types of schema definitions (e.g. object class definitions, and
+ * attribute type definitions).
+ */
+abstract class SchemaElement
+{
+  // The description for this definition.
+  final String description;
+
+  // The set of additional name-value pairs.
+  final Map<String, List<String>> extraProperties;
+
+
+
+  SchemaElement(String description,
+      Map<String, List<String>> extraProperties)
+  {
+    Validator.ensureNotNull(description, extraProperties);
+    this.description = description;
+    this.extraProperties = extraProperties;
+  }
+
+
+
+  /**
+   * Retrieves the description for this schema definition.
+   *
+   * @return The description for this schema definition.
+   */
+  public final String getDescription()
+  {
+
+    return description;
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the value(s) of the specified "extra"
+   * property for this schema definition.
+   *
+   * @param name
+   *          The name of the "extra" property for which to retrieve the
+   *          value(s).
+   * @return Returns an iterable over the value(s) of the specified
+   *         "extra" property for this schema definition, or
+   *         <code>null</code> if no such property is defined.
+   */
+  public final Iterable<String> getExtraProperty(String name)
+  {
+
+    return extraProperties.get(name);
+  }
+
+
+
+  /**
+   * Retrieves an iterable over the names of "extra" properties
+   * associated with this schema definition.
+   *
+   * @return Returns an iterable over the names of "extra" properties
+   *         associated with this schema definition.
+   */
+  public final Iterable<String> getExtraPropertyNames()
+  {
+
+    return extraProperties.keySet();
+  }
+
+
+
+  /**
+   * Builds a string representation of this schema definition in the
+   * form specified in RFC 2252.
+   *
+   * @return The string representation of this schema definition in the
+   *         form specified in RFC 2252.
+   */
+  final String buildDefinition()
+  {
+    final StringBuilder buffer = new StringBuilder();
+
+    buffer.append("( ");
+
+    toStringContent(buffer);
+
+    if (!extraProperties.isEmpty())
+    {
+      for (final Map.Entry<String, List<String>> e : extraProperties
+          .entrySet())
+      {
+
+        final String property = e.getKey();
+
+        final List<String> valueList = e.getValue();
+
+        buffer.append(" ");
+        buffer.append(property);
+
+        if (valueList.size() == 1)
+        {
+          buffer.append(" '");
+          buffer.append(valueList.get(0));
+          buffer.append("'");
+        }
+        else
+        {
+          buffer.append(" ( ");
+
+          for (final String value : valueList)
+          {
+            buffer.append("'");
+            buffer.append(value);
+            buffer.append("' ");
+          }
+
+          buffer.append(")");
+        }
+      }
+    }
+
+    buffer.append(" )");
+
+    return buffer.toString();
+  }
+
+
+
+  /**
+   * Appends a string representation of this schema definition's
+   * non-generic properties to the provided buffer.
+   *
+   * @param buffer
+   *          The buffer to which the information should be appended.
+   */
+  abstract void toStringContent(StringBuilder buffer);
+
+
+
+  abstract void validate(List<Message> warnings, Schema schema)
+      throws SchemaException;
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaException.java b/sdk/src/org/opends/sdk/schema/SchemaException.java
new file mode 100644
index 0000000..4bd4ef1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaException.java
@@ -0,0 +1,89 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * Thrown when a schema could not be decoded or validated.
+ */
+@SuppressWarnings("serial")
+final class SchemaException extends Exception implements
+    LocalizableException
+{
+  // The I18N message associated with this exception.
+  private final Message message;
+
+
+
+  /**
+   * Creates a new schema exception with the provided message.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   */
+  public SchemaException(Message message)
+  {
+    super(String.valueOf(message));
+    this.message = message;
+  }
+
+
+
+  /**
+   * Creates a new schema exception with the provided message and cause.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @param cause
+   *          The cause which may be later retrieved by the
+   *          {@link #getCause} method. A {@code null} value is
+   *          permitted, and indicates that the cause is nonexistent or
+   *          unknown.
+   */
+  public SchemaException(Message message, Throwable cause)
+  {
+    super(String.valueOf(message), cause);
+    this.message = message;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Message getMessageObject()
+  {
+    return this.message;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaLocal.java b/sdk/src/org/opends/sdk/schema/SchemaLocal.java
new file mode 100644
index 0000000..748ea30
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaLocal.java
@@ -0,0 +1,130 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+/**
+ * This class provides schema-local variables. These variables differ
+ * from their normal counterparts in that each schema has its own
+ * independently initialized copy of the variable. {@code SchemaLocal}
+ * instances are typically private static fields in classes that wish to
+ * associate state with a schema (e.g., a schema dependent cache).
+ * 
+ * @param <T>
+ *          The type of the schema-local variable.
+ */
+public class SchemaLocal<T>
+{
+  /**
+   * Creates a schema-local variable.
+   */
+  public SchemaLocal()
+  {
+    // Nothing to do.
+  }
+
+
+
+  /**
+   * Returns the value in the provided schema's copy of this
+   * schema-local variable. If the variable has no value associated with
+   * the schema, it is first initialized to the value returned by an
+   * invocation of the {@link #initialValue} method.
+   * 
+   * @param schema
+   *          The schema whose copy of the schema-local variable is
+   *          being requested.
+   * @return The schema-local value.
+   */
+  public final T get(Schema schema)
+  {
+    // Schema calls back to initialValue() if this is the first time.
+    return schema.getAttachment(this);
+  }
+
+
+
+  /**
+   * Removes the provided schema's value for this schema-local variable.
+   * If this schema-local variable is subsequently read, its value will
+   * be reinitialized by invoking its {@link #initialValue} method,
+   * unless its value is set in the interim. This may result in multiple
+   * invocations of the {@link #initialValue} method.
+   * 
+   * @param schema
+   *          The schema whose copy of the schema-local variable is
+   *          being removed.
+   */
+  public final void remove(Schema schema)
+  {
+    schema.removeAttachment(this);
+  }
+
+
+
+  /**
+   * Sets the provided schema's copy of this schema-local variable to
+   * the specified value.
+   * 
+   * @param schema
+   *          The schema whose copy of the schema-local variable is
+   *          being set.
+   * @param value
+   *          The schema-local value.
+   */
+  public final void set(Schema schema, T value)
+  {
+    schema.setAttachment(this, value);
+  }
+
+
+
+  /**
+   * Returns the provided schema's "initial value" for this schema-local
+   * variable. This method will be invoked the first time the variable
+   * is accessed with the {@link #get} method for each schema, unless
+   * the {@link #set} method has been previously invoked, in which case
+   * the {@link #initialValue} method will not be invoked.
+   * <p>
+   * Normally, this method is invoked at most once per schema, but it
+   * may be invoked again in case of subsequent invocations of
+   * {@link #remove} followed by {@link #get}. This implementation
+   * simply returns {@code null}; if the programmer desires schema-local
+   * variables to have an initial value other than {@code null}, {@code
+   * SchemaLocal} must be subclassed, and this method overridden.
+   * Typically, an anonymous inner class will be used.
+   * 
+   * @return The initial value for this schema-local.
+   */
+  protected T initialValue()
+  {
+    // Default implementation.
+    return null;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java b/sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java
new file mode 100644
index 0000000..b4ceb1e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaNotFoundException.java
@@ -0,0 +1,93 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * Thrown when a schema could not be decoded or validated.
+ * <p>
+ * TODO: is this needed? Should it be a sub-type of
+ * ErrorResultException?
+ */
+@SuppressWarnings("serial")
+public class SchemaNotFoundException extends Exception implements
+    LocalizableException
+{
+  // The I18N message associated with this exception.
+  private final Message message;
+
+
+
+  /**
+   * Creates a new schema not found exception with the provided message.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   */
+  public SchemaNotFoundException(Message message)
+  {
+    super(String.valueOf(message));
+    this.message = message;
+  }
+
+
+
+  /**
+   * Creates a new schema not found exception with the provided message
+   * and cause.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @param cause
+   *          The cause which may be later retrieved by the
+   *          {@link #getCause} method. A {@code null} value is
+   *          permitted, and indicates that the cause is nonexistent or
+   *          unknown.
+   */
+  public SchemaNotFoundException(Message message, Throwable cause)
+  {
+    super(String.valueOf(message), cause);
+    this.message = message;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Message getMessageObject()
+  {
+    return this.message;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SchemaUtils.java b/sdk/src/org/opends/sdk/schema/SchemaUtils.java
new file mode 100644
index 0000000..7037b31
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SchemaUtils.java
@@ -0,0 +1,883 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.util.StaticUtils.isAlpha;
+import static org.opends.sdk.util.StaticUtils.isDigit;
+
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.SubstringReader;
+
+
+
+/**
+ * Schema utility methods.
+ */
+final class SchemaUtils
+{
+  /**
+   * Reads the value for an "extra" parameter. It will handle a single
+   * unquoted word (which is technically illegal, but we'll allow it), a
+   * single quoted string, or an open parenthesis followed by a
+   * space-delimited set of quoted strings or unquoted words followed by
+   * a close parenthesis.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The "extra" parameter value that was read.
+   * @throws DecodeException
+   *           If a problem occurs while attempting to read the value.
+   */
+  static List<String> readExtensions(SubstringReader reader)
+      throws DecodeException
+  {
+    int length = 0;
+    List<String> values;
+
+    // Skip over any leading spaces.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      // Look at the next character. If it is a quote, then parse until
+      // the next quote and end. If it is an open parenthesis, then
+      // parse individual values until the close parenthesis and end.
+      // Otherwise, parse until the next space and end.
+      char c = reader.read();
+      if (c == '\'')
+      {
+        reader.mark();
+        // Parse until the closing quote.
+        while (reader.read() != '\'')
+        {
+          length++;
+        }
+
+        reader.reset();
+        values = Collections.singletonList(reader.read(length));
+        reader.read();
+      }
+      else if (c == '(')
+      {
+        // Skip over any leading spaces;
+        reader.skipWhitespaces();
+        reader.mark();
+
+        c = reader.read();
+        if (c == ')')
+        {
+          values = Collections.emptyList();
+        }
+        else
+        {
+          values = new ArrayList<String>();
+          do
+          {
+            reader.reset();
+            values.add(readQuotedString(reader));
+            reader.skipWhitespaces();
+            reader.mark();
+          }
+          while (reader.read() != ')');
+        }
+      }
+      else
+      {
+        // Parse until the next space.
+        do
+        {
+          length++;
+        }
+        while (reader.read() != ' ');
+
+        reader.reset();
+        values = Collections.singletonList(reader.read(length));
+      }
+
+      return values;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  static List<String> readNameDescriptors(SubstringReader reader)
+      throws DecodeException
+  {
+    int length = 0;
+    List<String> values;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+
+    try
+    {
+      char c = reader.read();
+      if (c == '\'')
+      {
+        reader.mark();
+        // Parse until the closing quote.
+        while (reader.read() != '\'')
+        {
+          length++;
+        }
+
+        reader.reset();
+        values = Collections.singletonList(reader.read(length));
+        reader.read();
+      }
+      else if (c == '(')
+      {
+        // Skip over any leading spaces;
+        reader.skipWhitespaces();
+        reader.mark();
+
+        c = reader.read();
+        if (c == ')')
+        {
+          values = Collections.emptyList();
+        }
+        else
+        {
+          values = new LinkedList<String>();
+          do
+          {
+            reader.reset();
+            values.add(readQuotedDescriptor(reader));
+            reader.skipWhitespaces();
+            reader.mark();
+          }
+          while (reader.read() != ')');
+        }
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                .valueOf(c), reader.pos() - 1);
+        throw DecodeException.error(message);
+      }
+
+      return values;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  /**
+   * Reads the next OID from the definition, skipping over any leading
+   * spaces.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The OID read from the definition.
+   * @throws DecodeException
+   *           If a problem is encountered while reading the token name.
+   */
+  static String readNumericOID(SubstringReader reader)
+      throws DecodeException
+  {
+    // This must be a numeric OID. In that case, we will accept
+    // only digits and periods, but not consecutive periods.
+    boolean lastWasPeriod = false;
+    int length = 0;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      char c;
+      while ((c = reader.read()) != ' ' && c != '\'')
+      {
+        if (c == '.')
+        {
+          if (lastWasPeriod)
+          {
+            final Message message =
+                ERR_ATTR_SYNTAX_OID_CONSECUTIVE_PERIODS.get(reader
+                    .getString(), reader.pos() - 1);
+            throw DecodeException.error(message);
+          }
+          else
+          {
+            lastWasPeriod = true;
+          }
+        }
+        else if (!isDigit(c))
+        {
+          // Technically, this must be an illegal character. However, it
+          // is possible that someone just got sloppy and did not
+          // include a space between the name/OID and a closing
+          // parenthesis. In that case, we'll assume it's the end of the
+          // value.
+          if (c == ')')
+          {
+            break;
+          }
+
+          // This must have been an illegal character.
+          final Message message =
+              ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER.get(reader
+                  .getString(), reader.pos() - 1);
+          throw DecodeException.error(message);
+        }
+        else
+        {
+          lastWasPeriod = false;
+        }
+        length++;
+      }
+
+      if (length == 0)
+      {
+        final Message message = ERR_ATTR_SYNTAX_OID_NO_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      reader.reset();
+
+      return reader.read(length);
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  /**
+   * Reads the attribute description or numeric OID, skipping over any
+   * leading or trailing spaces.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The attribute description or numeric OID read from the
+   *         definition.
+   * @throws DecodeException
+   *           If a problem is encountered while reading the name or
+   *           OID.
+   */
+  static String readOID(SubstringReader reader) throws DecodeException
+  {
+    int length = 1;
+    boolean enclosingQuote = false;
+    String oid;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      // The next character must be either numeric (for an OID) or
+      // alphabetic (for an attribute description).
+      char c = reader.read();
+      if (c == '\'')
+      {
+        enclosingQuote = true;
+        reader.mark();
+        c = reader.read();
+      }
+      if (isDigit(c))
+      {
+        reader.reset();
+        oid = readNumericOID(reader);
+      }
+
+      else if (isAlpha(c))
+      {
+        // This must be an attribute description. In this case, we will
+        // only accept alphabetic characters, numeric digits, and the
+        // hyphen.
+        while (reader.remaining() > 0 && (c = reader.read()) != ' '
+            && c != ')' && !(c == '\'' && enclosingQuote))
+        {
+          if (length == 0 && !isAlpha(c))
+          {
+            // This is an illegal character.
+            final Message message =
+                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                    .valueOf(c), reader.pos() - 1);
+            throw DecodeException.error(message);
+          }
+
+          if (!isAlpha(c) && !isDigit(c) && c != '-' && c != '.'
+              && c != '_')
+          {
+            // This is an illegal character.
+            final Message message =
+                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                    .valueOf(c), reader.pos() - 1);
+            throw DecodeException.error(message);
+          }
+
+          length++;
+        }
+
+        reader.reset();
+
+        // Return the position of the first non-space character after
+        // the token.
+        oid = reader.read(length);
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                .valueOf(c), reader.pos() - 1);
+        throw DecodeException.error(message);
+      }
+
+      if (enclosingQuote)
+      {
+        reader.read();
+      }
+      return oid;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  /**
+   * Reads the next OID from the definition, skipping over any leading
+   * spaces. The OID may be followed by a integer length in brackets.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The OID read from the definition.
+   * @throws DecodeException
+   *           If a problem is encountered while reading the token name.
+   */
+  static String readOIDLen(SubstringReader reader)
+      throws DecodeException
+  {
+    int length = 1;
+    boolean enclosingQuote = false;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      // The next character must be either numeric (for an OID) or
+      // alphabetic (for an attribute description).
+      char c = reader.read();
+      if (c == '\'')
+      {
+        enclosingQuote = true;
+        reader.mark();
+        c = reader.read();
+      }
+      if (isDigit(c))
+      {
+        boolean lastWasPeriod = false;
+        while ((c = reader.read()) != ' ' && c != '{'
+            && !(c == '\'' && enclosingQuote))
+        {
+          if (c == '.')
+          {
+            if (lastWasPeriod)
+            {
+              final Message message =
+                  ERR_ATTR_SYNTAX_OID_CONSECUTIVE_PERIODS.get(reader
+                      .getString(), reader.pos() - 1);
+              throw DecodeException.error(message);
+            }
+            else
+            {
+              lastWasPeriod = true;
+            }
+          }
+          else if (!isDigit(c))
+          {
+            // Technically, this must be an illegal character. However,
+            // it is possible that someone just got sloppy and did not
+            // include a space between the name/OID and a closing
+            // parenthesis. In that case, we'll assume it's the end of
+            // the value.
+            if (c == ')')
+            {
+              break;
+            }
+
+            // This must have been an illegal character.
+            final Message message =
+                ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER.get(reader
+                    .getString(), reader.pos() - 1);
+            throw DecodeException.error(message);
+          }
+          else
+          {
+            lastWasPeriod = false;
+          }
+          length++;
+        }
+
+        if (length == 0)
+        {
+          final Message message = ERR_ATTR_SYNTAX_OID_NO_VALUE.get();
+          throw DecodeException.error(message);
+        }
+      }
+
+      else if (isAlpha(c))
+      {
+        // This must be an attribute description. In this case, we will
+        // only accept alphabetic characters, numeric digits, and the
+        // hyphen.
+        while ((c = reader.read()) != ' ' && c != ')' && c != '{'
+            && !(c == '\'' && enclosingQuote))
+        {
+          if (length == 0 && !isAlpha(c))
+          {
+            // This is an illegal character.
+            final Message message =
+                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                    .valueOf(c), reader.pos() - 1);
+            throw DecodeException.error(message);
+          }
+
+          if (!isAlpha(c) && !isDigit(c) && c != '-' && c != '.'
+              && c != '_')
+          {
+            // This is an illegal character.
+            final Message message =
+                ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                    .valueOf(c), reader.pos() - 1);
+            throw DecodeException.error(message);
+          }
+
+          length++;
+        }
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                .valueOf(c), reader.pos() - 1);
+        throw DecodeException.error(message);
+      }
+
+      reader.reset();
+
+      // Return the position of the first non-space character after the
+      // token.
+      final String oid = reader.read(length);
+
+      reader.mark();
+      if ((c = reader.read()) == '{')
+      {
+        reader.mark();
+        // The only thing we'll allow here will be numeric digits and
+        // the closing curly brace.
+        while ((c = reader.read()) != '}')
+        {
+          if (!isDigit(c))
+          {
+            final Message message =
+                ERR_ATTR_SYNTAX_OID_ILLEGAL_CHARACTER.get(reader
+                    .getString(), reader.pos() - 1);
+            throw DecodeException.error(message);
+          }
+        }
+      }
+      else if (c == '\'')
+      {
+        reader.mark();
+      }
+      else
+      {
+        reader.reset();
+      }
+
+      return oid;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  static Set<String> readOIDs(SubstringReader reader)
+      throws DecodeException
+  {
+    Set<String> values;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      final char c = reader.read();
+      if (c == '(')
+      {
+        values = new HashSet<String>();
+
+        do
+        {
+          values.add(readOID(reader));
+
+          // Skip over any trailing spaces;
+          reader.skipWhitespaces();
+        }
+        while (reader.read() != ')');
+      }
+      else
+      {
+        reader.reset();
+        values = Collections.singleton(readOID(reader));
+      }
+
+      return values;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  /**
+   * Reads the value of a string enclosed in single quotes, skipping
+   * over the quotes and any leading spaces.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The string value read from the definition.
+   * @throws DecodeException
+   *           If a problem is encountered while reading the quoted
+   *           string.
+   */
+  static String readQuotedDescriptor(SubstringReader reader)
+      throws DecodeException
+  {
+    int length = 0;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+
+    try
+    {
+      // The next character must be a single quote.
+      char c = reader.read();
+      if (c != '\'')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_EXPECTED_QUOTE_AT_POS.get(reader.pos() - 1,
+                String.valueOf(c));
+        throw DecodeException.error(message);
+      }
+
+      // Read until we find the closing quote.
+      reader.mark();
+      while ((c = reader.read()) != '\'')
+      {
+        if (length == 0 && !isAlpha(c))
+        {
+          // This is an illegal character.
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                  .valueOf(c), reader.pos() - 1);
+          throw DecodeException.error(message);
+        }
+
+        if (!isAlpha(c) && !isDigit(c) && c != '-' && c != '_'
+            && c != '.')
+        {
+          // This is an illegal character.
+          final Message message =
+              ERR_ATTR_SYNTAX_ILLEGAL_CHAR_IN_STRING_OID.get(String
+                  .valueOf(c), reader.pos() - 1);
+          throw DecodeException.error(message);
+        }
+
+        length++;
+      }
+
+      reader.reset();
+
+      final String descr = reader.read(length);
+      reader.read();
+      return descr;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  /**
+   * Reads the value of a string enclosed in single quotes, skipping
+   * over the quotes and any leading spaces.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The string value read from the definition.
+   * @throws DecodeException
+   *           If a problem is encountered while reading the quoted
+   *           string.
+   */
+  static String readQuotedString(SubstringReader reader)
+      throws DecodeException
+  {
+    int length = 0;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+
+    try
+    {
+      // The next character must be a single quote.
+      final char c = reader.read();
+      if (c != '\'')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_EXPECTED_QUOTE_AT_POS.get(reader.pos() - 1,
+                String.valueOf(c));
+        throw DecodeException.error(message);
+      }
+
+      // Read until we find the closing quote.
+      reader.mark();
+      while (reader.read() != '\'')
+      {
+        length++;
+      }
+
+      reader.reset();
+
+      final String str = reader.read(length);
+      reader.read();
+      return str;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  /**
+   * Reads the next ruleid from the definition, skipping over any
+   * leading spaces.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The ruleid read from the definition.
+   * @throws DecodeException
+   *           If a problem is encountered while reading the token name.
+   */
+  static Integer readRuleID(SubstringReader reader)
+      throws DecodeException
+  {
+    // This must be a ruleid. In that case, we will accept
+    // only digits.
+    int length = 0;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      while (reader.read() != ' ')
+      {
+        length++;
+      }
+
+      if (length == 0)
+      {
+        final Message message = ERR_ATTR_SYNTAX_RULE_ID_NO_VALUE.get();
+        throw DecodeException.error(message);
+      }
+
+      reader.reset();
+      final String ruleID = reader.read(length);
+
+      try
+      {
+        return Integer.valueOf(ruleID);
+      }
+      catch (final NumberFormatException e)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_RULE_ID_INVALID.get(ruleID);
+        throw DecodeException.error(message);
+      }
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  static Set<Integer> readRuleIDs(SubstringReader reader)
+      throws DecodeException
+  {
+    Set<Integer> values;
+
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      final char c = reader.read();
+      if (c == '(')
+      {
+        values = new HashSet<Integer>();
+
+        do
+        {
+          values.add(readRuleID(reader));
+
+          // Skip over any trailing spaces;
+          reader.skipWhitespaces();
+        }
+        while (reader.read() != ')');
+      }
+      else
+      {
+        reader.reset();
+        values = Collections.singleton(readRuleID(reader));
+      }
+
+      return values;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  /**
+   * Reads the next token name from the definition, skipping over any
+   * leading or trailing spaces or <code>null</code> if there are no
+   * moretokens to read.
+   * 
+   * @param reader
+   *          The string representation of the definition.
+   * @return The token name read from the definition or
+   *         <code>null</code> .
+   * @throws DecodeException
+   *           If a problem is encountered while reading the token name.
+   */
+  static String readTokenName(SubstringReader reader)
+      throws DecodeException
+  {
+    String token = null;
+    int length = 0;
+    // Skip over any spaces at the beginning of the value.
+    reader.skipWhitespaces();
+    reader.mark();
+
+    try
+    {
+      // Read until we find the next space.
+      char c;
+      while ((c = reader.read()) != ' ' && c != ')')
+      {
+        length++;
+      }
+
+      if (length > 0)
+      {
+        reader.reset();
+        token = reader.read(length);
+      }
+
+      // Skip over any trailing spaces after the value.
+      reader.skipWhitespaces();
+
+      if (token == null && reader.remaining() > 0)
+      {
+        reader.reset();
+        final Message message =
+            ERR_ATTR_SYNTAX_UNEXPECTED_CLOSE_PARENTHESIS.get(length);
+        throw DecodeException.error(message);
+      }
+
+      return token;
+    }
+    catch (final StringIndexOutOfBoundsException e)
+    {
+      final Message message = ERR_ATTR_SYNTAX_TRUNCATED_VALUE.get();
+      throw DecodeException.error(message);
+    }
+  }
+
+
+
+  // Prevent instantiation.
+  private SchemaUtils()
+  {
+    // Nothing to do.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java
new file mode 100644
index 0000000..0f9e446
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SubstringAssertionSyntaxImpl.java
@@ -0,0 +1,150 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_SUBSTRING_ASSERTION_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.SchemaMessages;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the substring assertion attribute syntax, which
+ * contains one or more substring components, as used in a substring
+ * search filter. For the purposes of matching, it will be treated like
+ * a Directory String syntax except that approximate matching will not
+ * be allowed.
+ */
+final class SubstringAssertionSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_SUBSTRING_ASSERTION_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Get the string representation of the value and check its length.
+    // A zero-length value is acceptable. A one-length value is
+    // acceptable as long as it is not an asterisk. For all other
+    // lengths, just ensure that there are no consecutive wildcards.
+    final String valueString = value.toString();
+    final int valueLength = valueString.length();
+    if (valueLength == 0)
+    {
+      return true;
+    }
+    else if (valueLength == 1)
+    {
+      if (valueString.charAt(0) == '*')
+      {
+        invalidReason
+            .append(SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_ONLY_WILDCARD
+                .get());
+
+        return false;
+      }
+      else
+      {
+        return true;
+      }
+    }
+    else
+    {
+      for (int i = 1; i < valueLength; i++)
+      {
+        if (valueString.charAt(i) == '*'
+            && valueString.charAt(i - 1) == '*')
+        {
+          invalidReason
+              .append(SchemaMessages.WARN_ATTR_SYNTAX_SUBSTRING_CONSECUTIVE_WILDCARDS
+                  .get(valueString, i));
+          return false;
+        }
+      }
+
+      return true;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java
new file mode 100644
index 0000000..732fd66
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SupportedAlgorithmSyntaxImpl.java
@@ -0,0 +1,108 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.schema.SchemaConstants.EMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_OCTET_STRING_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_SUPPORTED_ALGORITHM_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the supported algorithm attribute syntax. This
+ * should be restricted to holding only X.509 supported algorithms, but
+ * we will accept any set of bytes. It will be treated much like the
+ * octet string attribute syntax.
+ */
+final class SupportedAlgorithmSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_OCTET_STRING_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_SUPPORTED_ALGORITHM_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_OCTET_STRING_OID;
+  }
+
+
+
+  @Override
+  public boolean isBEREncodingRequired()
+  {
+    return true;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for the supported algorithm syntax.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/Syntax.java b/sdk/src/org/opends/sdk/schema/Syntax.java
new file mode 100644
index 0000000..efb0392
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/Syntax.java
@@ -0,0 +1,439 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class defines a data structure for storing and interacting with
+ * an LDAP syntaxes, which constrain the structure of attribute values
+ * stored in an LDAP directory, and determine the representation of
+ * attribute and assertion values transferred in the LDAP protocol.
+ * <p>
+ * Syntax implementations must extend the
+ * <code>SyntaxImplementation</code> class so they can be used by OpenDS
+ * to validate attribute values.
+ * <p>
+ * Where ordered sets of names, or extra properties are provided, the
+ * ordering will be preserved when the associated fields are accessed
+ * via their getters or via the {@link #toString()} methods.
+ */
+public final class Syntax extends SchemaElement
+{
+  private final String oid;
+  private final String definition;
+  private MatchingRule equalityMatchingRule;
+  private MatchingRule orderingMatchingRule;
+  private MatchingRule substringMatchingRule;
+  private MatchingRule approximateMatchingRule;
+  private Schema schema;
+  private SyntaxImpl impl;
+
+
+
+  Syntax(String oid, String description,
+      Map<String, List<String>> extraProperties, String definition,
+      SyntaxImpl implementation)
+  {
+    super(description, extraProperties);
+
+    Validator.ensureNotNull(oid);
+    this.oid = oid;
+
+    if (definition != null)
+    {
+      this.definition = definition;
+    }
+    else
+    {
+      this.definition = buildDefinition();
+    }
+    this.impl = implementation;
+  }
+
+  Syntax(String oid)
+  {
+    super("", Collections.singletonMap("X-SUBST",
+        Collections.singletonList(Schema.getDefaultSyntax().getOID())));
+
+    Validator.ensureNotNull(oid);
+    this.oid = oid;
+    this.definition = buildDefinition();
+    this.impl = Schema.getDefaultSyntax().impl;
+  }
+
+
+
+  /**
+   * Retrieves the default approximate matching rule that will be used
+   * for attributes with this syntax.
+   *
+   * @return The default approximate matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if approximate
+   *         matches will not be allowed for this type by default.
+   */
+  public MatchingRule getApproximateMatchingRule()
+  {
+    return approximateMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the default equality matching rule that will be used for
+   * attributes with this syntax.
+   *
+   * @return The default equality matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if equality
+   *         matches will not be allowed for this type by default.
+   */
+  public MatchingRule getEqualityMatchingRule()
+  {
+    return equalityMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the OID for this attribute syntax.
+   *
+   * @return The OID for this attribute syntax.
+   */
+  public String getOID()
+  {
+    return oid;
+  }
+
+
+
+  /**
+   * Retrieves the default ordering matching rule that will be used for
+   * attributes with this syntax.
+   *
+   * @return The default ordering matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if ordering
+   *         matches will not be allowed for this type by default.
+   */
+  public MatchingRule getOrderingMatchingRule()
+  {
+    return orderingMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the default substring matching rule that will be used for
+   * attributes with this syntax.
+   *
+   * @return The default substring matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if substring
+   *         matches will not be allowed for this type by default.
+   */
+  public MatchingRule getSubstringMatchingRule()
+  {
+    return substringMatchingRule;
+  }
+
+
+
+  /**
+   * Retrieves the hash code for this schema element. It will be
+   * calculated as the sum of the characters in the OID.
+   *
+   * @return The hash code for this attribute syntax.
+   */
+  @Override
+  public int hashCode()
+  {
+    return getOID().hashCode();
+  }
+
+
+
+  /**
+   * Indicates whether this attribute syntax requires that values must
+   * be encoded using the Basic Encoding Rules (BER) used by X.500
+   * directories and always include the {@code binary} attribute
+   * description option.
+   *
+   * @return {@code true} this attribute syntax requires that values
+   *         must be BER encoded and always include the {@code binary}
+   *         attribute description option, or {@code false} if not.
+   * @see <a href="http://tools.ietf.org/html/rfc4522">RFC 4522 -
+   *      Lightweight Directory Access Protocol (LDAP): The Binary
+   *      Encoding Option </a>
+   */
+  public boolean isBEREncodingRequired()
+  {
+    return impl.isBEREncodingRequired();
+  }
+
+
+
+  /**
+   * Indicates whether this attribute syntax would likely be a human
+   * readable string.
+   *
+   * @return {@code true} if this attribute syntax would likely be a
+   *         human readable string or {@code false} if not.
+   */
+  public boolean isHumanReadable()
+  {
+    return impl.isHumanReadable();
+  }
+
+
+
+  /**
+   * Retrieves a string representation of this attribute syntax in the
+   * format defined in RFC 2252.
+   *
+   * @return A string representation of this attribute syntax in the
+   *         format defined in RFC 2252.
+   */
+  @Override
+  public String toString()
+  {
+    return definition;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   *
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return {@code true} if the provided value is acceptable for use
+   *         with this syntax, or {@code false} if not.
+   */
+  public boolean valueIsAcceptable(ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    return impl.valueIsAcceptable(schema, value, invalidReason);
+  }
+
+
+
+  Syntax duplicate()
+  {
+    return new Syntax(oid, description, extraProperties, definition,
+        impl);
+  }
+
+
+
+  @Override
+  void toStringContent(StringBuilder buffer)
+  {
+    buffer.append(oid);
+
+    if (description != null && description.length() > 0)
+    {
+      buffer.append(" DESC '");
+      buffer.append(description);
+      buffer.append("'");
+    }
+  }
+
+
+
+  @Override
+  void validate(List<Message> warnings, Schema schema)
+      throws SchemaException
+  {
+    this.schema = schema;
+    if (impl == null)
+    {
+      // See if we need to override the implementation of the syntax
+      for (final Map.Entry<String, List<String>> property : extraProperties
+          .entrySet())
+      {
+        // Enums are handled in the schema builder.
+        if (property.getKey().equalsIgnoreCase("x-subst"))
+        {
+          /**
+           * One unimplemented syntax can be substituted by another
+           * defined syntax. A substitution syntax is an
+           * LDAPSyntaxDescriptionSyntax with X-SUBST extension.
+           */
+          final Iterator<String> values =
+              property.getValue().iterator();
+          if (values.hasNext())
+          {
+            final String value = values.next();
+            if (value.equals(oid))
+            {
+              final Message message =
+                  ERR_ATTR_SYNTAX_CYCLIC_SUB_SYNTAX.get(oid);
+              throw new SchemaException(message);
+            }
+            if (!schema.hasSyntax(value))
+            {
+              final Message message =
+                  ERR_ATTR_SYNTAX_UNKNOWN_SUB_SYNTAX.get(oid, value);
+              throw new SchemaException(message);
+            }
+            final Syntax subSyntax = schema.getSyntax(value);
+            if (subSyntax.impl == null)
+            {
+              // The substitution syntax was never validated.
+              subSyntax.validate(warnings, schema);
+            }
+            impl = subSyntax.impl;
+          }
+        }
+        else if (property.getKey().equalsIgnoreCase("x-pattern"))
+        {
+          final Iterator<String> values =
+              property.getValue().iterator();
+          if (values.hasNext())
+          {
+            final String value = values.next();
+            try
+            {
+              final Pattern pattern = Pattern.compile(value);
+              impl = new RegexSyntaxImpl(pattern);
+            }
+            catch (final Exception e)
+            {
+              final Message message =
+                  WARN_ATTR_SYNTAX_LDAPSYNTAX_REGEX_INVALID_PATTERN
+                      .get(oid, value);
+              throw new SchemaException(message);
+            }
+          }
+        }
+      }
+
+      // Try to find an implementation in the core schema
+      if (impl == null && Schema.getDefaultSchema().hasSyntax(oid))
+      {
+        impl = Schema.getDefaultSchema().getSyntax(oid).impl;
+      }
+      if (impl == null && Schema.getCoreSchema().hasSyntax(oid))
+      {
+        impl = Schema.getCoreSchema().getSyntax(oid).impl;
+      }
+
+      if (impl == null)
+      {
+        impl = Schema.getDefaultSyntax().impl;
+        final Message message =
+            WARN_ATTR_SYNTAX_NOT_IMPLEMENTED.get(oid, Schema
+                .getDefaultSyntax().getOID());
+        warnings.add(message);
+      }
+    }
+
+    // Get references to the default matching rules. It will be ok
+    // if we can't find some. Just warn.
+    if (impl.getEqualityMatchingRule() != null)
+    {
+      if (schema.hasMatchingRule(impl.getEqualityMatchingRule()))
+      {
+        equalityMatchingRule =
+            schema.getMatchingRule(impl.getEqualityMatchingRule());
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(impl
+                .getEqualityMatchingRule(), impl.getName());
+        warnings.add(message);
+      }
+    }
+
+    if (impl.getOrderingMatchingRule() != null)
+    {
+      if (schema.hasMatchingRule(impl.getOrderingMatchingRule()))
+      {
+        orderingMatchingRule =
+            schema.getMatchingRule(impl.getOrderingMatchingRule());
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(impl
+                .getOrderingMatchingRule(), impl.getName());
+        warnings.add(message);
+      }
+    }
+
+    if (impl.getSubstringMatchingRule() != null)
+    {
+      if (schema.hasMatchingRule(impl.getSubstringMatchingRule()))
+      {
+        substringMatchingRule =
+            schema.getMatchingRule(impl.getSubstringMatchingRule());
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(impl
+                .getSubstringMatchingRule(), impl.getName());
+        warnings.add(message);
+      }
+    }
+
+    if (impl.getApproximateMatchingRule() != null)
+    {
+      if (schema.hasMatchingRule(impl.getApproximateMatchingRule()))
+      {
+        approximateMatchingRule =
+            schema.getMatchingRule(impl.getApproximateMatchingRule());
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UNKNOWN_APPROXIMATE_MATCHING_RULE.get(impl
+                .getApproximateMatchingRule(), impl.getName());
+        warnings.add(message);
+      }
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/SyntaxImpl.java b/sdk/src/org/opends/sdk/schema/SyntaxImpl.java
new file mode 100644
index 0000000..a232461
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/SyntaxImpl.java
@@ -0,0 +1,144 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines the set of methods and structures that must be
+ * implemented to define a new attribute syntax.
+ */
+public interface SyntaxImpl
+{
+  /**
+   * Retrieves the default approximate matching rule that will be used
+   * for attributes with this syntax.
+   * 
+   * @return The default approximate matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if approximate
+   *         matches will not be allowed for this type by default.
+   */
+  public String getApproximateMatchingRule();
+
+
+
+  /**
+   * Retrieves the default equality matching rule that will be used for
+   * attributes with this syntax.
+   * 
+   * @return The default equality matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if equality
+   *         matches will not be allowed for this type by default.
+   */
+  public String getEqualityMatchingRule();
+
+
+
+  /**
+   * Retrieves the common name for this attribute syntax.
+   * 
+   * @return The common name for this attribute syntax.
+   */
+  public String getName();
+
+
+
+  /**
+   * Retrieves the default ordering matching rule that will be used for
+   * attributes with this syntax.
+   * 
+   * @return The default ordering matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if ordering
+   *         matches will not be allowed for this type by default.
+   */
+  public String getOrderingMatchingRule();
+
+
+
+  /**
+   * Retrieves the default substring matching rule that will be used for
+   * attributes with this syntax.
+   * 
+   * @return The default substring matching rule that will be used for
+   *         attributes with this syntax, or {@code null} if substring
+   *         matches will not be allowed for this type by default.
+   */
+  public String getSubstringMatchingRule();
+
+
+
+  /**
+   * Indicates whether this attribute syntax requires that values must
+   * be encoded using the Basic Encoding Rules (BER) used by X.500
+   * directories and always include the {@code binary} attribute
+   * description option.
+   * 
+   * @return {@code true} this attribute syntax requires that values
+   *         must be BER encoded and always include the {@code binary}
+   *         attribute description option, or {@code false} if not.
+   * @see <a href="http://tools.ietf.org/html/rfc4522">RFC 4522 -
+   *      Lightweight Directory Access Protocol (LDAP): The Binary
+   *      Encoding Option </a>
+   */
+  public boolean isBEREncodingRequired();
+
+
+
+  /**
+   * Indicates whether this attribute syntax would likely be a human
+   * readable string.
+   * 
+   * @return {@code true} if this attribute syntax would likely be a
+   *         human readable string or {@code false} if not.
+   */
+  boolean isHumanReadable();
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return {@code true} if the provided value is acceptable for use
+   *         with this syntax, or {@code false} if not.
+   */
+  boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason);
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..9c33ad0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelephoneNumberEqualityMatchingRuleImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the telephoneNumberMatch matching rule defined
+ * in X.520 and referenced in RFC 2252. Note that although the
+ * specification calls for a very rigorous format, this is widely
+ * ignored so this matching will compare only numeric digits and strip
+ * out everything else.
+ */
+final class TelephoneNumberEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final String valueString = value.toString();
+    final int valueLength = valueString.length();
+    final StringBuilder buffer = new StringBuilder(valueLength);
+
+    // Iterate through the characters in the value and filter out
+    // everything that isn't a digit.
+    for (int i = 0; i < valueLength; i++)
+    {
+      final char c = valueString.charAt(i);
+      if (StaticUtils.isDigit(c))
+      {
+        buffer.append(c);
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java
new file mode 100644
index 0000000..3d52f58
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelephoneNumberSubstringMatchingRuleImpl.java
@@ -0,0 +1,67 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the telephoneNumberSubstringsMatch matching
+ * rule defined in X.520 and referenced in RFC 2252. Note that although
+ * the specification calls for a very rigorous format, this is widely
+ * ignored so this matching will compare only numeric digits and strip
+ * out everything else.
+ */
+final class TelephoneNumberSubstringMatchingRuleImpl extends
+    AbstractSubstringMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    final String valueString = value.toString();
+    final int valueLength = valueString.length();
+    final StringBuilder buffer = new StringBuilder(valueLength);
+
+    // Iterate through the characters in the value and filter out
+    // everything that isn't a digit.
+    for (int i = 0; i < valueLength; i++)
+    {
+      final char c = valueString.charAt(i);
+      if (StaticUtils.isDigit(c))
+      {
+        buffer.append(c);
+      }
+    }
+
+    return ByteString.valueOf(buffer.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java
new file mode 100644
index 0000000..37aad35
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelephoneNumberSyntaxImpl.java
@@ -0,0 +1,203 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_EMPTY;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_ILLEGAL_CHAR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_NO_DIGITS;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEPHONE_NO_PLUS;
+import static org.opends.sdk.schema.SchemaConstants.EMR_TELEPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_TELEPHONE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_TELEPHONE_NAME;
+import static org.opends.sdk.util.StaticUtils.isDigit;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the telephone number attribute syntax, which is
+ * defined in RFC 2252. Note that this can have two modes of operation,
+ * depending on its configuration. Most of the time, it will be very
+ * lenient when deciding what to accept, and will allow anything but
+ * only pay attention to the digits. However, it can also be configured
+ * in a "strict" mode, in which case it will only accept values in the
+ * E.123 international telephone number format.
+ */
+final class TelephoneNumberSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_TELEPHONE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_TELEPHONE_NAME;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_TELEPHONE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // No matter what, the value can't be empty or null.
+    String valueStr;
+    if (value == null
+        || (valueStr = value.toString().trim()).length() == 0)
+    {
+      invalidReason.append(ERR_ATTR_SYNTAX_TELEPHONE_EMPTY.get());
+      return false;
+    }
+
+    final int length = valueStr.length();
+
+    if (schema.getSchemaCompatOptions().isTelephoneNumberSyntaxStrict())
+    {
+      // If the value does not start with a plus sign, then that's not
+      // acceptable.
+      if (valueStr.charAt(0) != '+')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_TELEPHONE_NO_PLUS.get(valueStr);
+        invalidReason.append(message);
+        return false;
+      }
+
+      // Iterate through the remaining characters in the value. There
+      // must be at least one digit, and it must contain only valid
+      // digits and separator characters.
+      boolean digitSeen = false;
+      for (int i = 1; i < length; i++)
+      {
+        final char c = valueStr.charAt(i);
+        if (isDigit(c))
+        {
+          digitSeen = true;
+        }
+        else if (!isSeparator(c))
+        {
+          final Message message =
+              ERR_ATTR_SYNTAX_TELEPHONE_ILLEGAL_CHAR.get(valueStr,
+                  String.valueOf(c), i);
+          invalidReason.append(message);
+          return false;
+        }
+      }
+
+      if (!digitSeen)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_TELEPHONE_NO_DIGITS.get(valueStr);
+        invalidReason.append(message);
+        return false;
+      }
+
+      // If we've gotten here, then we'll consider it acceptable.
+      return true;
+    }
+    else
+    {
+      // If we are not in strict mode, then all non-empty values
+      // containing at least one digit will be acceptable.
+      for (int i = 0; i < length; i++)
+      {
+        if (isDigit(valueStr.charAt(i)))
+        {
+          return true;
+        }
+      }
+
+      // If we made it here, then we didn't find any digits.
+      final Message message =
+          ERR_ATTR_SYNTAX_TELEPHONE_NO_DIGITS.get(valueStr);
+      invalidReason.append(message);
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Indicates whether the provided character is a valid separator for
+   * telephone number components when operating in strict mode.
+   * 
+   * @param c
+   *          The character for which to make the determination.
+   * @return <CODE>true</CODE> if the provided character is a valid
+   *         separator, or <CODE>false</CODE> if it is not.
+   */
+  private boolean isSeparator(char c)
+  {
+    switch (c)
+    {
+    case ' ':
+    case '-':
+      return true;
+    default:
+      return false;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java
new file mode 100644
index 0000000..32f6fe5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TeletexTerminalIdentifierSyntaxImpl.java
@@ -0,0 +1,271 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_TELETEX_TERM_ID_NAME;
+
+import java.util.HashSet;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the teletex terminal identifier attribute
+ * syntax, which contains a printable string (the terminal identifier)
+ * followed by zero or more parameters, which start with a dollar sign
+ * and are followed by a parameter name, a colon, and a value. The
+ * parameter value should consist of any string of bytes (the dollar
+ * sign and backslash must be escaped with a preceding backslash), and
+ * the parameter name must be one of the following strings:
+ * <UL>
+ * <LI>graphic</LI>
+ * <LI>control</LI>
+ * <LI>misc</LI>
+ * <LI>page</LI>
+ * <LI>private</LI>
+ * </UL>
+ */
+final class TeletexTerminalIdentifierSyntaxImpl extends
+    AbstractSyntaxImpl
+{
+  /**
+   * The set of allowed fax parameter values, formatted entirely in
+   * lowercase characters.
+   */
+  private static final HashSet<String> ALLOWED_TTX_PARAMETERS =
+      new HashSet<String>(5);
+
+  static
+  {
+    ALLOWED_TTX_PARAMETERS.add("graphic");
+    ALLOWED_TTX_PARAMETERS.add("control");
+    ALLOWED_TTX_PARAMETERS.add("misc");
+    ALLOWED_TTX_PARAMETERS.add("page");
+    ALLOWED_TTX_PARAMETERS.add("private");
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_TELETEX_TERM_ID_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Get a lowercase string representation of the value and find its
+    // length.
+    final String valueString = value.toString();
+    final int valueLength = valueString.length();
+
+    // The value must contain at least one character.
+    if (valueLength == 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_EMPTY.get());
+      return false;
+    }
+
+    // The first character must be a printable string character.
+    char c = valueString.charAt(0);
+    if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_NOT_PRINTABLE.get(
+          valueString, String.valueOf(c), 0));
+      return false;
+    }
+
+    // Continue reading until we find a dollar sign or the end of the
+    // string. Every intermediate character must be a printable string
+    // character.
+    int pos = 1;
+    for (; pos < valueLength; pos++)
+    {
+      c = valueString.charAt(pos);
+      if (c == '$')
+      {
+        pos++;
+        break;
+      }
+      else
+      {
+        if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+        {
+
+          invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_NOT_PRINTABLE
+              .get(valueString, String.valueOf(c), pos));
+        }
+      }
+    }
+
+    if (pos >= valueLength)
+    {
+      // We're at the end of the value, so it must be valid unless the
+      // last character was a dollar sign.
+      if (c == '$')
+      {
+
+        invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_END_WITH_DOLLAR
+            .get(valueString));
+        return false;
+      }
+      else
+      {
+        return true;
+      }
+    }
+
+    // Continue reading until we find the end of the string. Each
+    // substring must be a valid teletex terminal identifier parameter
+    // followed by a colon and the value. Dollar signs must be escaped
+    int paramStartPos = pos;
+    boolean escaped = false;
+    while (pos < valueLength)
+    {
+      if (escaped)
+      {
+        pos++;
+        continue;
+      }
+
+      c = valueString.charAt(pos++);
+      if (c == '\\')
+      {
+        escaped = true;
+        continue;
+      }
+      else if (c == '$')
+      {
+        final String paramStr =
+            valueString.substring(paramStartPos, pos);
+
+        final int colonPos = paramStr.indexOf(':');
+        if (colonPos < 0)
+        {
+
+          invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_PARAM_NO_COLON
+              .get(valueString));
+          return false;
+        }
+
+        final String paramName = paramStr.substring(0, colonPos);
+        if (!ALLOWED_TTX_PARAMETERS.contains(paramName))
+        {
+
+          invalidReason
+              .append(ERR_ATTR_SYNTAX_TELETEXID_ILLEGAL_PARAMETER.get(
+                  valueString, paramName));
+          return false;
+        }
+
+        paramStartPos = pos;
+      }
+    }
+
+    // We must be at the end of the value. Read the last parameter and
+    // make sure it is valid.
+    final String paramStr = valueString.substring(paramStartPos);
+    final int colonPos = paramStr.indexOf(':');
+    if (colonPos < 0)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_PARAM_NO_COLON
+          .get(valueString));
+      return false;
+    }
+
+    final String paramName = paramStr.substring(0, colonPos);
+    if (!ALLOWED_TTX_PARAMETERS.contains(paramName))
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELETEXID_ILLEGAL_PARAMETER
+          .get(valueString, paramName));
+      return false;
+    }
+
+    // If we've gotten here, then the value must be valid.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java
new file mode 100644
index 0000000..b171593
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/TelexNumberSyntaxImpl.java
@@ -0,0 +1,230 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_TOO_SHORT;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_TELEX_TRUNCATED;
+import static org.opends.sdk.schema.SchemaConstants.EMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.OMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SMR_CASE_IGNORE_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_TELEX_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the telex number attribute syntax, which
+ * contains three printable strings separated by dollar sign characters.
+ * Equality, ordering, and substring matching will be allowed by
+ * default.
+ */
+final class TelexNumberSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_TELEX_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_CASE_IGNORE_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Get a string representation of the value and find its length.
+    final String valueString = value.toString();
+    final int valueLength = valueString.length();
+
+    if (valueLength < 5)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELEX_TOO_SHORT
+          .get(valueString));
+      return false;
+    }
+
+    // The first character must be a printable string character.
+    char c = valueString.charAt(0);
+    if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE.get(
+          valueString, String.valueOf(c), 0));
+      return false;
+    }
+
+    // Continue reading until we find a dollar sign. Every intermediate
+    // character must be a printable string character.
+    int pos = 1;
+    for (; pos < valueLength; pos++)
+    {
+      c = valueString.charAt(pos);
+      if (c == '$')
+      {
+        pos++;
+        break;
+      }
+      else
+      {
+        if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+        {
+
+          invalidReason.append(ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR.get(
+              valueString, String.valueOf(c), pos));
+        }
+      }
+    }
+
+    if (pos >= valueLength)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELEX_TRUNCATED
+          .get(valueString));
+      return false;
+    }
+
+    // The next character must be a printable string character.
+    c = valueString.charAt(pos++);
+    if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE.get(
+          valueString, String.valueOf(c), (pos - 1)));
+      return false;
+    }
+
+    // Continue reading until we find another dollar sign. Every
+    // intermediate character must be a printable string character.
+    for (; pos < valueLength; pos++)
+    {
+      c = valueString.charAt(pos);
+      if (c == '$')
+      {
+        pos++;
+        break;
+      }
+      else
+      {
+        if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+        {
+
+          invalidReason.append(ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR.get(
+              valueString, String.valueOf(c), pos));
+          return false;
+        }
+      }
+    }
+
+    if (pos >= valueLength)
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELEX_TRUNCATED
+          .get(valueString));
+      return false;
+    }
+
+    // The next character must be a printable string character.
+    c = valueString.charAt(pos++);
+    if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+    {
+
+      invalidReason.append(ERR_ATTR_SYNTAX_TELEX_NOT_PRINTABLE.get(
+          valueString, String.valueOf(c), (pos - 1)));
+      return false;
+    }
+
+    // Continue reading until the end of the value. Every intermediate
+    // character must be a printable string character.
+    for (; pos < valueLength; pos++)
+    {
+      c = valueString.charAt(pos);
+      if (!PrintableStringSyntaxImpl.isPrintableCharacter(c))
+      {
+
+        invalidReason.append(ERR_ATTR_SYNTAX_TELEX_ILLEGAL_CHAR.get(
+            valueString, String.valueOf(c), pos));
+        return false;
+      }
+    }
+
+    // If we've gotten here, then we're at the end of the value and it
+    // is acceptable.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java
new file mode 100644
index 0000000..7cf9295
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UTCTimeSyntaxImpl.java
@@ -0,0 +1,767 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.*;
+import static org.opends.sdk.schema.SchemaConstants.*;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the UTC time attribute syntax. This is very
+ * similar to the generalized time syntax (and actually has been
+ * deprecated in favor of that), but requires that the minute be
+ * provided and does not allow for sub-second times. All matching will
+ * be performed using the generalized time matching rules, and equality,
+ * ordering, and substring matching will be allowed.
+ */
+final class UTCTimeSyntaxImpl extends AbstractSyntaxImpl
+{
+
+  /**
+   * The lock that will be used to provide threadsafe access to the date
+   * formatter.
+   */
+  private final static Object dateFormatLock;
+
+  /**
+   * The date formatter that will be used to convert dates into UTC time
+   * values. Note that all interaction with it must be synchronized.
+   */
+  private final static SimpleDateFormat dateFormat;
+
+  /*
+   * Create the date formatter that will be used to construct and parse
+   * normalized UTC time values.
+   */
+  static
+  {
+    dateFormat = new SimpleDateFormat(DATE_FORMAT_UTC_TIME);
+    dateFormat.setLenient(false);
+    dateFormat.setTimeZone(TimeZone.getTimeZone(TIME_ZONE_UTC));
+
+    dateFormatLock = new Object();
+  }
+
+
+
+  /**
+   * Retrieves an string containing a UTC time representation of the
+   * provided date.
+   * 
+   * @param d
+   *          The date for which to retrieve the UTC time value.
+   * @return The attribute value created from the date.
+   */
+  static String createUTCTimeValue(Date d)
+  {
+    synchronized (dateFormatLock)
+    {
+      return dateFormat.format(d);
+    }
+  }
+
+
+
+  /**
+   * Decodes the provided normalized value as a UTC time value and
+   * retrieves a Java <CODE>Date</CODE> object containing its
+   * representation.
+   * 
+   * @param valueString
+   *          The normalized UTC time value to decode to a Java
+   *          <CODE>Date</CODE>.
+   * @return The Java <CODE>Date</CODE> created from the provided UTC
+   *         time value.
+   * @throws DecodeException
+   *           If the provided value cannot be parsed as a valid UTC
+   *           time string.
+   */
+  static Date decodeUTCTimeValue(String valueString)
+      throws DecodeException
+  {
+    try
+    {
+      synchronized (dateFormatLock)
+      {
+        return dateFormat.parse(valueString);
+      }
+    }
+    catch (final Exception e)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_CANNOT_PARSE.get(valueString, String
+              .valueOf(e));
+      final DecodeException de = DecodeException.error(message, e);
+      StaticUtils.DEBUG_LOG.throwing("UTCTimeSyntax",
+          "decodeUTCTimeValue", de);
+      throw de;
+    }
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_GENERALIZED_TIME_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_UTC_TIME_NAME;
+  }
+
+
+
+  @Override
+  public String getOrderingMatchingRule()
+  {
+    return OMR_GENERALIZED_TIME_OID;
+  }
+
+
+
+  @Override
+  public String getSubstringMatchingRule()
+  {
+    return SMR_CASE_IGNORE_OID;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // Get the value as a string and verify that it is at least long
+    // enough for "YYYYMMDDhhmmZ", which is the shortest allowed value.
+    final String valueString = value.toString().toUpperCase();
+    final int length = valueString.length();
+    if (length < 11)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_TOO_SHORT.get(valueString);
+      invalidReason.append(message);
+      return false;
+    }
+
+    // The first two characters are the year, and they must be numeric
+    // digits between 0 and 9.
+    for (int i = 0; i < 2; i++)
+    {
+      switch (valueString.charAt(i))
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_YEAR.get(valueString,
+                String.valueOf(valueString.charAt(i)));
+        invalidReason.append(message);
+        return false;
+      }
+    }
+
+    // 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(2);
+    final char m2 = valueString.charAt(3);
+    switch (m1)
+    {
+    case '0':
+      // m2 must be a digit between 1 and 9.
+      switch (m2)
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(valueString,
+                valueString.substring(2, 4));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    case '1':
+      // m2 must be a digit between 0 and 2.
+      switch (m2)
+      {
+      case '0':
+      case '1':
+      case '2':
+        // These are all fine.
+        break;
+      default:
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(valueString,
+                valueString.substring(2, 4));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    default:
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MONTH.get(valueString,
+              valueString.substring(2, 4));
+      invalidReason.append(message);
+      return false;
+    }
+
+    // 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(4);
+    final char d2 = valueString.charAt(5);
+    switch (d1)
+    {
+    case '0':
+      // d2 must be a digit between 1 and 9.
+      switch (d2)
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+                valueString.substring(4, 6));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    case '1':
+      // Treated the same as '2'.
+    case '2':
+      // d2 must be a digit between 0 and 9.
+      switch (d2)
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+                valueString.substring(4, 6));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    case '3':
+      // d2 must be either 0 or 1.
+      switch (d2)
+      {
+      case '0':
+      case '1':
+        // These are all fine.
+        break;
+      default:
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+                valueString.substring(4, 6));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    default:
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_INVALID_DAY.get(valueString,
+              valueString.substring(4, 6));
+      invalidReason.append(message);
+      return false;
+    }
+
+    // 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(6);
+    final char h2 = valueString.charAt(7);
+    switch (h1)
+    {
+    case '0':
+      // This is treated the same as '1'.
+    case '1':
+      switch (h2)
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(valueString,
+                valueString.substring(6, 8));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    case '2':
+      // This must be a digit between 0 and 3.
+      switch (h2)
+      {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+        // These are all fine.
+        break;
+      default:
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(valueString,
+                valueString.substring(6, 8));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    default:
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_INVALID_HOUR.get(valueString,
+              valueString.substring(6, 8));
+      invalidReason.append(message);
+      return false;
+    }
+
+    // Next, there should be two digits comprising an integer between 00
+    // and 59 for the minute.
+    m1 = valueString.charAt(8);
+    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 < 11)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+                String.valueOf(m1), 8);
+        invalidReason.append(message);
+        return false;
+      }
+
+      switch (valueString.charAt(9))
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_MINUTE.get(valueString,
+                valueString.substring(8, 10));
+        invalidReason.append(message);
+        return false;
+      }
+
+      break;
+
+    default:
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString, String
+              .valueOf(m1), 8);
+      invalidReason.append(message);
+      return false;
+    }
+
+    // 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), or a plus or minus
+    // sign followed by four digits (for the UTC offset).
+    final char s1 = valueString.charAt(10);
+    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 < 13)
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+                String.valueOf(s1), 10);
+        invalidReason.append(message);
+        return false;
+      }
+
+      switch (valueString.charAt(11))
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_SECOND.get(valueString,
+                valueString.substring(10, 12));
+        invalidReason.append(message);
+        return false;
+      }
+
+      break;
+    case '6':
+      // There must be at least two more characters and the next one
+      // must be a 0.
+      if (length < 13)
+      {
+
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+                String.valueOf(s1), 10);
+        invalidReason.append(message);
+        return false;
+      }
+
+      if (valueString.charAt(11) != '0')
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_SECOND.get(valueString,
+                valueString.substring(10, 12));
+        invalidReason.append(message);
+        return false;
+      }
+
+      break;
+    case 'Z':
+      // This is fine only if we are at the end of the value.
+      if (length == 11)
+      {
+        return true;
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+                String.valueOf(s1), 10);
+        invalidReason.append(message);
+        return false;
+      }
+
+    case '+':
+    case '-':
+      // These are fine only if there are exactly four more digits that
+      // specify a valid offset.
+      if (length == 15)
+      {
+        return hasValidOffset(valueString, 11, invalidReason);
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+                String.valueOf(s1), 10);
+        invalidReason.append(message);
+        return false;
+      }
+
+    default:
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString, String
+              .valueOf(s1), 10);
+      invalidReason.append(message);
+      return false;
+    }
+
+    // The last element should be either a letter 'Z' (for the UTC
+    // specifier), or a plus or minus sign followed by four digits (for
+    // the UTC offset).
+    switch (valueString.charAt(12))
+    {
+    case 'Z':
+      // This is fine only if we are at the end of the value.
+      if (length == 13)
+      {
+        return true;
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+                String.valueOf(valueString.charAt(12)), 12);
+        invalidReason.append(message);
+        return false;
+      }
+
+    case '+':
+    case '-':
+      // These are fine only if there are four or two more digits that
+      // specify a valid offset.
+      if (length == 17 || length == 15)
+      {
+        return hasValidOffset(valueString, 13, invalidReason);
+      }
+      else
+      {
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString,
+                String.valueOf(valueString.charAt(12)), 12);
+        invalidReason.append(message);
+        return false;
+      }
+
+    default:
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_INVALID_CHAR.get(valueString, String
+              .valueOf(valueString.charAt(12)), 12);
+      invalidReason.append(message);
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Indicates whether the provided string contains a valid set of two
+   * or four UTC offset digits. The provided string must have either two
+   * or four characters from the provided start position to the end of
+   * the value.
+   * 
+   * @param value
+   *          The whole value, including the offset.
+   * @param startPos
+   *          The position of the first character that is contained in
+   *          the offset.
+   * @param invalidReason
+   *          The buffer to which the invalid reason may be appended if
+   *          the string does not contain a valid set of UTC offset
+   *          digits.
+   * @return <CODE>true</CODE> if the provided offset string is valid,
+   *         or <CODE>false</CODE> if it is not.
+   */
+  private boolean hasValidOffset(String value, int startPos,
+      MessageBuilder invalidReason)
+  {
+    final int offsetLength = value.length() - startPos;
+    if (offsetLength < 2)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_TOO_SHORT.get(value);
+      invalidReason.append(message);
+      return false;
+    }
+
+    // The first two characters must be an integer between 00 and 23.
+    switch (value.charAt(startPos))
+    {
+    case '0':
+    case '1':
+      switch (value.charAt(startPos + 1))
+      {
+      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 Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+                .substring(startPos, startPos + offsetLength));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    case '2':
+      switch (value.charAt(startPos + 1))
+      {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+        // These are all fine.
+        break;
+      default:
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+                .substring(startPos, startPos + offsetLength));
+        invalidReason.append(message);
+        return false;
+      }
+      break;
+    default:
+      final Message message =
+          ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+              .substring(startPos, startPos + offsetLength));
+      invalidReason.append(message);
+      return false;
+    }
+
+    // If there are two more characters, then they must be an integer
+    // between 00 and 59.
+    if (offsetLength == 4)
+    {
+      switch (value.charAt(startPos + 2))
+      {
+      case '0':
+      case '1':
+      case '2':
+      case '3':
+      case '4':
+      case '5':
+        switch (value.charAt(startPos + 3))
+        {
+        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 Message message =
+              ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+                  .substring(startPos, startPos + offsetLength));
+          invalidReason.append(message);
+          return false;
+        }
+        break;
+      default:
+        final Message message =
+            ERR_ATTR_SYNTAX_UTC_TIME_INVALID_OFFSET.get(value, value
+                .substring(startPos, startPos + offsetLength));
+        invalidReason.append(message);
+        return false;
+      }
+    }
+
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..6d739b5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UUIDEqualityMatchingRuleImpl.java
@@ -0,0 +1,135 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the uuidMatch matching rule defined in RFC 4530.
+ * It will be used as the default equality matching rule for the UUID
+ * syntax.
+ */
+final class UUIDEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    if (value.length() != 36)
+    {
+      final Message message =
+          WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH.get(value.toString(),
+              value.length());
+      throw DecodeException.error(message);
+    }
+
+    final StringBuilder builder = new StringBuilder(36);
+    char c;
+    for (int i = 0; i < 36; i++)
+    {
+      // The 9th, 14th, 19th, and 24th characters must be dashes. All
+      // others must be hex. Convert all uppercase hex characters to
+      // lowercase.
+      c = (char) value.byteAt(i);
+      switch (i)
+      {
+      case 8:
+      case 13:
+      case 18:
+      case 23:
+        if (c != '-')
+        {
+          final Message message =
+              WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH.get(value.toString(),
+                  i, String.valueOf(c));
+          throw DecodeException.error(message);
+        }
+        builder.append(c);
+        break;
+      default:
+        switch (c)
+        {
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case 'a':
+        case 'b':
+        case 'c':
+        case 'd':
+        case 'e':
+        case 'f':
+          // These are all fine.
+          builder.append(c);
+          break;
+        case 'A':
+          builder.append('a');
+          break;
+        case 'B':
+          builder.append('b');
+          break;
+        case 'C':
+          builder.append('c');
+          break;
+        case 'D':
+          builder.append('d');
+          break;
+        case 'E':
+          builder.append('e');
+          break;
+        case 'F':
+          builder.append('f');
+          break;
+        default:
+          final Message message =
+              WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX.get(value.toString(),
+                  i, String.valueOf(value.byteAt(i)));
+          throw DecodeException.error(message);
+        }
+      }
+    }
+
+    return ByteString.valueOf(builder.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java
new file mode 100644
index 0000000..2ce5503
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UUIDOrderingMatchingRuleImpl.java
@@ -0,0 +1,135 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH;
+
+import org.opends.messages.Message;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class defines the uuidOrderingMatch matching rule defined in RFC
+ * 4530. This will be the default ordering matching rule for the UUID
+ * syntax.
+ */
+final class UUIDOrderingMatchingRuleImpl extends
+    AbstractOrderingMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    if (value.length() != 36)
+    {
+      final Message message =
+          WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH.get(value.toString(),
+              value.length());
+      throw DecodeException.error(message);
+    }
+
+    final StringBuilder builder = new StringBuilder(36);
+    char c;
+    for (int i = 0; i < 36; i++)
+    {
+      // The 9th, 14th, 19th, and 24th characters must be dashes. All
+      // others must be hex. Convert all uppercase hex characters to
+      // lowercase.
+      c = (char) value.byteAt(i);
+      switch (i)
+      {
+      case 8:
+      case 13:
+      case 18:
+      case 23:
+        if (c != '-')
+        {
+          final Message message =
+              WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH.get(value.toString(),
+                  i, String.valueOf(c));
+          throw DecodeException.error(message);
+        }
+        builder.append(c);
+        break;
+      default:
+        switch (c)
+        {
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case 'a':
+        case 'b':
+        case 'c':
+        case 'd':
+        case 'e':
+        case 'f':
+          // These are all fine.
+          builder.append(c);
+          break;
+        case 'A':
+          builder.append('a');
+          break;
+        case 'B':
+          builder.append('b');
+          break;
+        case 'C':
+          builder.append('c');
+          break;
+        case 'D':
+          builder.append('d');
+          break;
+        case 'E':
+          builder.append('e');
+          break;
+        case 'F':
+          builder.append('f');
+          break;
+        default:
+          final Message message =
+              WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX.get(value.toString(),
+                  i, String.valueOf(value.byteAt(i)));
+          throw DecodeException.error(message);
+        }
+      }
+    }
+
+    return ByteString.valueOf(builder.toString());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java
new file mode 100644
index 0000000..c8df196
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UUIDSyntaxImpl.java
@@ -0,0 +1,150 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX;
+import static org.opends.messages.SchemaMessages.WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_UUID_NAME;
+
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class implements the UUID syntax, which is defined in RFC 4530.
+ * Equality and ordering matching will be allowed by default.
+ */
+final class UUIDSyntaxImpl extends AbstractSyntaxImpl
+{
+  public String getName()
+  {
+    return SYNTAX_UUID_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in an
+   * attribute with this syntax. If it is not, then the reason may be
+   * appended to the provided buffer.
+   * 
+   * @param schema
+   *          The schema in which this syntax is defined.
+   * @param value
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          The buffer to which the invalid reason should be appended.
+   * @return <CODE>true</CODE> if the provided value is acceptable for
+   *         use with this syntax, or <CODE>false</CODE> if not.
+   */
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We will only accept values that look like valid UUIDs. This means
+    // that all values must be in the form
+    // HHHHHHHH-HHHH-HHHH-HHHH-HHHHHHHHHHHH, where "H" represents a
+    // hexadecimal digit. First, make sure that the value is exactly 36
+    // bytes long.
+    final String valueString = value.toString();
+    if (valueString.length() != 36)
+    {
+
+      invalidReason.append(WARN_ATTR_SYNTAX_UUID_INVALID_LENGTH.get(
+          valueString, valueString.length()));
+      return false;
+    }
+
+    // Next, iterate through each character. Make sure that the 9th,
+    // 14th, 19th, and 24th characters are dashes and the rest are hex
+    // digits.
+    for (int i = 0; i < 36; i++)
+    {
+      switch (i)
+      {
+      case 8:
+      case 13:
+      case 18:
+      case 23:
+        if (valueString.charAt(i) != '-')
+        {
+
+          invalidReason.append(WARN_ATTR_SYNTAX_UUID_EXPECTED_DASH.get(
+              valueString, i, String.valueOf(valueString.charAt(i))));
+          return false;
+        }
+        break;
+      default:
+        switch (valueString.charAt(i))
+        {
+        case '0':
+        case '1':
+        case '2':
+        case '3':
+        case '4':
+        case '5':
+        case '6':
+        case '7':
+        case '8':
+        case '9':
+        case 'a':
+        case 'b':
+        case 'c':
+        case 'd':
+        case 'e':
+        case 'f':
+        case 'A':
+        case 'B':
+        case 'C':
+        case 'D':
+        case 'E':
+        case 'F':
+          break;
+        default:
+
+          invalidReason.append(WARN_ATTR_SYNTAX_UUID_EXPECTED_HEX.get(
+              valueString, i, String.valueOf(valueString.charAt(i))));
+          return false;
+        }
+      }
+    }
+
+    // If we've gotten here, then the value is acceptable.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..ec975da
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UniqueMemberEqualityMatchingRuleImpl.java
@@ -0,0 +1,50 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the uniqueMemberMatch matching rule defined in
+ * X.520 and referenced in RFC 2252. It is based on the name and
+ * optional UID syntax, and will compare values with a distinguished
+ * name and optional bit string suffix.
+ */
+final class UniqueMemberEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return value.toByteString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java b/sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java
new file mode 100644
index 0000000..2849440
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UnknownSchemaElementException.java
@@ -0,0 +1,56 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+
+
+
+/**
+ * Thrown when a schema query fails because the requested schema element
+ * could not be found or is ambiguous.
+ */
+@SuppressWarnings("serial")
+public class UnknownSchemaElementException extends
+    LocalizedIllegalArgumentException
+{
+  /**
+   * Creates a new unknown schema element exception with the provided
+   * message.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   */
+  public UnknownSchemaElementException(Message message)
+  {
+    super(message);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..43c03cd
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UserPasswordExactEqualityMatchingRuleImpl.java
@@ -0,0 +1,78 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+
+
+
+/**
+ * This class implements the userPasswordExactMatch matching rule, which
+ * will simply compare encoded hashed password values to see if they are
+ * exactly equal to each other.
+ */
+final class UserPasswordExactEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value) throws DecodeException
+  {
+    // The normalized form of this matching rule is exactly equal to the
+    // non-normalized form, except that the scheme needs to be converted
+    // to lowercase (if there is one).
+
+    if (UserPasswordSyntaxImpl.isEncoded(value))
+    {
+      final StringBuilder builder = new StringBuilder(value.length());
+      int closingBracePos = -1;
+      for (int i = 1; i < value.length(); i++)
+      {
+        if (value.byteAt(i) == '}')
+        {
+          closingBracePos = i;
+          break;
+        }
+      }
+      final ByteSequence seq1 =
+          value.subSequence(0, closingBracePos + 1);
+      final ByteSequence seq2 =
+          value.subSequence(closingBracePos + 1, value.length());
+      StaticUtils.toLowerCase(seq1, builder);
+      builder.append(seq2);
+      return ByteString.valueOf(builder.toString());
+    }
+    else
+    {
+      return value.toByteString();
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java b/sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java
new file mode 100644
index 0000000..8c7dc4d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/UserPasswordSyntaxImpl.java
@@ -0,0 +1,202 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_CLOSING_BRACE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_OPENING_BRACE;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_SCHEME;
+import static org.opends.messages.SchemaMessages.ERR_ATTR_SYNTAX_USERPW_NO_VALUE;
+import static org.opends.sdk.schema.SchemaConstants.EMR_USER_PASSWORD_EXACT_OID;
+import static org.opends.sdk.schema.SchemaConstants.SYNTAX_USER_PASSWORD_NAME;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+
+
+
+/**
+ * This class defines an attribute syntax used for storing values that
+ * have been encoded using a password storage scheme. The format for
+ * attribute values with this syntax is the concatenation of the
+ * following elements in the given order: <BR>
+ * <UL>
+ * <LI>An opening curly brace ("{") character.</LI>
+ * <LI>The name of the storage scheme used to encode the value.</LI>
+ * <LI>A closing curly brace ("}") character.</LI>
+ * <LI>The encoded value.</LI>
+ * </UL>
+ */
+final class UserPasswordSyntaxImpl extends AbstractSyntaxImpl
+{
+  /**
+   * Decodes the provided user password value into its component parts.
+   * 
+   * @param userPasswordValue
+   *          The user password value to be decoded.
+   * @return A two-element string array whose elements are the storage
+   *         scheme name (in all lowercase characters) and the encoded
+   *         value, in that order.
+   * @throws DecodeException
+   *           If a problem is encountered while attempting to decode
+   *           the value.
+   */
+  static String[] decodeUserPassword(String userPasswordValue)
+      throws DecodeException
+  {
+    // Make sure that there actually is a value to decode.
+    if (userPasswordValue == null || userPasswordValue.length() == 0)
+    {
+      final Message message = ERR_ATTR_SYNTAX_USERPW_NO_VALUE.get();
+      throw DecodeException.error(message);
+    }
+
+    // The first character of an encoded value must be an opening curly
+    // brace.
+    if (userPasswordValue.charAt(0) != '{')
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_USERPW_NO_OPENING_BRACE.get();
+      throw DecodeException.error(message);
+    }
+
+    // There must be a corresponding closing brace.
+    final int closePos = userPasswordValue.indexOf('}');
+    if (closePos < 0)
+    {
+      final Message message =
+          ERR_ATTR_SYNTAX_USERPW_NO_CLOSING_BRACE.get();
+      throw DecodeException.error(message);
+    }
+
+    // Get the storage scheme name and encoded value.
+    final String schemeName = userPasswordValue.substring(1, closePos);
+    final String encodedValue =
+        userPasswordValue.substring(closePos + 1);
+
+    if (schemeName.length() == 0)
+    {
+      final Message message = ERR_ATTR_SYNTAX_USERPW_NO_SCHEME.get();
+      throw DecodeException.error(message);
+    }
+
+    return new String[] { toLowerCase(schemeName), encodedValue };
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is encoded using the user
+   * password syntax.
+   * 
+   * @param value
+   *          The value for which to make the determination.
+   * @return <CODE>true</CODE> if the value appears to be encoded using
+   *         the user password syntax, or <CODE>false</CODE> if not.
+   */
+  static boolean isEncoded(ByteSequence value)
+  {
+    // If the value is null or empty, then it's not.
+    if (value == null || value.length() == 0)
+    {
+      return false;
+    }
+
+    // If the value doesn't start with an opening curly brace, then it's
+    // not.
+    if (value.byteAt(0) != '{')
+    {
+      return false;
+    }
+
+    // There must be a corresponding closing curly brace, and there must
+    // be at least one character inside the brace.
+    int closingBracePos = -1;
+    for (int i = 1; i < value.length(); i++)
+    {
+      if (value.byteAt(i) == '}')
+      {
+        closingBracePos = i;
+        break;
+      }
+    }
+
+    if (closingBracePos < 0 || closingBracePos == 1)
+    {
+      return false;
+    }
+
+    // The closing curly brace must not be the last character of the
+    // password.
+    if (closingBracePos == value.length() - 1)
+    {
+      return false;
+    }
+
+    // If we've gotten here, then it looks to be encoded.
+    return true;
+  }
+
+
+
+  @Override
+  public String getEqualityMatchingRule()
+  {
+    return EMR_USER_PASSWORD_EXACT_OID;
+  }
+
+
+
+  public String getName()
+  {
+    return SYNTAX_USER_PASSWORD_NAME;
+  }
+
+
+
+  public boolean isHumanReadable()
+  {
+    return true;
+  }
+
+
+
+  public boolean valueIsAcceptable(Schema schema, ByteSequence value,
+      MessageBuilder invalidReason)
+  {
+    // We have to accept any value here because in many cases the value
+    // will not have been encoded by the time this method is called.
+    // TODO: Is this correct for client-side use?
+    return true;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java b/sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java
new file mode 100644
index 0000000..077a7c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/schema/WordEqualityMatchingRuleImpl.java
@@ -0,0 +1,184 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.schema;
+
+
+
+import static org.opends.sdk.util.StringPrepProfile.CASE_FOLD;
+import static org.opends.sdk.util.StringPrepProfile.TRIM;
+import static org.opends.sdk.util.StringPrepProfile.prepareUnicode;
+
+import org.opends.sdk.Assertion;
+import org.opends.sdk.ConditionResult;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.util.ByteSequence;
+import org.opends.sdk.util.ByteString;
+
+
+
+/**
+ * This class implements the wordMatch matching rule defined in X.520.
+ * That document defines "word" as implementation-specific, but in this
+ * case we will consider it a match if the assertion value is contained
+ * within the attribute value and is bounded by the edge of the value or
+ * any of the following characters: <BR>
+ * <UL>
+ * <LI>A space</LI>
+ * <LI>A period</LI>
+ * <LI>A comma</LI>
+ * <LI>A slash</LI>
+ * <LI>A dollar sign</LI>
+ * <LI>A plus sign</LI>
+ * <LI>A dash</LI>
+ * <LI>An underscore</LI>
+ * <LI>An octothorpe</LI>
+ * <LI>An equal sign</LI>
+ * </UL>
+ */
+final class WordEqualityMatchingRuleImpl extends
+    AbstractMatchingRuleImpl
+{
+  @Override
+  public Assertion getAssertion(Schema schema, ByteSequence value)
+      throws DecodeException
+  {
+    final String normalStr = normalize(value);
+
+    return new Assertion()
+    {
+      public ConditionResult matches(ByteSequence attributeValue)
+      {
+        // See if the assertion value is contained in the attribute
+        // value. If not, then it isn't a match.
+        final String valueStr1 = attributeValue.toString();
+
+        final int pos = valueStr1.indexOf(normalStr);
+        if (pos < 0)
+        {
+          return ConditionResult.FALSE;
+        }
+
+        if (pos > 0)
+        {
+          final char c = valueStr1.charAt(pos - 1);
+          switch (c)
+          {
+          case ' ':
+          case '.':
+          case ',':
+          case '/':
+          case '$':
+          case '+':
+          case '-':
+          case '_':
+          case '#':
+          case '=':
+            // These are all acceptable.
+            break;
+
+          default:
+            // Anything else is not.
+            return ConditionResult.FALSE;
+          }
+        }
+
+        if (valueStr1.length() > pos + normalStr.length())
+        {
+          final char c = valueStr1.charAt(pos + normalStr.length());
+          switch (c)
+          {
+          case ' ':
+          case '.':
+          case ',':
+          case '/':
+          case '$':
+          case '+':
+          case '-':
+          case '_':
+          case '#':
+          case '=':
+            // These are all acceptable.
+            break;
+
+          default:
+            // Anything else is not.
+            return ConditionResult.FALSE;
+          }
+        }
+
+        // If we've gotten here, then we can assume it is a match.
+        return ConditionResult.TRUE;
+      }
+    };
+  }
+
+
+
+  public ByteString normalizeAttributeValue(Schema schema,
+      ByteSequence value)
+  {
+    return ByteString.valueOf(normalize(value));
+  }
+
+
+
+  private String normalize(ByteSequence value)
+  {
+    final StringBuilder buffer = new StringBuilder();
+    prepareUnicode(buffer, value, TRIM, CASE_FOLD);
+
+    final int bufferLength = buffer.length();
+    if (bufferLength == 0)
+    {
+      if (value.length() > 0)
+      {
+        // This should only happen if the value is composed entirely of
+        // spaces. In that case, the normalized value is a single space.
+        return " ".intern();
+      }
+      else
+      {
+        // The value is empty, so it is already normalized.
+        return "".intern();
+      }
+    }
+
+    // Replace any consecutive spaces with a single space.
+    for (int pos = bufferLength - 1; pos > 0; pos--)
+    {
+      if (buffer.charAt(pos) == ' ')
+      {
+        if (buffer.charAt(pos - 1) == ' ')
+        {
+          buffer.delete(pos, pos + 1);
+        }
+      }
+    }
+
+    return buffer.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/Argument.java b/sdk/src/org/opends/sdk/tools/Argument.java
new file mode 100644
index 0000000..15206eb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/Argument.java
@@ -0,0 +1,808 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.sdk.util.StaticUtils.toLowerCase;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines a generic argument that may be used in the
+ * argument list for an application. This is an abstract class that must
+ * be subclassed in order to provide specific functionality.
+ */
+abstract class Argument
+{
+  // Indicates whether this argument should be hidden in the usage
+  // information.
+  private boolean isHidden;
+
+  // Indicates whether this argument may be specified more than once for
+  // multiple values.
+  private boolean isMultiValued;
+
+  // Indicates whether this argument was provided in the set of
+  // command-line
+  // arguments.
+  private boolean isPresent;
+
+  // Indicates whether this argument is required to have a value.
+  private boolean isRequired;
+
+  // Indicates whether this argument requires a value.
+  private boolean needsValue;
+
+  // The single-character identifier for this argument.
+  private Character shortIdentifier;
+
+  // The unique ID of the description for this argument.
+  private Message description;
+
+  // The set of values for this argument.
+  private LinkedList<String> values;
+
+  // The default value for the argument if none other is provided.
+  private String defaultValue;
+
+  // The long identifier for this argument.
+  private String longIdentifier;
+
+  // The generic name that will be used to refer to this argument.
+  private String name;
+
+  // The name of the property that can be used to set the default value.
+  private String propertyName;
+
+  // The value placeholder for this argument, which will be used in
+  // usage
+  // information.
+  private Message valuePlaceholder;
+
+  // Indicates whether this argument was provided in the set of
+  // properties
+  // found is a properties file.
+  private boolean isValueSetByProperty;
+
+
+
+  /**
+   * Creates a new argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  protected Argument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      boolean needsValue, Message valuePlaceholder,
+      String defaultValue, String propertyName, Message description)
+      throws ArgumentException
+  {
+    this.name = name;
+    this.shortIdentifier = shortIdentifier;
+    this.longIdentifier = longIdentifier;
+    this.isRequired = isRequired;
+    this.isMultiValued = isMultiValued;
+    this.needsValue = needsValue;
+    this.valuePlaceholder = valuePlaceholder;
+    this.defaultValue = defaultValue;
+    this.propertyName = propertyName;
+    this.description = description;
+    this.isValueSetByProperty = false;
+
+    if ((shortIdentifier == null) && (longIdentifier == null))
+    {
+      Message message = ERR_ARG_NO_IDENTIFIER.get(name);
+      throw new ArgumentException(message);
+    }
+
+    if (needsValue && (valuePlaceholder == null))
+    {
+      Message message = ERR_ARG_NO_VALUE_PLACEHOLDER.get(name);
+      throw new ArgumentException(message);
+    }
+
+    values = new LinkedList<String>();
+    isPresent = false;
+    isHidden = false;
+  }
+
+
+
+  /**
+   * Retrieves the generic name that will be used to refer to this
+   * argument.
+   * 
+   * @return The generic name that will be used to refer to this
+   *         argument.
+   */
+  public String getName()
+  {
+    return name;
+  }
+
+
+
+  /**
+   * Retrieves the single-character identifier that may be used to
+   * specify the value of this argument.
+   * 
+   * @return The single-character identifier that may be used to specify
+   *         the value of this argument, or <CODE>null</CODE> if there
+   *         is none.
+   */
+  public Character getShortIdentifier()
+  {
+    return shortIdentifier;
+  }
+
+
+
+  /**
+   * Retrieves the long (multi-character) identifier that may be used to
+   * specify the value of this argument.
+   * 
+   * @return The long (multi-character) identifier that may be used to
+   *         specify the value of this argument.
+   */
+  public String getLongIdentifier()
+  {
+    return longIdentifier;
+  }
+
+
+
+  /**
+   * Indicates whether this argument is required to have at least one
+   * value.
+   * 
+   * @return <CODE>true</CODE> if this argument is required to have at
+   *         least one value, or <CODE>false</CODE> if it does not need
+   *         to have a value.
+   */
+  public boolean isRequired()
+  {
+    return isRequired;
+  }
+
+
+
+  /**
+   * Specifies whether this argument is required to have at least one
+   * value.
+   * 
+   * @param isRequired
+   *          Indicates whether this argument is required to have at
+   *          least one value.
+   */
+  public void setRequired(boolean isRequired)
+  {
+    this.isRequired = isRequired;
+  }
+
+
+
+  /**
+   * Indicates whether this argument is present in the parsed set of
+   * command-line arguments.
+   * 
+   * @return <CODE>true</CODE> if this argument is present in the parsed
+   *         set of command-line arguments, or <CODE>false</CODE> if
+   *         not.
+   */
+  public boolean isPresent()
+  {
+    return isPresent;
+  }
+
+
+
+  /**
+   * Specifies whether this argument is present in the parsed set of
+   * command-line arguments.
+   * 
+   * @param isPresent
+   *          Indicates whether this argument is present in the set of
+   *          command-line arguments.
+   */
+  public void setPresent(boolean isPresent)
+  {
+    this.isPresent = isPresent;
+  }
+
+
+
+  /**
+   * Indicates whether this argument should be hidden from the usage
+   * information.
+   * 
+   * @return <CODE>true</CODE> if this argument should be hidden from
+   *         the usage information, or <CODE>false</CODE> if not.
+   */
+  public boolean isHidden()
+  {
+    return isHidden;
+  }
+
+
+
+  /**
+   * Specifies whether this argument should be hidden from the usage
+   * information.
+   * 
+   * @param isHidden
+   *          Indicates whether this argument should be hidden from the
+   *          usage information.
+   */
+  public void setHidden(boolean isHidden)
+  {
+    this.isHidden = isHidden;
+  }
+
+
+
+  /**
+   * Indicates whether this argument may be provided more than once on
+   * the command line to specify multiple values.
+   * 
+   * @return <CODE>true</CODE> if this argument may be provided more
+   *         than once on the command line to specify multiple values,
+   *         or <CODE>false</CODE> if it may have at most one value.
+   */
+  public boolean isMultiValued()
+  {
+    return isMultiValued;
+  }
+
+
+
+  /**
+   * Specifies whether this argument may be provided more than once on
+   * the command line to specify multiple values.
+   * 
+   * @param isMultiValued
+   *          Indicates whether this argument may be provided more than
+   *          once on the command line to specify multiple values.
+   */
+  public void setMultiValued(boolean isMultiValued)
+  {
+    this.isMultiValued = isMultiValued;
+  }
+
+
+
+  /**
+   * Indicates whether a value must be provided with this argument if it
+   * is present.
+   * 
+   * @return <CODE>true</CODE> if a value must be provided with the
+   *         argument if it is present, or <CODE>false</CODE> if the
+   *         argument does not take a value and the presence of the
+   *         argument identifier itself is sufficient to convey the
+   *         necessary information.
+   */
+  public boolean needsValue()
+  {
+    return needsValue;
+  }
+
+
+
+  /**
+   * Specifies whether a value must be provided with this argument if it
+   * is present. If this is changed from <CODE>false</CODE> to
+   * <CODE>true</CODE>, then a value placeholder must also be provided.
+   * 
+   * @param needsValue
+   *          Indicates whether a value must be provided with this
+   *          argument if it is present.
+   */
+  public void setNeedsValue(boolean needsValue)
+  {
+    this.needsValue = needsValue;
+  }
+
+
+
+  /**
+   * Retrieves the value placeholder that will be displayed for this
+   * argument in the generated usage information.
+   * 
+   * @return The value placeholder that will be displayed for this
+   *         argument in the generated usage information, or
+   *         <CODE>null</CODE> if there is none.
+   */
+  public Message getValuePlaceholder()
+  {
+    return valuePlaceholder;
+  }
+
+
+
+  /**
+   * Specifies the value placeholder that will be displayed for this
+   * argument in the generated usage information. It may be
+   * <CODE>null</CODE> only if <CODE>needsValue()</CODE> returns
+   * <CODE>false</CODE>.
+   * 
+   * @param valuePlaceholder
+   *          The value placeholder that will be displayed for this
+   *          argument in the generated usage information.
+   */
+  public void setValuePlaceholder(Message valuePlaceholder)
+  {
+    this.valuePlaceholder = valuePlaceholder;
+  }
+
+
+
+  /**
+   * Retrieves the default value that will be used for this argument if
+   * it is not specified on the command line and it is not set from a
+   * properties file.
+   * 
+   * @return The default value that will be used for this argument if it
+   *         is not specified on the command line and it is not set from
+   *         a properties file, or <CODE>null</CODE> if there is no
+   *         default value.
+   */
+  public String getDefaultValue()
+  {
+    return defaultValue;
+  }
+
+
+
+  /**
+   * Specifies the default value that will be used for this argument if
+   * it is not specified on the command line and it is not set from a
+   * properties file.
+   * 
+   * @param defaultValue
+   *          The default value that will be used for this argument if
+   *          it is not specified on the command line and it is not set
+   *          from a properties file.
+   */
+  public void setDefaultValue(String defaultValue)
+  {
+    this.defaultValue = defaultValue;
+  }
+
+
+
+  /**
+   * Retrieves the name of a property in a properties file that may be
+   * used to set the default value for this argument if it is present. A
+   * value read from a properties file will override the default value
+   * returned from the <CODE>getDefaultValue</CODE>, but the properties
+   * file value will be overridden by a value supplied on the command
+   * line.
+   * 
+   * @return The name of a property in a properties file that may be
+   *         used to set the default value for this argument if it is
+   *         present.
+   */
+  public String getPropertyName()
+  {
+    return propertyName;
+  }
+
+
+
+  /**
+   * Specifies the name of a property in a properties file that may be
+   * used to set the default value for this argument if it is present.
+   * 
+   * @param propertyName
+   *          The name of a property in a properties file that may be
+   *          used to set the default value for this argument if it is
+   *          present.
+   */
+  public void setPropertyName(String propertyName)
+  {
+    this.propertyName = propertyName;
+  }
+
+
+
+  /**
+   * Indicates whether this argument was provided in the set of
+   * properties found is a properties file.
+   * 
+   * @return <CODE>true</CODE> if this argument was provided in the set
+   *         of properties found is a properties file, or
+   *         <CODE>false</CODE> if not.
+   */
+  public boolean isValueSetByProperty()
+  {
+    return isValueSetByProperty;
+  }
+
+
+
+  /**
+   * Specifies whether this argument was provided in the set of
+   * properties found is a properties file.
+   * 
+   * @param isValueSetByProperty
+   *          Specify whether this argument was provided in the set of
+   *          properties found is a properties file.
+   */
+  public void setValueSetByProperty(boolean isValueSetByProperty)
+  {
+    this.isValueSetByProperty = isValueSetByProperty;
+  }
+
+
+
+  /**
+   * Retrieves the human-readable description for this argument.
+   * 
+   * @return The human-readable description for this argument.
+   */
+  public Message getDescription()
+  {
+    return description != null ? description : Message.EMPTY;
+  }
+
+
+
+  /**
+   * Indicates whether this argument has at least one value.
+   * 
+   * @return <CODE>true</CODE> if this argument has at least one value,
+   *         or <CODE>false</CODE> if it does not have any values.
+   */
+  public boolean hasValue()
+  {
+    return (!values.isEmpty());
+  }
+
+
+
+  /**
+   * Retrieves the string vale for this argument. If it has multiple
+   * values, then the first will be returned. If it does not have any
+   * values, then the default value will be returned.
+   * 
+   * @return The string value for this argument, or <CODE>null</CODE> if
+   *         there are no values and no default value has been given.
+   */
+  public String getValue()
+  {
+    if (values.isEmpty())
+    {
+      return defaultValue;
+    }
+
+    return values.getFirst();
+  }
+
+
+
+  /**
+   * Retrieves the set of string values for this argument.
+   * 
+   * @return The set of string values for this argument.
+   */
+  public LinkedList<String> getValues()
+  {
+    return values;
+  }
+
+
+
+  /**
+   * Retrieves the value of this argument as an integer.
+   * 
+   * @return The value of this argument as an integer.
+   * @throws ArgumentException
+   *           If there are multiple values, or the value cannot be
+   *           parsed as an integer.
+   */
+  public int getIntValue() throws ArgumentException
+  {
+    if (values.isEmpty())
+    {
+      Message message = ERR_ARG_NO_INT_VALUE.get(name);
+      throw new ArgumentException(message);
+    }
+
+    Iterator<String> iterator = values.iterator();
+    String valueString = iterator.next();
+
+    int intValue;
+    try
+    {
+      intValue = Integer.parseInt(valueString);
+    }
+    catch (Exception e)
+    {
+      Message message =
+          ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+      throw new ArgumentException(message, e);
+    }
+
+    if (iterator.hasNext())
+    {
+      Message message = ERR_ARG_INT_MULTIPLE_VALUES.get(name);
+      throw new ArgumentException(message);
+    }
+    else
+    {
+      return intValue;
+    }
+  }
+
+
+
+  /**
+   * Retrieves the set of values for this argument as a list of
+   * integers.
+   * 
+   * @return A list of the integer representations of the values for
+   *         this argument.
+   * @throws ArgumentException
+   *           If any of the values cannot be parsed as an integer.
+   */
+  public LinkedList<Integer> getIntValues() throws ArgumentException
+  {
+    LinkedList<Integer> intList = new LinkedList<Integer>();
+
+    Iterator<String> iterator = values.iterator();
+    while (iterator.hasNext())
+    {
+      String valueString = iterator.next();
+
+      try
+      {
+        intList.add(Integer.valueOf(valueString));
+      }
+      catch (Exception e)
+      {
+        Message message =
+            ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+        throw new ArgumentException(message, e);
+      }
+    }
+
+    return intList;
+  }
+
+
+
+  /**
+   * Retrieves the value of this argument as an integer.
+   * 
+   * @return The value of this argument as an integer.
+   * @throws ArgumentException
+   *           If there are multiple values, or the value cannot be
+   *           parsed as an integer.
+   */
+  public double getDoubleValue() throws ArgumentException
+  {
+    if (values.isEmpty())
+    {
+      Message message = ERR_ARG_NO_INT_VALUE.get(name);
+      throw new ArgumentException(message);
+    }
+
+    Iterator<String> iterator = values.iterator();
+    String valueString = iterator.next();
+
+    double intValue;
+    try
+    {
+      intValue = Double.parseDouble(valueString);
+    }
+    catch (Exception e)
+    {
+      Message message =
+          ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+      throw new ArgumentException(message, e);
+    }
+
+    if (iterator.hasNext())
+    {
+      Message message = ERR_ARG_INT_MULTIPLE_VALUES.get(name);
+      throw new ArgumentException(message);
+    }
+    else
+    {
+      return intValue;
+    }
+  }
+
+
+
+  /**
+   * Retrieves the set of values for this argument as a list of
+   * integers.
+   * 
+   * @return A list of the integer representations of the values for
+   *         this argument.
+   * @throws ArgumentException
+   *           If any of the values cannot be parsed as an integer.
+   */
+  public LinkedList<Double> getDoubleValues() throws ArgumentException
+  {
+    LinkedList<Double> intList = new LinkedList<Double>();
+
+    Iterator<String> iterator = values.iterator();
+    while (iterator.hasNext())
+    {
+      String valueString = iterator.next();
+
+      try
+      {
+        intList.add(Double.valueOf(valueString));
+      }
+      catch (Exception e)
+      {
+        Message message =
+            ERR_ARG_CANNOT_DECODE_AS_INT.get(valueString, name);
+        throw new ArgumentException(message, e);
+      }
+    }
+
+    return intList;
+  }
+
+
+
+  /**
+   * Retrieves the value of this argument as a <CODE>Boolean</CODE>.
+   * 
+   * @return The value of this argument as a <CODE>Boolean</CODE>.
+   * @throws ArgumentException
+   *           If this argument cannot be interpreted as a Boolean
+   *           value.
+   */
+  public boolean getBooleanValue() throws ArgumentException
+  {
+    if (values.isEmpty())
+    {
+      Message message = ERR_ARG_NO_BOOLEAN_VALUE.get(name);
+      throw new ArgumentException(message);
+    }
+
+    Iterator<String> iterator = values.iterator();
+    String valueString = toLowerCase(iterator.next());
+
+    boolean booleanValue;
+    if (valueString.equals("true") || valueString.equals("yes")
+        || valueString.equals("on") || valueString.equals("1"))
+    {
+      booleanValue = true;
+    }
+    else if (valueString.equals("false") || valueString.equals("no")
+        || valueString.equals("off") || valueString.equals("0"))
+    {
+      booleanValue = false;
+    }
+    else
+    {
+      Message message =
+          ERR_ARG_CANNOT_DECODE_AS_BOOLEAN.get(valueString, name);
+      throw new ArgumentException(message);
+    }
+
+    if (iterator.hasNext())
+    {
+      Message message = ERR_ARG_BOOLEAN_MULTIPLE_VALUES.get(name);
+      throw new ArgumentException(message);
+    }
+    else
+    {
+      return booleanValue;
+    }
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in this
+   * argument.
+   * 
+   * @param valueString
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          A buffer into which the invalid reason may be written if
+   *          the value is not acceptable.
+   * @return <CODE>true</CODE> if the value is acceptable, or
+   *         <CODE>false</CODE> if it is not.
+   */
+  public abstract boolean valueIsAcceptable(String valueString,
+      MessageBuilder invalidReason);
+
+
+
+  /**
+   * Adds a value to the set of values for this argument. This should
+   * only be called if the value is allowed by the
+   * <CODE>valueIsAcceptable</CODE> method.
+   * 
+   * @param valueString
+   *          The string representation of the value to add to this
+   *          argument.
+   */
+  public void addValue(String valueString)
+  {
+    values.add(valueString);
+  }
+
+
+
+  /**
+   * Clears the set of values assigned to this argument.
+   */
+  public void clearValues()
+  {
+    values.clear();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentException.java b/sdk/src/org/opends/sdk/tools/ArgumentException.java
new file mode 100644
index 0000000..6521e03
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentException.java
@@ -0,0 +1,96 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.LocalizableException;
+
+
+
+/**
+ * This class defines an exception that may be thrown if there is a
+ * problem with an argument definition.
+ */
+final class ArgumentException extends Exception implements
+    LocalizableException
+{
+  /**
+   * The serial version identifier required to satisfy the compiler
+   * because this class extends <CODE>java.lang.Exception</CODE>, which
+   * implements the <CODE>java.io.Serializable</CODE> interface. This
+   * value was generated using the <CODE>serialver</CODE> command-line
+   * utility included with the Java SDK.
+   */
+  private static final long serialVersionUID = 5623155045312160730L;
+
+  // The I18N message associated with this exception.
+  private final Message message;
+
+
+
+  /**
+   * Creates a new argument exception with the provided message.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   */
+  ArgumentException(Message message)
+  {
+    super(String.valueOf(message));
+    this.message = message;
+  }
+
+
+
+  /**
+   * Creates a new argument exception with the provided message and root
+   * cause.
+   * 
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @param cause
+   *          The exception that was caught to trigger this exception.
+   */
+  ArgumentException(Message message, Throwable cause)
+  {
+    super(String.valueOf(message), cause);
+    this.message = message;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Message getMessageObject()
+  {
+    return this.message;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentGroup.java b/sdk/src/org/opends/sdk/tools/ArgumentGroup.java
new file mode 100644
index 0000000..65b3394
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentGroup.java
@@ -0,0 +1,209 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * Class for organizing options into logical groups when arguement usage
+ * is printed. To use an argument group, create an instance and use
+ * {@link org.opends.server.util.args.ArgumentParser #addArgument(Argument, ArgumentGroup)}
+ * when adding arguments for to the parser.
+ */
+final class ArgumentGroup implements Comparable<ArgumentGroup>
+{
+
+  // Description for this group of arguments
+  private Message description = null;
+
+  // List of arguments belonging to this group
+  private List<Argument> args = null;
+
+  // Governs groups position within usage statement
+  private Integer priority;
+
+
+
+  /**
+   * Creates a parameterized instance.
+   * 
+   * @param description
+   *          for options in this group that is printed before argument
+   *          descriptions in usage output
+   * @param priority
+   *          number governing the position of this group within the
+   *          usage statement. Groups with higher priority values appear
+   *          before groups with lower priority.
+   */
+  ArgumentGroup(Message description, int priority)
+  {
+    this.description = description;
+    this.priority = priority;
+    this.args = new LinkedList<Argument>();
+  }
+
+
+
+  /**
+   * Gets the description for this group of arguments.
+   * 
+   * @return description for this argument group
+   */
+  Message getDescription()
+  {
+    return this.description;
+  }
+
+
+
+  /**
+   * Sets the description for this group of arguments.
+   * 
+   * @param description
+   *          for this argument group
+   */
+  void setDescription(Message description)
+  {
+    this.description = description;
+  }
+
+
+
+  /**
+   * Gets the list of arguments associated with this group.
+   * 
+   * @return list of associated arguments
+   */
+  List<Argument> getArguments()
+  {
+    return Collections.unmodifiableList(args);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(ArgumentGroup o)
+  {
+    // Groups with higher priority numbers appear before
+    // those with lower priority in the usage output
+    return -1 * priority.compareTo(o.priority);
+  }
+
+
+
+  /**
+   * Indicates whether this group contains any members.
+   * 
+   * @return boolean where true means this group contains members
+   */
+  boolean containsArguments()
+  {
+    return this.args.size() > 0;
+  }
+
+
+
+  /**
+   * Indicates whether this group contains any non-hidden members.
+   * 
+   * @return boolean where true means this group contains non-hidden
+   *         members
+   */
+  boolean containsNonHiddenArguments()
+  {
+    for (Argument arg : args)
+    {
+      if (!arg.isHidden())
+      {
+        return true;
+      }
+    }
+    return false;
+  }
+
+
+
+  /**
+   * Adds an argument to this group.
+   * 
+   * @param arg
+   *          to add
+   * @return boolean where true indicates the add was successful
+   */
+  boolean addArgument(Argument arg)
+  {
+    boolean success = false;
+    if (arg != null)
+    {
+      Character newShort = arg.getShortIdentifier();
+      String newLong = arg.getLongIdentifier();
+
+      // See if there is already an argument in this group that the
+      // new argument should replace
+      for (Iterator<Argument> it = this.args.iterator(); it.hasNext();)
+      {
+        Argument a = it.next();
+        if (newShort != null && newShort.equals(a.getShortIdentifier())
+            || newLong != null && newLong.equals(a.getLongIdentifier()))
+        {
+          it.remove();
+          break;
+        }
+      }
+
+      success = this.args.add(arg);
+    }
+    return success;
+  }
+
+
+
+  /**
+   * Removes an argument from this group.
+   * 
+   * @param arg
+   *          to remove
+   * @return boolean where true indicates the remove was successful
+   */
+  boolean removeArgument(Argument arg)
+  {
+    return this.args.remove(arg);
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentParser.java b/sdk/src/org/opends/sdk/tools/ArgumentParser.java
new file mode 100644
index 0000000..4c0a056
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentParser.java
@@ -0,0 +1,2007 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_GENERAL_ARGS;
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_IO_ARGS;
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_LDAP_CONNECTION_ARGS;
+import static org.opends.messages.ToolMessages.INFO_DESCRIPTION_PRODUCT_VERSION;
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.ServerConstants.EOL;
+import static org.opends.server.util.ServerConstants.PROPERTY_SCRIPT_NAME;
+import static org.opends.server.util.StaticUtils.getBytes;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+import static org.opends.server.util.StaticUtils.toLowerCase;
+import static org.opends.server.util.StaticUtils.wrapText;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.*;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.server.util.SetupUtils;
+
+
+
+/**
+ * This class defines a utility that can be used to deal with
+ * command-line arguments for applications in a CLIP-compliant manner
+ * using either short one-character or longer word-based arguments. It
+ * is also integrated with the Directory Server message catalog so that
+ * it can display messages in an internationalizeable format, can
+ * automatically generate usage information, can detect conflicts
+ * between arguments, and can interact with a properties file to obtain
+ * default values for arguments there if they are not specified on the
+ * command-line.
+ */
+final class ArgumentParser
+{
+  /**
+   * The argument that will be used to indicate the file properties.
+   */
+  private StringArgument filePropertiesPathArgument;
+
+  /**
+   * The argument that will be used to indicate that we'll not look for
+   * default properties file.
+   */
+  private BooleanArgument noPropertiesFileArgument;
+
+  // The argument that will be used to trigger the display of usage
+  // information.
+  private Argument usageArgument;
+
+  // The argument that will be used to trigger the display of the OpenDS
+  // version.
+  private Argument versionArgument;
+
+  // The set of unnamed trailing arguments that were provided for this
+  // parser.
+  private ArrayList<String> trailingArguments;
+
+  // Indicates whether this parser will allow additional unnamed
+  // arguments at
+  // the end of the list.
+  private boolean allowsTrailingArguments;
+
+  // Indicates whether long arguments should be treated in a
+  // case-sensitive
+  // manner.
+  private boolean longArgumentsCaseSensitive;
+
+  // Indicates whether the usage or version information has been
+  // displayed.
+  private boolean usageOrVersionDisplayed;
+
+  // Indicates whether the version argument was provided.
+  private boolean versionPresent;
+
+  // The set of arguments defined for this parser, referenced by short
+  // ID.
+  private HashMap<Character, Argument> shortIDMap;
+
+  // The set of arguments defined for this parser, referenced by
+  // argument name.
+  private HashMap<String, Argument> argumentMap;
+
+  // The set of arguments defined for this parser, referenced by long
+  // ID.
+  private HashMap<String, Argument> longIDMap;
+
+  // The maximum number of unnamed trailing arguments that may be
+  // provided.
+  private int maxTrailingArguments;
+
+  // The minimum number of unnamed trailing arguments that may be
+  // provided.
+  private int minTrailingArguments;
+
+  // The total set of arguments defined for this parser.
+  private LinkedList<Argument> argumentList;
+
+  // The output stream to which usage information should be printed.
+  private OutputStream usageOutputStream;
+
+  // The fully-qualified name of the Java class that should be invoked
+  // to launch
+  // the program with which this argument parser is associated.
+  private String mainClassName;
+
+  // A human-readable description for the tool, which will be included
+  // when
+  // displaying usage information.
+  private Message toolDescription;
+
+  // The display name that will be used for the trailing arguments in
+  // the usage
+  // information.
+  private String trailingArgsDisplayName;
+
+  // The raw set of command-line arguments that were provided.
+  private String[] rawArguments;
+
+  /** Set of argument groups. */
+  private Set<ArgumentGroup> argumentGroups;
+
+  /**
+   * Group for arguments that have not been explicitly grouped. These
+   * will appear at the top of the usage statement without a header.
+   */
+  private ArgumentGroup defaultArgGroup =
+      new ArgumentGroup(Message.EMPTY, Integer.MAX_VALUE);
+
+  /**
+   * Group for arguments that are related to connection through LDAP.
+   * This includes options like the bind DN, the port, etc.
+   */
+  private ArgumentGroup ldapArgGroup =
+      new ArgumentGroup(INFO_DESCRIPTION_LDAP_CONNECTION_ARGS.get(),
+          Integer.MIN_VALUE + 2);
+
+  /**
+   * Group for arguments that are related to utility input/output like
+   * properties file, no-prompt etc. These will appear toward the bottom
+   * of the usage statement.
+   */
+  private ArgumentGroup ioArgGroup =
+      new ArgumentGroup(INFO_DESCRIPTION_IO_ARGS.get(),
+          Integer.MIN_VALUE + 1);
+
+  /**
+   * Group for arguments that are general like help, version etc. These
+   * will appear at the end of the usage statement.
+   */
+  private ArgumentGroup generalArgGroup =
+      new ArgumentGroup(INFO_DESCRIPTION_GENERAL_ARGS.get(),
+          Integer.MIN_VALUE);
+
+  private final static String INDENT = "    ";
+  private final static int MAX_LENGTH =
+      SetupUtils.isWindows() ? 79 : 80;
+
+
+
+  /**
+   * Creates a new instance of this argument parser with no arguments.
+   * Unnamed trailing arguments will not be allowed.
+   * 
+   * @param mainClassName
+   *          The fully-qualified name of the Java class that should be
+   *          invoked to launch the program with which this argument
+   *          parser is associated.
+   * @param toolDescription
+   *          A human-readable description for the tool, which will be
+   *          included when displaying usage information.
+   * @param longArgumentsCaseSensitive
+   *          Indicates whether long arguments should be treated in a
+   *          case-sensitive manner.
+   */
+  public ArgumentParser(String mainClassName, Message toolDescription,
+      boolean longArgumentsCaseSensitive)
+  {
+    this.mainClassName = mainClassName;
+    this.toolDescription = toolDescription;
+    this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
+
+    argumentList = new LinkedList<Argument>();
+    argumentMap = new HashMap<String, Argument>();
+    shortIDMap = new HashMap<Character, Argument>();
+    longIDMap = new HashMap<String, Argument>();
+    allowsTrailingArguments = false;
+    usageOrVersionDisplayed = false;
+    versionPresent = false;
+    trailingArgsDisplayName = null;
+    maxTrailingArguments = 0;
+    minTrailingArguments = 0;
+    trailingArguments = new ArrayList<String>();
+    rawArguments = null;
+    usageArgument = null;
+    filePropertiesPathArgument = null;
+    noPropertiesFileArgument = null;
+    usageOutputStream = System.out;
+    initGroups();
+  }
+
+
+
+  /**
+   * Creates a new instance of this argument parser with no arguments
+   * that may or may not be allowed to have unnamed trailing arguments.
+   * 
+   * @param mainClassName
+   *          The fully-qualified name of the Java class that should be
+   *          invoked to launch the program with which this argument
+   *          parser is associated.
+   * @param toolDescription
+   *          A human-readable description for the tool, which will be
+   *          included when displaying usage information.
+   * @param longArgumentsCaseSensitive
+   *          Indicates whether long arguments should be treated in a
+   *          case-sensitive manner.
+   * @param allowsTrailingArguments
+   *          Indicates whether this parser allows unnamed trailing
+   *          arguments to be provided.
+   * @param minTrailingArguments
+   *          The minimum number of unnamed trailing arguments that must
+   *          be provided. A value less than or equal to zero indicates
+   *          that no minimum will be enforced.
+   * @param maxTrailingArguments
+   *          The maximum number of unnamed trailing arguments that may
+   *          be provided. A value less than or equal to zero indicates
+   *          that no maximum will be enforced.
+   * @param trailingArgsDisplayName
+   *          The display name that should be used as a placeholder for
+   *          unnamed trailing arguments in the generated usage
+   *          information.
+   */
+  public ArgumentParser(String mainClassName, Message toolDescription,
+      boolean longArgumentsCaseSensitive,
+      boolean allowsTrailingArguments, int minTrailingArguments,
+      int maxTrailingArguments, String trailingArgsDisplayName)
+  {
+    this.mainClassName = mainClassName;
+    this.toolDescription = toolDescription;
+    this.longArgumentsCaseSensitive = longArgumentsCaseSensitive;
+    this.allowsTrailingArguments = allowsTrailingArguments;
+    this.minTrailingArguments = minTrailingArguments;
+    this.maxTrailingArguments = maxTrailingArguments;
+    this.trailingArgsDisplayName = trailingArgsDisplayName;
+
+    argumentList = new LinkedList<Argument>();
+    argumentMap = new HashMap<String, Argument>();
+    shortIDMap = new HashMap<Character, Argument>();
+    longIDMap = new HashMap<String, Argument>();
+    trailingArguments = new ArrayList<String>();
+    usageOrVersionDisplayed = false;
+    versionPresent = false;
+    rawArguments = null;
+    usageArgument = null;
+    usageOutputStream = System.out;
+    initGroups();
+  }
+
+
+
+  /**
+   * Retrieves the fully-qualified name of the Java class that should be
+   * invoked to launch the program with which this argument parser is
+   * associated.
+   * 
+   * @return The fully-qualified name of the Java class that should be
+   *         invoked to launch the program with which this argument
+   *         parser is associated.
+   */
+  public String getMainClassName()
+  {
+    return mainClassName;
+  }
+
+
+
+  /**
+   * Retrieves a human-readable description for this tool, which should
+   * be included at the top of the command-line usage information.
+   * 
+   * @return A human-readable description for this tool, or {@code null}
+   *         if none is available.
+   */
+  public Message getToolDescription()
+  {
+    return toolDescription;
+  }
+
+
+
+  /**
+   * Indicates whether this parser will allow unnamed trailing
+   * arguments. These will be arguments at the end of the list that are
+   * not preceded by either a long or short identifier and will need to
+   * be manually parsed by the application using this parser. Note that
+   * once an unnamed trailing argument has been identified, all
+   * remaining arguments will be classified as such.
+   * 
+   * @return <CODE>true</CODE> if this parser allows unnamed trailing
+   *         arguments, or <CODE>false</CODE> if it does not.
+   */
+  public boolean allowsTrailingArguments()
+  {
+    return allowsTrailingArguments;
+  }
+
+
+
+  /**
+   * Retrieves the minimum number of unnamed trailing arguments that
+   * must be provided.
+   * 
+   * @return The minimum number of unnamed trailing arguments that must
+   *         be provided, or a value less than or equal to zero if no
+   *         minimum will be enforced.
+   */
+  public int getMinTrailingArguments()
+  {
+    return minTrailingArguments;
+  }
+
+
+
+  /**
+   * Retrieves the maximum number of unnamed trailing arguments that may
+   * be provided.
+   * 
+   * @return The maximum number of unnamed trailing arguments that may
+   *         be provided, or a value less than or equal to zero if no
+   *         maximum will be enforced.
+   */
+  public int getMaxTrailingArguments()
+  {
+    return maxTrailingArguments;
+  }
+
+
+
+  /**
+   * Retrieves the list of all arguments that have been defined for this
+   * argument parser.
+   * 
+   * @return The list of all arguments that have been defined for this
+   *         argument parser.
+   */
+  public LinkedList<Argument> getArgumentList()
+  {
+    return argumentList;
+  }
+
+
+
+  /**
+   * Retrieves the argument with the specified name.
+   * 
+   * @param name
+   *          The name of the argument to retrieve.
+   * @return The argument with the specified name, or <CODE>null</CODE>
+   *         if there is no such argument.
+   */
+  public Argument getArgument(String name)
+  {
+    return argumentMap.get(name);
+  }
+
+
+
+  /**
+   * Retrieves the set of arguments mapped by the short identifier that
+   * may be used to reference them. Note that arguments that do not have
+   * a short identifier will not be present in this list.
+   * 
+   * @return The set of arguments mapped by the short identifier that
+   *         may be used to reference them.
+   */
+  public HashMap<Character, Argument> getArgumentsByShortID()
+  {
+    return shortIDMap;
+  }
+
+
+
+  /**
+   * Retrieves the argument with the specified short identifier.
+   * 
+   * @param shortID
+   *          The short ID for the argument to retrieve.
+   * @return The argument with the specified short identifier, or
+   *         <CODE>null</CODE> if there is no such argument.
+   */
+  public Argument getArgumentForShortID(Character shortID)
+  {
+    return shortIDMap.get(shortID);
+  }
+
+
+
+  /**
+   * Retrieves the set of arguments mapped by the long identifier that
+   * may be used to reference them. Note that arguments that do not have
+   * a long identifier will not be present in this list.
+   * 
+   * @return The set of arguments mapped by the long identifier that may
+   *         be used to reference them.
+   */
+  public HashMap<String, Argument> getArgumentsByLongID()
+  {
+    return longIDMap;
+  }
+
+
+
+  /**
+   * Retrieves the argument with the specified long identifier.
+   * 
+   * @param longID
+   *          The long identifier of the argument to retrieve.
+   * @return The argument with the specified long identifier, or
+   *         <CODE>null</CODE> if there is no such argument.
+   */
+  public Argument getArgumentForLongID(String longID)
+  {
+    return longIDMap.get(longID);
+  }
+
+
+
+  /**
+   * Retrieves the set of unnamed trailing arguments that were provided
+   * on the command line.
+   * 
+   * @return The set of unnamed trailing arguments that were provided on
+   *         the command line.
+   */
+  public ArrayList<String> getTrailingArguments()
+  {
+    return trailingArguments;
+  }
+
+
+
+  /**
+   * Retrieves the raw set of arguments that were provided.
+   * 
+   * @return The raw set of arguments that were provided, or
+   *         <CODE>null</CODE> if the argument list has not yet been
+   *         parsed.
+   */
+  public String[] getRawArguments()
+  {
+    return rawArguments;
+  }
+
+
+
+  /**
+   * Sets the usage group description for the default argument group.
+   * 
+   * @param description
+   *          for the default group
+   */
+  public void setDefaultArgumentGroupDescription(Message description)
+  {
+    this.defaultArgGroup.setDescription(description);
+  }
+
+
+
+  /**
+   * Sets the usage group description for the LDAP argument group.
+   * 
+   * @param description
+   *          for the LDAP group
+   */
+  public void setLdapArgumentGroupDescription(Message description)
+  {
+    this.ldapArgGroup.setDescription(description);
+  }
+
+
+
+  /**
+   * Sets the usage group description for the input/output argument
+   * group.
+   * 
+   * @param description
+   *          for the input/output group
+   */
+  public void setInputOutputArgumentGroupDescription(Message description)
+  {
+    this.ioArgGroup.setDescription(description);
+  }
+
+
+
+  /**
+   * Sets the usage group description for the general argument group.
+   * 
+   * @param description
+   *          for the general group
+   */
+  public void setGeneralArgumentGroupDescription(Message description)
+  {
+    this.generalArgGroup.setDescription(description);
+  }
+
+
+
+  /**
+   * Adds the provided argument to the set of arguments handled by this
+   * parser.
+   * 
+   * @param argument
+   *          The argument to be added.
+   * @throws ArgumentException
+   *           If the provided argument conflicts with another argument
+   *           that has already been defined.
+   */
+  public void addArgument(Argument argument) throws ArgumentException
+  {
+    addArgument(argument, null);
+  }
+
+
+
+  /**
+   * Adds the provided argument to the set of arguments handled by this
+   * parser and puts the arguement in the default group.
+   * 
+   * @param argument
+   *          The argument to be added.
+   * @throws ArgumentException
+   *           If the provided argument conflicts with another argument
+   *           that has already been defined.
+   */
+  public void addDefaultArgument(Argument argument)
+      throws ArgumentException
+  {
+    addArgument(argument, defaultArgGroup);
+  }
+
+
+
+  /**
+   * Adds the provided argument to the set of arguments handled by this
+   * parser and puts the argument in the LDAP connection group.
+   * 
+   * @param argument
+   *          The argument to be added.
+   * @throws ArgumentException
+   *           If the provided argument conflicts with another argument
+   *           that has already been defined.
+   */
+  public void addLdapConnectionArgument(Argument argument)
+      throws ArgumentException
+  {
+    addArgument(argument, ldapArgGroup);
+  }
+
+
+
+  /**
+   * Adds the provided argument to the set of arguments handled by this
+   * parser and puts the argument in the input/output group.
+   * 
+   * @param argument
+   *          The argument to be added.
+   * @throws ArgumentException
+   *           If the provided argument conflicts with another argument
+   *           that has already been defined.
+   */
+  public void addInputOutputArgument(Argument argument)
+      throws ArgumentException
+  {
+    addArgument(argument, ioArgGroup);
+  }
+
+
+
+  /**
+   * Adds the provided argument to the set of arguments handled by this
+   * parser and puts the arguement in the general group.
+   * 
+   * @param argument
+   *          The argument to be added.
+   * @throws ArgumentException
+   *           If the provided argument conflicts with another argument
+   *           that has already been defined.
+   */
+  public void addGeneralArgument(Argument argument)
+      throws ArgumentException
+  {
+    addArgument(argument, generalArgGroup);
+  }
+
+
+
+  /**
+   * Adds the provided argument to the set of arguments handled by this
+   * parser.
+   * 
+   * @param argument
+   *          The argument to be added.
+   * @param group
+   *          The argument group to which the argument belongs.
+   * @throws ArgumentException
+   *           If the provided argument conflicts with another argument
+   *           that has already been defined.
+   */
+  public void addArgument(Argument argument, ArgumentGroup group)
+      throws ArgumentException
+  {
+
+    Character shortID = argument.getShortIdentifier();
+    if ((shortID != null) && shortIDMap.containsKey(shortID))
+    {
+      String conflictingName = shortIDMap.get(shortID).getName();
+
+      Message message =
+          ERR_ARGPARSER_DUPLICATE_SHORT_ID.get(argument.getName(),
+              String.valueOf(shortID), conflictingName);
+      throw new ArgumentException(message);
+    }
+
+    if (versionArgument != null)
+    {
+      if (shortID == versionArgument.getShortIdentifier())
+      {
+        // Update the version argument to not display its short
+        // identifier.
+        try
+        {
+          versionArgument =
+              new BooleanArgument(OPTION_LONG_PRODUCT_VERSION, null,
+                  OPTION_LONG_PRODUCT_VERSION,
+                  INFO_DESCRIPTION_PRODUCT_VERSION.get());
+          this.generalArgGroup.addArgument(versionArgument);
+        }
+        catch (ArgumentException e)
+        {
+          // ignore
+        }
+      }
+    }
+
+    String longID = argument.getLongIdentifier();
+    if (longID != null)
+    {
+      if (!longArgumentsCaseSensitive)
+      {
+        longID = toLowerCase(longID);
+      }
+      if (longIDMap.containsKey(longID))
+      {
+        String conflictingName = longIDMap.get(longID).getName();
+
+        Message message =
+            ERR_ARGPARSER_DUPLICATE_LONG_ID.get(argument.getName(),
+                argument.getLongIdentifier(), conflictingName);
+        throw new ArgumentException(message);
+      }
+    }
+
+    if (shortID != null)
+    {
+      shortIDMap.put(shortID, argument);
+    }
+
+    if (longID != null)
+    {
+      longIDMap.put(longID, argument);
+    }
+
+    argumentList.add(argument);
+
+    if (group == null)
+    {
+      group = getStandardGroup(argument);
+    }
+    group.addArgument(argument);
+    argumentGroups.add(group);
+  }
+
+
+
+  /**
+   * Sets the provided argument as one which will automatically trigger
+   * the output of usage information if it is provided on the command
+   * line and no further argument validation will be performed. Note
+   * that the caller will still need to add this argument to the parser
+   * with the <CODE>addArgument</CODE> method, and the argument should
+   * not be required and should not take a value. Also, the caller will
+   * still need to check for the presence of the usage argument after
+   * calling <CODE>parseArguments</CODE> to know that no further
+   * processing will be required.
+   * 
+   * @param argument
+   *          The argument whose presence should automatically trigger
+   *          the display of usage information.
+   */
+  public void setUsageArgument(Argument argument)
+  {
+    usageArgument = argument;
+    usageOutputStream = System.out;
+  }
+
+
+
+  /**
+   * Sets the provided argument as one which will automatically trigger
+   * the output of usage information if it is provided on the command
+   * line and no further argument validation will be performed. Note
+   * that the caller will still need to add this argument to the parser
+   * with the <CODE>addArgument</CODE> method, and the argument should
+   * not be required and should not take a value. Also, the caller will
+   * still need to check for the presence of the usage argument after
+   * calling <CODE>parseArguments</CODE> to know that no further
+   * processing will be required.
+   * 
+   * @param argument
+   *          The argument whose presence should automatically trigger
+   *          the display of usage information.
+   * @param outputStream
+   *          The output stream to which the usage information should be
+   *          written.
+   */
+  public void setUsageArgument(Argument argument,
+      OutputStream outputStream)
+  {
+    usageArgument = argument;
+    usageOutputStream = outputStream;
+  }
+
+
+
+  /**
+   * Sets the provided argument which will be used to identify the file
+   * properties.
+   * 
+   * @param argument
+   *          The argument which will be used to identify the file
+   *          properties.
+   */
+  public void setFilePropertiesArgument(StringArgument argument)
+  {
+    filePropertiesPathArgument = argument;
+  }
+
+
+
+  /**
+   * Sets the provided argument which will be used to identify the file
+   * properties.
+   * 
+   * @param argument
+   *          The argument which will be used to indicate if we have to
+   *          look for properties file.
+   */
+  public void setNoPropertiesFileArgument(BooleanArgument argument)
+  {
+    noPropertiesFileArgument = argument;
+  }
+
+
+
+  /**
+   * Parses the provided set of arguments and updates the information
+   * associated with this parser accordingly.
+   * 
+   * @param rawArguments
+   *          The raw set of arguments to parse.
+   * @throws ArgumentException
+   *           If a problem was encountered while parsing the provided
+   *           arguments.
+   */
+  public void parseArguments(String[] rawArguments)
+      throws ArgumentException
+  {
+    parseArguments(rawArguments, null);
+  }
+
+
+
+  /**
+   * Parses the provided set of arguments and updates the information
+   * associated with this parser accordingly. Default values for
+   * unspecified arguments may be read from the specified properties
+   * file.
+   * 
+   * @param rawArguments
+   *          The set of raw arguments to parse.
+   * @param propertiesFile
+   *          The path to the properties file to use to obtain default
+   *          values for unspecified properties.
+   * @param requirePropertiesFile
+   *          Indicates whether the parsing should fail if the provided
+   *          properties file does not exist or is not accessible.
+   * @throws ArgumentException
+   *           If a problem was encountered while parsing the provided
+   *           arguments or interacting with the properties file.
+   */
+  public void parseArguments(String[] rawArguments,
+      String propertiesFile, boolean requirePropertiesFile)
+      throws ArgumentException
+  {
+    this.rawArguments = rawArguments;
+
+    Properties argumentProperties = null;
+
+    try
+    {
+      Properties p = new Properties();
+      FileInputStream fis = new FileInputStream(propertiesFile);
+      p.load(fis);
+      fis.close();
+      argumentProperties = p;
+    }
+    catch (Exception e)
+    {
+      if (requirePropertiesFile)
+      {
+        Message message =
+            ERR_ARGPARSER_CANNOT_READ_PROPERTIES_FILE.get(String
+                .valueOf(propertiesFile), getExceptionMessage(e));
+        throw new ArgumentException(message, e);
+      }
+    }
+
+    parseArguments(rawArguments, argumentProperties);
+  }
+
+
+
+  /**
+   * Parses the provided set of arguments and updates the information
+   * associated with this parser accordingly. Default values for
+   * unspecified arguments may be read from the specified properties if
+   * any are provided.
+   * 
+   * @param rawArguments
+   *          The set of raw arguments to parse.
+   * @param argumentProperties
+   *          A set of properties that may be used to provide default
+   *          values for arguments not included in the given raw
+   *          arguments.
+   * @throws ArgumentException
+   *           If a problem was encountered while parsing the provided
+   *           arguments.
+   */
+  public void parseArguments(String[] rawArguments,
+      Properties argumentProperties) throws ArgumentException
+  {
+    this.rawArguments = rawArguments;
+
+    boolean inTrailingArgs = false;
+
+    int numArguments = rawArguments.length;
+    for (int i = 0; i < numArguments; i++)
+    {
+      String arg = rawArguments[i];
+
+      if (inTrailingArgs)
+      {
+        trailingArguments.add(arg);
+        if ((maxTrailingArguments > 0)
+            && (trailingArguments.size() > maxTrailingArguments))
+        {
+          Message message =
+              ERR_ARGPARSER_TOO_MANY_TRAILING_ARGS
+                  .get(maxTrailingArguments);
+          throw new ArgumentException(message);
+        }
+
+        continue;
+      }
+
+      if (arg.equals("--"))
+      {
+        // This is a special indicator that we have reached the end of
+        // the named
+        // arguments and that everything that follows after this should
+        // be
+        // considered trailing arguments.
+        inTrailingArgs = true;
+      }
+      else if (arg.startsWith("--"))
+      {
+        // This indicates that we are using the long name to reference
+        // the
+        // argument. It may be in any of the following forms:
+        // --name
+        // --name value
+        // --name=value
+
+        String argName = arg.substring(2);
+        String argValue = null;
+        int equalPos = argName.indexOf('=');
+        if (equalPos < 0)
+        {
+          // This is fine. The value is not part of the argument name
+          // token.
+        }
+        else if (equalPos == 0)
+        {
+          // The argument starts with "--=", which is not acceptable.
+          Message message =
+              ERR_ARGPARSER_LONG_ARG_WITHOUT_NAME.get(arg);
+          throw new ArgumentException(message);
+        }
+        else
+        {
+          // The argument is in the form --name=value, so parse them
+          // both out.
+          argValue = argName.substring(equalPos + 1);
+          argName = argName.substring(0, equalPos);
+        }
+
+        // If we're not case-sensitive, then convert the name to
+        // lowercase.
+        String origArgName = argName;
+        if (!longArgumentsCaseSensitive)
+        {
+          argName = toLowerCase(argName);
+        }
+
+        // Get the argument with the specified name.
+        Argument a = longIDMap.get(argName);
+        if (a == null)
+        {
+          if (argName.equals(OPTION_LONG_HELP))
+          {
+            // "--help" will always be interpreted as requesting usage
+            // information.
+            try
+            {
+              getUsage(usageOutputStream);
+            }
+            catch (Exception e)
+            {
+            }
+
+            return;
+          }
+          else if (argName.equals(OPTION_LONG_PRODUCT_VERSION))
+          {
+            // "--version" will always be interpreted as requesting
+            // version
+            // information.
+            usageOrVersionDisplayed = true;
+            versionPresent = true;
+            try
+            {
+              // TODO
+              // DirectoryServer.printVersion(usageOutputStream);
+            }
+            catch (Exception e)
+            {
+            }
+
+            return;
+          }
+          else
+          {
+            // There is no such argument registered.
+            Message message =
+                ERR_ARGPARSER_NO_ARGUMENT_WITH_LONG_ID.get(origArgName);
+            throw new ArgumentException(message);
+          }
+        }
+        else
+        {
+          a.setPresent(true);
+
+          // If this is the usage argument, then immediately stop and
+          // print
+          // usage information.
+          if ((usageArgument != null)
+              && usageArgument.getName().equals(a.getName()))
+          {
+            try
+            {
+              getUsage(usageOutputStream);
+            }
+            catch (Exception e)
+            {
+            }
+
+            return;
+          }
+        }
+
+        // See if the argument takes a value. If so, then make sure one
+        // was
+        // provided. If not, then make sure none was provided.
+        if (a.needsValue())
+        {
+          if (argValue == null)
+          {
+            if ((i + 1) == numArguments)
+            {
+              Message message =
+                  ERR_ARGPARSER_NO_VALUE_FOR_ARGUMENT_WITH_LONG_ID
+                      .get(origArgName);
+              throw new ArgumentException(message);
+            }
+
+            argValue = rawArguments[++i];
+          }
+
+          MessageBuilder invalidReason = new MessageBuilder();
+          if (!a.valueIsAcceptable(argValue, invalidReason))
+          {
+            Message message =
+                ERR_ARGPARSER_VALUE_UNACCEPTABLE_FOR_LONG_ID.get(
+                    argValue, origArgName, invalidReason.toString());
+            throw new ArgumentException(message);
+          }
+
+          // If the argument already has a value, then make sure it is
+          // acceptable to have more than one.
+          if (a.hasValue() && (!a.isMultiValued()))
+          {
+            Message message =
+                ERR_ARGPARSER_NOT_MULTIVALUED_FOR_LONG_ID
+                    .get(origArgName);
+            throw new ArgumentException(message);
+          }
+
+          a.addValue(argValue);
+        }
+        else
+        {
+          if (argValue != null)
+          {
+            Message message =
+                ERR_ARGPARSER_ARG_FOR_LONG_ID_DOESNT_TAKE_VALUE
+                    .get(origArgName);
+            throw new ArgumentException(message);
+          }
+        }
+      }
+      else if (arg.startsWith("-"))
+      {
+        // This indicates that we are using the 1-character name to
+        // reference
+        // the argument. It may be in any of the following forms:
+        // -n
+        // -nvalue
+        // -n value
+        if (arg.equals("-"))
+        {
+          Message message =
+              ERR_ARGPARSER_INVALID_DASH_AS_ARGUMENT.get();
+          throw new ArgumentException(message);
+        }
+
+        char argCharacter = arg.charAt(1);
+        String argValue;
+        if (arg.length() > 2)
+        {
+          argValue = arg.substring(2);
+        }
+        else
+        {
+          argValue = null;
+        }
+
+        // Get the argument with the specified short ID.
+        Argument a = shortIDMap.get(argCharacter);
+        if (a == null)
+        {
+          if (argCharacter == '?')
+          {
+            // "-?" will always be interpreted as requesting usage
+            // information.
+            try
+            {
+              getUsage(usageOutputStream);
+            }
+            catch (Exception e)
+            {
+            }
+
+            return;
+          }
+          else if ((argCharacter == OPTION_SHORT_PRODUCT_VERSION)
+              && (!shortIDMap.containsKey(OPTION_SHORT_PRODUCT_VERSION)))
+          {
+            // "-V" will always be interpreted as requesting
+            // version information except if it's already defined (e.g
+            // in
+            // ldap tools).
+            usageOrVersionDisplayed = true;
+            versionPresent = true;
+            try
+            {
+              // TODO
+              // DirectoryServer.printVersion(usageOutputStream);
+            }
+            catch (Exception e)
+            {
+            }
+            return;
+          }
+          else
+          {
+            // There is no such argument registered.
+            Message message =
+                ERR_ARGPARSER_NO_ARGUMENT_WITH_SHORT_ID.get(String
+                    .valueOf(argCharacter));
+            throw new ArgumentException(message);
+          }
+        }
+        else
+        {
+          a.setPresent(true);
+
+          // If this is the usage argument, then immediately stop and
+          // print
+          // usage information.
+          if ((usageArgument != null)
+              && usageArgument.getName().equals(a.getName()))
+          {
+            try
+            {
+              getUsage(usageOutputStream);
+            }
+            catch (Exception e)
+            {
+            }
+
+            return;
+          }
+        }
+
+        // See if the argument takes a value. If so, then make sure one
+        // was
+        // provided. If not, then make sure none was provided.
+        if (a.needsValue())
+        {
+          if (argValue == null)
+          {
+            if ((i + 1) == numArguments)
+            {
+              Message message =
+                  ERR_ARGPARSER_NO_VALUE_FOR_ARGUMENT_WITH_SHORT_ID
+                      .get(String.valueOf(argCharacter));
+              throw new ArgumentException(message);
+            }
+
+            argValue = rawArguments[++i];
+          }
+
+          MessageBuilder invalidReason = new MessageBuilder();
+          if (!a.valueIsAcceptable(argValue, invalidReason))
+          {
+            Message message =
+                ERR_ARGPARSER_VALUE_UNACCEPTABLE_FOR_SHORT_ID.get(
+                    argValue, String.valueOf(argCharacter),
+                    invalidReason.toString());
+            throw new ArgumentException(message);
+          }
+
+          // If the argument already has a value, then make sure it is
+          // acceptable to have more than one.
+          if (a.hasValue() && (!a.isMultiValued()))
+          {
+            Message message =
+                ERR_ARGPARSER_NOT_MULTIVALUED_FOR_SHORT_ID.get(String
+                    .valueOf(argCharacter));
+            throw new ArgumentException(message);
+          }
+
+          a.addValue(argValue);
+        }
+        else
+        {
+          if (argValue != null)
+          {
+            // If we've gotten here, then it means that we're in a
+            // scenario like
+            // "-abc" where "a" is a valid argument that doesn't take a
+            // value.
+            // However, this could still be valid if all remaining
+            // characters in
+            // the value are also valid argument characters that don't
+            // take
+            // values.
+            int valueLength = argValue.length();
+            for (int j = 0; j < valueLength; j++)
+            {
+              char c = argValue.charAt(j);
+              Argument b = shortIDMap.get(c);
+              if (b == null)
+              {
+                // There is no such argument registered.
+                Message message =
+                    ERR_ARGPARSER_NO_ARGUMENT_WITH_SHORT_ID.get(String
+                        .valueOf(argCharacter));
+                throw new ArgumentException(message);
+              }
+              else if (b.needsValue())
+              {
+                // This means we're in a scenario like "-abc" where b is
+                // a
+                // valid argument that takes a value. We don't support
+                // that.
+                Message message =
+                    ERR_ARGPARSER_CANT_MIX_ARGS_WITH_VALUES.get(String
+                        .valueOf(argCharacter), argValue, String
+                        .valueOf(c));
+                throw new ArgumentException(message);
+              }
+              else
+              {
+                b.setPresent(true);
+
+                // If this is the usage argument, then immediately stop
+                // and
+                // print usage information.
+                if ((usageArgument != null)
+                    && usageArgument.getName().equals(b.getName()))
+                {
+                  try
+                  {
+                    getUsage(usageOutputStream);
+                  }
+                  catch (Exception e)
+                  {
+                  }
+
+                  return;
+                }
+              }
+            }
+          }
+        }
+      }
+      else if (allowsTrailingArguments)
+      {
+        // It doesn't start with a dash, so it must be a trailing
+        // argument if
+        // that is acceptable.
+        inTrailingArgs = true;
+        trailingArguments.add(arg);
+      }
+      else
+      {
+        // It doesn't start with a dash and we don't allow trailing
+        // arguments,
+        // so this is illegal.
+        Message message =
+            ERR_ARGPARSER_DISALLOWED_TRAILING_ARGUMENT.get(arg);
+        throw new ArgumentException(message);
+      }
+    }
+
+    // If we allow trailing arguments and there is a minimum number,
+    // then make
+    // sure at least that many were provided.
+    if (allowsTrailingArguments && (minTrailingArguments > 0))
+    {
+      if (trailingArguments.size() < minTrailingArguments)
+      {
+        Message message =
+            ERR_ARGPARSER_TOO_FEW_TRAILING_ARGUMENTS
+                .get(minTrailingArguments);
+        throw new ArgumentException(message);
+      }
+    }
+
+    // If we don't have the argumentProperties, try to load a properties
+    // file.
+    if (argumentProperties == null)
+    {
+      argumentProperties = checkExternalProperties();
+    }
+
+    // Iterate through all of the arguments. For any that were not
+    // provided on
+    // the command line, see if there is an alternate default that can
+    // be used.
+    // For cases where there is not, see that argument is required.
+    for (Argument a : argumentList)
+    {
+      if (!a.isPresent())
+      {
+        // See if there is a value in the properties that can be used
+        if ((argumentProperties != null)
+            && (a.getPropertyName() != null))
+        {
+          String value =
+              argumentProperties.getProperty(a.getPropertyName()
+                  .toLowerCase());
+          MessageBuilder invalidReason = new MessageBuilder();
+          if (value != null)
+          {
+            Boolean addValue = true;
+            if (!(a instanceof BooleanArgument))
+            {
+              addValue = a.valueIsAcceptable(value, invalidReason);
+            }
+            if (addValue)
+            {
+              a.addValue(value);
+              if (a.needsValue())
+              {
+                a.setPresent(true);
+              }
+              a.setValueSetByProperty(true);
+            }
+          }
+        }
+      }
+
+      if ((!a.isPresent()) && a.needsValue())
+      {
+        // See if the argument defines a default.
+        if (a.getDefaultValue() != null)
+        {
+          a.addValue(a.getDefaultValue());
+        }
+
+        // If there is still no value and the argument is required, then
+        // that's
+        // a problem.
+        if ((!a.hasValue()) && a.isRequired())
+        {
+          Message message =
+              ERR_ARGPARSER_NO_VALUE_FOR_REQUIRED_ARG.get(a.getName());
+          throw new ArgumentException(message);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Check if we have a properties file.
+   * 
+   * @return The properties found in the properties file or null.
+   * @throws ArgumentException
+   *           If a problem was encountered while parsing the provided
+   *           arguments.
+   */
+  Properties checkExternalProperties() throws ArgumentException
+  {
+    // We don't look for properties file.
+    if ((noPropertiesFileArgument != null)
+        && (noPropertiesFileArgument.isPresent()))
+    {
+      return null;
+    }
+
+    // Check if we have a properties file argument
+    if (filePropertiesPathArgument == null)
+    {
+      return null;
+    }
+
+    // check if the properties file argument has been set. If not
+    // look for default location.
+    String propertiesFilePath = null;
+    if (filePropertiesPathArgument.isPresent())
+    {
+      propertiesFilePath = filePropertiesPathArgument.getValue();
+    }
+    else
+    {
+      // Check in "user home"/.opends directory
+      String userDir = System.getProperty("user.home");
+      propertiesFilePath =
+          findPropertiesFile(userDir + File.separator
+              + DEFAULT_OPENDS_CONFIG_DIR);
+
+      // TODO
+      /*
+       * if (propertiesFilePath == null) { // check
+       * "Opends instance"/config directory String instanceDir =
+       * DirectoryServer.getInstanceRoot(); propertiesFilePath =
+       * findPropertiesFile(instanceDir+ File.separator + "config"); }
+       */
+    }
+
+    // We don't have a properties file location
+    if (propertiesFilePath == null)
+    {
+      return null;
+    }
+
+    // We have a location for the properties file.
+    Properties argumentProperties = new Properties();
+    String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+    try
+    {
+      Properties p = new Properties();
+      FileInputStream fis = new FileInputStream(propertiesFilePath);
+      p.load(fis);
+      fis.close();
+
+      for (Enumeration<?> e = p.propertyNames(); e.hasMoreElements();)
+      {
+        String currentPropertyName = (String) e.nextElement();
+        String propertyName = currentPropertyName;
+
+        // Property name form <script name>.<property name> has the
+        // precedence to <property name>
+        if (scriptName != null)
+        {
+          if (currentPropertyName.startsWith(scriptName))
+          {
+            propertyName =
+                currentPropertyName.substring(scriptName.length() + 1);
+          }
+          else
+          {
+            if (p.containsKey(scriptName + "." + currentPropertyName))
+            {
+              continue;
+            }
+          }
+        }
+        argumentProperties.setProperty(propertyName.toLowerCase(), p
+            .getProperty(currentPropertyName));
+      }
+    }
+    catch (Exception e)
+    {
+      Message message =
+          ERR_ARGPARSER_CANNOT_READ_PROPERTIES_FILE.get(String
+              .valueOf(propertiesFilePath), getExceptionMessage(e));
+      throw new ArgumentException(message, e);
+    }
+    return argumentProperties;
+  }
+
+
+
+  /**
+   * Get the absolute path of the properties file.
+   * 
+   * @param directory
+   *          The location in which we should look for properties file
+   * @return The absolute path of the properties file or null
+   */
+  private String findPropertiesFile(String directory)
+  {
+    // Look for the tools properties file
+    File f =
+        new File(directory, DEFAULT_OPENDS_PROPERTIES_FILE_NAME
+            + DEFAULT_OPENDS_PROPERTIES_FILE_EXTENSION);
+    if (f.exists() && f.canRead())
+    {
+      return f.getAbsolutePath();
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+
+
+  /**
+   * Appends usage information based on the defined arguments to the
+   * provided buffer.
+   * 
+   * @param buffer
+   *          The buffer to which the usage information should be
+   *          appended.
+   */
+  public void getUsage(StringBuilder buffer)
+  {
+    usageOrVersionDisplayed = true;
+    if ((toolDescription != null) && (toolDescription.length() > 0))
+    {
+      buffer
+          .append(wrapText(toolDescription.toString(), MAX_LENGTH - 1));
+      buffer.append(EOL);
+      buffer.append(EOL);
+    }
+
+    String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+    if ((scriptName == null) || (scriptName.length() == 0))
+    {
+      buffer.append(INFO_ARGPARSER_USAGE_JAVA_CLASSNAME
+          .get(mainClassName));
+    }
+    else
+    {
+      buffer.append(INFO_ARGPARSER_USAGE_JAVA_SCRIPTNAME
+          .get(scriptName));
+    }
+
+    if (allowsTrailingArguments)
+    {
+      if (trailingArgsDisplayName == null)
+      {
+        buffer.append(" " + INFO_ARGPARSER_USAGE_TRAILINGARGS.get());
+      }
+      else
+      {
+        buffer.append(" ");
+        buffer.append(trailingArgsDisplayName);
+      }
+    }
+    buffer.append(EOL);
+    buffer.append(INFO_SUBCMDPARSER_WHERE_OPTIONS_INCLUDE.get());
+    buffer.append(EOL);
+    buffer.append(EOL);
+
+    Argument helpArgument = null;
+
+    boolean printHeaders = printUsageGroupHeaders();
+    for (ArgumentGroup argGroup : argumentGroups)
+    {
+      if (argGroup.containsArguments() && printHeaders)
+      {
+        // Print the groups description if any
+        Message groupDesc = argGroup.getDescription();
+        if (groupDesc != null && !Message.EMPTY.equals(groupDesc))
+        {
+          buffer.append(EOL);
+          buffer.append(wrapText(groupDesc.toString(), MAX_LENGTH - 1));
+          buffer.append(EOL);
+          buffer.append(EOL);
+        }
+      }
+
+      for (Argument a : argGroup.getArguments())
+      {
+        // If this argument is hidden, then skip it.
+        if (a.isHidden())
+        {
+          continue;
+        }
+
+        // Help argument should be printed at the end
+        if ((usageArgument != null)
+            && usageArgument.getName().equals(a.getName()))
+        {
+          helpArgument = a;
+          continue;
+        }
+        printArgumentUsage(a, buffer);
+      }
+    }
+    if (helpArgument != null)
+    {
+      printArgumentUsage(helpArgument, buffer);
+    }
+    else
+    {
+      buffer.append(EOL);
+      buffer.append("-?");
+      buffer.append(EOL);
+    }
+  }
+
+
+
+  /**
+   * Retrieves a message containing usage information based on the
+   * defined arguments.
+   * 
+   * @return A string containing usage information based on the defined
+   *         arguments.
+   */
+  public Message getUsageMessage()
+  {
+    StringBuilder buffer = new StringBuilder();
+    getUsage(buffer);
+
+    // TODO: rework getUsage(OutputStream) to work with messages
+    // framework
+    return Message.raw(buffer.toString());
+  }
+
+
+
+  /**
+   * Retrieves a string containing usage information based on the
+   * defined arguments.
+   * 
+   * @return A string containing usage information based on the defined
+   *         arguments.
+   */
+  public String getUsage()
+  {
+    StringBuilder buffer = new StringBuilder();
+    getUsage(buffer);
+
+    return buffer.toString();
+  }
+
+
+
+  /**
+   * Writes usage information based on the defined arguments to the
+   * provided output stream.
+   * 
+   * @param outputStream
+   *          The output stream to which the usage information should be
+   *          written.
+   * @throws IOException
+   *           If a problem occurs while attempting to write the usage
+   *           information to the provided output stream.
+   */
+  public void getUsage(OutputStream outputStream) throws IOException
+  {
+    StringBuilder buffer = new StringBuilder();
+    getUsage(buffer);
+
+    outputStream.write(getBytes(buffer.toString()));
+  }
+
+
+
+  /**
+   * Indicates whether the version or the usage information has been
+   * displayed to the end user either by an explicit argument like "-H"
+   * or "--help", or by a built-in argument like "-?".
+   * 
+   * @return {@code true} if the usage information has been displayed,
+   *         or {@code false} if not.
+   */
+  public boolean usageOrVersionDisplayed()
+  {
+    return usageOrVersionDisplayed;
+  }
+
+
+
+  /**
+   * Appends argument usage information to the provided buffer.
+   * 
+   * @param a
+   *          The argument to handle.
+   * @param buffer
+   *          The buffer to which the usage information should be
+   *          appended.
+   */
+  private void printArgumentUsage(Argument a, StringBuilder buffer)
+  {
+    // Write a line with the short and/or long identifiers that may be
+    // used
+    // for the argument.
+    final int indentLength = INDENT.length();
+    Character shortID = a.getShortIdentifier();
+    String longID = a.getLongIdentifier();
+    if (shortID != null)
+    {
+      int currentLength = buffer.length();
+
+      if (usageArgument.getName().equals(a.getName()))
+      {
+        buffer.append("-?, ");
+      }
+
+      buffer.append("-");
+      buffer.append(shortID.charValue());
+
+      if (a.needsValue() && longID == null)
+      {
+        buffer.append(" ");
+        buffer.append(a.getValuePlaceholder());
+      }
+
+      if (longID != null)
+      {
+        StringBuilder newBuffer = new StringBuilder();
+        newBuffer.append(", --");
+        newBuffer.append(longID);
+
+        if (a.needsValue())
+        {
+          newBuffer.append(" ");
+          newBuffer.append(a.getValuePlaceholder());
+        }
+
+        int lineLength =
+            (buffer.length() - currentLength) + newBuffer.length();
+        if (lineLength > MAX_LENGTH)
+        {
+          buffer.append(EOL);
+          buffer.append(newBuffer.toString());
+        }
+        else
+        {
+          buffer.append(newBuffer.toString());
+        }
+      }
+
+      buffer.append(EOL);
+    }
+    else
+    {
+      if (longID != null)
+      {
+        if (usageArgument.getName().equals(a.getName()))
+        {
+          buffer.append("-?, ");
+        }
+        buffer.append("--");
+        buffer.append(longID);
+
+        if (a.needsValue())
+        {
+          buffer.append(" ");
+          buffer.append(a.getValuePlaceholder());
+        }
+
+        buffer.append(EOL);
+      }
+    }
+
+    // Write one or more lines with the description of the argument.
+    // We will
+    // indent the description five characters and try our best to wrap
+    // at or
+    // before column 79 so it will be friendly to 80-column displays.
+    Message description = a.getDescription();
+    int descMaxLength = MAX_LENGTH - indentLength - 1;
+    if (description.length() <= descMaxLength)
+    {
+      buffer.append(INDENT);
+      buffer.append(description);
+      buffer.append(EOL);
+    }
+    else
+    {
+      String s = description.toString();
+      while (s.length() > descMaxLength)
+      {
+        int spacePos = s.lastIndexOf(' ', descMaxLength);
+        if (spacePos > 0)
+        {
+          buffer.append(INDENT);
+          buffer.append(s.substring(0, spacePos).trim());
+          s = s.substring(spacePos + 1).trim();
+          buffer.append(EOL);
+        }
+        else
+        {
+          // There are no spaces in the first 74 columns. See if there
+          // is one
+          // after that point. If so, then break there. If not, then
+          // don't
+          // break at all.
+          spacePos = s.indexOf(' ');
+          if (spacePos > 0)
+          {
+            buffer.append(INDENT);
+            buffer.append(s.substring(0, spacePos).trim());
+            s = s.substring(spacePos + 1).trim();
+            buffer.append(EOL);
+          }
+          else
+          {
+            buffer.append(INDENT);
+            buffer.append(s);
+            s = "";
+            buffer.append(EOL);
+          }
+        }
+      }
+
+      if (s.length() > 0)
+      {
+        buffer.append(INDENT);
+        buffer.append(s);
+        buffer.append(EOL);
+      }
+    }
+
+    if (a.needsValue() && (a.getDefaultValue() != null)
+        && (a.getDefaultValue().length() > 0))
+    {
+      buffer.append(INDENT);
+      buffer.append(INFO_ARGPARSER_USAGE_DEFAULT_VALUE.get(
+          a.getDefaultValue()).toString());
+      buffer.append(EOL);
+    }
+  }
+
+
+
+  /**
+   * Given an argument, returns an appropriate group. Arguments may be
+   * part of one of the special groups or the default group.
+   * 
+   * @param argument
+   *          for which a group is requested
+   * @return argument group appropriate for <code>argument</code>
+   */
+  ArgumentGroup getStandardGroup(Argument argument)
+  {
+    ArgumentGroup group;
+    if (isInputOutputArgument(argument))
+    {
+      group = ioArgGroup;
+    }
+    else if (isGeneralArgument(argument))
+    {
+      group = generalArgGroup;
+    }
+    else if (isLdapConnectionArgument(argument))
+    {
+      group = ldapArgGroup;
+    }
+    else
+    {
+      group = defaultArgGroup;
+    }
+    return group;
+  }
+
+
+
+  /**
+   * Indicates whether or not argument group description headers should
+   * be printed.
+   * 
+   * @return boolean where true means print the descriptions
+   */
+  boolean printUsageGroupHeaders()
+  {
+    // If there is only a single group then we won't print them.
+    int groupsContainingArgs = 0;
+    for (ArgumentGroup argGroup : argumentGroups)
+    {
+      if (argGroup.containsNonHiddenArguments())
+      {
+        groupsContainingArgs++;
+      }
+    }
+    return groupsContainingArgs > 1;
+  }
+
+
+
+  private void initGroups()
+  {
+    this.argumentGroups = new TreeSet<ArgumentGroup>();
+    this.argumentGroups.add(defaultArgGroup);
+    this.argumentGroups.add(ldapArgGroup);
+    this.argumentGroups.add(generalArgGroup);
+    this.argumentGroups.add(ioArgGroup);
+
+    try
+    {
+      versionArgument =
+          new BooleanArgument(OPTION_LONG_PRODUCT_VERSION,
+              OPTION_SHORT_PRODUCT_VERSION,
+              OPTION_LONG_PRODUCT_VERSION,
+              INFO_DESCRIPTION_PRODUCT_VERSION.get());
+      this.generalArgGroup.addArgument(versionArgument);
+    }
+    catch (ArgumentException e)
+    {
+      // ignore
+    }
+  }
+
+
+
+  private boolean isInputOutputArgument(Argument arg)
+  {
+    boolean io = false;
+    if (arg != null)
+    {
+      String longId = arg.getLongIdentifier();
+      io =
+          OPTION_LONG_VERBOSE.equals(longId)
+              || OPTION_LONG_QUIET.equals(longId)
+              || OPTION_LONG_NO_PROMPT.equals(longId)
+              || OPTION_LONG_PROP_FILE_PATH.equals(longId)
+              || OPTION_LONG_NO_PROP_FILE.equals(longId)
+              || OPTION_LONG_SCRIPT_FRIENDLY.equals(longId)
+              || OPTION_LONG_DONT_WRAP.equals(longId)
+              || OPTION_LONG_ENCODING.equals(longId)
+              || OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT.equals(longId)
+              || OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH
+                  .equals(longId)
+              || OPTION_LONG_BATCH_FILE_PATH.equals(longId);
+    }
+    return io;
+  }
+
+
+
+  private boolean isLdapConnectionArgument(Argument arg)
+  {
+    boolean ldap = false;
+    if (arg != null)
+    {
+      String longId = arg.getLongIdentifier();
+      ldap =
+          OPTION_LONG_USE_SSL.equals(longId)
+              || OPTION_LONG_START_TLS.equals(longId)
+              || OPTION_LONG_HOST.equals(longId)
+              || OPTION_LONG_PORT.equals(longId)
+              || OPTION_LONG_BINDDN.equals(longId)
+              || OPTION_LONG_BINDPWD.equals(longId)
+              || OPTION_LONG_BINDPWD_FILE.equals(longId)
+              || OPTION_LONG_SASLOPTION.equals(longId)
+              || OPTION_LONG_TRUSTALL.equals(longId)
+              || OPTION_LONG_TRUSTSTOREPATH.equals(longId)
+              || OPTION_LONG_TRUSTSTORE_PWD.equals(longId)
+              || OPTION_LONG_TRUSTSTORE_PWD_FILE.equals(longId)
+              || OPTION_LONG_KEYSTOREPATH.equals(longId)
+              || OPTION_LONG_KEYSTORE_PWD.equals(longId)
+              || OPTION_LONG_KEYSTORE_PWD_FILE.equals(longId)
+              || OPTION_LONG_CERT_NICKNAME.equals(longId)
+              || OPTION_LONG_REFERENCED_HOST_NAME.equals(longId)
+              || OPTION_LONG_ADMIN_UID.equals(longId)
+              || OPTION_LONG_REPORT_AUTHZ_ID.equals(longId)
+              || OPTION_LONG_USE_PW_POLICY_CTL.equals(longId)
+              || OPTION_LONG_USE_SASL_EXTERNAL.equals(longId)
+              || OPTION_LONG_PROTOCOL_VERSION.equals(longId);
+    }
+    return ldap;
+  }
+
+
+
+  private boolean isGeneralArgument(Argument arg)
+  {
+    boolean general = false;
+    if (arg != null)
+    {
+      String longId = arg.getLongIdentifier();
+      general =
+          OPTION_LONG_HELP.equals(longId)
+              || OPTION_LONG_PRODUCT_VERSION.equals(longId);
+    }
+    return general;
+  }
+
+
+
+  /**
+   * Returns whether the usage argument was provided or not. This method
+   * should be called after a call to parseArguments.
+   * 
+   * @return <CODE>true</CODE> if the usage argument was provided and
+   *         <CODE>false</CODE> otherwise.
+   */
+  public boolean isUsageArgumentPresent()
+  {
+    boolean isUsageArgumentPresent = false;
+    if (usageArgument != null)
+    {
+      isUsageArgumentPresent = usageArgument.isPresent();
+    }
+    return isUsageArgumentPresent;
+  }
+
+
+
+  /**
+   * Returns whether the version argument was provided or not. This
+   * method should be called after a call to parseArguments.
+   * 
+   * @return <CODE>true</CODE> if the version argument was provided and
+   *         <CODE>false</CODE> otherwise.
+   */
+  public boolean isVersionArgumentPresent()
+  {
+    return versionPresent;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java b/sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java
new file mode 100644
index 0000000..6fc6de3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ArgumentParserConnectionFactory.java
@@ -0,0 +1,940 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.AdminToolMessages.INFO_DESCRIPTION_ADMIN_UID;
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.util.logging.Logger;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.X509KeyManager;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginException;
+
+import org.opends.admin.ads.util.ApplicationKeyManager;
+import org.opends.messages.Message;
+import org.opends.quicksetup.Constants;
+import org.opends.sdk.*;
+import org.opends.sdk.ldap.LDAPConnectionFactory;
+import org.opends.sdk.ldap.LDAPConnectionOptions;
+import org.opends.sdk.requests.BindRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.sasl.*;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.SSLUtils;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.sdk.util.ssl.PromptingTrustManager;
+import org.opends.sdk.util.ssl.TrustAllTrustManager;
+import org.opends.sdk.util.ssl.TrustStoreTrustManager;
+import org.opends.server.util.SelectableCertificateKeyManager;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * A connection factory designed for use with command line tools.
+ */
+final class ArgumentParserConnectionFactory extends
+    AbstractConnectionFactory<AsynchronousConnection> implements
+    ConnectionFactory<AsynchronousConnection>
+{
+  /**
+   * End Of Line.
+   */
+  static final String EOL = System.getProperty("line.separator");
+
+  /**
+   * The Logger.
+   */
+  static final Logger LOG = Logger
+      .getLogger(ArgumentParserConnectionFactory.class.getName());
+
+  /**
+   * The 'hostName' global argument.
+   */
+  private StringArgument hostNameArg = null;
+
+  /**
+   * The 'port' global argument.
+   */
+  private IntegerArgument portArg = null;
+
+  /**
+   * The 'bindDN' global argument.
+   */
+  private StringArgument bindDnArg = null;
+
+  /**
+   * The 'adminUID' global argument.
+   */
+  private StringArgument adminUidArg = null;
+
+  /**
+   * The 'bindPasswordFile' global argument.
+   */
+  private FileBasedArgument bindPasswordFileArg = null;
+
+  /**
+   * The 'bindPassword' global argument.
+   */
+  private StringArgument bindPasswordArg = null;
+
+  /**
+   * The 'trustAllArg' global argument.
+   */
+  private BooleanArgument trustAllArg = null;
+
+  /**
+   * The 'trustStore' global argument.
+   */
+  private StringArgument trustStorePathArg = null;
+
+  /**
+   * The 'trustStorePassword' global argument.
+   */
+  private StringArgument trustStorePasswordArg = null;
+
+  /**
+   * The 'trustStorePasswordFile' global argument.
+   */
+  private FileBasedArgument trustStorePasswordFileArg = null;
+
+  /**
+   * The 'keyStore' global argument.
+   */
+  private StringArgument keyStorePathArg = null;
+
+  /**
+   * The 'keyStorePassword' global argument.
+   */
+  private StringArgument keyStorePasswordArg = null;
+
+  /**
+   * The 'keyStorePasswordFile' global argument.
+   */
+  private FileBasedArgument keyStorePasswordFileArg = null;
+
+  /**
+   * The 'certNicknameArg' global argument.
+   */
+  private StringArgument certNicknameArg = null;
+
+  /**
+   * The 'useSSLArg' global argument.
+   */
+  private BooleanArgument useSSLArg = null;
+
+  /**
+   * The 'useStartTLSArg' global argument.
+   */
+  private BooleanArgument useStartTLSArg = null;
+
+  /**
+   * Argument indicating a SASL option.
+   */
+  private StringArgument saslOptionArg = null;
+
+  /**
+   * Whether to request that the server return the authorization ID in
+   * the bind response.
+   */
+  private final BooleanArgument reportAuthzID;
+
+  /**
+   * Whether to use the password policy control in the bind request.
+   */
+  private final BooleanArgument usePasswordPolicyControl;
+
+  private int port = 389;
+
+  private SSLContext sslContext;
+
+  private ConnectionFactory<? extends AsynchronousConnection> connFactory;
+
+  private BindRequest bindRequest = null;
+
+  private final ConsoleApplication app;
+
+
+
+  public ArgumentParserConnectionFactory(ArgumentParser argumentParser,
+      ConsoleApplication app) throws ArgumentException
+  {
+    this(argumentParser, app, "cn=Directory Manager", 389, false);
+  }
+
+
+
+  public ArgumentParserConnectionFactory(ArgumentParser argumentParser,
+      ConsoleApplication app, String defaultBindDN, int defaultPort,
+      boolean alwaysSSL) throws ArgumentException
+  {
+    this.app = app;
+    useSSLArg = new BooleanArgument("useSSL", OPTION_SHORT_USE_SSL,
+        OPTION_LONG_USE_SSL, INFO_DESCRIPTION_USE_SSL.get());
+    useSSLArg.setPropertyName(OPTION_LONG_USE_SSL);
+    if (!alwaysSSL)
+    {
+      argumentParser.addLdapConnectionArgument(useSSLArg);
+    }
+    else
+    {
+      // simulate that the useSSL arg has been given in the CLI
+      useSSLArg.setPresent(true);
+    }
+
+    useStartTLSArg = new BooleanArgument("startTLS",
+        OPTION_SHORT_START_TLS, OPTION_LONG_START_TLS,
+        INFO_DESCRIPTION_START_TLS.get());
+    useStartTLSArg.setPropertyName(OPTION_LONG_START_TLS);
+    if (!alwaysSSL)
+    {
+      argumentParser.addLdapConnectionArgument(useStartTLSArg);
+    }
+
+    String defaultHostName;
+    try
+    {
+      defaultHostName = InetAddress.getLocalHost().getHostName();
+    }
+    catch (Exception e)
+    {
+      defaultHostName = "Unknown (" + e + ")";
+    }
+    hostNameArg = new StringArgument("host", OPTION_SHORT_HOST,
+        OPTION_LONG_HOST, false, false, true, INFO_HOST_PLACEHOLDER
+            .get(), defaultHostName, null, INFO_DESCRIPTION_HOST.get());
+    hostNameArg.setPropertyName(OPTION_LONG_HOST);
+    argumentParser.addLdapConnectionArgument(hostNameArg);
+
+    Message portDescription = INFO_DESCRIPTION_PORT.get();
+    if (alwaysSSL)
+    {
+      portDescription = INFO_DESCRIPTION_ADMIN_PORT.get();
+    }
+
+    portArg = new IntegerArgument("port", OPTION_SHORT_PORT,
+        OPTION_LONG_PORT, false, false, true, INFO_PORT_PLACEHOLDER
+            .get(), defaultPort, null, portDescription);
+    portArg.setPropertyName(OPTION_LONG_PORT);
+    argumentParser.addLdapConnectionArgument(portArg);
+
+    bindDnArg = new StringArgument("bindDN", OPTION_SHORT_BINDDN,
+        OPTION_LONG_BINDDN, false, false, true, INFO_BINDDN_PLACEHOLDER
+            .get(), defaultBindDN, null, INFO_DESCRIPTION_BINDDN.get());
+    bindDnArg.setPropertyName(OPTION_LONG_BINDDN);
+    argumentParser.addLdapConnectionArgument(bindDnArg);
+
+    // It is up to the classes that required admin UID to make this
+    // argument
+    // visible and add it.
+    adminUidArg = new StringArgument("adminUID", 'I',
+        OPTION_LONG_ADMIN_UID, false, false, true,
+        INFO_ADMINUID_PLACEHOLDER.get(), Constants.GLOBAL_ADMIN_UID,
+        null, INFO_DESCRIPTION_ADMIN_UID.get());
+    adminUidArg.setPropertyName(OPTION_LONG_ADMIN_UID);
+    adminUidArg.setHidden(true);
+
+    bindPasswordArg = new StringArgument("bindPassword",
+        OPTION_SHORT_BINDPWD, OPTION_LONG_BINDPWD, false, false, true,
+        INFO_BINDPWD_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_BINDPASSWORD.get());
+    bindPasswordArg.setPropertyName(OPTION_LONG_BINDPWD);
+    argumentParser.addLdapConnectionArgument(bindPasswordArg);
+
+    bindPasswordFileArg = new FileBasedArgument("bindPasswordFile",
+        OPTION_SHORT_BINDPWD_FILE, OPTION_LONG_BINDPWD_FILE, false,
+        false, INFO_BINDPWD_FILE_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_BINDPASSWORDFILE.get());
+    bindPasswordFileArg.setPropertyName(OPTION_LONG_BINDPWD_FILE);
+    argumentParser.addLdapConnectionArgument(bindPasswordFileArg);
+
+    saslOptionArg = new StringArgument("sasloption",
+        OPTION_SHORT_SASLOPTION, OPTION_LONG_SASLOPTION, false, true,
+        true, INFO_SASL_OPTION_PLACEHOLDER.get(), null, null,
+        INFO_LDAP_CONN_DESCRIPTION_SASLOPTIONS.get());
+    saslOptionArg.setPropertyName(OPTION_LONG_SASLOPTION);
+    argumentParser.addLdapConnectionArgument(saslOptionArg);
+
+    trustAllArg = new BooleanArgument("trustAll",
+        OPTION_SHORT_TRUSTALL, OPTION_LONG_TRUSTALL,
+        INFO_DESCRIPTION_TRUSTALL.get());
+    trustAllArg.setPropertyName(OPTION_LONG_TRUSTALL);
+    argumentParser.addLdapConnectionArgument(trustAllArg);
+
+    trustStorePathArg = new StringArgument("trustStorePath",
+        OPTION_SHORT_TRUSTSTOREPATH, OPTION_LONG_TRUSTSTOREPATH, false,
+        false, true, INFO_TRUSTSTOREPATH_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_TRUSTSTOREPATH.get());
+    trustStorePathArg.setPropertyName(OPTION_LONG_TRUSTSTOREPATH);
+    argumentParser.addLdapConnectionArgument(trustStorePathArg);
+
+    trustStorePasswordArg = new StringArgument("trustStorePassword",
+        OPTION_SHORT_TRUSTSTORE_PWD, OPTION_LONG_TRUSTSTORE_PWD, false,
+        false, true, INFO_TRUSTSTORE_PWD_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_TRUSTSTOREPASSWORD.get());
+    trustStorePasswordArg.setPropertyName(OPTION_LONG_TRUSTSTORE_PWD);
+    argumentParser.addLdapConnectionArgument(trustStorePasswordArg);
+
+    trustStorePasswordFileArg = new FileBasedArgument(
+        "trustStorePasswordFile", OPTION_SHORT_TRUSTSTORE_PWD_FILE,
+        OPTION_LONG_TRUSTSTORE_PWD_FILE, false, false,
+        INFO_TRUSTSTORE_PWD_FILE_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_TRUSTSTOREPASSWORD_FILE.get());
+    trustStorePasswordFileArg
+        .setPropertyName(OPTION_LONG_TRUSTSTORE_PWD_FILE);
+    argumentParser.addLdapConnectionArgument(trustStorePasswordFileArg);
+
+    keyStorePathArg = new StringArgument("keyStorePath",
+        OPTION_SHORT_KEYSTOREPATH, OPTION_LONG_KEYSTOREPATH, false,
+        false, true, INFO_KEYSTOREPATH_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_KEYSTOREPATH.get());
+    keyStorePathArg.setPropertyName(OPTION_LONG_KEYSTOREPATH);
+    argumentParser.addLdapConnectionArgument(keyStorePathArg);
+
+    keyStorePasswordArg = new StringArgument("keyStorePassword",
+        OPTION_SHORT_KEYSTORE_PWD, OPTION_LONG_KEYSTORE_PWD, false,
+        false, true, INFO_KEYSTORE_PWD_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_KEYSTOREPASSWORD.get());
+    keyStorePasswordArg.setPropertyName(OPTION_LONG_KEYSTORE_PWD);
+    argumentParser.addLdapConnectionArgument(keyStorePasswordArg);
+
+    keyStorePasswordFileArg = new FileBasedArgument(
+        "keystorePasswordFile", OPTION_SHORT_KEYSTORE_PWD_FILE,
+        OPTION_LONG_KEYSTORE_PWD_FILE, false, false,
+        INFO_KEYSTORE_PWD_FILE_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_KEYSTOREPASSWORD_FILE.get());
+    keyStorePasswordFileArg
+        .setPropertyName(OPTION_LONG_KEYSTORE_PWD_FILE);
+    argumentParser.addLdapConnectionArgument(keyStorePasswordFileArg);
+
+    certNicknameArg = new StringArgument("certNickname",
+        OPTION_SHORT_CERT_NICKNAME, OPTION_LONG_CERT_NICKNAME, false,
+        false, true, INFO_NICKNAME_PLACEHOLDER.get(), null, null,
+        INFO_DESCRIPTION_CERT_NICKNAME.get());
+    certNicknameArg.setPropertyName(OPTION_LONG_CERT_NICKNAME);
+    argumentParser.addLdapConnectionArgument(certNicknameArg);
+
+    reportAuthzID = new BooleanArgument("reportauthzid", 'E',
+        OPTION_LONG_REPORT_AUTHZ_ID, INFO_DESCRIPTION_REPORT_AUTHZID
+            .get());
+    reportAuthzID.setPropertyName(OPTION_LONG_REPORT_AUTHZ_ID);
+    argumentParser.addArgument(reportAuthzID);
+
+    usePasswordPolicyControl = new BooleanArgument(
+        "usepwpolicycontrol", null, OPTION_LONG_USE_PW_POLICY_CTL,
+        INFO_DESCRIPTION_USE_PWP_CONTROL.get());
+    usePasswordPolicyControl
+        .setPropertyName(OPTION_LONG_USE_PW_POLICY_CTL);
+    argumentParser.addArgument(usePasswordPolicyControl);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public <P> ConnectionFuture<? extends AsynchronousConnection> getAsynchronousConnection(
+      ConnectionResultHandler<? super AsynchronousConnection, P> handler,
+      P p)
+  {
+    return connFactory.getAsynchronousConnection(handler, p);
+  }
+
+
+
+  public void validate() throws ArgumentException
+  {
+    port = portArg.getIntValue();
+
+    // Couldn't have at the same time bindPassword and bindPasswordFile
+    if (bindPasswordArg.isPresent() && bindPasswordFileArg.isPresent())
+    {
+      Message message = ERR_TOOL_CONFLICTING_ARGS
+          .get(bindPasswordArg.getLongIdentifier(), bindPasswordFileArg
+              .getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    // Couldn't have at the same time trustAll and
+    // trustStore related arg
+    if (trustAllArg.isPresent() && trustStorePathArg.isPresent())
+    {
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(trustAllArg
+          .getLongIdentifier(), trustStorePathArg.getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+    if (trustAllArg.isPresent() && trustStorePasswordArg.isPresent())
+    {
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(trustAllArg
+          .getLongIdentifier(), trustStorePasswordArg
+          .getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+    if (trustAllArg.isPresent()
+        && trustStorePasswordFileArg.isPresent())
+    {
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(trustAllArg
+          .getLongIdentifier(), trustStorePasswordFileArg
+          .getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    // Couldn't have at the same time trustStorePasswordArg and
+    // trustStorePasswordFileArg
+    if (trustStorePasswordArg.isPresent()
+        && trustStorePasswordFileArg.isPresent())
+    {
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(
+          trustStorePasswordArg.getLongIdentifier(),
+          trustStorePasswordFileArg.getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    if (trustStorePathArg.isPresent())
+    {
+      // Check that the path exists and is readable
+      String value = trustStorePathArg.getValue();
+      if (!canRead(trustStorePathArg.getValue()))
+      {
+        Message message = ERR_CANNOT_READ_TRUSTSTORE.get(value);
+        throw new ArgumentException(message);
+      }
+    }
+
+    if (keyStorePathArg.isPresent())
+    {
+      // Check that the path exists and is readable
+      String value = keyStorePathArg.getValue();
+      if (!canRead(trustStorePathArg.getValue()))
+      {
+        Message message = ERR_CANNOT_READ_KEYSTORE.get(value);
+        throw new ArgumentException(message);
+      }
+    }
+
+    // Couldn't have at the same time startTLSArg and
+    // useSSLArg
+    if (useStartTLSArg.isPresent() && useSSLArg.isPresent())
+    {
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(useStartTLSArg
+          .getLongIdentifier(), useSSLArg.getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    try
+    {
+      if (useSSLArg.isPresent() || useStartTLSArg.isPresent())
+      {
+        String clientAlias;
+        if (certNicknameArg.isPresent())
+        {
+          clientAlias = certNicknameArg.getValue();
+        }
+        else
+        {
+          clientAlias = null;
+        }
+
+        if (sslContext == null)
+        {
+          TrustManager trustManager = getTrustManager();
+
+          KeyManager keyManager = null;
+          X509KeyManager akm = getKeyManager(keyStorePathArg.getValue());
+
+          if (keyManager != null && clientAlias != null)
+          {
+            keyManager = new SelectableCertificateKeyManager(akm,
+                clientAlias);
+          }
+          sslContext = SSLUtils.getSSLContext(trustManager, keyManager);
+        }
+      }
+    }
+    catch (Exception e)
+    {
+      throw new ArgumentException(ERR_LDAP_CONN_CANNOT_INITIALIZE_SSL
+          .get(e.toString()), e);
+    }
+
+    if (sslContext != null)
+    {
+      LDAPConnectionOptions options = LDAPConnectionOptions
+          .defaultOptions().setSSLContext(sslContext).setUseStartTLS(
+              useStartTLSArg.isPresent());
+      connFactory = new LDAPConnectionFactory(hostNameArg.getValue(),
+          port, options);
+    }
+    else
+    {
+      connFactory = new LDAPConnectionFactory(hostNameArg.getValue(),
+          port);
+    }
+
+    try
+    {
+      bindRequest = getBindRequest();
+    }
+    catch (CLIException e)
+    {
+      throw new ArgumentException(Message.raw("Error reading input: "
+          + e.toString()));
+    }
+    if (bindRequest != null)
+    {
+      connFactory = new AuthenticatedConnectionFactory(connFactory,
+          bindRequest).setRebindAllowed(true);
+    }
+  }
+
+
+
+  private BindRequest getBindRequest() throws CLIException,
+      ArgumentException
+  {
+    String mech = null;
+    for (String s : saslOptionArg.getValues())
+    {
+      if (s.startsWith(SASL_PROPERTY_MECH))
+      {
+        mech = parseSASLOptionValue(s);
+        break;
+      }
+    }
+
+    if (mech == null)
+    {
+      if (bindDnArg.isPresent() || bindPasswordFileArg.isPresent()
+          || bindPasswordArg.isPresent())
+      {
+        return Requests.newSimpleBindRequest(getBindDN(), getPassword());
+      }
+      return null;
+    }
+
+    if (mech.equals(DigestMD5SASLBindRequest.SASL_MECHANISM_DIGEST_MD5))
+    {
+      return new DigestMD5SASLBindRequest(
+          getAuthID(DigestMD5SASLBindRequest.SASL_MECHANISM_DIGEST_MD5),
+          getAuthzID(), getPassword(), getRealm());
+    }
+    if (mech.equals(CRAMMD5SASLBindRequest.SASL_MECHANISM_CRAM_MD5))
+    {
+      return new CRAMMD5SASLBindRequest(
+          getAuthID(CRAMMD5SASLBindRequest.SASL_MECHANISM_CRAM_MD5),
+          getPassword());
+    }
+    if (mech.equals(GSSAPISASLBindRequest.SASL_MECHANISM_GSSAPI))
+    {
+      try
+      {
+        Subject subject = GSSAPISASLBindRequest.Kerberos5Login(
+            getAuthID(GSSAPISASLBindRequest.SASL_MECHANISM_GSSAPI),
+            getPassword(), getRealm(), getKDC());
+        return new GSSAPISASLBindRequest(subject, getAuthzID());
+      }
+      catch (LoginException e)
+      {
+        Message message = ERR_LDAPAUTH_GSSAPI_LOCAL_AUTHENTICATION_FAILED
+            .get(StaticUtils.getExceptionMessage(e));
+        throw new ArgumentException(message, e);
+      }
+    }
+    if (mech.equals(ExternalSASLBindRequest.SASL_MECHANISM_EXTERNAL))
+    {
+      if (sslContext == null)
+      {
+        Message message = ERR_TOOL_SASLEXTERNAL_NEEDS_SSL_OR_TLS.get();
+        throw new ArgumentException(message);
+      }
+      if (!keyStorePathArg.isPresent() && getKeyStore() == null)
+      {
+        Message message = ERR_TOOL_SASLEXTERNAL_NEEDS_KEYSTORE.get();
+        throw new ArgumentException(message);
+      }
+      return new ExternalSASLBindRequest(getAuthzID());
+    }
+    if (mech.equals(PlainSASLBindRequest.SASL_MECHANISM_PLAIN))
+    {
+      return new PlainSASLBindRequest(
+          getAuthID(PlainSASLBindRequest.SASL_MECHANISM_PLAIN),
+          getAuthzID(), getPassword());
+    }
+
+    throw new ArgumentException(ERR_LDAPAUTH_UNSUPPORTED_SASL_MECHANISM
+        .get(mech));
+  }
+
+
+
+  private DN getBindDN() throws CLIException, ArgumentException
+  {
+    String value = "";
+    if (bindDnArg.isPresent())
+    {
+      value = bindDnArg.getValue();
+    }
+    else if (app.isInteractive())
+    {
+      value = app.readInput(Message.raw("Bind DN:"), bindDnArg
+          .getDefaultValue() == null ? value : bindDnArg
+          .getDefaultValue());
+    }
+
+    try
+    {
+      return DN.valueOf(value);
+    }
+    catch (LocalizedIllegalArgumentException e)
+    {
+      throw new ArgumentException(e.getMessageObject());
+    }
+  }
+
+
+
+  private String getAuthID(String mech) throws CLIException,
+      ArgumentException
+  {
+    String value = null;
+    for (String s : saslOptionArg.getValues())
+    {
+      if (s.startsWith(SASL_PROPERTY_AUTHID))
+      {
+        value = parseSASLOptionValue(s);
+        break;
+      }
+    }
+    if (value == null && bindDnArg.isPresent())
+    {
+      value = "dn: " + bindDnArg.getValue();
+    }
+    if (value == null && app.isInteractive())
+    {
+      value = app.readInput(Message.raw("Authentication ID:"),
+          bindDnArg.getDefaultValue() == null ? null : "dn: "
+              + bindDnArg.getDefaultValue());
+    }
+    if (value == null)
+    {
+      Message message = ERR_LDAPAUTH_SASL_AUTHID_REQUIRED.get(mech);
+      throw new ArgumentException(message);
+    }
+    return value;
+  }
+
+
+
+  private String getAuthzID() throws CLIException, ArgumentException
+  {
+    String value = null;
+    for (String s : saslOptionArg.getValues())
+    {
+      if (s.startsWith(SASL_PROPERTY_AUTHZID))
+      {
+        value = parseSASLOptionValue(s);
+        break;
+      }
+    }
+    return value;
+  }
+
+
+
+  /**
+   * Get the password which has to be used for the command. If no
+   * password was specified, return null.
+   *
+   * @return The password stored into the specified file on by the
+   *         command line argument, or null it if not specified.
+   */
+  private ByteString getPassword() throws CLIException
+  {
+    String value = "";
+    if (bindPasswordArg.isPresent())
+    {
+      value = bindPasswordArg.getValue();
+    }
+    else if (bindPasswordFileArg.isPresent())
+    {
+      value = bindPasswordFileArg.getValue();
+    }
+    if (value.length() == 0 && app.isInteractive())
+    {
+      value = app.readLineOfInput(Message.raw("Bind Password:"));
+    }
+
+    return ByteString.valueOf(value);
+  }
+
+
+
+  private String getRealm() throws ArgumentException, CLIException
+  {
+    String value = null;
+    for (String s : saslOptionArg.getValues())
+    {
+      if (s.startsWith(SASL_PROPERTY_REALM))
+      {
+        value = parseSASLOptionValue(s);
+        break;
+      }
+    }
+    return value;
+  }
+
+
+
+  private String getKDC() throws ArgumentException, CLIException
+  {
+    String value = null;
+    for (String s : saslOptionArg.getValues())
+    {
+      if (s.startsWith(SASL_PROPERTY_KDC))
+      {
+        value = parseSASLOptionValue(s);
+        break;
+      }
+    }
+    return value;
+  }
+
+
+
+  /**
+   * Returns <CODE>true</CODE> if we can read on the provided path and
+   * <CODE>false</CODE> otherwise.
+   *
+   * @param path
+   *          the path.
+   * @return <CODE>true</CODE> if we can read on the provided path and
+   *         <CODE>false</CODE> otherwise.
+   */
+  private boolean canRead(String path)
+  {
+    boolean canRead;
+    File file = new File(path);
+    canRead = file.exists() && file.canRead();
+    return canRead;
+  }
+
+
+
+  /**
+   * Retrieves a <CODE>TrustManager</CODE> object that may be used for
+   * interactions requiring access to a trust manager.
+   *
+   * @return A set of <CODE>TrustManager</CODE> objects that may be used
+   *         for interactions requiring access to a trust manager.
+   * @throws KeyStoreException
+   *           If a problem occurs while interacting with the trust
+   *           store.
+   */
+  private TrustManager getTrustManager() throws KeyStoreException,
+      IOException, NoSuchAlgorithmException, CertificateException
+  {
+    if (trustAllArg.isPresent())
+    {
+      return new TrustAllTrustManager();
+    }
+
+    TrustStoreTrustManager tm = null;
+    if (trustStorePathArg.isPresent()
+        && trustStorePathArg.getValue().length() > 0)
+    {
+      tm = new TrustStoreTrustManager(trustStorePathArg.getValue(),
+          getTrustStorePIN(), hostNameArg.getValue(), true);
+    }
+    else if (getTrustStore() != null)
+    {
+      tm = new TrustStoreTrustManager(getTrustStore(),
+          getTrustStorePIN(), hostNameArg.getValue(), true);
+    }
+
+    if (app != null && !app.isQuiet())
+    {
+      return new PromptingTrustManager(app, tm);
+    }
+    return null;
+  }
+
+
+
+  /**
+   * Retrieves a <CODE>KeyManager</CODE> object that may be used for
+   * interactions requiring access to a key manager.
+   *
+   * @param keyStoreFile
+   *          The path to the file containing the key store data.
+   * @return A set of <CODE>KeyManager</CODE> objects that may be used
+   *         for interactions requiring access to a key manager.
+   * @throws java.security.KeyStoreException
+   *           If a problem occurs while interacting with the key store.
+   */
+
+  private X509KeyManager getKeyManager(String keyStoreFile)
+      throws KeyStoreException, IOException, NoSuchAlgorithmException,
+      CertificateException
+  {
+    if (keyStoreFile == null)
+    {
+      // Lookup the file name through the JDK property.
+      keyStoreFile = getKeyStore();
+    }
+
+    if (keyStoreFile == null)
+    {
+      return null;
+    }
+
+    String keyStorePass = getKeyStorePIN();
+    char[] keyStorePIN = null;
+    if (keyStorePass != null)
+    {
+      keyStorePIN = keyStorePass.toCharArray();
+    }
+
+    FileInputStream fos = new FileInputStream(keyStoreFile);
+    KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+    keystore.load(fos, keyStorePIN);
+    fos.close();
+
+    return new ApplicationKeyManager(keystore, keyStorePIN);
+  }
+
+
+
+  /**
+   * Read the KeyStore PIN from the JSSE system property.
+   *
+   * @return The PIN that should be used to access the key store.
+   */
+
+  private String getKeyStorePIN()
+  {
+    String pwd;
+    if (keyStorePasswordArg.isPresent())
+    {
+      pwd = keyStorePasswordArg.getValue();
+    }
+    else if (keyStorePasswordFileArg.isPresent())
+    {
+      pwd = keyStorePasswordFileArg.getValue();
+    }
+    else
+    {
+      pwd = System.getProperty("javax.net.ssl.keyStorePassword");
+    }
+    return pwd;
+  }
+
+
+
+  /**
+   * Read the TrustStore PIN from the JSSE system property.
+   *
+   * @return The PIN that should be used to access the trust store.
+   */
+
+  private String getTrustStorePIN()
+  {
+    String pwd;
+    if (trustStorePasswordArg.isPresent())
+    {
+      pwd = trustStorePasswordArg.getValue();
+    }
+    else if (trustStorePasswordFileArg.isPresent())
+    {
+      pwd = trustStorePasswordFileArg.getValue();
+    }
+    else
+    {
+      pwd = System.getProperty("javax.net.ssl.trustStorePassword");
+    }
+    return pwd;
+  }
+
+
+
+  /**
+   * Read the KeyStore from the JSSE system property.
+   *
+   * @return The path to the key store file.
+   */
+
+  private String getKeyStore()
+  {
+    return System.getProperty("javax.net.ssl.keyStore");
+  }
+
+
+
+  /**
+   * Read the TrustStore from the JSSE system property.
+   *
+   * @return The path to the trust store file.
+   */
+
+  private String getTrustStore()
+  {
+    return System.getProperty("javax.net.ssl.trustStore");
+  }
+
+
+
+  private String parseSASLOptionValue(String option)
+      throws ArgumentException
+  {
+    int equalPos = option.indexOf('=');
+    if (equalPos <= 0)
+    {
+      Message message = ERR_LDAP_CONN_CANNOT_PARSE_SASL_OPTION
+          .get(option);
+      throw new ArgumentException(message);
+    }
+
+    return option.substring(equalPos + 1, option.length());
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/BooleanArgument.java b/sdk/src/org/opends/sdk/tools/BooleanArgument.java
new file mode 100644
index 0000000..3707350
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/BooleanArgument.java
@@ -0,0 +1,125 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_BOOLEANARG_NO_VALUE_ALLOWED;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will be used to represent
+ * Boolean values. These arguments will never take values from the
+ * command line but and will never be required. If the argument is
+ * provided, then it will be considered true, and if not then it will be
+ * considered false. As such, the default value will always be "false".
+ */
+final class BooleanArgument extends Argument
+{
+  /**
+   * Creates a new Boolean argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public BooleanArgument(String name, Character shortIdentifier,
+      String longIdentifier, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, false, false, false,
+        null, String.valueOf(false), null, description);
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in this
+   * argument.
+   * 
+   * @param valueString
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          A buffer into which the invalid reason may be written if
+   *          the value is not acceptable.
+   * @return <CODE>true</CODE> if the value is acceptable, or
+   *         <CODE>false</CODE> if it is not.
+   */
+  public boolean valueIsAcceptable(String valueString,
+      MessageBuilder invalidReason)
+  {
+    // This argument type should never have a value, so any value
+    // provided will
+    // be unacceptable.
+
+    invalidReason
+        .append(ERR_BOOLEANARG_NO_VALUE_ALLOWED.get(getName()));
+
+    return false;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  final public void addValue(String valueString)
+  {
+    if (valueString != null)
+    {
+      clearValues();
+      super.addValue(valueString);
+      super.setPresent(Boolean.valueOf(valueString));
+    }
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  final public void setPresent(boolean isPresent)
+  {
+    addValue(String.valueOf(isPresent));
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/DataSource.java b/sdk/src/org/opends/sdk/tools/DataSource.java
new file mode 100644
index 0000000..b469d97
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/DataSource.java
@@ -0,0 +1,481 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.*;
+
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * A source of data for performance tools.
+ */
+final class DataSource
+{
+  private static interface IDataSource
+  {
+    public Object getData();
+
+
+
+    public IDataSource duplicate();
+  }
+
+
+
+  private static class RandomNumberDataSource implements IDataSource
+  {
+    private final Random random;
+    private final int offset;
+    private final int range;
+
+
+
+    public RandomNumberDataSource(long seed, int low, int high)
+    {
+      random = new Random(seed);
+      offset = low;
+      range = high - low;
+    }
+
+
+
+    public Object getData()
+    {
+      return random.nextInt(range) + offset;
+    }
+
+
+
+    public IDataSource duplicate()
+    {
+      // There is no state info so threads can just share one instance.
+      return this;
+    }
+  }
+
+
+
+  private static class IncrementNumberDataSource implements IDataSource
+  {
+    private final int low;
+    private int next;
+    private final int high;
+
+
+
+    public IncrementNumberDataSource(int low, int high)
+    {
+      this.low = this.next = low;
+      this.high = high;
+    }
+
+
+
+    public Object getData()
+    {
+      if (next == high)
+      {
+        next = low;
+        return high;
+      }
+
+      return next++;
+    }
+
+
+
+    public IDataSource duplicate()
+    {
+      return new IncrementNumberDataSource(low, high);
+    }
+  }
+
+
+
+  private static class RandomLineFileDataSource implements IDataSource
+  {
+    private final List<String> lines;
+    private final Random random;
+
+
+
+    public RandomLineFileDataSource(long seed, String file)
+        throws IOException
+    {
+      lines = new ArrayList<String>();
+      random = new Random(seed);
+      BufferedReader in = new BufferedReader(new FileReader(file));
+      String line;
+      while ((line = in.readLine()) != null)
+      {
+        lines.add(line);
+      }
+    }
+
+
+
+    public Object getData()
+    {
+      return lines.get(random.nextInt(lines.size()));
+    }
+
+
+
+    public IDataSource duplicate()
+    {
+      return this;
+    }
+  }
+
+
+
+  private static class IncrementLineFileDataSource implements
+      IDataSource
+  {
+    private final List<String> lines;
+    private int next;
+
+
+
+    public IncrementLineFileDataSource(String file) throws IOException
+    {
+      lines = new ArrayList<String>();
+      BufferedReader in = new BufferedReader(new FileReader(file));
+      String line;
+      while ((line = in.readLine()) != null)
+      {
+        lines.add(line);
+      }
+    }
+
+
+
+    private IncrementLineFileDataSource(List<String> lines)
+    {
+      this.lines = lines;
+    }
+
+
+
+    public Object getData()
+    {
+      if (next == lines.size())
+      {
+        next = 0;
+      }
+
+      return lines.get(next++);
+    }
+
+
+
+    public IDataSource duplicate()
+    {
+      return new IncrementLineFileDataSource(lines);
+    }
+  }
+
+
+
+  private static class RandomStringDataSource implements IDataSource
+  {
+    private final Random random;
+    private final int length;
+    private final Character[] charSet;
+
+
+
+    private RandomStringDataSource(int seed, int length, String charSet)
+    {
+      this.length = length;
+      Set<Character> chars = new HashSet<Character>();
+      for (int i = 0; i < charSet.length(); i++)
+      {
+        char c = charSet.charAt(i);
+        if (c == '[')
+        {
+          i += 1;
+          char start = charSet.charAt(i);
+          i += 2;
+          char end = charSet.charAt(i);
+          i += 1;
+          for (int j = start; j <= end; j++)
+          {
+            chars.add((char) j);
+          }
+        }
+        else
+        {
+          chars.add(c);
+        }
+      }
+      this.charSet = chars.toArray(new Character[chars.size()]);
+      this.random = new Random(seed);
+    }
+
+
+
+    public Object getData()
+    {
+      char[] str = new char[length];
+      for (int i = 0; i < length; i++)
+      {
+        str[i] = charSet[random.nextInt(charSet.length)];
+      }
+      return new String(str);
+    }
+
+
+
+    public IDataSource duplicate()
+    {
+      return this;
+    }
+  }
+
+
+
+  private static class StaticDataSource implements IDataSource
+  {
+    private final Object data;
+
+
+
+    private StaticDataSource(Object data)
+    {
+      this.data = data;
+    }
+
+
+
+    public Object getData()
+    {
+      return data;
+    }
+
+
+
+    public IDataSource duplicate()
+    {
+      // There is no state info so threads can just share one instance.
+      return this;
+    }
+  }
+
+  private IDataSource impl;
+
+
+
+  private DataSource(IDataSource impl)
+  {
+    this.impl = impl;
+  }
+
+
+
+  public Object getData()
+  {
+    return impl.getData();
+  }
+
+
+
+  public DataSource duplicate()
+  {
+    IDataSource dup = impl.duplicate();
+    if (dup == impl)
+    {
+      return this;
+    }
+    else
+    {
+      return new DataSource(dup);
+    }
+  }
+
+
+
+  /**
+   * Parses a list of source definitions into an array of data source
+   * objects. A data source is defined as follows: - rand({min},{max})
+   * generates a random integer between the min and max. -
+   * rand({filename}) retrieves a random line from a file. -
+   * inc({min},{max}) returns incremental integer between the min and
+   * max. - inc({filename}) retrieves lines in order from a file. -
+   * {number} always return the integer as given. - {string} always
+   * return the string as given.
+   * 
+   * @param sources
+   *          The list of source definitions to parse.
+   * @return The array of parsed data sources.
+   * @throws IOException
+   *           If an exception occurs while reading a file.
+   */
+  public static DataSource[] parse(List<String> sources)
+      throws IOException
+  {
+    Validator.ensureNotNull(sources);
+    DataSource[] dataSources = new DataSource[sources.size()];
+    for (int i = 0; i < sources.size(); i++)
+    {
+      String dataSourceDef = sources.get(i);
+      if (dataSourceDef.startsWith("rand(")
+          && dataSourceDef.endsWith(")"))
+      {
+        int lparenPos = dataSourceDef.indexOf("(");
+        int commaPos = dataSourceDef.indexOf(",");
+        int rparenPos = dataSourceDef.indexOf(")");
+        if (commaPos < 0)
+        {
+          // This is a file name
+          dataSources[i] =
+              new DataSource(new RandomLineFileDataSource(0,
+                  dataSourceDef.substring(lparenPos + 1, rparenPos)));
+        }
+        else
+        {
+          // This range of integers
+          int low =
+              Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+                  commaPos));
+          int high =
+              Integer.parseInt(dataSourceDef.substring(commaPos + 1,
+                  rparenPos));
+          dataSources[i] =
+              new DataSource(new RandomNumberDataSource(0, low, high));
+        }
+      }
+      else if (dataSourceDef.startsWith("randstr(")
+          && dataSourceDef.endsWith(")"))
+      {
+        int lparenPos = dataSourceDef.indexOf("(");
+        int commaPos = dataSourceDef.indexOf(",");
+        int rparenPos = dataSourceDef.indexOf(")");
+        int length;
+        String charSet;
+        if (commaPos < 0)
+        {
+          length =
+              Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+                  rparenPos));
+          charSet = "[A-Z][a-z][0-9]";
+        }
+        else
+        {
+          // length and charSet
+          length =
+              Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+                  commaPos));
+          charSet = dataSourceDef.substring(commaPos + 1, rparenPos);
+        }
+        dataSources[i] =
+            new DataSource(new RandomStringDataSource(0, length,
+                charSet));
+
+      }
+      else if (dataSourceDef.startsWith("inc(")
+          && dataSourceDef.endsWith(")"))
+      {
+        int lparenPos = dataSourceDef.indexOf("(");
+        int commaPos = dataSourceDef.indexOf(",");
+        int rparenPos = dataSourceDef.indexOf(")");
+        if (commaPos < 0)
+        {
+          // This is a file name
+          dataSources[i] =
+              new DataSource(new IncrementLineFileDataSource(
+                  dataSourceDef.substring(lparenPos + 1, rparenPos)));
+        }
+        else
+        {
+          int low =
+              Integer.parseInt(dataSourceDef.substring(lparenPos + 1,
+                  commaPos));
+          int high =
+              Integer.parseInt(dataSourceDef.substring(commaPos + 1,
+                  rparenPos));
+          dataSources[i] =
+              new DataSource(new IncrementNumberDataSource(low, high));
+        }
+      }
+      else
+      {
+        try
+        {
+          dataSources[i] =
+              new DataSource(new StaticDataSource(Integer
+                  .parseInt(dataSourceDef)));
+        }
+        catch (NumberFormatException nfe)
+        {
+          dataSources[i] =
+              new DataSource(new StaticDataSource(dataSourceDef));
+        }
+      }
+    }
+
+    return dataSources;
+  }
+
+
+
+  /**
+   * Returns Generated data from the specified data sources. Generated
+   * data will be placed in the specified data array. If the data array
+   * is null or smaller than the number of data sources, one will be
+   * allocated.
+   * 
+   * @param dataSources
+   *          Data sources that will generate arguments referenced by
+   *          the format specifiers in the format string.
+   * @param data
+   *          The array where genereated data will be placed to format
+   *          the string.
+   * @return A formatted string
+   */
+  public static Object[] generateData(DataSource[] dataSources,
+      Object[] data)
+  {
+    if (data == null || data.length < dataSources.length)
+    {
+      data = new Object[dataSources.length];
+    }
+    for (int i = 0; i < dataSources.length; i++)
+    {
+      data[i] = dataSources[i].getData();
+    }
+    return data;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/FileBasedArgument.java b/sdk/src/org/opends/sdk/tools/FileBasedArgument.java
new file mode 100644
index 0000000..2225feb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/FileBasedArgument.java
@@ -0,0 +1,283 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.server.util.StaticUtils.getExceptionMessage;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.LinkedHashMap;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument whose value will be read from a file
+ * rather than actually specified on the command-line. When a value is
+ * specified on the command line, it will be treated as the path to the
+ * file containing the actual value rather than the value itself. <BR>
+ * <BR>
+ * Note that if if no filename is provided on the command line but a
+ * default value is specified programatically or if the default value is
+ * read from a specified property, then that default value will be taken
+ * as the actual value rather than a filename. <BR>
+ * <BR>
+ * Also note that this argument type assumes that the entire value for
+ * the argument is on a single line in the specified file. If the file
+ * contains multiple lines, then only the first line will be read.
+ */
+final class FileBasedArgument extends Argument
+{
+  // The mapping between filenames specified and the first lines read
+  // from those
+  // files.
+  private LinkedHashMap<String, String> namesToValues;
+
+
+
+  /**
+   * Creates a new file-based argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public FileBasedArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired,
+      Message valuePlaceholder, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired, false,
+        true, valuePlaceholder, null, null, description);
+
+    namesToValues = new LinkedHashMap<String, String>();
+  }
+
+
+
+  /**
+   * Creates a new file-based argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public FileBasedArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      Message valuePlaceholder, String defaultValue,
+      String propertyName, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired,
+        isMultiValued, true, valuePlaceholder, defaultValue,
+        propertyName, description);
+
+    namesToValues = new LinkedHashMap<String, String>();
+  }
+
+
+
+  /**
+   * Retrieves a map between the filenames specified on the command line
+   * and the first lines read from those files.
+   * 
+   * @return A map between the filenames specified on the command line
+   *         and the first lines read from those files.
+   */
+  public LinkedHashMap<String, String> getNameToValueMap()
+  {
+    return namesToValues;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in this
+   * argument.
+   * 
+   * @param valueString
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          A buffer into which the invalid reason may be written if
+   *          the value is not acceptable.
+   * @return <CODE>true</CODE> if the value is acceptable, or
+   *         <CODE>false</CODE> if it is not.
+   */
+  public boolean valueIsAcceptable(String valueString,
+      MessageBuilder invalidReason)
+  {
+    // First, make sure that the specified file exists.
+    File valueFile;
+    try
+    {
+      valueFile = new File(valueString);
+      if (!valueFile.exists())
+      {
+        invalidReason.append(ERR_FILEARG_NO_SUCH_FILE.get(valueString,
+            getName()));
+        return false;
+      }
+    }
+    catch (Exception e)
+    {
+      invalidReason.append(ERR_FILEARG_CANNOT_VERIFY_FILE_EXISTENCE
+          .get(valueString, getName(), getExceptionMessage(e)));
+      return false;
+    }
+
+    // Open the file for reading.
+    BufferedReader reader;
+    try
+    {
+      reader = new BufferedReader(new FileReader(valueFile));
+    }
+    catch (Exception e)
+    {
+      invalidReason.append(ERR_FILEARG_CANNOT_OPEN_FILE.get(
+          valueString, getName(), getExceptionMessage(e)));
+      return false;
+    }
+
+    // Read the first line and close the file.
+    String line;
+    try
+    {
+      line = reader.readLine();
+    }
+    catch (Exception e)
+    {
+      invalidReason.append(ERR_FILEARG_CANNOT_READ_FILE.get(
+          valueString, getName(), getExceptionMessage(e)));
+      return false;
+    }
+    finally
+    {
+      try
+      {
+        reader.close();
+      }
+      catch (Exception e)
+      {
+      }
+    }
+
+    // If the line read is null, then that means the file was empty.
+    if (line == null)
+    {
+
+      invalidReason.append(ERR_FILEARG_EMPTY_FILE.get(valueString,
+          getName()));
+      return false;
+    }
+
+    // Store the value in the hash so it will be available for addValue.
+    // We
+    // won't do any validation on the value itself, so anything that we
+    // read
+    // will be considered acceptable.
+    namesToValues.put(valueString, line);
+    return true;
+  }
+
+
+
+  /**
+   * Adds a value to the set of values for this argument. This should
+   * only be called if the value is allowed by the
+   * <CODE>valueIsAcceptable</CODE> method. Note that in this case,
+   * correct behavior depends on a previous successful call to
+   * <CODE>valueIsAcceptable</CODE> so that the value read from the file
+   * may be stored in the name-to-value hash and used in place of the
+   * filename here.
+   * 
+   * @param valueString
+   *          The string representation of the value to add to this
+   *          argument.
+   */
+  public void addValue(String valueString)
+  {
+    String actualValue = namesToValues.get(valueString);
+    if (actualValue != null)
+    {
+      super.addValue(actualValue);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/IntegerArgument.java b/sdk/src/org/opends/sdk/tools/IntegerArgument.java
new file mode 100644
index 0000000..41f44f4
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/IntegerArgument.java
@@ -0,0 +1,547 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_ARG_CANNOT_DECODE_AS_INT;
+import static org.opends.messages.UtilityMessages.ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND;
+import static org.opends.messages.UtilityMessages.ERR_INTARG_VALUE_ABOVE_UPPER_BOUND;
+import static org.opends.messages.UtilityMessages.ERR_INTARG_VALUE_BELOW_LOWER_BOUND;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will only accept integer
+ * values, and potentially only those in a given range.
+ */
+final class IntegerArgument extends Argument
+{
+  // Indicates whether a lower bound will be enforced for this argument.
+  private boolean hasLowerBound;
+
+  // Indicates whether an upper bound will be enforced for this
+  // argument.
+  private boolean hasUpperBound;
+
+  // The lower bound that will be enforced for this argument.
+  private double lowerBound;
+
+  // The upper bound that will be enforced for this argument.
+  private double upperBound;
+
+
+
+  /**
+   * Creates a new integer argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public IntegerArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean needsValue,
+      Message valuePlaceholder, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired, false,
+        needsValue, valuePlaceholder, null, null, description);
+
+    hasLowerBound = false;
+    hasUpperBound = false;
+    lowerBound = Double.MIN_VALUE;
+    upperBound = Double.MAX_VALUE;
+  }
+
+
+
+  /**
+   * Creates a new integer argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param hasLowerBound
+   *          Indicates whether a lower bound should be enforced for
+   *          values of this argument.
+   * @param lowerBound
+   *          The lower bound that should be enforced for values of this
+   *          argument.
+   * @param hasUpperBound
+   *          Indicates whether an upperbound should be enforced for
+   *          values of this argument.
+   * @param upperBound
+   *          The upper bound that should be enforced for values of this
+   *          argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public IntegerArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean needsValue,
+      Message valuePlaceholder, boolean hasLowerBound,
+      double lowerBound, boolean hasUpperBound, double upperBound,
+      Message description) throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired, false,
+        needsValue, valuePlaceholder, null, null, description);
+
+    this.hasLowerBound = hasLowerBound;
+    this.hasUpperBound = hasUpperBound;
+    this.lowerBound = lowerBound;
+    this.upperBound = upperBound;
+
+    if (hasLowerBound && hasUpperBound && (lowerBound > upperBound))
+    {
+      Message message =
+          ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND.get(name,
+              lowerBound, upperBound);
+      throw new ArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Creates a new integer argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public IntegerArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      boolean needsValue, Message valuePlaceholder, int defaultValue,
+      String propertyName, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired,
+        isMultiValued, needsValue, valuePlaceholder, String
+            .valueOf(defaultValue), propertyName, description);
+
+    hasLowerBound = false;
+    hasUpperBound = false;
+    lowerBound = Integer.MIN_VALUE;
+    upperBound = Integer.MAX_VALUE;
+  }
+
+
+
+  /**
+   * Creates a new integer argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public IntegerArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      boolean needsValue, Message valuePlaceholder,
+      double defaultValue, String propertyName, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired,
+        isMultiValued, needsValue, valuePlaceholder, String.format(
+            "%f", defaultValue), propertyName, description);
+
+    hasLowerBound = false;
+    hasUpperBound = false;
+    lowerBound = Integer.MIN_VALUE;
+    upperBound = Integer.MAX_VALUE;
+  }
+
+
+
+  /**
+   * Creates a new integer argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param hasLowerBound
+   *          Indicates whether a lower bound should be enforced for
+   *          values of this argument.
+   * @param lowerBound
+   *          The lower bound that should be enforced for values of this
+   *          argument.
+   * @param hasUpperBound
+   *          Indicates whether an upperbound should be enforced for
+   *          values of this argument.
+   * @param upperBound
+   *          The upper bound that should be enforced for values of this
+   *          argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public IntegerArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      boolean needsValue, Message valuePlaceholder, int defaultValue,
+      String propertyName, boolean hasLowerBound, double lowerBound,
+      boolean hasUpperBound, double upperBound, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired,
+        isMultiValued, needsValue, valuePlaceholder, String
+            .valueOf(defaultValue), propertyName, description);
+
+    this.hasLowerBound = hasLowerBound;
+    this.hasUpperBound = hasUpperBound;
+    this.lowerBound = lowerBound;
+    this.upperBound = upperBound;
+
+    if (hasLowerBound && hasUpperBound && (lowerBound > upperBound))
+    {
+      Message message =
+          ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND.get(name,
+              lowerBound, upperBound);
+      throw new ArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Creates a new integer argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param hasLowerBound
+   *          Indicates whether a lower bound should be enforced for
+   *          values of this argument.
+   * @param lowerBound
+   *          The lower bound that should be enforced for values of this
+   *          argument.
+   * @param hasUpperBound
+   *          Indicates whether an upperbound should be enforced for
+   *          values of this argument.
+   * @param upperBound
+   *          The upper bound that should be enforced for values of this
+   *          argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public IntegerArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      boolean needsValue, Message valuePlaceholder,
+      double defaultValue, String propertyName, boolean hasLowerBound,
+      double lowerBound, boolean hasUpperBound, double upperBound,
+      Message description) throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired,
+        isMultiValued, needsValue, valuePlaceholder, String
+            .valueOf(defaultValue), propertyName, description);
+
+    this.hasLowerBound = hasLowerBound;
+    this.hasUpperBound = hasUpperBound;
+    this.lowerBound = lowerBound;
+    this.upperBound = upperBound;
+
+    if (hasLowerBound && hasUpperBound && (lowerBound > upperBound))
+    {
+      Message message =
+          ERR_INTARG_LOWER_BOUND_ABOVE_UPPER_BOUND.get(name,
+              lowerBound, upperBound);
+      throw new ArgumentException(message);
+    }
+  }
+
+
+
+  /**
+   * Indicates whether a lower bound should be enforced for values of
+   * this argument.
+   * 
+   * @return <CODE>true</CODE> if a lower bound should be enforced for
+   *         values of this argument, or <CODE>false</CODE> if not.
+   */
+  public boolean hasLowerBound()
+  {
+    return hasLowerBound;
+  }
+
+
+
+  /**
+   * Retrieves the lower bound that may be enforced for values of this
+   * argument.
+   * 
+   * @return The lower bound that may be enforced for values of this
+   *         argument.
+   */
+  public double getLowerBound()
+  {
+    return lowerBound;
+  }
+
+
+
+  /**
+   * Indicates whether a upper bound should be enforced for values of
+   * this argument.
+   * 
+   * @return <CODE>true</CODE> if a upper bound should be enforced for
+   *         values of this argument, or <CODE>false</CODE> if not.
+   */
+  public boolean hasUpperBound()
+  {
+    return hasUpperBound;
+  }
+
+
+
+  /**
+   * Retrieves the upper bound that may be enforced for values of this
+   * argument.
+   * 
+   * @return The upper bound that may be enforced for values of this
+   *         argument.
+   */
+  public double getUpperBound()
+  {
+    return upperBound;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in this
+   * argument.
+   * 
+   * @param valueString
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          A buffer into which the invalid reason may be written if
+   *          the value is not acceptable.
+   * @return <CODE>true</CODE> if the value is acceptable, or
+   *         <CODE>false</CODE> if it is not.
+   */
+  public boolean valueIsAcceptable(String valueString,
+      MessageBuilder invalidReason)
+  {
+    // First, the value must be decodable as an integer.
+    double intValue;
+    try
+    {
+      intValue = Double.parseDouble(valueString);
+    }
+    catch (Exception e)
+    {
+      invalidReason.append(ERR_ARG_CANNOT_DECODE_AS_INT.get(
+          valueString, getName()));
+      return false;
+    }
+
+    // If there is a lower bound, then the value must be greater than or
+    // equal
+    // to it.
+    if (hasLowerBound && (intValue < lowerBound))
+    {
+      invalidReason.append(ERR_INTARG_VALUE_BELOW_LOWER_BOUND.get(
+          getName(), intValue, lowerBound));
+      return false;
+    }
+
+    // If there is an upper bound, then the value must be less than or
+    // equal to
+    // it.
+    if (hasUpperBound && (intValue > upperBound))
+    {
+
+      invalidReason.append(ERR_INTARG_VALUE_ABOVE_UPPER_BOUND.get(
+          getName(), intValue, upperBound));
+      return false;
+    }
+
+    // At this point, the value should be acceptable.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPCompare.java b/sdk/src/org/opends/sdk/tools/LDAPCompare.java
new file mode 100644
index 0000000..8af5264
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPCompare.java
@@ -0,0 +1,674 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.*;
+import java.util.ArrayList;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.controls.AssertionControl;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.controls.ProxiedAuthV2Control;
+import org.opends.sdk.requests.CompareRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.Base64;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides a tool that can be used to issue compare requests
+ * to the Directory Server.
+ */
+public final class LDAPCompare extends ConsoleApplication
+{
+  private BooleanArgument verbose;
+
+
+
+  /**
+   * The main method for LDAPModify tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   */
+
+  public static void main(String[] args)
+  {
+    int retCode = mainCompare(args, System.in, System.out, System.err);
+
+    if (retCode != 0)
+    {
+      System.exit(filterExitCode(retCode));
+    }
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the LDAPModify tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @return The error code.
+   */
+
+  public static int mainCompare(String[] args)
+  {
+    return mainCompare(args, System.in, System.out, System.err);
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the LDAPModify tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   *          specified, the number of matching entries should be
+   *          returned or not.
+   * @param inStream
+   *          The input stream to use for standard input, or
+   *          <CODE>null</CODE> if standard input is not needed.
+   * @param outStream
+   *          The output stream to use for standard output, or
+   *          <CODE>null</CODE> if standard output is not needed.
+   * @param errStream
+   *          The output stream to use for standard error, or
+   *          <CODE>null</CODE> if standard error is not needed.
+   * @return The error code.
+   */
+  public static int mainCompare(String[] args, InputStream inStream,
+      OutputStream outStream, OutputStream errStream)
+  {
+    return new LDAPCompare(inStream, outStream, errStream).run(args);
+  }
+
+
+
+  private LDAPCompare(InputStream in, OutputStream out, OutputStream err)
+  {
+    super(in, out, err);
+
+  }
+
+
+
+  private int run(String[] args)
+  {
+    // Create the command-line argument parser for use with this
+    // program.
+    Message toolDescription = INFO_LDAPCOMPARE_TOOL_DESCRIPTION.get();
+    ArgumentParser argParser =
+        new ArgumentParser(LDAPCompare.class.getName(),
+            toolDescription, false, true, 1, 0,
+            "attribute:value [DN ...]");
+    ArgumentParserConnectionFactory connectionFactory;
+
+    BooleanArgument continueOnError;
+    BooleanArgument noop;
+    BooleanArgument showUsage;
+    IntegerArgument version;
+    StringArgument assertionFilter;
+    StringArgument controlStr;
+    StringArgument encodingStr;
+    StringArgument filename;
+    StringArgument proxyAuthzID;
+    StringArgument propertiesFileArgument;
+    BooleanArgument noPropertiesFileArgument;
+
+    try
+    {
+      connectionFactory =
+          new ArgumentParserConnectionFactory(argParser, this);
+      propertiesFileArgument =
+          new StringArgument("propertiesFilePath", null,
+              OPTION_LONG_PROP_FILE_PATH, false, false, true,
+              INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PROP_FILE_PATH.get());
+      argParser.addArgument(propertiesFileArgument);
+      argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+      noPropertiesFileArgument =
+          new BooleanArgument("noPropertiesFileArgument", null,
+              OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+                  .get());
+      argParser.addArgument(noPropertiesFileArgument);
+      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+      filename =
+          new StringArgument("filename", OPTION_SHORT_FILENAME,
+              OPTION_LONG_FILENAME, false, false, true,
+              INFO_FILE_PLACEHOLDER.get(), null, null,
+              INFO_LDAPMODIFY_DESCRIPTION_FILENAME.get());
+      filename.setPropertyName(OPTION_LONG_FILENAME);
+      argParser.addArgument(filename);
+
+      proxyAuthzID =
+          new StringArgument("proxy_authzid", OPTION_SHORT_PROXYAUTHID,
+              OPTION_LONG_PROXYAUTHID, false, false, true,
+              INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PROXY_AUTHZID.get());
+      proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
+      argParser.addArgument(proxyAuthzID);
+
+      assertionFilter =
+          new StringArgument("assertionfilter", null,
+              OPTION_LONG_ASSERTION_FILE, false, false, true,
+              INFO_ASSERTION_FILTER_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_ASSERTION_FILTER.get());
+      assertionFilter.setPropertyName(OPTION_LONG_ASSERTION_FILE);
+      argParser.addArgument(assertionFilter);
+
+      controlStr =
+          new StringArgument("control", 'J', "control", false, true,
+              true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_CONTROLS.get());
+      controlStr.setPropertyName("control");
+      argParser.addArgument(controlStr);
+
+      version =
+          new IntegerArgument("version", OPTION_SHORT_PROTOCOL_VERSION,
+              OPTION_LONG_PROTOCOL_VERSION, false, false, true,
+              INFO_PROTOCOL_VERSION_PLACEHOLDER.get(), 3, null,
+              INFO_DESCRIPTION_VERSION.get());
+      version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+      argParser.addArgument(version);
+
+      encodingStr =
+          new StringArgument("encoding", 'i', "encoding", false, false,
+              true, INFO_ENCODING_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_ENCODING.get());
+      encodingStr.setPropertyName("encoding");
+      argParser.addArgument(encodingStr);
+
+      continueOnError =
+          new BooleanArgument("continueOnError", 'c',
+              "continueOnError", INFO_DESCRIPTION_CONTINUE_ON_ERROR
+                  .get());
+      continueOnError.setPropertyName("continueOnError");
+      argParser.addArgument(continueOnError);
+
+      noop =
+          new BooleanArgument("no-op", OPTION_SHORT_DRYRUN,
+              OPTION_LONG_DRYRUN, INFO_DESCRIPTION_NOOP.get());
+      noop.setPropertyName(OPTION_LONG_DRYRUN);
+      argParser.addArgument(noop);
+
+      verbose =
+          new BooleanArgument("verbose", 'v', "verbose",
+              INFO_DESCRIPTION_VERBOSE.get());
+      verbose.setPropertyName("verbose");
+      argParser.addArgument(verbose);
+
+      showUsage =
+          new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+              OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+      argParser.addArgument(showUsage);
+      argParser.setUsageArgument(showUsage, getOutputStream());
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // Parse the command-line arguments provided to this program.
+    try
+    {
+      argParser.parseArguments(args);
+      connectionFactory.validate();
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+      println(message);
+      println(argParser.getUsageMessage());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // If we should just display usage or version information,
+    // then print it and exit.
+    if (argParser.usageOrVersionDisplayed())
+    {
+      return 0;
+    }
+
+    try
+    {
+      int versionNumber = version.getIntValue();
+      if (versionNumber != 2 && versionNumber != 3)
+      {
+        println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+            .valueOf(versionNumber)));
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+    catch (ArgumentException ae)
+    {
+      println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+          .valueOf(version.getValue())));
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    ArrayList<String> dnStrings = new ArrayList<String>();
+    ArrayList<String> attrAndDNStrings =
+        argParser.getTrailingArguments();
+
+    if (attrAndDNStrings.isEmpty())
+    {
+      Message message = ERR_LDAPCOMPARE_NO_ATTR.get();
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // First element should be an attribute string.
+    String attributeString = attrAndDNStrings.remove(0);
+
+    // Rest are DN strings
+    for (String s : attrAndDNStrings)
+    {
+      dnStrings.add(s);
+    }
+
+    // If no DNs were provided, then exit with an error.
+    if (dnStrings.isEmpty() && (!filename.isPresent()))
+    {
+      println(ERR_LDAPCOMPARE_NO_DNS.get());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // If trailing DNs were provided and the filename argument was also
+    // provided, exit with an error.
+    if (!dnStrings.isEmpty() && filename.isPresent())
+    {
+      println(ERR_LDAPCOMPARE_FILENAME_AND_DNS.get());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // parse the attribute string
+    int idx = attributeString.indexOf(":");
+    if (idx == -1)
+    {
+      Message message =
+          ERR_LDAPCOMPARE_INVALID_ATTR_STRING.get(attributeString);
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+    String attributeType = attributeString.substring(0, idx);
+    ByteString attributeVal;
+    String remainder =
+        attributeString.substring(idx + 1, attributeString.length());
+    if (remainder.length() > 0)
+    {
+      char nextChar = remainder.charAt(0);
+      if (nextChar == ':')
+      {
+        String base64 = remainder.substring(1, remainder.length());
+        try
+        {
+          attributeVal = Base64.decode(base64);
+        }
+        catch (LocalizedIllegalArgumentException e)
+        {
+          println(INFO_COMPARE_CANNOT_BASE64_DECODE_ASSERTION_VALUE
+              .get());
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+      else if (nextChar == '<')
+      {
+        try
+        {
+          String filePath = remainder.substring(1, remainder.length());
+          attributeVal =
+              ByteString.wrap(Utils.readBytesFromFile(filePath));
+        }
+        catch (Exception e)
+        {
+          println(INFO_COMPARE_CANNOT_READ_ASSERTION_VALUE_FROM_FILE
+              .get(String.valueOf(e)));
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+      else
+      {
+        attributeVal = ByteString.valueOf(remainder);
+      }
+    }
+    else
+    {
+      attributeVal = ByteString.valueOf(remainder);
+    }
+
+    CompareRequest compare =
+        Requests.newCompareRequest("", attributeType, attributeVal);
+
+    if (controlStr.isPresent())
+    {
+      for (String ctrlString : controlStr.getValues())
+      {
+        try
+        {
+          Control ctrl = Utils.getControl(ctrlString);
+          compare.addControl(ctrl);
+        }
+        catch (DecodeException de)
+        {
+          Message message =
+              ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString);
+          println(message);
+          ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+    }
+
+    if (proxyAuthzID.isPresent())
+    {
+      Control proxyControl =
+          new ProxiedAuthV2Control(proxyAuthzID.getValue());
+      compare.addControl(proxyControl);
+    }
+
+    if (assertionFilter.isPresent())
+    {
+      String filterString = assertionFilter.getValue();
+      Filter filter;
+      try
+      {
+        filter = Filter.valueOf(filterString);
+
+        // FIXME -- Change this to the correct OID when the official one
+        // is
+        // assigned.
+        Control assertionControl = new AssertionControl(true, filter);
+        compare.addControl(assertionControl);
+      }
+      catch (LocalizedIllegalArgumentException le)
+      {
+        Message message =
+            ERR_LDAP_ASSERTION_INVALID_FILTER.get(le.getMessage());
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+
+    BufferedReader rdr = null;
+    if (!filename.isPresent() && dnStrings.isEmpty())
+    {
+      // Read from stdin.
+      rdr = new BufferedReader(new InputStreamReader(System.in));
+    }
+    else if (filename.isPresent())
+    {
+      try
+      {
+        rdr = new BufferedReader(new FileReader(filename.getValue()));
+      }
+      catch (FileNotFoundException t)
+      {
+        println(ERR_LDAPCOMPARE_ERROR_READING_FILE.get(filename
+            .getValue(), t.toString()));
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+
+    Connection connection = null;
+    if (!noop.isPresent())
+    {
+      try
+      {
+        connection = connectionFactory.getConnection();
+      }
+      catch (ErrorResultException ere)
+      {
+        println(Message.raw(ere.getMessage()));
+        return ere.getResult().getResultCode().intValue();
+      }
+    }
+
+    try
+    {
+      int result;
+      if (rdr == null)
+      {
+        for (String dn : dnStrings)
+        {
+          compare.setName(dn);
+          result = executeCompare(compare, connection);
+          if (result != 0 && !continueOnError.isPresent())
+          {
+            return result;
+          }
+        }
+      }
+      else
+      {
+        String dn;
+        try
+        {
+          while ((dn = rdr.readLine()) != null)
+          {
+            compare.setName(dn);
+            result = executeCompare(compare, connection);
+            if (result != 0 && !continueOnError.isPresent())
+            {
+              return result;
+            }
+          }
+        }
+        catch (IOException ioe)
+        {
+          println(ERR_LDAPCOMPARE_ERROR_READING_FILE.get(filename
+              .getValue(), ioe.toString()));
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+    }
+    finally
+    {
+      if (connection != null)
+      {
+        connection.close();
+      }
+      if (rdr != null)
+      {
+        try
+        {
+          rdr.close();
+        }
+        catch (IOException ioe)
+        {
+          // Just ignore
+        }
+      }
+    }
+
+    return 0;
+  }
+
+
+
+  private int executeCompare(CompareRequest request,
+      Connection connection)
+  {
+    println(INFO_PROCESSING_COMPARE_OPERATION.get(request
+        .getAttributeDescription().toString(), request
+        .getAssertionValueAsString(), request.getName().toString()));
+    if (connection != null)
+    {
+      try
+      {
+        Result result;
+        try
+        {
+          result = connection.compare(request);
+        }
+        catch (InterruptedException e)
+        {
+          // This shouldn't happen because there are no other threads to
+          // interrupt this one.
+          result = Responses.newResult(
+              ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+              .setDiagnosticMessage(e.getLocalizedMessage());
+          throw ErrorResultException.wrap(result);
+        }
+
+        if (result.getResultCode() == ResultCode.COMPARE_FALSE)
+        {
+          println(INFO_COMPARE_OPERATION_RESULT_FALSE.get(request
+              .getName().toString()));
+        }
+        else
+        {
+
+          println(INFO_COMPARE_OPERATION_RESULT_TRUE.get(request
+              .getName().toString()));
+        }
+      }
+      catch (ErrorResultException ere)
+      {
+        Message msg = INFO_OPERATION_FAILED.get("COMPARE");
+        println(msg);
+        Result r = ere.getResult();
+        println(ERR_TOOL_RESULT_CODE.get(r.getResultCode().intValue(),
+            r.getResultCode().toString()));
+        if ((r.getDiagnosticMessage() != null)
+            && (r.getDiagnosticMessage().length() > 0))
+        {
+          println(Message.raw(r.getDiagnosticMessage()));
+        }
+        if (r.getMatchedDN() != null && r.getMatchedDN().length() > 0)
+        {
+          println(ERR_TOOL_MATCHED_DN.get(r.getMatchedDN()));
+        }
+        return r.getResultCode().intValue();
+      }
+    }
+    return ResultCode.SUCCESS.intValue();
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested advanced mode.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         advanced mode.
+   */
+  public boolean isAdvancedMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested interactive
+   * behavior.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         interactive behavior.
+   */
+  public boolean isInteractive()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not this console application is running in its
+   * menu-driven mode. This can be used to dictate whether output should
+   * go to the error stream or not. In addition, it may also dictate
+   * whether or not sub-menus should display a cancel option as well as
+   * a quit option.
+   *
+   * @return Returns <code>true</code> if this console application is
+   *         running in its menu-driven mode.
+   */
+  public boolean isMenuDrivenMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested quiet output.
+   *
+   * @return Returns <code>true</code> if the user has requested quiet
+   *         output.
+   */
+  public boolean isQuiet()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested script-friendly
+   * output.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         script-friendly output.
+   */
+  public boolean isScriptFriendly()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested verbose output.
+   *
+   * @return Returns <code>true</code> if the user has requested verbose
+   *         output.
+   */
+  public boolean isVerbose()
+  {
+    return verbose.isPresent();
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPModify.java b/sdk/src/org/opends/sdk/tools/LDAPModify.java
new file mode 100644
index 0000000..fdad8c0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPModify.java
@@ -0,0 +1,801 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.StringTokenizer;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.ldif.*;
+import org.opends.sdk.requests.AddRequest;
+import org.opends.sdk.requests.DeleteRequest;
+import org.opends.sdk.requests.ModifyDNRequest;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides a tool that can be used to issue modify requests
+ * to the Directory Server.
+ */
+public final class LDAPModify extends ConsoleApplication
+{
+  private Connection connection;
+  private EntryWriter writer;
+  private Collection<Control> controls;
+  private BooleanArgument verbose;
+
+
+
+  /**
+   * The main method for LDAPModify tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   */
+
+  public static void main(String[] args)
+  {
+    int retCode = mainModify(args, System.in, System.out, System.err);
+
+    if (retCode != 0)
+    {
+      System.exit(filterExitCode(retCode));
+    }
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the LDAPModify tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @return The error code.
+   */
+
+  public static int mainModify(String[] args)
+  {
+    return mainModify(args, System.in, System.out, System.err);
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the LDAPModify tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   *          specified, the number of matching entries should be
+   *          returned or not.
+   * @param inStream
+   *          The input stream to use for standard input, or
+   *          <CODE>null</CODE> if standard input is not needed.
+   * @param outStream
+   *          The output stream to use for standard output, or
+   *          <CODE>null</CODE> if standard output is not needed.
+   * @param errStream
+   *          The output stream to use for standard error, or
+   *          <CODE>null</CODE> if standard error is not needed.
+   * @return The error code.
+   */
+  public static int mainModify(String[] args, InputStream inStream,
+      OutputStream outStream, OutputStream errStream)
+  {
+    return new LDAPModify(inStream, outStream, errStream).run(args);
+  }
+
+
+
+  private LDAPModify(InputStream in, OutputStream out, OutputStream err)
+  {
+    super(in, out, err);
+
+  }
+
+
+
+  private int run(String[] args)
+  {
+    // Create the command-line argument parser for use with this
+    // program.
+    Message toolDescription = INFO_LDAPMODIFY_TOOL_DESCRIPTION.get();
+    ArgumentParser argParser =
+        new ArgumentParser(LDAPModify.class.getName(), toolDescription,
+            false);
+    ArgumentParserConnectionFactory connectionFactory;
+
+    BooleanArgument continueOnError;
+    // TODO: Remove this due to new LDIF reader api?
+    BooleanArgument defaultAdd;
+    BooleanArgument noop;
+    BooleanArgument showUsage;
+    IntegerArgument version;
+    StringArgument assertionFilter;
+    StringArgument controlStr;
+    StringArgument encodingStr;
+    StringArgument filename;
+    StringArgument postReadAttributes;
+    StringArgument preReadAttributes;
+    StringArgument proxyAuthzID;
+    StringArgument propertiesFileArgument;
+    BooleanArgument noPropertiesFileArgument;
+
+    try
+    {
+      connectionFactory =
+          new ArgumentParserConnectionFactory(argParser, this);
+      propertiesFileArgument =
+          new StringArgument("propertiesFilePath", null,
+              OPTION_LONG_PROP_FILE_PATH, false, false, true,
+              INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PROP_FILE_PATH.get());
+      argParser.addArgument(propertiesFileArgument);
+      argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+      noPropertiesFileArgument =
+          new BooleanArgument("noPropertiesFileArgument", null,
+              OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+                  .get());
+      argParser.addArgument(noPropertiesFileArgument);
+      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+      defaultAdd =
+          new BooleanArgument("defaultAdd", 'a', "defaultAdd",
+              INFO_MODIFY_DESCRIPTION_DEFAULT_ADD.get());
+      argParser.addArgument(defaultAdd);
+
+      filename =
+          new StringArgument("filename", OPTION_SHORT_FILENAME,
+              OPTION_LONG_FILENAME, false, false, true,
+              INFO_FILE_PLACEHOLDER.get(), null, null,
+              INFO_LDAPMODIFY_DESCRIPTION_FILENAME.get());
+      filename.setPropertyName(OPTION_LONG_FILENAME);
+      argParser.addArgument(filename);
+
+      proxyAuthzID =
+          new StringArgument("proxy_authzid", OPTION_SHORT_PROXYAUTHID,
+              OPTION_LONG_PROXYAUTHID, false, false, true,
+              INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PROXY_AUTHZID.get());
+      proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
+      argParser.addArgument(proxyAuthzID);
+
+      assertionFilter =
+          new StringArgument("assertionfilter", null,
+              OPTION_LONG_ASSERTION_FILE, false, false, true,
+              INFO_ASSERTION_FILTER_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_ASSERTION_FILTER.get());
+      assertionFilter.setPropertyName(OPTION_LONG_ASSERTION_FILE);
+      argParser.addArgument(assertionFilter);
+
+      preReadAttributes =
+          new StringArgument("prereadattrs", null, "preReadAttributes",
+              false, false, true,
+              INFO_ATTRIBUTE_LIST_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PREREAD_ATTRS.get());
+      preReadAttributes.setPropertyName("preReadAttributes");
+      argParser.addArgument(preReadAttributes);
+
+      postReadAttributes =
+          new StringArgument("postreadattrs", null,
+              "postReadAttributes", false, false, true,
+              INFO_ATTRIBUTE_LIST_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_POSTREAD_ATTRS.get());
+      postReadAttributes.setPropertyName("postReadAttributes");
+      argParser.addArgument(postReadAttributes);
+
+      controlStr =
+          new StringArgument("control", 'J', "control", false, true,
+              true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_CONTROLS.get());
+      controlStr.setPropertyName("control");
+      argParser.addArgument(controlStr);
+
+      version =
+          new IntegerArgument("version", OPTION_SHORT_PROTOCOL_VERSION,
+              OPTION_LONG_PROTOCOL_VERSION, false, false, true,
+              INFO_PROTOCOL_VERSION_PLACEHOLDER.get(), 3, null,
+              INFO_DESCRIPTION_VERSION.get());
+      version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+      argParser.addArgument(version);
+
+      encodingStr =
+          new StringArgument("encoding", 'i', "encoding", false, false,
+              true, INFO_ENCODING_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_ENCODING.get());
+      encodingStr.setPropertyName("encoding");
+      argParser.addArgument(encodingStr);
+
+      continueOnError =
+          new BooleanArgument("continueOnError", 'c',
+              "continueOnError", INFO_DESCRIPTION_CONTINUE_ON_ERROR
+                  .get());
+      continueOnError.setPropertyName("continueOnError");
+      argParser.addArgument(continueOnError);
+
+      noop =
+          new BooleanArgument("no-op", OPTION_SHORT_DRYRUN,
+              OPTION_LONG_DRYRUN, INFO_DESCRIPTION_NOOP.get());
+      noop.setPropertyName(OPTION_LONG_DRYRUN);
+      argParser.addArgument(noop);
+
+      verbose =
+          new BooleanArgument("verbose", 'v', "verbose",
+              INFO_DESCRIPTION_VERBOSE.get());
+      verbose.setPropertyName("verbose");
+      argParser.addArgument(verbose);
+
+      showUsage =
+          new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+              OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+      argParser.addArgument(showUsage);
+      argParser.setUsageArgument(showUsage, getOutputStream());
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // Parse the command-line arguments provided to this program.
+    try
+    {
+      argParser.parseArguments(args);
+      connectionFactory.validate();
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+      println(message);
+      println(argParser.getUsageMessage());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // If we should just display usage or version information,
+    // then print it and exit.
+    if (argParser.usageOrVersionDisplayed())
+    {
+      return 0;
+    }
+
+    try
+    {
+      int versionNumber = version.getIntValue();
+      if (versionNumber != 2 && versionNumber != 3)
+      {
+        println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+            .valueOf(versionNumber)));
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+    catch (ArgumentException ae)
+    {
+      println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+          .valueOf(version.getValue())));
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // modifyOptions.setShowOperations(noop.isPresent());
+    // modifyOptions.setVerbose(verbose.isPresent());
+    // modifyOptions.setContinueOnError(continueOnError.isPresent());
+    // modifyOptions.setEncoding(encodingStr.getValue());
+    // modifyOptions.setDefaultAdd(defaultAdd.isPresent());
+
+    controls = new LinkedList<Control>();
+    if (controlStr.isPresent())
+    {
+      for (String ctrlString : controlStr.getValues())
+      {
+        try
+        {
+          Control ctrl = Utils.getControl(ctrlString);
+          controls.add(ctrl);
+        }
+        catch (DecodeException de)
+        {
+          Message message =
+              ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString);
+          println(message);
+          ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+    }
+
+    if (proxyAuthzID.isPresent())
+    {
+      Control proxyControl =
+          new ProxiedAuthV2Control(proxyAuthzID.getValue());
+      controls.add(proxyControl);
+    }
+
+    if (assertionFilter.isPresent())
+    {
+      String filterString = assertionFilter.getValue();
+      Filter filter;
+      try
+      {
+        filter = Filter.valueOf(filterString);
+
+        // FIXME -- Change this to the correct OID when the official one
+        // is
+        // assigned.
+        Control assertionControl = new AssertionControl(true, filter);
+        controls.add(assertionControl);
+      }
+      catch (LocalizedIllegalArgumentException le)
+      {
+        Message message =
+            ERR_LDAP_ASSERTION_INVALID_FILTER.get(le.getMessage());
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+
+    if (preReadAttributes.isPresent())
+    {
+      String valueStr = preReadAttributes.getValue();
+      StringTokenizer tokenizer = new StringTokenizer(valueStr, ", ");
+      PreReadControl.Request control = new PreReadControl.Request(true);
+      while (tokenizer.hasMoreTokens())
+      {
+        control.addAttribute(tokenizer.nextToken());
+      }
+      controls.add(control);
+    }
+
+    if (postReadAttributes.isPresent())
+    {
+      String valueStr = postReadAttributes.getValue();
+      StringTokenizer tokenizer = new StringTokenizer(valueStr, ", ");
+      PostReadControl.Request control =
+          new PostReadControl.Request(true);
+      while (tokenizer.hasMoreTokens())
+      {
+        control.addAttribute(tokenizer.nextToken());
+      }
+      controls.add(control);
+    }
+
+    if (!noop.isPresent())
+    {
+      try
+      {
+        connection = connectionFactory.getConnection();
+      }
+      catch (ErrorResultException ere)
+      {
+        return Utils.printErrorMessage(this, ere);
+      }
+    }
+
+    Utils.printPasswordPolicyResults(this, connection);
+
+    writer = new LDIFEntryWriter(getOutputStream());
+    VisitorImpl visitor = new VisitorImpl();
+    try
+    {
+      ChangeRecordReader reader;
+      if (filename.isPresent())
+      {
+        try
+        {
+          reader =
+              new LDIFChangeRecordReader(new FileInputStream(filename
+                  .getValue()));
+        }
+        catch (Exception e)
+        {
+          Message message =
+              ERR_LDIF_FILE_CANNOT_OPEN_FOR_READ.get(filename
+                  .getValue(), e.getLocalizedMessage());
+          println(message);
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+      else
+      {
+        reader = new LDIFChangeRecordReader(getInputStream());
+      }
+
+      ChangeRecord cr;
+      try
+      {
+        int result;
+        while ((cr = reader.readChangeRecord()) != null)
+        {
+          result = cr.accept(visitor, null);
+          if (result != 0 && !continueOnError.isPresent())
+          {
+            return result;
+          }
+        }
+      }
+      catch (IOException ioe)
+      {
+        Message message =
+            ERR_LDIF_FILE_READ_ERROR.get(filename.getValue(), ioe
+                .getLocalizedMessage());
+        println(message);
+        return ResultCode.CLIENT_SIDE_LOCAL_ERROR.intValue();
+      }
+    }
+    finally
+    {
+      if (connection != null)
+      {
+        connection.close();
+      }
+    }
+
+    return ResultCode.SUCCESS.intValue();
+  }
+
+
+
+  private class VisitorImpl implements
+      ChangeRecordVisitor<Integer, java.lang.Void>
+  {
+    private void printResult(String operationType, String name, Result r)
+    {
+      if (r.getResultCode() != ResultCode.SUCCESS
+          && r.getResultCode() != ResultCode.REFERRAL)
+      {
+        Message msg = INFO_OPERATION_FAILED.get(operationType);
+        println(msg);
+        println(ERR_TOOL_RESULT_CODE.get(r.getResultCode().intValue(),
+            r.getResultCode().toString()));
+        if ((r.getDiagnosticMessage() != null)
+            && (r.getDiagnosticMessage().length() > 0))
+        {
+          println(Message.raw(r.getDiagnosticMessage()));
+        }
+        if (r.getMatchedDN() != null && r.getMatchedDN().length() > 0)
+        {
+          println(ERR_TOOL_MATCHED_DN.get(r.getMatchedDN()));
+        }
+      }
+      else
+      {
+        Message msg =
+            INFO_OPERATION_SUCCESSFUL.get(operationType, name);
+        println(msg);
+        if ((r.getDiagnosticMessage() != null)
+            && (r.getDiagnosticMessage().length() > 0))
+        {
+          println(Message.raw(r.getDiagnosticMessage()));
+        }
+        if (r.getReferralURIs() != null)
+        {
+          for (String uri : r.getReferralURIs())
+          {
+            println(Message.raw(uri));
+          }
+        }
+      }
+
+      Control control =
+          r.getControl(PreReadControl.OID_LDAP_READENTRY_PREREAD);
+      if (control != null && control instanceof PreReadControl.Response)
+      {
+        PreReadControl.Response dc = (PreReadControl.Response) control;
+        println(INFO_LDAPMODIFY_PREREAD_ENTRY.get());
+        try
+        {
+          writer.writeEntry(dc.getSearchEntry());
+        }
+        catch (IOException ioe)
+        {
+          throw new RuntimeException(ioe);
+        }
+      }
+      control =
+          r.getControl(PostReadControl.OID_LDAP_READENTRY_POSTREAD);
+      if (control != null
+          && control instanceof PostReadControl.Response)
+      {
+        PostReadControl.Response dc =
+            (PostReadControl.Response) control;
+        println(INFO_LDAPMODIFY_POSTREAD_ENTRY.get());
+        try
+        {
+          writer.writeEntry(dc.getSearchEntry());
+        }
+        catch (IOException ioe)
+        {
+          throw new RuntimeException(ioe);
+        }
+      }
+      // TODO: CSN control
+    }
+
+
+
+    public Integer visitChangeRecord(Void aVoid, AddRequest change)
+    {
+      for (Control control : controls)
+      {
+        change.addControl(control);
+      }
+      String opType = "ADD";
+      println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+          .toString()));
+      if (connection != null)
+      {
+        try
+        {
+          Result r;
+          try
+          {
+            r = connection.add(change);
+          }
+          catch (InterruptedException e)
+          {
+            // This shouldn't happen because there are no other threads
+            // to interrupt this one.
+            r = Responses.newResult(
+                ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+                .setDiagnosticMessage(e.getLocalizedMessage());
+            throw ErrorResultException.wrap(r);
+          }
+          printResult(opType, change.getName().toString(), r);
+          return r.getResultCode().intValue();
+        }
+        catch (ErrorResultException ere)
+        {
+          return Utils.printErrorMessage(LDAPModify.this, ere);
+        }
+      }
+      return ResultCode.SUCCESS.intValue();
+    }
+
+
+
+    public Integer visitChangeRecord(Void aVoid, DeleteRequest change)
+    {
+      for (Control control : controls)
+      {
+        change.addControl(control);
+      }
+      String opType = "DELETE";
+      println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+          .toString()));
+      if (connection != null)
+      {
+        try
+        {
+          Result r;
+          try
+          {
+            r = connection.delete(change);
+          }
+          catch (InterruptedException e)
+          {
+            // This shouldn't happen because there are no other threads
+            // to interrupt this one.
+            r = Responses.newResult(
+                ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+                .setDiagnosticMessage(e.getLocalizedMessage());
+            throw ErrorResultException.wrap(r);
+          }
+          printResult(opType, change.getName().toString(), r);
+          return r.getResultCode().intValue();
+        }
+        catch (ErrorResultException ere)
+        {
+          return Utils.printErrorMessage(LDAPModify.this, ere);
+        }
+      }
+      return ResultCode.SUCCESS.intValue();
+    }
+
+
+
+    public Integer visitChangeRecord(Void aVoid, ModifyDNRequest change)
+    {
+      for (Control control : controls)
+      {
+        change.addControl(control);
+      }
+      String opType = "MODIFY DN";
+      println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+          .toString()));
+      if (connection != null)
+      {
+        try
+        {
+          Result r;
+          try
+          {
+            r = connection.modifyDN(change);
+          }
+          catch (InterruptedException e)
+          {
+            // This shouldn't happen because there are no other threads
+            // to interrupt this one.
+            r = Responses.newResult(
+                ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+                .setDiagnosticMessage(e.getLocalizedMessage());
+            throw ErrorResultException.wrap(r);
+          }
+          printResult(opType, change.getName().toString(), r);
+          return r.getResultCode().intValue();
+        }
+        catch (ErrorResultException ere)
+        {
+          return Utils.printErrorMessage(LDAPModify.this, ere);
+        }
+      }
+      return ResultCode.SUCCESS.intValue();
+    }
+
+
+
+    public Integer visitChangeRecord(Void aVoid, ModifyRequest change)
+    {
+      for (Control control : controls)
+      {
+        change.addControl(control);
+      }
+      String opType = "MODIFY";
+      println(INFO_PROCESSING_OPERATION.get(opType, change.getName()
+          .toString()));
+      if (connection != null)
+      {
+        try
+        {
+          Result r;
+          try
+          {
+            r = connection.modify(change);
+          }
+          catch (InterruptedException e)
+          {
+            // This shouldn't happen because there are no other threads
+            // to interrupt this one.
+            r = Responses.newResult(
+                ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+                .setDiagnosticMessage(e.getLocalizedMessage());
+            throw ErrorResultException.wrap(r);
+          }
+          printResult(opType, change.getName().toString(), r);
+          return r.getResultCode().intValue();
+        }
+        catch (ErrorResultException ere)
+        {
+          return Utils.printErrorMessage(LDAPModify.this, ere);
+        }
+      }
+      return ResultCode.SUCCESS.intValue();
+    }
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested advanced mode.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         advanced mode.
+   */
+  public boolean isAdvancedMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested interactive
+   * behavior.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         interactive behavior.
+   */
+  public boolean isInteractive()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not this console application is running in its
+   * menu-driven mode. This can be used to dictate whether output should
+   * go to the error stream or not. In addition, it may also dictate
+   * whether or not sub-menus should display a cancel option as well as
+   * a quit option.
+   *
+   * @return Returns <code>true</code> if this console application is
+   *         running in its menu-driven mode.
+   */
+  public boolean isMenuDrivenMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested quiet output.
+   *
+   * @return Returns <code>true</code> if the user has requested quiet
+   *         output.
+   */
+  public boolean isQuiet()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested script-friendly
+   * output.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         script-friendly output.
+   */
+  public boolean isScriptFriendly()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested verbose output.
+   *
+   * @return Returns <code>true</code> if the user has requested verbose
+   *         output.
+   */
+  public boolean isVerbose()
+  {
+    return verbose.isPresent();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java b/sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java
new file mode 100644
index 0000000..6f51b57
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPPasswordModify.java
@@ -0,0 +1,475 @@
+package org.opends.sdk.tools;
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Connection;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.ResultCode;
+import org.opends.sdk.controls.Control;
+import org.opends.sdk.extensions.PasswordModifyRequest;
+import org.opends.sdk.extensions.PasswordModifyResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.server.util.cli.ConsoleApplication;
+
+/**
+ * This program provides a utility that uses the LDAP password modify extended
+ * operation to change the password for a user.  It exposes the three primary
+ * options available for this operation, which are:
+ *
+ * <UL>
+ *   <LI>The user identity whose password should be changed.</LI>
+ *   <LI>The current password for the user.</LI>
+ *   <LI>The new password for the user.
+ * </UL>
+ *
+ * All of these are optional components that may be included or omitted from the
+ * request.
+ */
+public class LDAPPasswordModify extends ConsoleApplication
+{
+  private BooleanArgument verbose;
+
+  /**
+   * Parses the command-line arguments, establishes a connection to the
+   * Directory Server, sends the password modify request, and reads the
+   * response.
+   *
+   * @param  args  The command-line arguments provided to this program.
+   */
+  public static void main(String[] args)
+  {
+    int retCode = mainPasswordModify(args, System.in, System.out, System.err);
+
+    if (retCode != 0)
+    {
+      System.exit(filterExitCode(retCode));
+    }
+  }
+
+
+
+  /**
+   * Parses the command-line arguments, establishes a connection to the
+   * Directory Server, sends the password modify request, and reads the
+   * response.
+   *
+   * @param  args  The command-line arguments provided to this program.
+   *
+   * @return  An integer value of zero if everything completed successfully, or
+   *          a nonzero value if an error occurred.
+   */
+  public static int mainPasswordModify(String[] args)
+  {
+    return mainPasswordModify(args, System.in, System.out, System.err);
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the LDAPPasswordModify tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   *          specified, the number of matching entries should be
+   *          returned or not.
+   * @param inStream
+   *          The input stream to use for standard input, or
+   *          <CODE>null</CODE> if standard input is not needed.
+   * @param outStream
+   *          The output stream to use for standard output, or
+   *          <CODE>null</CODE> if standard output is not needed.
+   * @param errStream
+   *          The output stream to use for standard error, or
+   *          <CODE>null</CODE> if standard error is not needed.
+   * @return The error code.
+   */
+  public static int mainPasswordModify(String[] args, InputStream inStream,
+                                       OutputStream outStream, OutputStream errStream)
+  {
+    return new LDAPPasswordModify(inStream, outStream, errStream).run(args);
+  }
+
+
+
+  private LDAPPasswordModify(InputStream in, OutputStream out, OutputStream err)
+  {
+    super(in, out, err);
+
+  }
+
+  private int run(String[] args)
+  {
+    // Create the command-line argument parser for use with this
+    // program.
+    Message toolDescription = INFO_LDAPPWMOD_TOOL_DESCRIPTION.get();
+    ArgumentParser argParser =
+        new ArgumentParser(LDAPPasswordModify.class.getName(), toolDescription,
+            false);
+    ArgumentParserConnectionFactory connectionFactory;
+
+    FileBasedArgument currentPWFile;
+    FileBasedArgument newPWFile;
+    BooleanArgument showUsage;
+    IntegerArgument version;
+    StringArgument    currentPW;
+    StringArgument controlStr;
+    StringArgument    newPW;
+    StringArgument proxyAuthzID;
+    StringArgument propertiesFileArgument;
+    BooleanArgument noPropertiesFileArgument;
+
+    try
+    {
+      connectionFactory =
+          new ArgumentParserConnectionFactory(argParser, this);
+      propertiesFileArgument =
+          new StringArgument("propertiesFilePath", null,
+              OPTION_LONG_PROP_FILE_PATH, false, false, true,
+              INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PROP_FILE_PATH.get());
+      argParser.addArgument(propertiesFileArgument);
+      argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+      noPropertiesFileArgument =
+          new BooleanArgument("noPropertiesFileArgument", null,
+              OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+                  .get());
+      argParser.addArgument(noPropertiesFileArgument);
+      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+      newPW = new StringArgument("newpw", 'n', "newPassword", false, false,
+          true, INFO_NEW_PASSWORD_PLACEHOLDER.get(),
+          null, null,
+          INFO_LDAPPWMOD_DESCRIPTION_NEWPW.get());
+      newPW.setPropertyName("newPassword");
+      argParser.addArgument(newPW);
+
+
+      newPWFile = new FileBasedArgument(
+          "newpwfile", 'F', "newPasswordFile",
+          false, false, INFO_FILE_PLACEHOLDER.get(), null, null,
+          INFO_LDAPPWMOD_DESCRIPTION_NEWPWFILE.get());
+      newPWFile.setPropertyName("newPasswordFile");
+      argParser.addArgument(newPWFile);
+
+
+      currentPW =
+          new StringArgument("currentpw", 'c', "currentPassword", false, false,
+              true, INFO_CURRENT_PASSWORD_PLACEHOLDER.get(),
+              null,  null,
+              INFO_LDAPPWMOD_DESCRIPTION_CURRENTPW.get());
+      currentPW.setPropertyName("currentPassword");
+      argParser.addArgument(currentPW);
+
+
+      currentPWFile =
+          new FileBasedArgument(
+              "currentpwfile", 'C', "currentPasswordFile",
+              false, false, INFO_FILE_PLACEHOLDER.get(), null, null,
+              INFO_LDAPPWMOD_DESCRIPTION_CURRENTPWFILE.get());
+      currentPWFile.setPropertyName("currentPasswordFile");
+      argParser.addArgument(currentPWFile);
+
+      proxyAuthzID =
+          new StringArgument("authzid", 'a', "authzID", false, false,
+              true, INFO_PROXYAUTHID_PLACEHOLDER.get(),
+              null, null,
+              INFO_LDAPPWMOD_DESCRIPTION_AUTHZID.get());
+      proxyAuthzID.setPropertyName("authzID");
+      argParser.addArgument(proxyAuthzID);
+
+      controlStr =
+          new StringArgument("control", 'J', "control", false, true,
+              true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_CONTROLS.get());
+      controlStr.setPropertyName("control");
+      argParser.addArgument(controlStr);
+
+      version =
+          new IntegerArgument("version", OPTION_SHORT_PROTOCOL_VERSION,
+              OPTION_LONG_PROTOCOL_VERSION, false, false, true,
+              INFO_PROTOCOL_VERSION_PLACEHOLDER.get(), 3, null,
+              INFO_DESCRIPTION_VERSION.get());
+      version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+      argParser.addArgument(version);
+
+      verbose =
+          new BooleanArgument("verbose", 'v', "verbose",
+              INFO_DESCRIPTION_VERBOSE.get());
+      verbose.setPropertyName("verbose");
+      argParser.addArgument(verbose);
+
+      showUsage =
+          new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+              OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+      argParser.addArgument(showUsage);
+      argParser.setUsageArgument(showUsage, getOutputStream());
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // Parse the command-line arguments provided to this program.
+    try
+    {
+      argParser.parseArguments(args);
+      connectionFactory.validate();
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+      println(message);
+      println(argParser.getUsageMessage());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // If we should just display usage or version information,
+    // then print it and exit.
+    if (argParser.usageOrVersionDisplayed())
+    {
+      return 0;
+    }
+
+    PasswordModifyRequest request = new PasswordModifyRequest();
+    try
+    {
+      int versionNumber = version.getIntValue();
+      if (versionNumber != 2 && versionNumber != 3)
+      {
+        println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+            .valueOf(versionNumber)));
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+    catch (ArgumentException ae)
+    {
+      println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+          .valueOf(version.getValue())));
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    if (controlStr.isPresent())
+    {
+      for (String ctrlString : controlStr.getValues())
+      {
+        try
+        {
+          Control ctrl = Utils.getControl(ctrlString);
+          request.addControl(ctrl);
+        }
+        catch (DecodeException de)
+        {
+          Message message =
+              ERR_TOOL_INVALID_CONTROL_STRING.get(ctrlString);
+          println(message);
+          ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+    }
+
+    if (newPW.isPresent() && newPWFile.isPresent())
+    {
+      Message message = ERR_LDAPPWMOD_CONFLICTING_ARGS.get(
+          newPW.getLongIdentifier(),
+          newPWFile.getLongIdentifier());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    if (currentPW.isPresent() && currentPWFile.isPresent())
+    {
+      Message message = ERR_LDAPPWMOD_CONFLICTING_ARGS.get(
+          currentPW.getLongIdentifier(),
+          currentPWFile.getLongIdentifier());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    Connection connection;
+    try
+    {
+      connection = connectionFactory.getConnection();
+    }
+    catch (ErrorResultException ere)
+    {
+      return Utils.printErrorMessage(this, ere);
+    }
+
+    if (proxyAuthzID.isPresent())
+    {
+      request.setUserIdentity(proxyAuthzID.getValue());
+    }
+
+    if (currentPW.isPresent())
+    {
+      request.setOldPassword(ByteString.valueOf(currentPW.getValue()));
+    }
+    else if (currentPWFile.isPresent())
+    {
+      request.setOldPassword(ByteString.valueOf(currentPWFile.getValue()));
+    }
+
+    if (newPW.isPresent())
+    {
+      request.setNewPassword(ByteString.valueOf(newPW.getValue()));
+    }
+    else if (newPWFile.isPresent())
+    {
+      request.setNewPassword(ByteString.valueOf(newPWFile.getValue()));
+    }
+
+    PasswordModifyResult result;
+    try
+    {
+      try
+      {
+        result = connection.extendedRequest(request);
+      }
+      catch (InterruptedException e)
+      {
+        // This shouldn't happen because there are no other threads to
+        // interrupt this one.
+        result = new PasswordModifyResult(
+            ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+            .setDiagnosticMessage(e.getLocalizedMessage());
+        throw ErrorResultException.wrap(result);
+      }
+    }
+    catch (ErrorResultException e)
+    {
+      Message message =
+          ERR_LDAPPWMOD_FAILED.get(e.getResult().getResultCode().intValue(),
+              e.getResult().getResultCode().toString());
+      println(message);
+
+      String errorMessage = e.getResult().getDiagnosticMessage();
+      if ((errorMessage != null) && (errorMessage.length() > 0))
+      {
+        message = ERR_LDAPPWMOD_FAILURE_ERROR_MESSAGE.get(errorMessage);
+        println(message);
+      }
+
+      String matchedDN = e.getResult().getMatchedDN();
+      if (matchedDN != null && matchedDN.length() > 0)
+      {
+        message = ERR_LDAPPWMOD_FAILURE_MATCHED_DN.get(matchedDN);
+        println(message);
+      }
+      return e.getResult().getResultCode().intValue();
+    }
+
+    Message message = INFO_LDAPPWMOD_SUCCESSFUL.get();
+    println(message);
+
+    String additionalInfo = result.getDiagnosticMessage();
+    if ((additionalInfo != null) && (additionalInfo.length() > 0))
+    {
+
+      message = INFO_LDAPPWMOD_ADDITIONAL_INFO.get(additionalInfo);
+      println(message);
+    }
+
+    if(result.getGenPassword() != null)
+    {
+      message = INFO_LDAPPWMOD_GENERATED_PASSWORD.get(result.getGenPassword().toString());
+      println(message);
+    }
+
+    return 0;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested advanced mode.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         advanced mode.
+   */
+  public boolean isAdvancedMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested interactive
+   * behavior.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         interactive behavior.
+   */
+  public boolean isInteractive()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not this console application is running in its
+   * menu-driven mode. This can be used to dictate whether output should
+   * go to the error stream or not. In addition, it may also dictate
+   * whether or not sub-menus should display a cancel option as well as
+   * a quit option.
+   *
+   * @return Returns <code>true</code> if this console application is
+   *         running in its menu-driven mode.
+   */
+  public boolean isMenuDrivenMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested quiet output.
+   *
+   * @return Returns <code>true</code> if the user has requested quiet
+   *         output.
+   */
+  public boolean isQuiet()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested script-friendly
+   * output.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         script-friendly output.
+   */
+  public boolean isScriptFriendly()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested verbose output.
+   *
+   * @return Returns <code>true</code> if the user has requested verbose
+   *         output.
+   */
+  public boolean isVerbose()
+  {
+    return verbose.isPresent();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/LDAPSearch.java b/sdk/src/org/opends/sdk/tools/LDAPSearch.java
new file mode 100644
index 0000000..bc443df
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/LDAPSearch.java
@@ -0,0 +1,1241 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.ServerConstants.*;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.ldif.EntryWriter;
+import org.opends.sdk.ldif.LDIFEntryWriter;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.Responses;
+import org.opends.sdk.responses.Result;
+import org.opends.sdk.responses.SearchResultEntry;
+import org.opends.sdk.responses.SearchResultReference;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.LocalizedIllegalArgumentException;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides a tool that can be used to issue search requests
+ * to the Directory Server.
+ */
+public final class LDAPSearch extends ConsoleApplication
+{
+  private BooleanArgument verbose;
+
+  private EntryWriter ldifWriter;
+
+
+
+  private class LDAPSearchResultHandler implements
+      SearchResultHandler<Void>
+  {
+    private int entryCount = 0;
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void handleEntry(Void p, SearchResultEntry entry)
+    {
+      entryCount++;
+
+      Control control = entry.getControl(OID_ENTRY_CHANGE_NOTIFICATION);
+      if (control != null
+          && control instanceof EntryChangeNotificationControl)
+      {
+        EntryChangeNotificationControl dc = (EntryChangeNotificationControl) control;
+        println(INFO_LDAPSEARCH_PSEARCH_CHANGE_TYPE.get(dc
+            .getChangeType().toString()));
+        String previousDN = dc.getPreviousDN();
+        if (previousDN != null)
+        {
+          println(INFO_LDAPSEARCH_PSEARCH_PREVIOUS_DN.get(previousDN));
+        }
+      }
+      control = entry.getControl(OID_ACCOUNT_USABLE_CONTROL);
+      if (control != null
+          && control instanceof AccountUsabilityControl.Response)
+      {
+        AccountUsabilityControl.Response dc = (AccountUsabilityControl.Response) control;
+
+        println(INFO_LDAPSEARCH_ACCTUSABLE_HEADER.get());
+        if (dc.isUsable())
+        {
+
+          println(INFO_LDAPSEARCH_ACCTUSABLE_IS_USABLE.get());
+          if (dc.getSecondsBeforeExpiration() > 0)
+          {
+            int timeToExp = dc.getSecondsBeforeExpiration();
+            Message timeToExpStr = Utils.secondsToTimeString(timeToExp);
+
+            println(INFO_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_EXPIRATION
+                .get(timeToExpStr));
+          }
+        }
+        else
+        {
+
+          println(INFO_LDAPSEARCH_ACCTUSABLE_NOT_USABLE.get());
+          if (dc.isInactive())
+          {
+            println(INFO_LDAPSEARCH_ACCTUSABLE_ACCT_INACTIVE.get());
+          }
+          if (dc.isReset())
+          {
+            println(INFO_LDAPSEARCH_ACCTUSABLE_PW_RESET.get());
+          }
+          if (dc.isExpired())
+          {
+            println(INFO_LDAPSEARCH_ACCTUSABLE_PW_EXPIRED.get());
+
+            if (dc.getRemainingGraceLogins() > 0)
+            {
+              println(INFO_LDAPSEARCH_ACCTUSABLE_REMAINING_GRACE.get(dc
+                  .getRemainingGraceLogins()));
+            }
+          }
+          if (dc.isLocked())
+          {
+            println(INFO_LDAPSEARCH_ACCTUSABLE_LOCKED.get());
+            if (dc.getSecondsBeforeUnlock() > 0)
+            {
+              int timeToUnlock = dc.getSecondsBeforeUnlock();
+              Message timeToUnlockStr = Utils
+                  .secondsToTimeString(timeToUnlock);
+
+              println(INFO_LDAPSEARCH_ACCTUSABLE_TIME_UNTIL_UNLOCK
+                  .get(timeToUnlockStr));
+            }
+          }
+        }
+      }
+      try
+      {
+        ldifWriter.writeEntry(entry);
+        ldifWriter.flush();
+      }
+      catch (IOException ioe)
+      {
+        // Something is seriously wrong
+        throw new RuntimeException(ioe);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void handleReference(Void p, SearchResultReference reference)
+    {
+      println(Message.raw(reference.toString()));
+    }
+  }
+
+
+
+  /**
+   * The main method for LDAPSearch tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   */
+
+  public static void main(String[] args)
+  {
+    int retCode = mainSearch(args, false, System.in, System.out,
+        System.err);
+
+    if (retCode != 0)
+    {
+      System.exit(Utils.filterExitCode(retCode));
+    }
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the ldapsearch tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @return The error code.
+   */
+
+  public static int mainSearch(String[] args)
+  {
+    return mainSearch(args, true, System.in, System.out, System.err);
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the ldapsearch tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @param inStream
+   *          The input stream to use for standard input, or
+   *          <CODE>null</CODE> if standard input is not needed.
+   * @param outStream
+   *          The output stream to use for standard output, or
+   *          <CODE>null</CODE> if standard output is not needed.
+   * @param errStream
+   *          The output stream to use for standard error, or
+   *          <CODE>null</CODE> if standard error is not needed.
+   * @return The error code.
+   */
+  public static int mainSearch(String[] args, InputStream inStream,
+      OutputStream outStream, OutputStream errStream)
+  {
+    return mainSearch(args, true, inStream, outStream, errStream);
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the ldapsearch tool.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @param returnMatchingEntries
+   *          whether when the option --countEntries is specified, the
+   *          number of matching entries should be returned or not.
+   * @param inStream
+   *          The input stream to use for standard input, or
+   *          <CODE>null</CODE> if standard input is not needed.
+   * @param outStream
+   *          The output stream to use for standard output, or
+   *          <CODE>null</CODE> if standard output is not needed.
+   * @param errStream
+   *          The output stream to use for standard error, or
+   *          <CODE>null</CODE> if standard error is not needed.
+   * @return The error code.
+   */
+
+  public static int mainSearch(String[] args,
+      boolean returnMatchingEntries, InputStream inStream,
+      OutputStream outStream, OutputStream errStream)
+  {
+    return new LDAPSearch(inStream, outStream, errStream).run(args,
+        returnMatchingEntries);
+  }
+
+
+
+  private LDAPSearch(InputStream in, OutputStream out, OutputStream err)
+  {
+    super(in, out, err);
+
+  }
+
+
+
+  private int run(String[] args, boolean returnMatchingEntries)
+  {
+    // Create the command-line argument parser for use with this
+    // program.
+    Message toolDescription = INFO_LDAPSEARCH_TOOL_DESCRIPTION.get();
+    ArgumentParser argParser = new ArgumentParser(LDAPSearch.class
+        .getName(), toolDescription, false, true, 0, 0,
+        "[filter] [attributes ...]");
+    ArgumentParserConnectionFactory connectionFactory;
+
+    BooleanArgument countEntries;
+    BooleanArgument dontWrap;
+    BooleanArgument noop;
+    BooleanArgument typesOnly;
+    IntegerArgument simplePageSize;
+    IntegerArgument timeLimit;
+    IntegerArgument version;
+    StringArgument baseDN;
+    StringArgument controlStr;
+    MultiChoiceArgument<DereferenceAliasesPolicy> dereferencePolicy;
+    StringArgument filename;
+    StringArgument matchedValuesFilter;
+    StringArgument pSearchInfo;
+    MultiChoiceArgument<SearchScope> searchScope;
+    StringArgument vlvDescriptor;
+    StringArgument effectiveRightsUser;
+    StringArgument effectiveRightsAttrs;
+    StringArgument sortOrder;
+    StringArgument proxyAuthzID;
+    StringArgument assertionFilter;
+    IntegerArgument sizeLimit;
+    try
+    {
+      connectionFactory = new ArgumentParserConnectionFactory(
+          argParser, this);
+      StringArgument propertiesFileArgument = new StringArgument(
+          "propertiesFilePath", null, OPTION_LONG_PROP_FILE_PATH,
+          false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(),
+          null, null, INFO_DESCRIPTION_PROP_FILE_PATH.get());
+      argParser.addArgument(propertiesFileArgument);
+      argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+      BooleanArgument noPropertiesFileArgument = new BooleanArgument(
+          "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
+          INFO_DESCRIPTION_NO_PROP_FILE.get());
+      argParser.addArgument(noPropertiesFileArgument);
+      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+      baseDN = new StringArgument("baseDN", OPTION_SHORT_BASEDN,
+          OPTION_LONG_BASEDN, true, false, true,
+          INFO_BASEDN_PLACEHOLDER.get(), null, null,
+          INFO_SEARCH_DESCRIPTION_BASEDN.get());
+      baseDN.setPropertyName(OPTION_LONG_BASEDN);
+      argParser.addArgument(baseDN);
+
+      searchScope = new MultiChoiceArgument<SearchScope>("searchScope",
+          's', "searchScope", false, true,
+          INFO_SEARCH_SCOPE_PLACEHOLDER.get(), SearchScope.values(),
+          false, INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE.get());
+      searchScope.setPropertyName("searchScope");
+      searchScope.setDefaultValue(SearchScope.WHOLE_SUBTREE);
+      argParser.addArgument(searchScope);
+
+      filename = new StringArgument("filename", OPTION_SHORT_FILENAME,
+          OPTION_LONG_FILENAME, false, false, true,
+          INFO_FILE_PLACEHOLDER.get(), null, null,
+          INFO_SEARCH_DESCRIPTION_FILENAME.get());
+      searchScope.setPropertyName(OPTION_LONG_FILENAME);
+      argParser.addArgument(filename);
+
+      proxyAuthzID = new StringArgument("proxy_authzid",
+          OPTION_SHORT_PROXYAUTHID, OPTION_LONG_PROXYAUTHID, false,
+          false, true, INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_PROXY_AUTHZID.get());
+      proxyAuthzID.setPropertyName(OPTION_LONG_PROXYAUTHID);
+      argParser.addArgument(proxyAuthzID);
+
+      pSearchInfo = new StringArgument("psearchinfo", 'C',
+          "persistentSearch", false, false, true,
+          INFO_PSEARCH_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_PSEARCH_INFO.get());
+      pSearchInfo.setPropertyName("persistentSearch");
+      argParser.addArgument(pSearchInfo);
+
+      simplePageSize = new IntegerArgument("simplepagesize", null,
+          "simplePageSize", false, false, true,
+          INFO_NUM_ENTRIES_PLACEHOLDER.get(), 1000, null, true, 1,
+          false, 0, INFO_DESCRIPTION_SIMPLE_PAGE_SIZE.get());
+      simplePageSize.setPropertyName("simplePageSize");
+      argParser.addArgument(simplePageSize);
+
+      assertionFilter = new StringArgument("assertionfilter", null,
+          OPTION_LONG_ASSERTION_FILE, false, false, true,
+          INFO_ASSERTION_FILTER_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_ASSERTION_FILTER.get());
+      assertionFilter.setPropertyName(OPTION_LONG_ASSERTION_FILE);
+      argParser.addArgument(assertionFilter);
+
+      matchedValuesFilter = new StringArgument("matchedvalues", null,
+          "matchedValuesFilter", false, true, true,
+          INFO_FILTER_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_MATCHED_VALUES_FILTER.get());
+      matchedValuesFilter.setPropertyName("matchedValuesFilter");
+      argParser.addArgument(matchedValuesFilter);
+
+      sortOrder = new StringArgument("sortorder", 'S', "sortOrder",
+          false, false, true, INFO_SORT_ORDER_PLACEHOLDER.get(), null,
+          null, INFO_DESCRIPTION_SORT_ORDER.get());
+      sortOrder.setPropertyName("sortOrder");
+      argParser.addArgument(sortOrder);
+
+      vlvDescriptor = new StringArgument("vlvdescriptor", 'G',
+          "virtualListView", false, false, true, INFO_VLV_PLACEHOLDER
+              .get(), null, null, INFO_DESCRIPTION_VLV.get());
+      vlvDescriptor.setPropertyName("virtualListView");
+      argParser.addArgument(vlvDescriptor);
+
+      controlStr = new StringArgument("control", 'J', "control", false,
+          true, true, INFO_LDAP_CONTROL_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_CONTROLS.get());
+      controlStr.setPropertyName("control");
+      argParser.addArgument(controlStr);
+
+      effectiveRightsUser = new StringArgument("effectiveRightsUser",
+          OPTION_SHORT_EFFECTIVERIGHTSUSER,
+          OPTION_LONG_EFFECTIVERIGHTSUSER, false, false, true,
+          INFO_PROXYAUTHID_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_EFFECTIVERIGHTS_USER.get());
+      effectiveRightsUser
+          .setPropertyName(OPTION_LONG_EFFECTIVERIGHTSUSER);
+      argParser.addArgument(effectiveRightsUser);
+
+      effectiveRightsAttrs = new StringArgument("effectiveRightsAttrs",
+          OPTION_SHORT_EFFECTIVERIGHTSATTR,
+          OPTION_LONG_EFFECTIVERIGHTSATTR, false, true, true,
+          INFO_ATTRIBUTE_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_EFFECTIVERIGHTS_ATTR.get());
+      effectiveRightsAttrs
+          .setPropertyName(OPTION_LONG_EFFECTIVERIGHTSATTR);
+      argParser.addArgument(effectiveRightsAttrs);
+
+      version = new IntegerArgument("version",
+          OPTION_SHORT_PROTOCOL_VERSION, OPTION_LONG_PROTOCOL_VERSION,
+          false, false, true, INFO_PROTOCOL_VERSION_PLACEHOLDER.get(),
+          3, null, INFO_DESCRIPTION_VERSION.get());
+      version.setPropertyName(OPTION_LONG_PROTOCOL_VERSION);
+      argParser.addArgument(version);
+
+      StringArgument encodingStr = new StringArgument("encoding", 'i',
+          "encoding", false, false, true, INFO_ENCODING_PLACEHOLDER
+              .get(), null, null, INFO_DESCRIPTION_ENCODING.get());
+      encodingStr.setPropertyName("encoding");
+      argParser.addArgument(encodingStr);
+
+      dereferencePolicy = new MultiChoiceArgument<DereferenceAliasesPolicy>(
+          "derefpolicy", 'a', "dereferencePolicy", false, true,
+          INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(),
+          DereferenceAliasesPolicy.values(), false,
+          INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY.get());
+      dereferencePolicy.setPropertyName("dereferencePolicy");
+      dereferencePolicy.setDefaultValue(DereferenceAliasesPolicy.NEVER);
+      argParser.addArgument(dereferencePolicy);
+
+      typesOnly = new BooleanArgument("typesOnly", 'A', "typesOnly",
+          INFO_DESCRIPTION_TYPES_ONLY.get());
+      typesOnly.setPropertyName("typesOnly");
+      argParser.addArgument(typesOnly);
+
+      sizeLimit = new IntegerArgument("sizeLimit", 'z', "sizeLimit",
+          false, false, true, INFO_SIZE_LIMIT_PLACEHOLDER.get(), 0,
+          null, INFO_SEARCH_DESCRIPTION_SIZE_LIMIT.get());
+      sizeLimit.setPropertyName("sizeLimit");
+      argParser.addArgument(sizeLimit);
+
+      timeLimit = new IntegerArgument("timeLimit", 'l', "timeLimit",
+          false, false, true, INFO_TIME_LIMIT_PLACEHOLDER.get(), 0,
+          null, INFO_SEARCH_DESCRIPTION_TIME_LIMIT.get());
+      timeLimit.setPropertyName("timeLimit");
+      argParser.addArgument(timeLimit);
+
+      dontWrap = new BooleanArgument("dontwrap", 't', "dontWrap",
+          INFO_DESCRIPTION_DONT_WRAP.get());
+      dontWrap.setPropertyName("dontWrap");
+      argParser.addArgument(dontWrap);
+
+      countEntries = new BooleanArgument("countentries", null,
+          "countEntries", INFO_DESCRIPTION_COUNT_ENTRIES.get());
+      countEntries.setPropertyName("countEntries");
+      argParser.addArgument(countEntries);
+
+      BooleanArgument continueOnError = new BooleanArgument(
+          "continueOnError", 'c', "continueOnError",
+          INFO_DESCRIPTION_CONTINUE_ON_ERROR.get());
+      continueOnError.setPropertyName("continueOnError");
+      argParser.addArgument(continueOnError);
+
+      noop = new BooleanArgument("noop", OPTION_SHORT_DRYRUN,
+          OPTION_LONG_DRYRUN, INFO_DESCRIPTION_NOOP.get());
+      noop.setPropertyName(OPTION_LONG_DRYRUN);
+      argParser.addArgument(noop);
+
+      verbose = new BooleanArgument("verbose", 'v', "verbose",
+          INFO_DESCRIPTION_VERBOSE.get());
+      verbose.setPropertyName("verbose");
+      argParser.addArgument(verbose);
+
+      BooleanArgument showUsage = new BooleanArgument("showUsage",
+          OPTION_SHORT_HELP, OPTION_LONG_HELP,
+          INFO_DESCRIPTION_SHOWUSAGE.get());
+      argParser.addArgument(showUsage);
+      argParser.setUsageArgument(showUsage, getOutputStream());
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // Parse the command-line arguments provided to this program.
+    try
+    {
+      argParser.parseArguments(args);
+      connectionFactory.validate();
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+      println(message);
+      println(argParser.getUsageMessage());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // If we should just display usage or version information,
+    // then print it and exit.
+    if (argParser.usageOrVersionDisplayed())
+    {
+      return 0;
+    }
+
+    List<Filter> filters = new LinkedList<Filter>();
+    List<String> attributes = new LinkedList<String>();
+    ArrayList<String> filterAndAttributeStrings = argParser
+        .getTrailingArguments();
+    if (filterAndAttributeStrings.size() > 0)
+    {
+      // the list of trailing arguments should be structured as follow:
+      // - If a filter file is present, trailing arguments are
+      // considered as attributes
+      // - If filter file is not present, the first trailing argument is
+      // considered the filter, the other as attributes.
+      if (!filename.isPresent())
+      {
+        String filterString = filterAndAttributeStrings.remove(0);
+
+        try
+        {
+          filters.add(Filter.valueOf(filterString));
+        }
+        catch (LocalizedIllegalArgumentException e)
+        {
+          println(e.getMessageObject());
+          return ResultCode.CLIENT_SIDE_FILTER_ERROR.intValue();
+        }
+      }
+      // The rest are attributes
+      for (String s : filterAndAttributeStrings)
+      {
+        attributes.add(s);
+      }
+    }
+
+    if (filename.isPresent())
+    {
+      // Read the filter strings.
+      BufferedReader in = null;
+      try
+      {
+        in = new BufferedReader(new FileReader(filename.getValue()));
+        String line = null;
+
+        while ((line = in.readLine()) != null)
+        {
+          if (line.trim().equals(""))
+          {
+            // ignore empty lines.
+            continue;
+          }
+          Filter ldapFilter = Filter.valueOf(line);
+          filters.add(ldapFilter);
+        }
+      }
+      catch (LocalizedIllegalArgumentException e)
+      {
+        println(e.getMessageObject());
+        return ResultCode.CLIENT_SIDE_FILTER_ERROR.intValue();
+      }
+      catch (IOException e)
+      {
+        println(Message.raw(e.toString()));
+        return ResultCode.CLIENT_SIDE_FILTER_ERROR.intValue();
+      }
+      finally
+      {
+        if (in != null)
+        {
+          try
+          {
+            in.close();
+          }
+          catch (IOException ioe)
+          {
+          }
+        }
+      }
+    }
+
+    if (filters.isEmpty())
+    {
+      println(ERR_SEARCH_NO_FILTERS.get());
+      println(argParser.getUsageMessage());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    SearchScope scope;
+    try
+    {
+      scope = searchScope.getTypedValue();
+    }
+    catch (ArgumentException ex1)
+    {
+      println(ex1.getMessageObject());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    SearchRequest search;
+    try
+    {
+      search = Requests.newSearchRequest(DN.valueOf(baseDN.getValue()), scope, filters.get(0),
+          attributes.toArray(new String[attributes
+              .size()]));
+    }
+    catch (LocalizedIllegalArgumentException e)
+    {
+      println(e.getMessageObject());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // Read the LDAP version number.
+    try
+    {
+      int versionNumber = version.getIntValue();
+      if (versionNumber != 2 && versionNumber != 3)
+      {
+        println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+            .valueOf(versionNumber)));
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+    catch (ArgumentException ae)
+    {
+      println(ERR_DESCRIPTION_INVALID_VERSION.get(String
+          .valueOf(version.getValue())));
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    search.setTypesOnly(typesOnly.isPresent());
+    // searchOptions.setShowOperations(noop.isPresent());
+    // searchOptions.setVerbose(verbose.isPresent());
+    // searchOptions.setContinueOnError(continueOnError.isPresent());
+    // searchOptions.setEncoding(encodingStr.getValue());
+    // searchOptions.setCountMatchingEntries(countEntries.isPresent());
+    try
+    {
+      search.setTimeLimit(timeLimit.getIntValue());
+      search.setSizeLimit(sizeLimit.getIntValue());
+    }
+    catch (ArgumentException ex1)
+    {
+      println(ex1.getMessageObject());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+    try
+    {
+      search.setDereferenceAliasesPolicy(dereferencePolicy
+          .getTypedValue());
+    }
+    catch (ArgumentException ex1)
+    {
+      println(ex1.getMessageObject());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    if (controlStr.isPresent())
+    {
+      for (String ctrlString : controlStr.getValues())
+      {
+        try
+        {
+          Control ctrl = Utils.getControl(ctrlString);
+          search.addControl(ctrl);
+        }
+        catch (DecodeException de)
+        {
+          Message message = ERR_TOOL_INVALID_CONTROL_STRING
+              .get(ctrlString);
+          println(message);
+          ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+    }
+
+    if (effectiveRightsUser.isPresent())
+    {
+      String authzID = effectiveRightsUser.getValue();
+      if (!authzID.startsWith("dn:"))
+      {
+        Message message = ERR_EFFECTIVERIGHTS_INVALID_AUTHZID
+            .get(authzID);
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+      Control effectiveRightsControl = new GetEffectiveRightsRequestControl(
+          false, authzID.substring(3), effectiveRightsAttrs.getValues()
+              .toArray(
+                  new String[effectiveRightsAttrs.getValues().size()]));
+      search.addControl(effectiveRightsControl);
+    }
+
+    if (proxyAuthzID.isPresent())
+    {
+      Control proxyControl = new ProxiedAuthV2Control(proxyAuthzID
+          .getValue());
+      search.addControl(proxyControl);
+    }
+
+    if (pSearchInfo.isPresent())
+    {
+      String infoString = StaticUtils.toLowerCase(pSearchInfo
+          .getValue().trim());
+      boolean changesOnly = true;
+      boolean returnECs = true;
+
+      StringTokenizer tokenizer = new StringTokenizer(infoString, ":");
+
+      if (!tokenizer.hasMoreTokens())
+      {
+        Message message = ERR_PSEARCH_MISSING_DESCRIPTOR.get();
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+      else
+      {
+        String token = tokenizer.nextToken();
+        if (!token.equals("ps"))
+        {
+          Message message = ERR_PSEARCH_DOESNT_START_WITH_PS.get(String
+              .valueOf(infoString));
+          println(message);
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+
+      ArrayList<PersistentSearchChangeType> ct = new ArrayList<PersistentSearchChangeType>(
+          4);
+      if (tokenizer.hasMoreTokens())
+      {
+        StringTokenizer st = new StringTokenizer(tokenizer.nextToken(),
+            ", ");
+        if (!st.hasMoreTokens())
+        {
+          ct.add(PersistentSearchChangeType.ADD);
+          ct.add(PersistentSearchChangeType.DELETE);
+          ct.add(PersistentSearchChangeType.MODIFY);
+          ct.add(PersistentSearchChangeType.MODIFY_DN);
+        }
+        else
+          do
+          {
+            String token = st.nextToken();
+            if (token.equals("add"))
+            {
+              ct.add(PersistentSearchChangeType.ADD);
+            }
+            else if (token.equals("delete") || token.equals("del"))
+            {
+              ct.add(PersistentSearchChangeType.DELETE);
+            }
+            else if (token.equals("modify") || token.equals("mod"))
+            {
+              ct.add(PersistentSearchChangeType.MODIFY);
+            }
+            else if (token.equals("modifydn") || token.equals("moddn")
+                || token.equals("modrdn"))
+            {
+              ct.add(PersistentSearchChangeType.MODIFY_DN);
+            }
+            else if (token.equals("any") || token.equals("all"))
+            {
+              ct.add(PersistentSearchChangeType.ADD);
+              ct.add(PersistentSearchChangeType.DELETE);
+              ct.add(PersistentSearchChangeType.MODIFY);
+              ct.add(PersistentSearchChangeType.MODIFY_DN);
+            }
+            else
+            {
+              Message message = ERR_PSEARCH_INVALID_CHANGE_TYPE
+                  .get(String.valueOf(token));
+              println(message);
+              return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+            }
+          } while (st.hasMoreTokens());
+      }
+
+      if (tokenizer.hasMoreTokens())
+      {
+        String token = tokenizer.nextToken();
+        if (token.equals("1") || token.equals("true")
+            || token.equals("yes"))
+        {
+          changesOnly = true;
+        }
+        else if (token.equals("0") || token.equals("false")
+            || token.equals("no"))
+        {
+          changesOnly = false;
+        }
+        else
+        {
+          Message message = ERR_PSEARCH_INVALID_CHANGESONLY.get(String
+              .valueOf(token));
+          println(message);
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+
+      if (tokenizer.hasMoreTokens())
+      {
+        String token = tokenizer.nextToken();
+        if (token.equals("1") || token.equals("true")
+            || token.equals("yes"))
+        {
+          returnECs = true;
+        }
+        else if (token.equals("0") || token.equals("false")
+            || token.equals("no"))
+        {
+          returnECs = false;
+        }
+        else
+        {
+          Message message = ERR_PSEARCH_INVALID_RETURN_ECS.get(String
+              .valueOf(token));
+          println(message);
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+
+      PersistentSearchControl psearchControl = new PersistentSearchControl(
+          changesOnly, returnECs, ct
+              .toArray(new PersistentSearchChangeType[ct.size()]));
+      search.addControl(psearchControl);
+    }
+
+    if (assertionFilter.isPresent())
+    {
+      String filterString = assertionFilter.getValue();
+      Filter filter;
+      try
+      {
+        filter = Filter.valueOf(filterString);
+
+        // FIXME -- Change this to the correct OID when the official one
+        // is assigned.
+        Control assertionControl = new AssertionControl(true, filter);
+        search.addControl(assertionControl);
+      }
+      catch (LocalizedIllegalArgumentException le)
+      {
+        Message message = ERR_LDAP_ASSERTION_INVALID_FILTER.get(le
+            .getMessage());
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+
+    if (matchedValuesFilter.isPresent())
+    {
+      LinkedList<String> mvFilterStrings = matchedValuesFilter
+          .getValues();
+      List<Filter> mvFilters = new ArrayList<Filter>();
+      for (String s : mvFilterStrings)
+      {
+        try
+        {
+          Filter f = Filter.valueOf(s);
+          mvFilters.add(f);
+        }
+        catch (LocalizedIllegalArgumentException le)
+        {
+          Message message = ERR_LDAP_MATCHEDVALUES_INVALID_FILTER
+              .get(le.getMessage());
+          println(message);
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+
+      MatchedValuesControl mvc = new MatchedValuesControl(true,
+          mvFilters.toArray(new Filter[mvFilters.size()]));
+      search.addControl(mvc);
+    }
+
+    if (sortOrder.isPresent())
+    {
+      try
+      {
+        search.addControl(new ServerSideSortControl.Request(false,
+            sortOrder.getValue()));
+      }
+      catch (DecodeException le)
+      {
+        Message message = ERR_LDAP_SORTCONTROL_INVALID_ORDER.get(le
+            .getMessageObject());
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+
+    if (vlvDescriptor.isPresent())
+    {
+      if (!sortOrder.isPresent())
+      {
+        Message message = ERR_LDAPSEARCH_VLV_REQUIRES_SORT.get(
+            vlvDescriptor.getLongIdentifier(), sortOrder
+                .getLongIdentifier());
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+
+      StringTokenizer tokenizer = new StringTokenizer(vlvDescriptor
+          .getValue(), ":");
+      int numTokens = tokenizer.countTokens();
+      if (numTokens == 3)
+      {
+        try
+        {
+          int beforeCount = Integer.parseInt(tokenizer.nextToken());
+          int afterCount = Integer.parseInt(tokenizer.nextToken());
+          ByteString assertionValue = ByteString.valueOf(tokenizer
+              .nextToken());
+          search.addControl(new VLVControl.Request(beforeCount,
+              afterCount, assertionValue));
+        }
+        catch (Exception e)
+        {
+          Message message = ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get();
+          println(message);
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+      else if (numTokens == 4)
+      {
+        try
+        {
+          int beforeCount = Integer.parseInt(tokenizer.nextToken());
+          int afterCount = Integer.parseInt(tokenizer.nextToken());
+          int offset = Integer.parseInt(tokenizer.nextToken());
+          int contentCount = Integer.parseInt(tokenizer.nextToken());
+          search.addControl(new VLVControl.Request(beforeCount,
+              afterCount, offset, contentCount));
+        }
+        catch (Exception e)
+        {
+          Message message = ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get();
+          println(message);
+          return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+        }
+      }
+      else
+      {
+        Message message = ERR_LDAPSEARCH_VLV_INVALID_DESCRIPTOR.get();
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+
+    int pageSize = 0;
+    if (simplePageSize.isPresent())
+    {
+      if (filters.size() > 1)
+      {
+        Message message = ERR_PAGED_RESULTS_REQUIRES_SINGLE_FILTER
+            .get();
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+
+      try
+      {
+        pageSize = simplePageSize.getIntValue();
+        search.addControl(new PagedResultsControl(pageSize, ByteString
+            .empty()));
+      }
+      catch (ArgumentException ae)
+      {
+        Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+        println(message);
+        return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+      }
+    }
+    /*
+     * if(connectionOptions.useSASLExternal()) {
+     * if(!connectionOptions.useSSL() &&
+     * !connectionOptions.useStartTLS()) { Message message =
+     * ERR_TOOL_SASLEXTERNAL_NEEDS_SSL_OR_TLS.get();
+     * err.println(wrapText(message, MAX_LINE_WIDTH)); return
+     * CLIENT_SIDE_PARAM_ERROR; } if(keyStorePathValue == null) {
+     * Message message = ERR_TOOL_SASLEXTERNAL_NEEDS_KEYSTORE.get();
+     * err.println(wrapText(message, MAX_LINE_WIDTH)); return
+     * CLIENT_SIDE_PARAM_ERROR; } }
+     * connectionOptions.setVerbose(verbose.isPresent());
+     */
+
+    int wrapColumn = 80;
+    if (dontWrap.isPresent())
+    {
+      wrapColumn = 0;
+    }
+
+    if (noop.isPresent())
+    {
+      // We don't actually need to open a connection or perform the
+      // search,
+      // so we're done. We should return 0 to either mean that the
+      // processing
+      // was successful or that there were no matching entries, based on
+      // countEntries.isPresent() (but in either case the return value
+      // should
+      // be zero).
+      return 0;
+    }
+
+    Connection connection;
+    try
+    {
+      connection = connectionFactory.getConnection();
+    }
+    catch (ErrorResultException ere)
+    {
+      return Utils.printErrorMessage(this, ere);
+    }
+
+    Utils.printPasswordPolicyResults(this, connection);
+
+    try
+    {
+      int filterIndex = 0;
+      ldifWriter = new LDIFEntryWriter(getOutputStream())
+          .setWrapColumn(wrapColumn);
+      LDAPSearchResultHandler resultHandler = new LDAPSearchResultHandler();
+      while (true)
+      {
+        Result result;
+        try
+        {
+          result = connection.search(search, resultHandler, null);
+        }
+        catch (InterruptedException e)
+        {
+          // This shouldn't happen because there are no other threads to
+          // interrupt this one.
+          result = Responses.newResult(
+              ResultCode.CLIENT_SIDE_USER_CANCELLED).setCause(e)
+              .setDiagnosticMessage(e.getLocalizedMessage());
+          throw ErrorResultException.wrap(result);
+        }
+
+        Control control = result
+            .getControl(ServerSideSortControl.OID_SERVER_SIDE_SORT_RESPONSE_CONTROL);
+        if (control != null
+            && control instanceof ServerSideSortControl.Response)
+        {
+          ServerSideSortControl.Response dc = (ServerSideSortControl.Response) control;
+          if (dc.getSortResult() != SortResult.SUCCESS)
+          {
+            Message msg = WARN_LDAPSEARCH_SORT_ERROR.get(dc
+                .getSortResult().toString());
+            println(msg);
+          }
+        }
+        control = result
+            .getControl(VLVControl.OID_VLV_RESPONSE_CONTROL);
+        if (control != null && control instanceof VLVControl.Response)
+        {
+          VLVControl.Response dc = (VLVControl.Response) control;
+          if (dc.getVLVResult() == VLVResult.SUCCESS)
+          {
+            Message msg = INFO_LDAPSEARCH_VLV_TARGET_OFFSET.get(dc
+                .getTargetPosition());
+            println(msg);
+
+            msg = INFO_LDAPSEARCH_VLV_CONTENT_COUNT.get(dc
+                .getContentCount());
+            println(msg);
+          }
+          else
+          {
+            Message msg = WARN_LDAPSEARCH_VLV_ERROR.get(dc
+                .getVLVResult().toString());
+            println(msg);
+          }
+        }
+
+        control = result
+            .getControl(PagedResultsControl.OID_PAGED_RESULTS_CONTROL);
+        if (control != null && control instanceof PagedResultsControl)
+        {
+          PagedResultsControl pagedControl = (PagedResultsControl) control;
+          if (pagedControl.getCookie().length() > 0)
+          {
+            if (!isQuiet())
+            {
+              pressReturnToContinue();
+            }
+            pagedControl = new PagedResultsControl(pageSize,
+                pagedControl.getCookie());
+            search
+                .removeControl(PagedResultsControl.OID_PAGED_RESULTS_CONTROL);
+            search.addControl(pagedControl);
+            continue;
+          }
+        }
+
+        println();
+        println(ERR_TOOL_RESULT_CODE.get(result.getResultCode()
+            .intValue(), result.getResultCode().toString()));
+        if ((result.getDiagnosticMessage() != null)
+            && (result.getDiagnosticMessage().length() > 0))
+        {
+          println(Message.raw(result.getDiagnosticMessage()));
+        }
+        if (result.getMatchedDN() != null
+            && result.getMatchedDN().length() > 0)
+        {
+          println(ERR_TOOL_MATCHED_DN.get(result.getMatchedDN()));
+        }
+
+        filterIndex++;
+        if (filterIndex < filters.size())
+        {
+          search.setFilter(filters.get(filterIndex));
+        }
+        else
+        {
+          break;
+        }
+      }
+      if (countEntries.isPresent() && !isQuiet())
+      {
+        Message message = INFO_LDAPSEARCH_MATCHING_ENTRY_COUNT
+            .get(resultHandler.entryCount);
+        println(message);
+        println();
+      }
+    }
+    catch (ErrorResultException ere)
+    {
+      return Utils.printErrorMessage(this, ere);
+    }
+    finally
+    {
+      connection.close();
+    }
+
+    return 0;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested advanced mode.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         advanced mode.
+   */
+  public boolean isAdvancedMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested interactive
+   * behavior.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         interactive behavior.
+   */
+  public boolean isInteractive()
+  {
+    return true;
+  }
+
+
+
+  /**
+   * Indicates whether or not this console application is running in its
+   * menu-driven mode. This can be used to dictate whether output should
+   * go to the error stream or not. In addition, it may also dictate
+   * whether or not sub-menus should display a cancel option as well as
+   * a quit option.
+   *
+   * @return Returns <code>true</code> if this console application is
+   *         running in its menu-driven mode.
+   */
+  public boolean isMenuDrivenMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested quiet output.
+   *
+   * @return Returns <code>true</code> if the user has requested quiet
+   *         output.
+   */
+  public boolean isQuiet()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested script-friendly
+   * output.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         script-friendly output.
+   */
+  public boolean isScriptFriendly()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested verbose output.
+   *
+   * @return Returns <code>true</code> if the user has requested verbose
+   *         output.
+   */
+  public boolean isVerbose()
+  {
+    return verbose.isPresent();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/ModRate.java b/sdk/src/org/opends/sdk/tools/ModRate.java
new file mode 100644
index 0000000..9643894
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/ModRate.java
@@ -0,0 +1,431 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.filterExitCode;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.requests.ModifyRequest;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.responses.Result;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Modrate benchmarking tool.
+ */
+public final class ModRate extends ConsoleApplication
+{
+  private BooleanArgument verbose;
+
+
+
+  /**
+   * The main method for SearchRate tool.
+   * 
+   * @param args
+   *          The command-line arguments provided to this program.
+   */
+
+  public static void main(String[] args)
+  {
+    int retCode = mainModRate(args, System.in, System.out, System.err);
+
+    if (retCode != 0)
+    {
+      System.exit(filterExitCode(retCode));
+    }
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the ldapsearch tool.
+   * 
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @return The error code.
+   */
+
+  public static int mainSearchRate(String[] args)
+  {
+    return mainModRate(args, System.in, System.out, System.err);
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the ldapsearch tool.
+   * 
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @param inStream
+   *          The input stream to use for standard input, or
+   *          <CODE>null</CODE> if standard input is not needed.
+   * @param outStream
+   *          The output stream to use for standard output, or
+   *          <CODE>null</CODE> if standard output is not needed.
+   * @param errStream
+   *          The output stream to use for standard error, or
+   *          <CODE>null</CODE> if standard error is not needed.
+   * @return The error code.
+   */
+
+  public static int mainModRate(String[] args, InputStream inStream,
+      OutputStream outStream, OutputStream errStream)
+
+  {
+    return new ModRate(inStream, outStream, errStream).run(args);
+  }
+
+
+
+  private ModRate(InputStream in, OutputStream out, OutputStream err)
+  {
+    super(in, out, err);
+
+  }
+
+
+
+  private int run(String[] args)
+  {
+    // Create the command-line argument parser for use with this
+    // program.
+    Message toolDescription =
+        Message.raw("This utility can be used to "
+            + "measure modify performance");
+    // TODO: correct usage
+    ArgumentParser argParser =
+        new ArgumentParser(SearchRate.class.getName(), toolDescription,
+            false, true, 1, 0, "[modifyString ...]");
+    ArgumentParserConnectionFactory connectionFactory;
+    ModifyPerformanceRunner runner;
+
+    BooleanArgument showUsage;
+    StringArgument propertiesFileArgument;
+    BooleanArgument noPropertiesFileArgument;
+    StringArgument baseDN;
+
+    try
+    {
+      connectionFactory =
+          new ArgumentParserConnectionFactory(argParser, this);
+      runner = new ModifyPerformanceRunner(argParser, this);
+      propertiesFileArgument =
+          new StringArgument("propertiesFilePath", null,
+              OPTION_LONG_PROP_FILE_PATH, false, false, true,
+              INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PROP_FILE_PATH.get());
+      argParser.addArgument(propertiesFileArgument);
+      argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+      noPropertiesFileArgument =
+          new BooleanArgument("noPropertiesFileArgument", null,
+              OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+                  .get());
+      argParser.addArgument(noPropertiesFileArgument);
+      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+      baseDN =
+          new StringArgument("baseDN", OPTION_SHORT_BASEDN,
+              OPTION_LONG_BASEDN, true, false, true,
+              INFO_BASEDN_PLACEHOLDER.get(), null, null,
+              INFO_SEARCH_DESCRIPTION_BASEDN.get());
+      baseDN.setPropertyName(OPTION_LONG_BASEDN);
+      argParser.addArgument(baseDN);
+
+      verbose =
+          new BooleanArgument("verbose", 'v', "verbose",
+              INFO_DESCRIPTION_VERBOSE.get());
+      verbose.setPropertyName("verbose");
+      argParser.addArgument(verbose);
+
+      showUsage =
+          new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+              OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+      argParser.addArgument(showUsage);
+      argParser.setUsageArgument(showUsage, getOutputStream());
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // Parse the command-line arguments provided to this program.
+    try
+    {
+      argParser.parseArguments(args);
+      connectionFactory.validate();
+      runner.validate();
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+      println(message);
+      println(argParser.getUsageMessage());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // If we should just display usage or version information,
+    // then print it and exit.
+    if (argParser.usageOrVersionDisplayed())
+    {
+      return 0;
+    }
+
+    runner.modStrings =
+        argParser.getTrailingArguments().toArray(
+            new String[argParser.getTrailingArguments().size()]);
+    runner.baseDN = baseDN.getValue();
+
+    try
+    {
+
+      // Try it out to make sure the format string and data sources
+      // match.
+      Object[] data =
+          DataSource.generateData(runner.getDataSources(), null);
+      for (String modString : runner.modStrings)
+      {
+        String.format(modString, data);
+      }
+      String.format(runner.baseDN, data);
+    }
+    catch (Exception ex1)
+    {
+      println(Message.raw("Error formatting filter or base DN: "
+          + ex1.toString()));
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    return runner.run(connectionFactory);
+  }
+
+
+
+  private class ModifyPerformanceRunner extends PerformanceRunner
+  {
+    private String baseDN;
+    private String[] modStrings;
+
+
+
+    private ModifyPerformanceRunner(ArgumentParser argParser,
+        ConsoleApplication app) throws ArgumentException
+    {
+      super(argParser, app);
+    }
+
+
+
+    WorkerThread<?> newWorkerThread(AsynchronousConnection connection,
+        ConnectionFactory<?> connectionFactory)
+    {
+      return new ModifyWorkerThread(connection, connectionFactory);
+    }
+
+
+
+    StatsThread newStatsThread()
+    {
+      return new StatsThread(new String[0]);
+    }
+
+
+
+    private class ModifyWorkerThread extends
+        WorkerThread<ResultHandler<Result, Void>>
+    {
+      private ModifyRequest mr;
+      private Object[] data;
+
+
+
+      private ModifyWorkerThread(AsynchronousConnection connection,
+          ConnectionFactory<?> connectionFactory)
+      {
+        super(connection, connectionFactory);
+      }
+
+
+
+      public ResultHandler<Result, Void> getHandler(long startTime)
+      {
+        return new UpdateStatsResultHandler<Result>(startTime);
+      }
+
+
+
+      public ResultFuture<?> performOperation(
+          AsynchronousConnection connection,
+          ResultHandler<Result, Void> handler, DataSource[] dataSources)
+      {
+        if (dataSources != null)
+        {
+          data = DataSource.generateData(dataSources, data);
+        }
+        mr = newModifyRequest(data);
+        return connection.modify(mr, handler, null);
+      }
+
+
+
+      private ModifyRequest newModifyRequest(Object[] data)
+      {
+        String formattedString;
+        int colonPos;
+        ModifyRequest mr;
+        if (data == null)
+        {
+          mr = Requests.newModifyRequest(baseDN);
+        }
+        else
+        {
+          mr = Requests.newModifyRequest(String.format(baseDN, data));
+        }
+        for (int i = 0; i < modStrings.length; i++)
+        {
+          if (data == null)
+          {
+            formattedString = modStrings[i];
+          }
+          else
+          {
+            formattedString = String.format(modStrings[i], data);
+          }
+          colonPos = formattedString.indexOf(':');
+          if (colonPos > 0)
+          {
+            mr.addChange(ModificationType.REPLACE, formattedString
+                .substring(0, colonPos), formattedString
+                .substring(colonPos + 1));
+          }
+        }
+        return mr;
+      }
+    }
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested advanced mode.
+   * 
+   * @return Returns <code>true</code> if the user has requested
+   *         advanced mode.
+   */
+  public boolean isAdvancedMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested interactive
+   * behavior.
+   * 
+   * @return Returns <code>true</code> if the user has requested
+   *         interactive behavior.
+   */
+  public boolean isInteractive()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not this console application is running in its
+   * menu-driven mode. This can be used to dictate whether output should
+   * go to the error stream or not. In addition, it may also dictate
+   * whether or not sub-menus should display a cancel option as well as
+   * a quit option.
+   * 
+   * @return Returns <code>true</code> if this console application is
+   *         running in its menu-driven mode.
+   */
+  public boolean isMenuDrivenMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested quiet output.
+   * 
+   * @return Returns <code>true</code> if the user has requested quiet
+   *         output.
+   */
+  public boolean isQuiet()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested script-friendly
+   * output.
+   * 
+   * @return Returns <code>true</code> if the user has requested
+   *         script-friendly output.
+   */
+  public boolean isScriptFriendly()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested verbose output.
+   * 
+   * @return Returns <code>true</code> if the user has requested verbose
+   *         output.
+   */
+  public boolean isVerbose()
+  {
+    return verbose.isPresent();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java b/sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java
new file mode 100644
index 0000000..05ebc4c
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/MultiChoiceArgument.java
@@ -0,0 +1,273 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_MCARG_VALUE_NOT_ALLOWED;
+
+import java.util.Collection;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will only accept one or more
+ * of a specific set of string values.
+ */
+final class MultiChoiceArgument<T> extends Argument
+{
+  // Indicates whether argument values should be treated in a
+  // case-sensitive
+  // manner.
+  private boolean caseSensitive;
+
+  // The set of values that will be allowed for use with this argument.
+  private Collection<T> allowedValues;
+
+
+
+  /**
+   * Creates a new string argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param allowedValues
+   *          The set of values that are allowed for use for this
+   *          argument. If they are not to be treated in a
+   *          case-sensitive value then they should all be formatted in
+   *          lowercase.
+   * @param caseSensitive
+   *          Indicates whether the set of allowed values should be
+   *          treated in a case-sensitive manner.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public MultiChoiceArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean needsValue,
+      Message valuePlaceholder, Collection<T> allowedValues,
+      boolean caseSensitive, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired, false,
+        needsValue, valuePlaceholder, null, null, description);
+
+    this.allowedValues = allowedValues;
+    this.caseSensitive = caseSensitive;
+  }
+
+
+
+  /**
+   * Creates a new string argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param allowedValues
+   *          The set of values that are allowed for use for this
+   *          argument. If they are not to be treated in a
+   *          case-sensitive value then they should all be formatted in
+   *          lowercase.
+   * @param caseSensitive
+   *          Indicates whether the set of allowed values should be
+   *          treated in a case-sensitive manner.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public MultiChoiceArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      boolean needsValue, Message valuePlaceholder,
+      String defaultValue, String propertyName,
+      Collection<T> allowedValues, boolean caseSensitive,
+      Message description) throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired,
+        isMultiValued, needsValue, valuePlaceholder, defaultValue,
+        propertyName, description);
+
+    this.allowedValues = allowedValues;
+    this.caseSensitive = caseSensitive;
+  }
+
+
+
+  /**
+   * Retrieves the set of allowed values for this argument. The contents
+   * of this set must not be altered by the caller.
+   * 
+   * @return The set of allowed values for this argument.
+   */
+  public Collection<T> getAllowedValues()
+  {
+    return allowedValues;
+  }
+
+
+
+  /**
+   * Indicates whether the set of allowed values for this argument
+   * should be treated in a case-sensitive manner.
+   * 
+   * @return <CODE>true</CODE> if the values are to be treated in a
+   *         case-sensitive manner, or <CODE>false</CODE> if not.
+   */
+  public boolean isCaseSensitive()
+  {
+    return caseSensitive;
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in this
+   * argument.
+   * 
+   * @param valueString
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          A buffer into which the invalid reason may be written if
+   *          the value is not acceptable.
+   * @return <CODE>true</CODE> if the value is acceptable, or
+   *         <CODE>false</CODE> if it is not.
+   */
+  public boolean valueIsAcceptable(String valueString,
+      MessageBuilder invalidReason)
+  {
+    for (T o : allowedValues)
+    {
+      if ((caseSensitive && o.toString().equals(valueString))
+          || o.toString().equalsIgnoreCase(valueString))
+      {
+        return true;
+      }
+    }
+    invalidReason.append(ERR_MCARG_VALUE_NOT_ALLOWED.get(getName(),
+        valueString));
+
+    return false;
+  }
+
+
+
+  /**
+   * Specifies the default value that will be used for this argument if
+   * it is not specified on the command line and it is not set from a
+   * properties file.
+   * 
+   * @param defaultValue
+   *          The default value that will be used for this argument if
+   *          it is not specified on the command line and it is not set
+   *          from a properties file.
+   */
+  public void setDefaultValue(T defaultValue)
+  {
+    super.setDefaultValue(defaultValue.toString());
+  }
+
+
+
+  /**
+   * Retrieves the string vale for this argument. If it has multiple
+   * values, then the first will be returned. If it does not have any
+   * values, then the default value will be returned.
+   * 
+   * @return The string value for this argument, or <CODE>null</CODE> if
+   *         there are no values and no default value has been given.
+   * @throws ArgumentException
+   *           The value cannot be parsed.
+   */
+  public T getTypedValue() throws ArgumentException
+  {
+    String v = super.getValue();
+    if (v == null)
+    {
+      return null;
+    }
+    for (T o : allowedValues)
+    {
+      if ((caseSensitive && o.toString().equals(v))
+          || o.toString().equalsIgnoreCase(v))
+      {
+        return o;
+      }
+    }
+    // TODO: Some message
+    throw new ArgumentException(null);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java b/sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java
new file mode 100644
index 0000000..0b1b7db
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/MultiColumnPrinter.java
@@ -0,0 +1,519 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Versiance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Utility class for printing aligned collumns of text.
+ * <P>
+ * This class allows you to specify:
+ * <UL>
+ * <LI>The number of collumns in the output. This will determine the
+ * dimension of the string arrays passed to add(String[]) or
+ * addTitle(String[]).
+ * <LI>spacing/gap between columns
+ * <LI>character to use for title border (null means no border)
+ * <LI>column alignment. Only LEFT/CENTER is supported for now.
+ * </UL>
+ * <P>
+ * Example usage:
+ * 
+ * <PRE>
+ * MyPrinter mp = new MyPrinter(3, 2, &quot;-&quot;);
+ * String oneRow[] = new String[3];
+ * oneRow[0] = &quot;User Name&quot;;
+ * oneRow[1] = &quot;Email Address&quot;;
+ * oneRow[2] = &quot;Phone Number&quot;;
+ * mp.addTitle(oneRow);
+ * oneRow[0] = &quot;Bob&quot;;
+ * oneRow[1] = &quot;bob@foo.com&quot;;
+ * oneRow[2] = &quot;123-4567&quot;;
+ * mp.add(oneRow);
+ * oneRow[0] = &quot;John&quot;;
+ * oneRow[1] = &quot;john@foo.com&quot;;
+ * oneRow[2] = &quot;456-7890&quot;;
+ * mp.add(oneRow);
+ * mp.print();
+ * </PRE>
+ * <P>
+ * The above would print:
+ * <P>
+ * 
+ * <PRE>
+ *  --------------------------------------
+ *  User Name  Email Address  Phone Number
+ *  --------------------------------------
+ *  Bob        bob@foo.com    123-4567
+ *  John       john@foo.com   456-7890
+ * </PRE>
+ * <P>
+ * This class also supports multi-row titles and having title strings
+ * spanning multiple collumns. Example usage:
+ * 
+ * <PRE>
+ * TestPrinter tp = new TestPrinter(4, 2, &quot;-&quot;);
+ * String oneRow[] = new String[4];
+ * int[] span = new int[4];
+ * span[0] = 2; // spans 2 collumns
+ * span[1] = 0; // spans 0 collumns
+ * span[2] = 2; // spans 2 collumns
+ * span[3] = 0; // spans 0 collumns
+ * tp.setTitleAlign(CENTER);
+ * oneRow[0] = &quot;Name&quot;;
+ * oneRow[1] = &quot;&quot;;
+ * oneRow[2] = &quot;Contact&quot;;
+ * oneRow[3] = &quot;&quot;;
+ * tp.addTitle(oneRow, span);
+ * oneRow[0] = &quot;First&quot;;
+ * oneRow[1] = &quot;Last&quot;;
+ * oneRow[2] = &quot;Email&quot;;
+ * oneRow[3] = &quot;Phone&quot;;
+ * tp.addTitle(oneRow);
+ * oneRow[0] = &quot;Bob&quot;;
+ * oneRow[1] = &quot;Jones&quot;;
+ * oneRow[2] = &quot;bob@foo.com&quot;;
+ * oneRow[3] = &quot;123-4567&quot;;
+ * tp.add(oneRow);
+ * oneRow[0] = &quot;John&quot;;
+ * oneRow[1] = &quot;Doe&quot;;
+ * oneRow[2] = &quot;john@foo.com&quot;;
+ * oneRow[3] = &quot;456-7890&quot;;
+ * tp.add(oneRow);
+ * tp.println();
+ * </PRE>
+ * <P>
+ * The above would print:
+ * <P>
+ * 
+ * <PRE>
+ *      ------------------------------------
+ *          Name             Contact
+ *      First  Last      Email       Phone
+ *      ------------------------------------
+ *      Bob    Jones  bob@foo.com   123-4567
+ *      John   Doe    john@foo.com  456-7890
+ * </PRE>
+ */
+final class MultiColumnPrinter
+{
+
+  final public static int LEFT = 0;
+  final public static int CENTER = 1;
+  final public static int RIGHT = 2;
+
+  private int numCol = 2;
+  private int gap = 4;
+  private int align = CENTER;
+  private int titleAlign = CENTER;
+  private String border = null;
+
+  private Vector<String[]> titleTable = null;
+  private Vector<int[]> titleSpanTable = null;
+  private int curLength[];
+
+  private final ConsoleApplication app;
+
+
+
+  /**
+   * Creates a new MultiColumnPrinter class.
+   * 
+   * @param numCol
+   *          number of columns
+   * @param gap
+   *          gap between each column
+   * @param border
+   *          character used to frame the titles
+   * @param align
+   *          type of alignment within columns
+   */
+  public MultiColumnPrinter(int numCol, int gap, String border,
+      int align, ConsoleApplication app)
+  {
+
+    titleTable = new Vector<String[]>();
+    titleSpanTable = new Vector<int[]>();
+    curLength = new int[numCol];
+
+    this.numCol = numCol;
+    this.gap = gap;
+    this.border = border;
+    this.align = align;
+    this.titleAlign = LEFT;
+
+    this.app = app;
+  }
+
+
+
+  /**
+   * Creates a sorted new MultiColumnPrinter class using LEFT alignment.
+   * 
+   * @param numCol
+   *          number of columns
+   * @param gap
+   *          gap between each column
+   * @param border
+   *          character used to frame the titles
+   */
+  public MultiColumnPrinter(int numCol, int gap, String border,
+      ConsoleApplication app)
+  {
+    this(numCol, gap, border, LEFT, app);
+  }
+
+
+
+  /**
+   * Creates a sorted new MultiColumnPrinter class using LEFT alignment
+   * and with no title border.
+   * 
+   * @param numCol
+   *          number of columns
+   * @param gap
+   *          gap between each column
+   */
+  public MultiColumnPrinter(int numCol, int gap, ConsoleApplication app)
+  {
+    this(numCol, gap, null, LEFT, app);
+  }
+
+
+
+  /**
+   * Adds to the row of strings to be used as the title for the table.
+   * 
+   * @param row
+   *          Array of strings to print in one row of title.
+   */
+  public void addTitle(String[] row)
+  {
+    if (row == null)
+      return;
+
+    int[] span = new int[row.length];
+    for (int i = 0; i < row.length; i++)
+    {
+      span[i] = 1;
+    }
+
+    addTitle(row, span);
+  }
+
+
+
+  /**
+   * Adds to the row of strings to be used as the title for the table.
+   * Also allows for certain title strings to span multiple collumns The
+   * span parameter is an array of integers which indicate how many
+   * collumns the corresponding title string will occupy. For a row that
+   * is 4 collumns wide, it is possible to have some title strings in a
+   * row to 'span' multiple collumns:
+   * <P>
+   * 
+   * <PRE>
+   * ------------------------------------
+   *     Name             Contact
+   * First  Last      Email       Phone
+   * ------------------------------------
+   * Bob    Jones  bob@foo.com   123-4567
+   * John   Doe    john@foo.com  456-7890
+   * </PRE>
+   * 
+   * In the example above, the title row has a string 'Name' that spans
+   * 2 collumns. The string 'Contact' also spans 2 collumns. The above
+   * is done by passing in to addTitle() an array that contains:
+   * 
+   * <PRE>
+   * span[0] = 2; // spans 2 collumns
+   * span[1] = 0; // spans 0 collumns, ignore
+   * span[2] = 2; // spans 2 collumns
+   * span[3] = 0; // spans 0 collumns, ignore
+   * </PRE>
+   * <P>
+   * A span value of 1 is the default. The method addTitle(String[] row)
+   * basically does:
+   * 
+   * <PRE>
+   * int[] span = new int[row.length];
+   * for (int i = 0; i &lt; row.length; i++)
+   * {
+   *   span[i] = 1;
+   * }
+   * addTitle(row, span);
+   * </PRE>
+   * 
+   * @param row
+   *          Array of strings to print in one row of title.
+   * @param span
+   *          Array of integers that reflect the number of collumns the
+   *          corresponding title string will occupy.
+   */
+  public void addTitle(String[] row, int span[])
+  {
+    // Need to create a new instance of it, otherwise the new values
+    // will always overwrite the old values.
+
+    String[] rowInstance = new String[(row.length)];
+    for (int i = 0; i < row.length; i++)
+    {
+      rowInstance[i] = row[i];
+    }
+    titleTable.addElement(rowInstance);
+
+    titleSpanTable.addElement(span);
+  }
+
+
+
+  /**
+   * Set alignment for title strings
+   * 
+   * @param titleAlign
+   */
+  public void setTitleAlign(int titleAlign)
+  {
+    this.titleAlign = titleAlign;
+  }
+
+
+
+  /**
+   * Clears title strings.
+   */
+  public void clearTitle()
+  {
+    titleTable.clear();
+    titleSpanTable.clear();
+  }
+
+
+
+  /**
+   * Prints the table title
+   */
+  public void printTitle()
+  {
+    // Get the longest string for each column and store in curLength[]
+
+    // Scan through title rows
+    Enumeration<String[]> elm = titleTable.elements();
+    Enumeration<int[]> spanEnum = titleSpanTable.elements();
+    while (elm.hasMoreElements())
+    {
+      String[] row = (String[]) elm.nextElement();
+      int[] curSpan = (int[]) spanEnum.nextElement();
+
+      for (int i = 0; i < numCol; i++)
+      {
+        // None of the fields should be null, but if it
+        // happens to be so, replace it with "-".
+        if (row[i] == null)
+          row[i] = "-";
+
+        int len = row[i].length();
+
+        /*
+         * If a title string spans multiple collumns, then the space it
+         * occupies in each collumn is at most len/span (since we have
+         * gap to take into account as well).
+         */
+        int span = curSpan[i], rem = 0;
+        if (span > 1)
+        {
+          rem = len % span;
+          len = len / span;
+        }
+
+        if (curLength[i] < len)
+        {
+          curLength[i] = len;
+
+          if ((span > 1) && ((i + span) <= numCol))
+          {
+            for (int j = i + 1; j < (i + span); ++j)
+            {
+              curLength[j] = len;
+            }
+
+            /*
+             * Add remainder to last collumn in span to avoid round-off
+             * errors.
+             */
+            curLength[(i + span) - 1] += rem;
+          }
+        }
+      }
+    }
+
+    printBorder();
+    elm = titleTable.elements();
+    spanEnum = titleSpanTable.elements();
+
+    while (elm.hasMoreElements())
+    {
+      String[] row = (String[]) elm.nextElement();
+      int[] curSpan = (int[]) spanEnum.nextElement();
+
+      for (int i = 0; i < numCol; i++)
+      {
+        int availableSpace = 0, span = curSpan[i];
+
+        if (span == 0)
+          continue;
+
+        availableSpace = curLength[i];
+
+        if ((span > 1) && ((i + span) <= numCol))
+        {
+          for (int j = i + 1; j < (i + span); ++j)
+          {
+            availableSpace += gap;
+            availableSpace += curLength[j];
+          }
+        }
+
+        if (titleAlign == RIGHT)
+        {
+          int space_before = availableSpace - row[i].length();
+          printSpaces(space_before);
+          app.getOutputStream().print(row[i]);
+          if (i < numCol - 1)
+            printSpaces(gap);
+        }
+        else if (titleAlign == CENTER)
+        {
+          int space_before, space_after;
+          space_before = (availableSpace - row[i].length()) / 2;
+          space_after = availableSpace - row[i].length() - space_before;
+
+          printSpaces(space_before);
+          app.getOutputStream().print(row[i]);
+          printSpaces(space_after);
+          if (i < numCol - 1)
+            printSpaces(gap);
+        }
+        else
+        {
+          app.getOutputStream().print(row[i]);
+          if (i < numCol - 1)
+            printSpaces(availableSpace - row[i].length() + gap);
+        }
+
+      }
+      app.getOutputStream().println("");
+    }
+    printBorder();
+  }
+
+
+
+  /**
+   * Adds one row of text to output.
+   * 
+   * @param row
+   *          Array of strings to print in one row.
+   */
+  public void printRow(String... row)
+  {
+    for (int i = 0; i < numCol; i++)
+    {
+      if (titleAlign == RIGHT)
+      {
+        int space_before = curLength[i] - row[i].length();
+        printSpaces(space_before);
+        app.getOutputStream().print(row[i]);
+        if (i < numCol - 1)
+          printSpaces(gap);
+      }
+      else if (align == CENTER)
+      {
+        int space1, space2;
+        space1 = (curLength[i] - row[i].length()) / 2;
+        space2 = curLength[i] - row[i].length() - space1;
+
+        printSpaces(space1);
+        app.getOutputStream().print(row[i]);
+        printSpaces(space2);
+        if (i < numCol - 1)
+          printSpaces(gap);
+      }
+      else
+      {
+        app.getOutputStream().print(row[i]);
+        if (i < numCol - 1)
+          printSpaces(curLength[i] - row[i].length() + gap);
+      }
+    }
+    app.getOutputStream().println("");
+  }
+
+
+
+  private void printSpaces(int count)
+  {
+    for (int i = 0; i < count; ++i)
+    {
+      app.getOutputStream().print(" ");
+    }
+  }
+
+
+
+  private void printBorder()
+  {
+    if (border == null)
+      return;
+
+    // For the value in each column
+    for (int i = 0; i < numCol; i++)
+    {
+      for (int j = 0; j < curLength[i]; j++)
+      {
+        app.getOutputStream().print(border);
+      }
+    }
+
+    // For the gap between each column
+    for (int i = 0; i < numCol - 1; i++)
+    {
+      for (int j = 0; j < gap; j++)
+      {
+        app.getOutputStream().print(border);
+      }
+    }
+    app.getOutputStream().println("");
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/PerformanceRunner.java b/sdk/src/org/opends/sdk/tools/PerformanceRunner.java
new file mode 100644
index 0000000..3a00dbe
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/PerformanceRunner.java
@@ -0,0 +1,943 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import java.io.IOException;
+import java.lang.management.GarbageCollectorMXBean;
+import java.lang.management.ManagementFactory;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.AuthenticatedConnectionFactory.AuthenticatedAsynchronousConnection;
+import org.opends.sdk.responses.Result;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Benchmark application framework.
+ */
+abstract class PerformanceRunner
+{
+  private final AtomicInteger operationRecentCount =
+      new AtomicInteger();
+  private final AtomicInteger successRecentCount = new AtomicInteger();
+  private final AtomicInteger failedRecentCount = new AtomicInteger();
+  private final AtomicLong waitRecentTime = new AtomicLong();
+  private final AtomicReference<ReversableArray> eTimeBuffer =
+      new AtomicReference<ReversableArray>(new ReversableArray(100000));
+  private final ConsoleApplication app;
+  private final ThreadLocal<DataSource[]> dataSources =
+      new ThreadLocal<DataSource[]>();
+
+  private volatile boolean stopRequested;
+  private int numThreads;
+  private int numConnections;
+  private int targetThroughput;
+  private int maxIterations;
+  private boolean isAsync;
+  private boolean noRebind;
+  private int statsInterval;
+
+  private IntegerArgument numThreadsArgument;
+  private IntegerArgument maxIterationsArgument;
+  private IntegerArgument statsIntervalArgument;
+  private IntegerArgument targetThroughputArgument;
+  private IntegerArgument numConnectionsArgument;
+  private IntegerArgument percentilesArgument;
+  private BooleanArgument keepConnectionsOpen;
+  private BooleanArgument noRebindArgument;
+  private BooleanArgument asyncArgument;
+
+  private StringArgument arguments;
+
+
+
+  PerformanceRunner(ArgumentParser argParser, ConsoleApplication app)
+      throws ArgumentException
+  {
+    this.app = app;
+    numThreadsArgument =
+        new IntegerArgument("numThreads", 't', "numThreads", false,
+            false, true, Message.raw("{numThreads}"), 1, null, true, 1,
+            false, 0, Message
+                .raw("number of search threads per connection"));
+    numThreadsArgument.setPropertyName("numThreads");
+    argParser.addArgument(numThreadsArgument);
+
+    numConnectionsArgument =
+        new IntegerArgument("numConnections", 'c', "numConnections",
+            false, false, true, Message.raw("{numConnections}"), 1,
+            null, true, 1, false, 0, Message
+                .raw("number of connections"));
+    numThreadsArgument.setPropertyName("numConnections");
+    argParser.addArgument(numConnectionsArgument);
+
+    maxIterationsArgument =
+        new IntegerArgument("maxIterations", 'm', "maxIterations",
+            false, false, true, Message.raw("{maxIterations}"), 0,
+            null, Message
+                .raw("max searches per thread, 0 for unlimited"));
+    numThreadsArgument.setPropertyName("maxIterations");
+    argParser.addArgument(maxIterationsArgument);
+
+    statsIntervalArgument =
+        new IntegerArgument(
+            "statInterval",
+            'i',
+            "statInterval",
+            false,
+            false,
+            true,
+            Message.raw("{statInterval}"),
+            5,
+            null,
+            true,
+            1,
+            false,
+            0,
+            Message
+                .raw("Display results each specified number of seconds"));
+    numThreadsArgument.setPropertyName("statInterval");
+    argParser.addArgument(statsIntervalArgument);
+
+    targetThroughputArgument =
+        new IntegerArgument("targetThroughput", 'M',
+            "targetThroughput", false, false, true, Message
+                .raw("{targetThroughput}"), 0, null, Message
+                .raw("Target average throughput to achieve"));
+    targetThroughputArgument.setPropertyName("targetThroughput");
+    argParser.addArgument(targetThroughputArgument);
+
+    percentilesArgument =
+        new IntegerArgument("percentile", 'e', "percentile", false,
+            true, Message.raw("{percentile}"), true, 50, true, 100,
+            Message.raw("Calculate max response time for a "
+                + "percentile of operations"));
+    percentilesArgument.setPropertyName("percentile");
+    argParser.addArgument(percentilesArgument);
+
+    keepConnectionsOpen =
+        new BooleanArgument("keepConnectionsOpen", 'f',
+            "keepConnectionsOpen", Message.raw("keep connections open"));
+    keepConnectionsOpen.setPropertyName("keepConnectionsOpen");
+    argParser.addArgument(keepConnectionsOpen);
+
+    noRebindArgument =
+        new BooleanArgument("noRebind", 'F', "noRebind", Message
+            .raw("keep connections open and don't rebind"));
+    keepConnectionsOpen.setPropertyName("noRebind");
+    argParser.addArgument(noRebindArgument);
+
+    asyncArgument =
+        new BooleanArgument("asynchronous", 'A', "asynchronous",
+            Message.raw("asynch, don't wait for results"));
+    keepConnectionsOpen.setPropertyName("asynchronous");
+    argParser.addArgument(asyncArgument);
+
+    arguments =
+        new StringArgument(
+            "arguments",
+            'g',
+            "arguments",
+            false,
+            true,
+            true,
+            Message.raw("{arguments}"),
+            null,
+            null,
+            Message
+                .raw("arguments for variables in the filter and/or base DN"));
+    arguments.setPropertyName("arguments");
+    argParser.addArgument(arguments);
+  }
+
+
+
+  public void validate() throws ArgumentException
+  {
+    numConnections = numConnectionsArgument.getIntValue();
+    numThreads = numThreadsArgument.getIntValue();
+    maxIterations = maxIterationsArgument.getIntValue();
+    statsInterval = statsIntervalArgument.getIntValue() * 1000;
+    targetThroughput = targetThroughputArgument.getIntValue();
+
+    isAsync = asyncArgument.isPresent();
+    noRebind = noRebindArgument.isPresent();
+
+    if (!noRebindArgument.isPresent() && this.numThreads > 1)
+    {
+      throw new ArgumentException(Message.raw("--"
+          + noRebindArgument.getLongIdentifier()
+          + " must be used if --"
+          + numThreadsArgument.getLongIdentifier() + " is > 1"));
+    }
+
+    if (!noRebindArgument.isPresent() && asyncArgument.isPresent())
+    {
+      throw new ArgumentException(Message.raw("--"
+          + noRebindArgument.getLongIdentifier()
+          + " must be used when using --"
+          + asyncArgument.getLongIdentifier()));
+    }
+
+    try
+    {
+      DataSource.parse(arguments.getValues());
+    }
+    catch (IOException ioe)
+    {
+      throw new ArgumentException(Message
+          .raw("Error occured while parsing arguments: "
+              + ioe.toString()));
+    }
+  }
+
+
+
+  final int run(ConnectionFactory<?> connectionFactory)
+  {
+    List<Thread> threads = new ArrayList<Thread>();
+
+    AsynchronousConnection connection = null;
+    Thread thread;
+    try
+    {
+      for (int i = 0; i < numConnections; i++)
+      {
+        if (keepConnectionsOpen.isPresent()
+            || noRebindArgument.isPresent())
+        {
+          connection =
+              connectionFactory.getAsynchronousConnection(null, null)
+                  .get();
+        }
+        for (int j = 0; j < numThreads; j++)
+        {
+          thread = newWorkerThread(connection, connectionFactory);
+
+          threads.add(thread);
+          thread.start();
+        }
+      }
+
+      Thread statsThread = newStatsThread();
+      statsThread.start();
+
+      for (Thread t : threads)
+      {
+        t.join();
+      }
+      stopRequested = true;
+      statsThread.join();
+    }
+    catch (InterruptedException e)
+    {
+      stopRequested = true;
+    }
+    catch (ErrorResultException e)
+    {
+      stopRequested = true;
+      app.println(Message.raw(e.getResult().getDiagnosticMessage()));
+    }
+
+    return 0;
+  }
+
+
+
+  final DataSource[] getDataSources()
+  {
+    try
+    {
+      return DataSource.parse(arguments.getValues());
+    }
+    catch (IOException ioe)
+    {
+      // Ignore as this shouldn've been handled eariler
+    }
+    return new DataSource[0];
+  }
+
+
+
+  abstract WorkerThread<?> newWorkerThread(
+      AsynchronousConnection connection,
+      ConnectionFactory<?> connectionFactory);
+
+
+
+  abstract StatsThread newStatsThread();
+
+
+
+  class UpdateStatsResultHandler<S extends Result> implements
+      ResultHandler<S, Void>
+  {
+    private long eTime;
+
+
+
+    UpdateStatsResultHandler(long eTime)
+    {
+      this.eTime = eTime;
+    }
+
+
+
+    public void handleResult(Void p, S result)
+    {
+      successRecentCount.getAndIncrement();
+      eTime = System.nanoTime() - eTime;
+      waitRecentTime.getAndAdd(eTime);
+      synchronized (this)
+      {
+        ReversableArray array = eTimeBuffer.get();
+        if (array.remaining() == 0)
+        {
+          array.set(array.size() - 1, eTime);
+        }
+        else
+        {
+          array.append(eTime);
+        }
+      }
+    }
+
+
+
+    public void handleErrorResult(Void p, ErrorResultException error)
+    {
+      failedRecentCount.getAndIncrement();
+      app.println(Message.raw(error.getResult().toString()));
+    }
+
+
+
+    public long getETime()
+    {
+      return eTime;
+    }
+  }
+
+
+
+  abstract class WorkerThread<R extends ResultHandler<?, ?>> extends
+      Thread
+  {
+    private int count;
+    private final AsynchronousConnection connection;
+    private final ConnectionFactory<?> connectionFactory;
+
+
+
+    WorkerThread(AsynchronousConnection connection,
+        ConnectionFactory<?> connectionFactory)
+    {
+      super("Worker Thread");
+      this.connection = connection;
+      this.connectionFactory = connectionFactory;
+    }
+
+
+
+    public abstract ResultFuture<?> performOperation(
+        AsynchronousConnection connection, R handler,
+        DataSource[] dataSources);
+
+
+
+    public abstract R getHandler(long startTime);
+
+
+
+    public void run()
+    {
+      if (dataSources.get() == null)
+      {
+        try
+        {
+          dataSources.set(DataSource.parse(arguments.getValues()));
+        }
+        catch (IOException ioe)
+        {
+          // Ignore as this shouldn've been handled eariler
+        }
+      }
+
+      ResultFuture<?> future;
+      AsynchronousConnection connection;
+      R handler;
+
+      double targetTimeInMS =
+          (1.0 / (targetThroughput / (numThreads * numConnections))) * 1000.0;
+      double sleepTimeInMS = 0;
+      long start;
+      while (!stopRequested
+          && !(maxIterations > 0 && count >= maxIterations))
+      {
+        start = System.nanoTime();
+        handler = getHandler(start);
+
+        if (this.connection == null)
+        {
+          try
+          {
+            connection =
+                connectionFactory.getAsynchronousConnection(null, null)
+                    .get();
+          }
+          catch (InterruptedException e)
+          {
+            // Ignore and check stop requested
+            continue;
+          }
+          catch (ErrorResultException e)
+          {
+            app.println(Message.raw(e.getResult()
+                .getDiagnosticMessage()));
+            if (e.getCause() != null && app.isVerbose())
+            {
+              e.getCause().printStackTrace(app.getErrorStream());
+            }
+            stopRequested = true;
+            break;
+          }
+        }
+        else
+        {
+          connection = this.connection;
+          if (!noRebind
+              && connection instanceof AuthenticatedAsynchronousConnection)
+          {
+            AuthenticatedAsynchronousConnection ac =
+                (AuthenticatedAsynchronousConnection) connection;
+            try
+            {
+              ac.rebind(null, null).get();
+            }
+            catch (InterruptedException e)
+            {
+              // Ignore and check stop requested
+              continue;
+            }
+            catch (ErrorResultException e)
+            {
+              app.println(Message.raw(e.getResult().toString()));
+              if (e.getCause() != null && app.isVerbose())
+              {
+                e.getCause().printStackTrace(app.getErrorStream());
+              }
+              stopRequested = true;
+              break;
+            }
+          }
+        }
+        future =
+            performOperation(connection, handler, dataSources.get());
+        operationRecentCount.getAndIncrement();
+        count++;
+        if (!isAsync)
+        {
+          try
+          {
+            future.get();
+          }
+          catch (InterruptedException e)
+          {
+            // Ignore and check stop requested
+            continue;
+          }
+          catch (ErrorResultException e)
+          {
+            if (e.getCause() instanceof IOException)
+            {
+              e.getCause().printStackTrace(app.getErrorStream());
+              stopRequested = true;
+              break;
+            }
+            // Ignore. Handled by result handler
+          }
+          if (this.connection == null)
+          {
+            connection.close();
+          }
+        }
+        if (targetThroughput > 0)
+        {
+          try
+          {
+            if (sleepTimeInMS > 1)
+            {
+              sleep((long) Math.floor(sleepTimeInMS));
+            }
+          }
+          catch (InterruptedException e)
+          {
+            continue;
+          }
+
+          sleepTimeInMS +=
+              targetTimeInMS
+                  - ((System.nanoTime() - start) / 1000000.0);
+          if (sleepTimeInMS < -60000)
+          {
+            // If we fall behind by 60 seconds, just forget about
+            // catching up
+            sleepTimeInMS = -60000;
+          }
+        }
+      }
+    }
+  }
+
+
+
+  class StatsThread extends Thread
+  {
+    protected final String[] EMPTY_STRINGS = new String[0];
+    private final MultiColumnPrinter printer;
+    private final List<GarbageCollectorMXBean> beans;
+    private final Set<Double> percentiles;
+    private final int numColumns;
+
+    private ReversableArray etimes = new ReversableArray(100000);
+    private final ReversableArray array = new ReversableArray(200000);
+
+    protected long totalSuccessCount;
+    protected long totalOperationCount;
+    protected long totalFailedCount;
+    protected long totalWaitTime;
+    protected int successCount;
+    protected int searchCount;
+    protected int failedCount;
+    protected long waitTime;
+
+    protected long lastStatTime;
+    protected long lastGCDuration;
+    protected double recentDuration;
+    protected double averageDuration;
+
+
+
+    public StatsThread(String[] additionalColumns)
+    {
+      super("Stats Thread");
+      TreeSet<Double> pSet = new TreeSet<Double>();
+      if (!percentilesArgument.isPresent())
+      {
+        pSet.add(.1);
+        pSet.add(.01);
+        pSet.add(.001);
+      }
+      else
+      {
+        for (String percentile : percentilesArgument.getValues())
+        {
+          pSet.add(100.0 - Double.parseDouble(percentile));
+        }
+      }
+      this.percentiles = pSet.descendingSet();
+      numColumns =
+          5 + this.percentiles.size() + additionalColumns.length
+              + (isAsync ? 1 : 0);
+      printer =
+          new MultiColumnPrinter(numColumns, 2, "-",
+              MultiColumnPrinter.RIGHT, app);
+      printer.setTitleAlign(MultiColumnPrinter.RIGHT);
+
+      String[] title = new String[numColumns];
+      Arrays.fill(title, "");
+      title[0] = "Throughput";
+      title[2] = "Response Time";
+      int[] span = new int[numColumns];
+      span[0] = 2;
+      span[1] = 0;
+      span[2] = 2 + this.percentiles.size();
+      Arrays.fill(span, 3, 4 + this.percentiles.size(), 0);
+      Arrays.fill(span, 4 + this.percentiles.size(), span.length, 1);
+      printer.addTitle(title, span);
+      title = new String[numColumns];
+      Arrays.fill(title, "");
+      title[0] = "(ops/second)";
+      title[2] = "(milliseconds)";
+      printer.addTitle(title, span);
+      title = new String[numColumns];
+      title[0] = "recent";
+      title[1] = "average";
+      title[2] = "recent";
+      title[3] = "average";
+      int i = 4;
+      for (Double percentile : this.percentiles)
+      {
+        title[i++] = Double.toString(100.0 - percentile) + "%";
+      }
+      title[i++] = "err/sec";
+      if (isAsync)
+      {
+        title[i++] = "req/res";
+      }
+      for (String column : additionalColumns)
+      {
+        title[i++] = column;
+      }
+      span = new int[numColumns];
+      Arrays.fill(span, 1);
+      printer.addTitle(title, span);
+      beans = ManagementFactory.getGarbageCollectorMXBeans();
+    }
+
+
+
+    String[] getAdditionalColumns()
+    {
+      return EMPTY_STRINGS;
+    }
+
+
+
+    @Override
+    public void run()
+    {
+      printer.printTitle();
+
+      String[] strings = new String[numColumns];
+
+      long startTime = System.currentTimeMillis();
+      long statTime = startTime;
+      long gcDuration = 0;
+      for (GarbageCollectorMXBean bean : beans)
+      {
+        gcDuration += bean.getCollectionTime();
+      }
+      while (!stopRequested)
+      {
+        try
+        {
+          sleep(statsInterval);
+        }
+        catch (InterruptedException ie)
+        {
+          // Ignore.
+        }
+
+        lastStatTime = statTime;
+        statTime = System.currentTimeMillis();
+
+        lastGCDuration = gcDuration;
+        gcDuration = 0;
+        for (GarbageCollectorMXBean bean : beans)
+        {
+          gcDuration += bean.getCollectionTime();
+        }
+
+        successCount = successRecentCount.getAndSet(0);
+        searchCount = operationRecentCount.getAndSet(0);
+        failedCount = failedRecentCount.getAndSet(0);
+        waitTime = waitRecentTime.getAndSet(0);
+        totalSuccessCount += successCount;
+        totalOperationCount += searchCount;
+        totalFailedCount += failedCount;
+        totalWaitTime += waitTime;
+        recentDuration = statTime - lastStatTime;
+        averageDuration = statTime - startTime;
+        recentDuration -= gcDuration - lastGCDuration;
+        averageDuration -= gcDuration;
+        recentDuration /= 1000.0;
+        averageDuration /= 1000.0;
+        strings[0] =
+            String.format("%.1f", successCount / recentDuration);
+        strings[1] =
+            String.format("%.1f", totalSuccessCount / averageDuration);
+        strings[2] =
+            String.format("%.3f",
+                (waitTime - (gcDuration - lastGCDuration))
+                    / successCount / 1000000.0);
+        strings[3] =
+            String.format("%.3f", (totalWaitTime - gcDuration)
+                / totalSuccessCount / 1000000.0);
+
+        boolean changed = false;
+        etimes = eTimeBuffer.getAndSet(etimes);
+        int appendLength = Math.min(array.remaining(), etimes.size());
+        if (appendLength > 0)
+        {
+          array.append(etimes, appendLength);
+          for (int i = array.size - appendLength; i < array.size; i++)
+          {
+            array.siftUp(0, i);
+          }
+          changed = true;
+        }
+
+        // Our window buffer is now full. Replace smallest with anything
+        // larger
+        // and re-heapify
+        for (int i = appendLength; i < etimes.size(); i++)
+        {
+          if (etimes.get(i) > array.get(0))
+          {
+            array.set(0, etimes.get(i));
+            array.siftDown(0, array.size() - 1);
+            changed = true;
+          }
+        }
+        etimes.clear();
+
+        if (changed)
+        {
+          // Perform heapsort
+          int i = array.size() - 1;
+          while (i > 0)
+          {
+            array.swap(i, 0);
+            array.siftDown(0, i - 1);
+            i--;
+          }
+          array.reverse();
+        }
+
+        // Now everything is ordered from smallest to largest
+        int index;
+        int i = 4;
+        for (Double percent : percentiles)
+        {
+          index =
+              array.size()
+                  - (int) Math.floor((percent / 100.0)
+                      * totalSuccessCount) - 1;
+          if (index < 0)
+          {
+            strings[i++] =
+                String.format("*%.3f", array.get(0) / 1000000.0);
+          }
+          else
+          {
+            strings[i++] =
+                String.format("%.3f", array.get(index) / 1000000.0);
+          }
+        }
+        strings[i++] =
+            String.format("%.1f", totalFailedCount / averageDuration);
+        if (isAsync)
+        {
+          strings[i++] =
+              String
+                  .format("%.1f", (double) searchCount / successCount);
+        }
+        for (String column : getAdditionalColumns())
+        {
+          strings[i++] = column;
+        }
+        printer.printRow(strings);
+      }
+    }
+  }
+
+
+
+  private static class ReversableArray
+  {
+    private final long[] array;
+    private boolean reversed;
+    private int size;
+
+
+
+    public ReversableArray(int capacity)
+    {
+      this.array = new long[capacity];
+    }
+
+
+
+    public void set(int index, long value)
+    {
+      if (index >= size)
+      {
+        throw new IndexOutOfBoundsException();
+      }
+      if (!reversed)
+      {
+        array[index] = value;
+      }
+      else
+      {
+        array[size - index - 1] = value;
+      }
+    }
+
+
+
+    public long get(int index)
+    {
+      if (index >= size)
+      {
+        throw new IndexOutOfBoundsException();
+      }
+      if (!reversed)
+      {
+        return array[index];
+      }
+      else
+      {
+        return array[size - index - 1];
+      }
+    }
+
+
+
+    public int size()
+    {
+      return size;
+    }
+
+
+
+    public void reverse()
+    {
+      reversed = !reversed;
+    }
+
+
+
+    public void append(long value)
+    {
+      if (size == array.length)
+      {
+        throw new IndexOutOfBoundsException();
+      }
+
+      if (!reversed)
+      {
+        array[size] = value;
+      }
+      else
+      {
+        System.arraycopy(array, 0, array, 1, size);
+        array[0] = value;
+      }
+      size++;
+    }
+
+
+
+    public void append(ReversableArray a, int length)
+    {
+      if (length > a.size() || length > remaining())
+      {
+        throw new IndexOutOfBoundsException();
+      }
+      if (!reversed)
+      {
+        System.arraycopy(a.array, 0, array, size, length);
+      }
+      else
+      {
+        System.arraycopy(array, 0, array, length, size);
+        System.arraycopy(a.array, 0, array, 0, length);
+      }
+      size += length;
+    }
+
+
+
+    public int remaining()
+    {
+      return array.length - size;
+    }
+
+
+
+    public void clear()
+    {
+      size = 0;
+    }
+
+
+
+    public void siftDown(int start, int end)
+    {
+      int root = start;
+      int child;
+      while (root * 2 + 1 <= end)
+      {
+        child = root * 2 + 1;
+        if (child + 1 <= end && get(child) > get(child + 1))
+        {
+          child = child + 1;
+        }
+        if (get(root) > get(child))
+        {
+          swap(root, child);
+          root = child;
+        }
+        else
+        {
+          return;
+        }
+      }
+    }
+
+
+
+    public void siftUp(int start, int end)
+    {
+      int child = end;
+      int parent;
+      while (child > start)
+      {
+        parent = (int) Math.floor((child - 1) / 2);
+        if (get(parent) > get(child))
+        {
+          swap(parent, child);
+          child = parent;
+        }
+        else
+        {
+          return;
+        }
+      }
+    }
+
+
+
+    private void swap(int i, int i2)
+    {
+      long temp = get(i);
+      set(i, get(i2));
+      set(i2, temp);
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/SearchRate.java b/sdk/src/org/opends/sdk/tools/SearchRate.java
new file mode 100644
index 0000000..b3c157e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/SearchRate.java
@@ -0,0 +1,519 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.tools.ToolConstants.*;
+import static org.opends.server.util.StaticUtils.filterExitCode;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.opends.messages.Message;
+import org.opends.sdk.*;
+import org.opends.sdk.requests.Requests;
+import org.opends.sdk.requests.SearchRequest;
+import org.opends.sdk.responses.*;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * Searchrate benchmarking tool.
+ */
+public final class SearchRate extends ConsoleApplication
+{
+  private BooleanArgument verbose;
+
+
+
+  /**
+   * The main method for SearchRate tool.
+   * 
+   * @param args
+   *          The command-line arguments provided to this program.
+   */
+
+  public static void main(String[] args)
+  {
+    int retCode =
+        mainSearchRate(args, System.in, System.out, System.err);
+
+    if (retCode != 0)
+    {
+      System.exit(filterExitCode(retCode));
+    }
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the ldapsearch tool.
+   * 
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @return The error code.
+   */
+
+  public static int mainSearchRate(String[] args)
+  {
+    return mainSearchRate(args, System.in, System.out, System.err);
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and uses that
+   * information to run the ldapsearch tool.
+   * 
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @param inStream
+   *          The input stream to use for standard input, or
+   *          <CODE>null</CODE> if standard input is not needed.
+   * @param outStream
+   *          The output stream to use for standard output, or
+   *          <CODE>null</CODE> if standard output is not needed.
+   * @param errStream
+   *          The output stream to use for standard error, or
+   *          <CODE>null</CODE> if standard error is not needed.
+   * @return The error code.
+   */
+
+  public static int mainSearchRate(String[] args, InputStream inStream,
+      OutputStream outStream, OutputStream errStream)
+
+  {
+    return new SearchRate(inStream, outStream, errStream).run(args);
+  }
+
+
+
+  private SearchRate(InputStream in, OutputStream out, OutputStream err)
+  {
+    super(in, out, err);
+
+  }
+
+
+
+  private int run(String[] args)
+  {
+    // Create the command-line argument parser for use with this
+    // program.
+    Message toolDescription =
+        Message.raw("This utility can be used to "
+            + "measure search performance");
+    // TODO: correct usage
+    ArgumentParser argParser =
+        new ArgumentParser(SearchRate.class.getName(), toolDescription,
+            false, true, 1, 0, "[filter] [attributes ...]");
+
+    ArgumentParserConnectionFactory connectionFactory;
+    SearchPerformanceRunner runner;
+
+    StringArgument baseDN;
+    MultiChoiceArgument<SearchScope> searchScope;
+    MultiChoiceArgument<DereferenceAliasesPolicy> dereferencePolicy;
+    BooleanArgument showUsage;
+    StringArgument propertiesFileArgument;
+    BooleanArgument noPropertiesFileArgument;
+
+    try
+    {
+      connectionFactory =
+          new ArgumentParserConnectionFactory(argParser, this);
+      runner = new SearchPerformanceRunner(argParser, this);
+
+      propertiesFileArgument =
+          new StringArgument("propertiesFilePath", null,
+              OPTION_LONG_PROP_FILE_PATH, false, false, true,
+              INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+              INFO_DESCRIPTION_PROP_FILE_PATH.get());
+      argParser.addArgument(propertiesFileArgument);
+      argParser.setFilePropertiesArgument(propertiesFileArgument);
+
+      noPropertiesFileArgument =
+          new BooleanArgument("noPropertiesFileArgument", null,
+              OPTION_LONG_NO_PROP_FILE, INFO_DESCRIPTION_NO_PROP_FILE
+                  .get());
+      argParser.addArgument(noPropertiesFileArgument);
+      argParser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+      showUsage =
+          new BooleanArgument("showUsage", OPTION_SHORT_HELP,
+              OPTION_LONG_HELP, INFO_DESCRIPTION_SHOWUSAGE.get());
+      argParser.addArgument(showUsage);
+      argParser.setUsageArgument(showUsage, getOutputStream());
+
+      baseDN =
+          new StringArgument("baseDN", OPTION_SHORT_BASEDN,
+              OPTION_LONG_BASEDN, true, false, true,
+              INFO_BASEDN_PLACEHOLDER.get(), null, null,
+              INFO_SEARCH_DESCRIPTION_BASEDN.get());
+      baseDN.setPropertyName(OPTION_LONG_BASEDN);
+      argParser.addArgument(baseDN);
+
+      searchScope =
+          new MultiChoiceArgument<SearchScope>("searchScope", 's',
+              "searchScope", false, true, INFO_SEARCH_SCOPE_PLACEHOLDER
+                  .get(), SearchScope.values(), false,
+              INFO_SEARCH_DESCRIPTION_SEARCH_SCOPE.get());
+      searchScope.setPropertyName("searchScope");
+      searchScope.setDefaultValue(SearchScope.WHOLE_SUBTREE);
+      argParser.addArgument(searchScope);
+
+      dereferencePolicy =
+          new MultiChoiceArgument<DereferenceAliasesPolicy>(
+              "derefpolicy", 'a', "dereferencePolicy", false, true,
+              INFO_DEREFERENCE_POLICE_PLACEHOLDER.get(),
+              DereferenceAliasesPolicy.values(), false,
+              INFO_SEARCH_DESCRIPTION_DEREFERENCE_POLICY.get());
+      dereferencePolicy.setPropertyName("dereferencePolicy");
+      dereferencePolicy.setDefaultValue(DereferenceAliasesPolicy.NEVER);
+      argParser.addArgument(dereferencePolicy);
+
+      verbose =
+          new BooleanArgument("verbose", 'v', "verbose",
+              INFO_DESCRIPTION_VERBOSE.get());
+      verbose.setPropertyName("verbose");
+      argParser.addArgument(verbose);
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_CANNOT_INITIALIZE_ARGS.get(ae.getMessage());
+      println(message);
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // Parse the command-line arguments provided to this program.
+    try
+    {
+      argParser.parseArguments(args);
+      connectionFactory.validate();
+      runner.validate();
+    }
+    catch (ArgumentException ae)
+    {
+      Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
+      println(message);
+      println(argParser.getUsageMessage());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    // If we should just display usage or version information,
+    // then print it and exit.
+    if (argParser.usageOrVersionDisplayed())
+    {
+      return 0;
+    }
+
+    List<String> attributes = new LinkedList<String>();
+    ArrayList<String> filterAndAttributeStrings =
+        argParser.getTrailingArguments();
+    if (filterAndAttributeStrings.size() > 0)
+    {
+      // the list of trailing arguments should be structured as follow:
+      // the first trailing argument is
+      // considered the filter, the other as attributes.
+      runner.filter = filterAndAttributeStrings.remove(0);
+      // The rest are attributes
+      for (String s : filterAndAttributeStrings)
+      {
+        attributes.add(s);
+      }
+    }
+    runner.attributes =
+        attributes.toArray(new String[attributes.size()]);
+    runner.baseDN = baseDN.getValue();
+    try
+    {
+      runner.scope = searchScope.getTypedValue();
+      runner.dereferencesAliasesPolicy =
+          dereferencePolicy.getTypedValue();
+    }
+    catch (ArgumentException ex1)
+    {
+      println(ex1.getMessageObject());
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    try
+    {
+      // Try it out to make sure the format string and data sources
+      // match.
+      Object[] data =
+          DataSource.generateData(runner.getDataSources(), null);
+      String.format(runner.filter, data);
+      String.format(runner.baseDN, data);
+    }
+    catch (Exception ex1)
+    {
+      println(Message.raw("Error formatting filter or base DN: "
+          + ex1.toString()));
+      return ResultCode.CLIENT_SIDE_PARAM_ERROR.intValue();
+    }
+
+    return runner.run(connectionFactory);
+  }
+
+  private final AtomicInteger entryRecentCount = new AtomicInteger();
+
+
+
+  private class SearchPerformanceRunner extends PerformanceRunner
+  {
+    private String filter;
+    private String baseDN;
+    private SearchScope scope;
+    private DereferenceAliasesPolicy dereferencesAliasesPolicy;
+    private String[] attributes;
+
+
+
+    private SearchPerformanceRunner(ArgumentParser argParser,
+        ConsoleApplication app) throws ArgumentException
+    {
+      super(argParser, app);
+    }
+
+
+
+    WorkerThread<?> newWorkerThread(AsynchronousConnection connection,
+        ConnectionFactory<?> connectionFactory)
+    {
+      return new SearchWorkerThread(connection, connectionFactory);
+    }
+
+
+
+    StatsThread newStatsThread()
+    {
+      return new SearchStatsThread();
+    }
+
+
+
+    private class SearchStatsHandler extends
+        UpdateStatsResultHandler<Result> implements
+        SearchResultHandler<Void>
+    {
+      private SearchStatsHandler(long eTime)
+      {
+        super(eTime);
+      }
+
+
+
+      public void handleEntry(Void p, SearchResultEntry entry)
+      {
+        entryRecentCount.getAndIncrement();
+      }
+
+
+
+      public void handleReference(Void p,
+          SearchResultReference reference)
+      {
+      }
+    }
+
+
+
+    private class SearchWorkerThread extends
+        WorkerThread<SearchStatsHandler>
+    {
+      private SearchRequest sr;
+      private Object[] data;
+
+
+
+      private SearchWorkerThread(AsynchronousConnection connection,
+          ConnectionFactory<?> connectionFactory)
+      {
+        super(connection, connectionFactory);
+      }
+
+
+
+      public SearchStatsHandler getHandler(long startTime)
+      {
+        return new SearchStatsHandler(startTime);
+      }
+
+
+
+      public ResultFuture<?> performOperation(
+          AsynchronousConnection connection,
+          SearchStatsHandler handler, DataSource[] dataSources)
+      {
+        if (sr == null)
+        {
+          if (dataSources == null)
+          {
+            sr = Requests.newSearchRequest(baseDN, scope, filter,
+                attributes);
+          }
+          else
+          {
+            data = DataSource.generateData(dataSources, data);
+            sr =
+                Requests.newSearchRequest(String.format(baseDN, data), scope,
+                    String.format(filter, data), attributes);
+          }
+          sr.setDereferenceAliasesPolicy(dereferencesAliasesPolicy);
+        }
+        else if (dataSources != null)
+        {
+          data = DataSource.generateData(dataSources, data);
+          sr.setFilter(String.format(filter, data));
+          sr.setName(String.format(baseDN, data));
+        }
+        return connection.search(sr, handler, handler, null);
+      }
+    }
+
+
+
+    private class SearchStatsThread extends StatsThread
+    {
+      private long totalEntryCount;
+      private final String[] extraColumn;
+
+
+
+      private SearchStatsThread()
+      {
+        super(new String[] { "Entries/Srch" });
+        extraColumn = new String[1];
+      }
+
+
+
+      String[] getAdditionalColumns()
+      {
+        int entryCount = entryRecentCount.getAndSet(0);
+        totalEntryCount += entryCount;
+        extraColumn[0] =
+            String.format("%.1f", (double) entryCount / successCount);
+        return extraColumn;
+      }
+    }
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested advanced mode.
+   * 
+   * @return Returns <code>true</code> if the user has requested
+   *         advanced mode.
+   */
+  public boolean isAdvancedMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested interactive
+   * behavior.
+   * 
+   * @return Returns <code>true</code> if the user has requested
+   *         interactive behavior.
+   */
+  public boolean isInteractive()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not this console application is running in its
+   * menu-driven mode. This can be used to dictate whether output should
+   * go to the error stream or not. In addition, it may also dictate
+   * whether or not sub-menus should display a cancel option as well as
+   * a quit option.
+   * 
+   * @return Returns <code>true</code> if this console application is
+   *         running in its menu-driven mode.
+   */
+  public boolean isMenuDrivenMode()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested quiet output.
+   * 
+   * @return Returns <code>true</code> if the user has requested quiet
+   *         output.
+   */
+  public boolean isQuiet()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested script-friendly
+   * output.
+   * 
+   * @return Returns <code>true</code> if the user has requested
+   *         script-friendly output.
+   */
+  public boolean isScriptFriendly()
+  {
+    return false;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested verbose output.
+   * 
+   * @return Returns <code>true</code> if the user has requested verbose
+   *         output.
+   */
+  public boolean isVerbose()
+  {
+    return verbose.isPresent();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/StringArgument.java b/sdk/src/org/opends/sdk/tools/StringArgument.java
new file mode 100644
index 0000000..b341a5e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/StringArgument.java
@@ -0,0 +1,150 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+
+
+
+/**
+ * This class defines an argument type that will accept any string
+ * value.
+ */
+final class StringArgument extends Argument
+{
+  /**
+   * Creates a new string argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public StringArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean needsValue,
+      Message valuePlaceholder, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired, false,
+        needsValue, valuePlaceholder, null, null, description);
+  }
+
+
+
+  /**
+   * Creates a new string argument with the provided information.
+   * 
+   * @param name
+   *          The generic name that should be used to refer to this
+   *          argument.
+   * @param shortIdentifier
+   *          The single-character identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param longIdentifier
+   *          The long identifier for this argument, or
+   *          <CODE>null</CODE> if there is none.
+   * @param isRequired
+   *          Indicates whether this argument must be specified on the
+   *          command line.
+   * @param isMultiValued
+   *          Indicates whether this argument may be specified more than
+   *          once to provide multiple values.
+   * @param needsValue
+   *          Indicates whether this argument requires a value.
+   * @param valuePlaceholder
+   *          The placeholder for the argument value that will be
+   *          displayed in usage information, or <CODE>null</CODE> if
+   *          this argument does not require a value.
+   * @param defaultValue
+   *          The default value that should be used for this argument if
+   *          none is provided in a properties file or on the command
+   *          line. This may be <CODE>null</CODE> if there is no generic
+   *          default.
+   * @param propertyName
+   *          The name of the property in a property file that may be
+   *          used to override the default value but will be overridden
+   *          by a command-line argument.
+   * @param description
+   *          Message for the description of this argument.
+   * @throws ArgumentException
+   *           If there is a problem with any of the parameters used to
+   *           create this argument.
+   */
+  public StringArgument(String name, Character shortIdentifier,
+      String longIdentifier, boolean isRequired, boolean isMultiValued,
+      boolean needsValue, Message valuePlaceholder,
+      String defaultValue, String propertyName, Message description)
+      throws ArgumentException
+  {
+    super(name, shortIdentifier, longIdentifier, isRequired,
+        isMultiValued, needsValue, valuePlaceholder, defaultValue,
+        propertyName, description);
+  }
+
+
+
+  /**
+   * Indicates whether the provided value is acceptable for use in this
+   * argument.
+   * 
+   * @param valueString
+   *          The value for which to make the determination.
+   * @param invalidReason
+   *          A buffer into which the invalid reason may be written if
+   *          the value is not acceptable.
+   * @return <CODE>true</CODE> if the value is acceptable, or
+   *         <CODE>false</CODE> if it is not.
+   */
+  public boolean valueIsAcceptable(String valueString,
+      MessageBuilder invalidReason)
+  {
+    // All values will be acceptable for this argument.
+    return true;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/tools/Utils.java b/sdk/src/org/opends/sdk/tools/Utils.java
new file mode 100644
index 0000000..da476c3
--- /dev/null
+++ b/sdk/src/org/opends/sdk/tools/Utils.java
@@ -0,0 +1,476 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.tools;
+
+
+
+import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_DAYS_HOURS_MINUTES_SECONDS;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_HOURS_MINUTES_SECONDS;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_MINUTES_SECONDS;
+import static org.opends.messages.UtilityMessages.INFO_TIME_IN_SECONDS;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import org.opends.messages.Message;
+import org.opends.sdk.Connection;
+import org.opends.sdk.DecodeException;
+import org.opends.sdk.ErrorResultException;
+import org.opends.sdk.AuthenticatedConnectionFactory.AuthenticatedConnection;
+import org.opends.sdk.controls.*;
+import org.opends.sdk.responses.BindResult;
+import org.opends.sdk.util.ByteString;
+import org.opends.sdk.util.StaticUtils;
+import org.opends.server.util.cli.ConsoleApplication;
+
+
+
+/**
+ * This class provides utility functions for all the client side tools.
+ */
+final class Utils
+{
+  // Prevent instantiation.
+  private Utils()
+  {
+    // Do nothing.
+  }
+
+
+
+  /**
+   * Parse the specified command line argument to create the appropriate
+   * LDAPControl. The argument string should be in the format
+   * controloid[:criticality[:value|::b64value|:&lt;fileurl]]
+   *
+   * @param argString
+   *          The argument string containing the encoded control
+   *          information.
+   * @return The control decoded from the provided string, or
+   *         <CODE>null</CODE> if an error occurs while parsing the
+   *         argument value.
+   * @throws org.opends.sdk.DecodeException
+   *           If an error occurs.
+   */
+  public static GenericControl getControl(String argString)
+      throws DecodeException
+  {
+    String controlOID = null;
+    boolean controlCriticality = false;
+    ByteString controlValue = null;
+
+    int idx = argString.indexOf(":");
+
+    if (idx < 0)
+    {
+      controlOID = argString;
+    }
+    else
+    {
+      controlOID = argString.substring(0, idx);
+    }
+
+    String lowerOID = StaticUtils.toLowerCase(controlOID);
+    if (lowerOID.equals("accountusable")
+        || lowerOID.equals("accountusability"))
+    {
+      controlOID = AccountUsabilityControl.OID_ACCOUNT_USABLE_CONTROL;
+    }
+    else if (lowerOID.equals("authzid")
+        || lowerOID.equals("authorizationidentity"))
+    {
+      controlOID = AuthorizationIdentityControl.OID_AUTHZID_REQUEST;
+    }
+    else if (lowerOID.equals("noop") || lowerOID.equals("no-op"))
+    {
+      // controlOID = OID_LDAP_NOOP_OPENLDAP_ASSIGNED;
+    }
+    else if (lowerOID.equals("subentries"))
+    {
+      // controlOID = OID_LDAP_SUBENTRIES;
+    }
+    else if (lowerOID.equals("managedsait"))
+    {
+      // controlOID = OID_MANAGE_DSAIT_CONTROL;
+    }
+    else if (lowerOID.equals("pwpolicy")
+        || lowerOID.equals("passwordpolicy"))
+    {
+      controlOID = PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL;
+    }
+    else if (lowerOID.equals("subtreedelete")
+        || lowerOID.equals("treedelete"))
+    {
+      controlOID = SubtreeDeleteControl.OID_SUBTREE_DELETE_CONTROL;
+    }
+    else if (lowerOID.equals("realattrsonly")
+        || lowerOID.equals("realattributesonly"))
+    {
+      // controlOID = OID_REAL_ATTRS_ONLY;
+    }
+    else if (lowerOID.equals("virtualattrsonly")
+        || lowerOID.equals("virtualattributesonly"))
+    {
+      // controlOID = OID_VIRTUAL_ATTRS_ONLY;
+    }
+    else if (lowerOID.equals("effectiverights")
+        || lowerOID.equals("geteffectiverights"))
+    {
+      controlOID = GetEffectiveRightsRequestControl.OID_GET_EFFECTIVE_RIGHTS;
+    }
+
+    if (idx < 0)
+    {
+      return new GenericControl(controlOID);
+    }
+
+    String remainder = argString.substring(idx + 1, argString.length());
+
+    idx = remainder.indexOf(":");
+    if (idx == -1)
+    {
+      if (remainder.equalsIgnoreCase("true"))
+      {
+        controlCriticality = true;
+      }
+      else if (remainder.equalsIgnoreCase("false"))
+      {
+        controlCriticality = false;
+      }
+      else
+      {
+        // TODO: I18N
+        throw DecodeException.error(Message
+            .raw("Invalid format for criticality value:" + remainder));
+      }
+      return new GenericControl(controlOID, controlCriticality);
+
+    }
+
+    String critical = remainder.substring(0, idx);
+    if (critical.equalsIgnoreCase("true"))
+    {
+      controlCriticality = true;
+    }
+    else if (critical.equalsIgnoreCase("false"))
+    {
+      controlCriticality = false;
+    }
+    else
+    {
+      // TODO: I18N
+      throw DecodeException.error(Message
+          .raw("Invalid format for criticality value:" + critical));
+    }
+
+    String valString = remainder.substring(idx + 1, remainder.length());
+    if (valString.charAt(0) == ':')
+    {
+      controlValue = ByteString.valueOf(valString.substring(1,
+          valString.length()));
+    }
+    else if (valString.charAt(0) == '<')
+    {
+      // Read data from the file.
+      String filePath = valString.substring(1, valString.length());
+      try
+      {
+        byte[] val = readBytesFromFile(filePath);
+        controlValue = ByteString.wrap(val);
+      }
+      catch (Exception e)
+      {
+        return null;
+      }
+    }
+    else
+    {
+      controlValue = ByteString.valueOf(valString);
+    }
+
+    return new GenericControl(controlOID, controlCriticality,
+        controlValue);
+  }
+
+
+
+  /**
+   * Read the data from the specified file and return it in a byte
+   * array.
+   *
+   * @param filePath
+   *          The path to the file that should be read.
+   * @return A byte array containing the contents of the requested file.
+   * @throws IOException
+   *           If a problem occurs while trying to read the specified
+   *           file.
+   */
+  public static byte[] readBytesFromFile(String filePath)
+      throws IOException
+  {
+    byte[] val = null;
+    FileInputStream fis = null;
+    try
+    {
+      File file = new File(filePath);
+      fis = new FileInputStream(file);
+      long length = file.length();
+      val = new byte[(int) length];
+      // Read in the bytes
+      int offset = 0;
+      int numRead = 0;
+      while (offset < val.length
+          && (numRead = fis.read(val, offset, val.length - offset)) >= 0)
+      {
+        offset += numRead;
+      }
+
+      // Ensure all the bytes have been read in
+      if (offset < val.length)
+      {
+        throw new IOException("Could not completely read file "
+            + filePath);
+      }
+
+      return val;
+    }
+    finally
+    {
+      if (fis != null)
+      {
+        fis.close();
+      }
+    }
+  }
+
+
+
+  /**
+   * Prints a multi-line error message with the provided information to
+   * the given print stream.
+   *
+   * @param app
+   *          The console app to use to write the error message.
+   * @param ere
+   *          The error result.
+   * @return The error code.
+   */
+  public static int printErrorMessage(ConsoleApplication app,
+      ErrorResultException ere)
+  {
+    // if ((ere.getMessage() != null) && (ere.getMessage().length() >
+    // 0))
+    // {
+    // app.println(Message.raw(ere.getMessage()));
+    // }
+
+    if (ere.getResult().getResultCode().intValue() >= 0)
+    {
+      app.println(ERR_TOOL_RESULT_CODE.get(ere.getResult()
+          .getResultCode().intValue(), ere.getResult().getResultCode()
+          .toString()));
+    }
+
+    if ((ere.getResult().getDiagnosticMessage() != null)
+        && (ere.getResult().getDiagnosticMessage().length() > 0))
+    {
+      app.println(ERR_TOOL_ERROR_MESSAGE.get(ere.getResult()
+          .getDiagnosticMessage()));
+    }
+
+    if (ere.getResult().getMatchedDN() != null
+        && ere.getResult().getMatchedDN().length() > 0)
+    {
+      app.println(ERR_TOOL_MATCHED_DN.get(ere.getResult()
+          .getMatchedDN()));
+    }
+
+    if (app.isVerbose() && ere.getResult().getCause() != null)
+    {
+      ere.getResult().getCause().printStackTrace(app.getErrorStream());
+    }
+
+    return ere.getResult().getResultCode().intValue();
+  }
+
+
+
+  /**
+   * Retrieves a user-friendly string that indicates the length of time
+   * (in days, hours, minutes, and seconds) in the specified number of
+   * seconds.
+   *
+   * @param numSeconds
+   *          The number of seconds to be converted to a more
+   *          user-friendly value.
+   * @return The user-friendly representation of the specified number of
+   *         seconds.
+   */
+  public static Message secondsToTimeString(int numSeconds)
+  {
+    if (numSeconds < 60)
+    {
+      // We can express it in seconds.
+      return INFO_TIME_IN_SECONDS.get(numSeconds);
+    }
+    else if (numSeconds < 3600)
+    {
+      // We can express it in minutes and seconds.
+      int m = numSeconds / 60;
+      int s = numSeconds % 60;
+      return INFO_TIME_IN_MINUTES_SECONDS.get(m, s);
+    }
+    else if (numSeconds < 86400)
+    {
+      // We can express it in hours, minutes, and seconds.
+      int h = numSeconds / 3600;
+      int m = (numSeconds % 3600) / 60;
+      int s = numSeconds % 3600 % 60;
+      return INFO_TIME_IN_HOURS_MINUTES_SECONDS.get(h, m, s);
+    }
+    else
+    {
+      // We can express it in days, hours, minutes, and seconds.
+      int d = numSeconds / 86400;
+      int h = (numSeconds % 86400) / 3600;
+      int m = (numSeconds % 86400 % 3600) / 60;
+      int s = numSeconds % 86400 % 3600 % 60;
+      return INFO_TIME_IN_DAYS_HOURS_MINUTES_SECONDS.get(d, h, m, s);
+    }
+  }
+
+
+
+  public static void printPasswordPolicyResults(ConsoleApplication app,
+      Connection connection)
+  {
+    if (connection instanceof AuthenticatedConnection)
+    {
+      AuthenticatedConnection conn = (AuthenticatedConnection) connection;
+      BindResult result = conn.getAuthenticatedBindResult();
+
+      Control control = result
+          .getControl(AuthorizationIdentityControl.OID_AUTHZID_RESPONSE);
+      if (control != null)
+      {
+        AuthorizationIdentityControl.Response dc = (AuthorizationIdentityControl.Response) control;
+        Message message = INFO_BIND_AUTHZID_RETURNED.get(dc
+            .getAuthorizationID());
+        app.println(message);
+      }
+      control = result
+          .getControl(PasswordExpiredControl.OID_NS_PASSWORD_EXPIRED);
+      if (control != null)
+      {
+        Message message = INFO_BIND_PASSWORD_EXPIRED.get();
+        app.println(message);
+      }
+      control = result
+          .getControl(PasswordExpiringControl.OID_NS_PASSWORD_EXPIRING);
+      if (control != null)
+      {
+        PasswordExpiringControl dc = (PasswordExpiringControl) control;
+        Message timeString = Utils.secondsToTimeString(dc
+            .getSecondsUntilExpiration());
+        Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+        app.println(message);
+      }
+      control = result
+          .getControl(PasswordPolicyControl.OID_PASSWORD_POLICY_CONTROL);
+      if (control != null)
+      {
+        PasswordPolicyControl.Response dc = (PasswordPolicyControl.Response) control;
+        PasswordPolicyErrorType errorType = dc.getErrorType();
+        if (errorType == PasswordPolicyErrorType.PASSWORD_EXPIRED)
+        {
+          Message message = INFO_BIND_PASSWORD_EXPIRED.get();
+          app.println(message);
+        }
+        else if (errorType == PasswordPolicyErrorType.ACCOUNT_LOCKED)
+        {
+          Message message = INFO_BIND_ACCOUNT_LOCKED.get();
+          app.println(message);
+        }
+        else if (errorType == PasswordPolicyErrorType.CHANGE_AFTER_RESET)
+        {
+
+          Message message = INFO_BIND_MUST_CHANGE_PASSWORD.get();
+          app.println(message);
+        }
+
+        PasswordPolicyWarningType warningType = dc.getWarningType();
+        if (warningType == PasswordPolicyWarningType.TIME_BEFORE_EXPIRATION)
+        {
+          Message timeString = Utils.secondsToTimeString(dc
+              .getWarningValue());
+          Message message = INFO_BIND_PASSWORD_EXPIRING.get(timeString);
+          app.println(message);
+        }
+        else if (warningType == PasswordPolicyWarningType.GRACE_LOGINS_REMAINING)
+        {
+          Message message = INFO_BIND_GRACE_LOGINS_REMAINING.get(dc
+              .getWarningValue());
+          app.println(message);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Filters the provided value to ensure that it is appropriate for use
+   * as an exit code. Exit code values are generally only allowed to be
+   * between 0 and 255, so any value outside of this range will be
+   * converted to 255, which is the typical exit code used to indicate
+   * an overflow value.
+   *
+   * @param exitCode
+   *          The exit code value to be processed.
+   * @return An integer value between 0 and 255, inclusive. If the
+   *         provided exit code was already between 0 and 255, then the
+   *         original value will be returned. If the provided value was
+   *         out of this range, then 255 will be returned.
+   */
+  public static int filterExitCode(int exitCode)
+  {
+    if (exitCode < 0)
+    {
+      return 255;
+    }
+    else if (exitCode > 255)
+    {
+      return 255;
+    }
+    else
+    {
+      return exitCode;
+    }
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ASCIICharProp.java b/sdk/src/org/opends/sdk/util/ASCIICharProp.java
new file mode 100644
index 0000000..621da56
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ASCIICharProp.java
@@ -0,0 +1,372 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * A {@code ASCIICharProp} provides fast access to ASCII character
+ * properties. In particular, the ability to query whether or not a
+ * character is a letter, a digit, hexadecimal character, as well as
+ * various methods for performing character conversions.
+ * <p>
+ * The methods in this class do not perform memory allocations nor
+ * calculations and so can be used safely in high performance
+ * situations.
+ */
+public final class ASCIICharProp implements Comparable<ASCIICharProp>
+{
+  private final char c;
+  private final char upperCaseChar;
+  private final char lowerCaseChar;
+  private final boolean isUpperCaseChar;
+  private final boolean isLowerCaseChar;
+  private final boolean isDigit;
+  private final boolean isLetter;
+  private final boolean isKeyChar;
+  private final boolean isHexChar;
+  private final int hexValue;
+  private final int decimalValue;
+  private final String stringValue;
+
+  private static final ASCIICharProp[] CHAR_PROPS =
+      new ASCIICharProp[128];
+
+  static
+  {
+    for (int i = 0; i < 128; i++)
+    {
+      CHAR_PROPS[i] = new ASCIICharProp((char) i);
+    }
+  }
+
+
+
+  /**
+   * Returns the character properties for the provided ASCII character.
+   * If a non-ASCII character is provided then this method returns
+   * {@code null}.
+   * 
+   * @param c
+   *          The ASCII character.
+   * @return The character properties for the provided ASCII character,
+   *         or {@code null} if {@code c} is greater than {@code \u007F}
+   *         .
+   */
+  public static ASCIICharProp valueOf(char c)
+  {
+    if (c < 128)
+    {
+      return CHAR_PROPS[c];
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+
+
+  /**
+   * Returns the character properties for the provided ASCII character.
+   * If a non-ASCII character is provided then this method returns
+   * {@code null}.
+   * 
+   * @param c
+   *          The ASCII character.
+   * @return The character properties for the provided ASCII character,
+   *         or {@code null} if {@code c} is less than zero or greater
+   *         than {@code \u007F} .
+   */
+  public static ASCIICharProp valueOf(int c)
+  {
+    if (c >= 0 && c < 128)
+    {
+      return CHAR_PROPS[c];
+    }
+    else
+    {
+      return null;
+    }
+  }
+
+
+
+  private ASCIICharProp(char c)
+  {
+    this.c = c;
+    this.stringValue = new String(new char[] { c });
+
+    if (c >= 'a' && c <= 'z')
+    {
+      this.upperCaseChar = (char) (c - 32);
+      this.lowerCaseChar = c;
+      this.isUpperCaseChar = false;
+      this.isLowerCaseChar = true;
+      this.isDigit = false;
+      this.isLetter = true;
+      this.isKeyChar = true;
+      this.decimalValue = -1;
+      if (c >= 'a' && c <= 'f')
+      {
+        this.isHexChar = true;
+        this.hexValue = c - 87;
+      }
+      else
+      {
+        this.isHexChar = false;
+        this.hexValue = -1;
+      }
+    }
+    else if (c >= 'A' && c <= 'Z')
+    {
+      this.upperCaseChar = c;
+      this.lowerCaseChar = (char) (c + 32);
+      this.isUpperCaseChar = true;
+      this.isLowerCaseChar = false;
+      this.isDigit = false;
+      this.isLetter = true;
+      this.isKeyChar = true;
+      this.decimalValue = -1;
+      if (c >= 'A' && c <= 'F')
+      {
+        this.isHexChar = true;
+        this.hexValue = c - 55;
+      }
+      else
+      {
+        this.isHexChar = false;
+        this.hexValue = -1;
+      }
+    }
+    else if (c >= '0' && c <= '9')
+    {
+      this.upperCaseChar = c;
+      this.lowerCaseChar = c;
+      this.isUpperCaseChar = false;
+      this.isLowerCaseChar = false;
+      this.isDigit = true;
+      this.isLetter = false;
+      this.isKeyChar = true;
+      this.isHexChar = true;
+      this.hexValue = c - 48;
+      this.decimalValue = c - 48;
+    }
+    else
+    {
+      this.upperCaseChar = c;
+      this.lowerCaseChar = c;
+      this.isUpperCaseChar = false;
+      this.isLowerCaseChar = false;
+      this.isDigit = false;
+      this.isLetter = false;
+      this.isKeyChar = c == '-';
+      this.isHexChar = false;
+      this.hexValue = -1;
+      this.decimalValue = -1;
+    }
+  }
+
+
+
+  /**
+   * Returns the char value associated with this {@code ASCIICharProp}.
+   * 
+   * @return The char value associated with this {@code ASCIICharProp}.
+   */
+  public char charValue()
+  {
+    return c;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(ASCIICharProp o)
+  {
+    return c - o.c;
+  }
+
+
+
+  /**
+   * Returns the decimal value associated with this {@code
+   * ASCIICharProp}, or {@code -1} if the value is not a decimal digit.
+   * 
+   * @return The decimal value associated with this {@code
+   *         ASCIICharProp}, or {@code -1} if the value is not a decimal
+   *         digit.
+   */
+  public int decimalValue()
+  {
+    return decimalValue;
+  }
+
+
+
+  /**
+   * Returns the hexadecimal value associated with this {@code
+   * ASCIICharProp} , or {@code -1} if the value is not a hexadecimal
+   * digit.
+   * 
+   * @return The hexadecimal value associated with this {@code
+   *         ASCIICharProp} , or {@code -1} if the value is not a
+   *         hexadecimal digit.
+   */
+  public int hexValue()
+  {
+    return hexValue;
+  }
+
+
+
+  /**
+   * Indicates whether or not the char value associated with this
+   * {@code ASCIICharProp} is a decimal digit.
+   * 
+   * @return {@code true} if the char value associated with this {@code
+   *         ASCIICharProp} is a decimal digit.
+   */
+  public boolean isDigit()
+  {
+    return isDigit;
+  }
+
+
+
+  /**
+   * Indicates whether or not the char value associated with this
+   * {@code ASCIICharProp} is a hexadecimal digit.
+   * 
+   * @return {@code true} if the char value associated with this {@code
+   *         ASCIICharProp} is a hexadecimal digit.
+   */
+  public boolean isHexDigit()
+  {
+    return isHexChar;
+  }
+
+
+
+  /**
+   * Indicates whether or not the char value associated with this
+   * {@code ASCIICharProp} is a {@code keychar} as defined in RFC 4512.
+   * A {@code keychar} is a letter, a digit, or a hyphen.
+   * 
+   * @return {@code true} if the char value associated with this {@code
+   *         ASCIICharProp} is a {@code keychar}.
+   */
+  public boolean isKeyChar()
+  {
+    return isKeyChar;
+  }
+
+
+
+  /**
+   * Indicates whether or not the char value associated with this
+   * {@code ASCIICharProp} is a letter.
+   * 
+   * @return {@code true} if the char value associated with this {@code
+   *         ASCIICharProp} is a letter.
+   */
+  public boolean isLetter()
+  {
+    return isLetter;
+  }
+
+
+
+  /**
+   * Indicates whether or not the char value associated with this
+   * {@code ASCIICharProp} is a lower-case character.
+   * 
+   * @return {@code true} if the char value associated with this {@code
+   *         ASCIICharProp} is a lower-case character.
+   */
+  public boolean isLowerCase()
+  {
+    return isLowerCaseChar;
+  }
+
+
+
+  /**
+   * Indicates whether or not the char value associated with this
+   * {@code ASCIICharProp} is an upper-case character.
+   * 
+   * @return {@code true} if the char value associated with this {@code
+   *         ASCIICharProp} is an upper-case character.
+   */
+  public boolean isUpperCase()
+  {
+    return isUpperCaseChar;
+  }
+
+
+
+  /**
+   * Returns the lower-case char value associated with this {@code
+   * ASCIICharProp}.
+   * 
+   * @return The lower-case char value associated with this {@code
+   *         ASCIICharProp}.
+   */
+  public char toLowerCase()
+  {
+    return lowerCaseChar;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return stringValue;
+  }
+
+
+
+  /**
+   * Returns the upper-case char value associated with this {@code
+   * ASCIICharProp}.
+   * 
+   * @return The upper-case char value associated with this {@code
+   *         ASCIICharProp}.
+   */
+  public char toUpperCase()
+  {
+    return upperCaseChar;
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/Base64.java b/sdk/src/org/opends/sdk/util/Base64.java
new file mode 100755
index 0000000..7d8d6bb
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Base64.java
@@ -0,0 +1,388 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_BASE64_DECODE_INVALID_CHARACTER;
+import static org.opends.messages.UtilityMessages.ERR_BASE64_DECODE_INVALID_LENGTH;
+import static org.opends.sdk.util.Validator.ensureNotNull;
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This class provides methods for performing base64 encoding and
+ * decoding. Base64 is a mechanism for encoding binary data in ASCII
+ * form by converting sets of three bytes with eight significant bits
+ * each to sets of four bytes with six significant bits each.
+ */
+public final class Base64
+{
+  /**
+   * The set of characters that may be used in base64-encoded values.
+   */
+  private static final char[] BASE64_ALPHABET =
+      ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+          + "0123456789+/").toCharArray();
+
+
+
+  /**
+   * Prevent instance creation.
+   */
+  private Base64()
+  {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * Encodes the provided data as a base64 string.
+   *
+   * @param bytes
+   *          The data to be encoded.
+   * @return The base64 encoded representation of {@code bytes}.
+   * @throws NullPointerException
+   *           If {@code bytes} was {@code null}.
+   */
+  public static String encode(ByteSequence bytes)
+      throws NullPointerException
+  {
+    ensureNotNull(bytes);
+
+    StringBuilder buffer = new StringBuilder(4 * bytes.length() / 3);
+
+    int pos = 0;
+    int iterations = bytes.length() / 3;
+    for (int i = 0; i < iterations; i++)
+    {
+      int value =
+          ((bytes.byteAt(pos++) & 0xFF) << 16)
+              | ((bytes.byteAt(pos++) & 0xFF) << 8)
+              | (bytes.byteAt(pos++) & 0xFF);
+
+      buffer.append(BASE64_ALPHABET[(value >>> 18) & 0x3F]);
+      buffer.append(BASE64_ALPHABET[(value >>> 12) & 0x3F]);
+      buffer.append(BASE64_ALPHABET[(value >>> 6) & 0x3F]);
+      buffer.append(BASE64_ALPHABET[value & 0x3F]);
+    }
+
+    switch (bytes.length() % 3)
+    {
+    case 1:
+      buffer.append(BASE64_ALPHABET[(bytes.byteAt(pos) >>> 2) & 0x3F]);
+      buffer.append(BASE64_ALPHABET[(bytes.byteAt(pos) << 4) & 0x3F]);
+      buffer.append("==");
+      break;
+    case 2:
+      int value =
+          ((bytes.byteAt(pos++) & 0xFF) << 8)
+              | (bytes.byteAt(pos) & 0xFF);
+      buffer.append(BASE64_ALPHABET[(value >>> 10) & 0x3F]);
+      buffer.append(BASE64_ALPHABET[(value >>> 4) & 0x3F]);
+      buffer.append(BASE64_ALPHABET[(value << 2) & 0x3F]);
+      buffer.append("=");
+      break;
+    }
+
+    return buffer.toString();
+  }
+
+
+
+  /**
+   * Decodes the provided base64 encoded data.
+   *
+   * @param base64
+   *          The base64 encoded data.
+   * @return The decoded data.
+   * @throws LocalizedIllegalArgumentException
+   *           If a problem occurs while attempting to decode {@code
+   *           base64}.
+   * @throws NullPointerException
+   *           If {@code base64} was {@code null}.
+   */
+  public static ByteString decode(String base64)
+      throws LocalizedIllegalArgumentException, NullPointerException
+  {
+    ensureNotNull(base64);
+
+    // The encoded value must have length that is a multiple of four
+    // bytes.
+    int length = base64.length();
+    if ((length % 4) != 0)
+    {
+      Message message = ERR_BASE64_DECODE_INVALID_LENGTH.get(base64);
+      throw new LocalizedIllegalArgumentException(message);
+    }
+
+    ByteStringBuilder builder = new ByteStringBuilder(length);
+    for (int i = 0; i < length; i += 4)
+    {
+      boolean append = true;
+      int value = 0;
+
+      for (int j = 0; j < 4; j++)
+      {
+        switch (base64.charAt(i + j))
+        {
+        case 'A':
+          value <<= 6;
+          break;
+        case 'B':
+          value = (value << 6) | 0x01;
+          break;
+        case 'C':
+          value = (value << 6) | 0x02;
+          break;
+        case 'D':
+          value = (value << 6) | 0x03;
+          break;
+        case 'E':
+          value = (value << 6) | 0x04;
+          break;
+        case 'F':
+          value = (value << 6) | 0x05;
+          break;
+        case 'G':
+          value = (value << 6) | 0x06;
+          break;
+        case 'H':
+          value = (value << 6) | 0x07;
+          break;
+        case 'I':
+          value = (value << 6) | 0x08;
+          break;
+        case 'J':
+          value = (value << 6) | 0x09;
+          break;
+        case 'K':
+          value = (value << 6) | 0x0A;
+          break;
+        case 'L':
+          value = (value << 6) | 0x0B;
+          break;
+        case 'M':
+          value = (value << 6) | 0x0C;
+          break;
+        case 'N':
+          value = (value << 6) | 0x0D;
+          break;
+        case 'O':
+          value = (value << 6) | 0x0E;
+          break;
+        case 'P':
+          value = (value << 6) | 0x0F;
+          break;
+        case 'Q':
+          value = (value << 6) | 0x10;
+          break;
+        case 'R':
+          value = (value << 6) | 0x11;
+          break;
+        case 'S':
+          value = (value << 6) | 0x12;
+          break;
+        case 'T':
+          value = (value << 6) | 0x13;
+          break;
+        case 'U':
+          value = (value << 6) | 0x14;
+          break;
+        case 'V':
+          value = (value << 6) | 0x15;
+          break;
+        case 'W':
+          value = (value << 6) | 0x16;
+          break;
+        case 'X':
+          value = (value << 6) | 0x17;
+          break;
+        case 'Y':
+          value = (value << 6) | 0x18;
+          break;
+        case 'Z':
+          value = (value << 6) | 0x19;
+          break;
+        case 'a':
+          value = (value << 6) | 0x1A;
+          break;
+        case 'b':
+          value = (value << 6) | 0x1B;
+          break;
+        case 'c':
+          value = (value << 6) | 0x1C;
+          break;
+        case 'd':
+          value = (value << 6) | 0x1D;
+          break;
+        case 'e':
+          value = (value << 6) | 0x1E;
+          break;
+        case 'f':
+          value = (value << 6) | 0x1F;
+          break;
+        case 'g':
+          value = (value << 6) | 0x20;
+          break;
+        case 'h':
+          value = (value << 6) | 0x21;
+          break;
+        case 'i':
+          value = (value << 6) | 0x22;
+          break;
+        case 'j':
+          value = (value << 6) | 0x23;
+          break;
+        case 'k':
+          value = (value << 6) | 0x24;
+          break;
+        case 'l':
+          value = (value << 6) | 0x25;
+          break;
+        case 'm':
+          value = (value << 6) | 0x26;
+          break;
+        case 'n':
+          value = (value << 6) | 0x27;
+          break;
+        case 'o':
+          value = (value << 6) | 0x28;
+          break;
+        case 'p':
+          value = (value << 6) | 0x29;
+          break;
+        case 'q':
+          value = (value << 6) | 0x2A;
+          break;
+        case 'r':
+          value = (value << 6) | 0x2B;
+          break;
+        case 's':
+          value = (value << 6) | 0x2C;
+          break;
+        case 't':
+          value = (value << 6) | 0x2D;
+          break;
+        case 'u':
+          value = (value << 6) | 0x2E;
+          break;
+        case 'v':
+          value = (value << 6) | 0x2F;
+          break;
+        case 'w':
+          value = (value << 6) | 0x30;
+          break;
+        case 'x':
+          value = (value << 6) | 0x31;
+          break;
+        case 'y':
+          value = (value << 6) | 0x32;
+          break;
+        case 'z':
+          value = (value << 6) | 0x33;
+          break;
+        case '0':
+          value = (value << 6) | 0x34;
+          break;
+        case '1':
+          value = (value << 6) | 0x35;
+          break;
+        case '2':
+          value = (value << 6) | 0x36;
+          break;
+        case '3':
+          value = (value << 6) | 0x37;
+          break;
+        case '4':
+          value = (value << 6) | 0x38;
+          break;
+        case '5':
+          value = (value << 6) | 0x39;
+          break;
+        case '6':
+          value = (value << 6) | 0x3A;
+          break;
+        case '7':
+          value = (value << 6) | 0x3B;
+          break;
+        case '8':
+          value = (value << 6) | 0x3C;
+          break;
+        case '9':
+          value = (value << 6) | 0x3D;
+          break;
+        case '+':
+          value = (value << 6) | 0x3E;
+          break;
+        case '/':
+          value = (value << 6) | 0x3F;
+          break;
+        case '=':
+          append = false;
+          switch (j)
+          {
+          case 2:
+            builder.append((byte) ((value >>> 4) & 0xFF));
+            break;
+          case 3:
+            builder.append((byte) ((value >>> 10) & 0xFF));
+            builder.append((byte) ((value >>> 2) & 0xFF));
+            break;
+          }
+          break;
+        default:
+          Message message =
+              ERR_BASE64_DECODE_INVALID_CHARACTER.get(base64, base64
+                  .charAt(i + j));
+          throw new LocalizedIllegalArgumentException(message);
+        }
+
+        if (!append)
+        {
+          break;
+        }
+      }
+
+      if (append)
+      {
+        builder.append((byte) ((value >>> 16) & 0xFF));
+        builder.append((byte) ((value >>> 8) & 0xFF));
+        builder.append((byte) (value & 0xFF));
+      }
+      else
+      {
+        break;
+      }
+    }
+
+    return builder.toByteString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteSequence.java b/sdk/src/org/opends/sdk/util/ByteSequence.java
new file mode 100755
index 0000000..922db42
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteSequence.java
@@ -0,0 +1,348 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+
+
+/**
+ * A {@code ByteSequence} is a readable sequence of byte values. This
+ * interface provides uniform, read-only access to many different kinds
+ * of byte sequences.
+ */
+public interface ByteSequence extends Comparable<ByteSequence>
+{
+
+  /**
+   * Returns a {@link ByteSequenceReader} which can be used to
+   * incrementally read and decode data from this byte sequence.
+   * <p>
+   * <b>NOTE:</b> any concurrent changes to the underlying byte sequence
+   * (if mutable) may cause subsequent reads to overrun and fail.
+   * 
+   * @return The {@link ByteSequenceReader} which can be used to
+   *         incrementally read and decode data from this byte sequence.
+   */
+  ByteSequenceReader asReader();
+
+
+
+  /**
+   * Returns the byte value at the specified index.
+   * <p>
+   * An index ranges from zero to {@code length() - 1}. The first byte
+   * value of the sequence is at index zero, the next at index one, and
+   * so on, as for array indexing.
+   * 
+   * @param index
+   *          The index of the byte to be returned.
+   * @return The byte value at the specified index.
+   * @throws IndexOutOfBoundsException
+   *           If the index argument is negative or not less than
+   *           length().
+   */
+  byte byteAt(int index) throws IndexOutOfBoundsException;
+
+
+
+  /**
+   * Compares this byte sequence with the specified byte array
+   * sub-sequence for order. Returns a negative integer, zero, or a
+   * positive integer depending on whether this byte sequence is less
+   * than, equal to, or greater than the specified byte array
+   * sub-sequence.
+   * 
+   * @param b
+   *          The byte array to compare.
+   * @param offset
+   *          The offset of the sub-sequence in the byte array to be
+   *          compared; must be non-negative and no larger than {@code
+   *          b.length} .
+   * @param length
+   *          The length of the sub-sequence in the byte array to be
+   *          compared; must be non-negative and no larger than {@code
+   *          b.length - offset}.
+   * @return A negative integer, zero, or a positive integer depending
+   *         on whether this byte sequence is less than, equal to, or
+   *         greater than the specified byte array sub-sequence.
+   * @throws IndexOutOfBoundsException
+   *           If {@code offset} is negative or if {@code length} is
+   *           negative or if {@code offset + length} is greater than
+   *           {@code b.length}.
+   */
+  int compareTo(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException;
+
+
+
+  /**
+   * Compares this byte sequence with the specified byte sequence for
+   * order. Returns a negative integer, zero, or a positive integer
+   * depending on whether this byte sequence is less than, equal to, or
+   * greater than the specified object.
+   * 
+   * @param o
+   *          The byte sequence to be compared.
+   * @return A negative integer, zero, or a positive integer depending
+   *         on whether this byte sequence is less than, equal to, or
+   *         greater than the specified object.
+   */
+  int compareTo(ByteSequence o);
+
+
+
+  /**
+   * Copies the contents of this byte sequence to the provided byte
+   * array.
+   * <p>
+   * Copying will stop when either the entire content of this sequence
+   * has been copied or if the end of the provided byte array has been
+   * reached.
+   * <p>
+   * An invocation of the form:
+   * 
+   * <pre>
+   * src.copyTo(b)
+   * </pre>
+   * 
+   * Behaves in exactly the same way as the invocation:
+   * 
+   * <pre>
+   * src.copyTo(b, 0);
+   * </pre>
+   * 
+   * @param b
+   *          The byte array to which bytes are to be copied.
+   * @return The byte array.
+   */
+  byte[] copyTo(byte[] b);
+
+
+
+  /**
+   * Copies the contents of this byte sequence to the specified location
+   * in the provided byte array.
+   * <p>
+   * Copying will stop when either the entire content of this sequence
+   * has been copied or if the end of the provided byte array has been
+   * reached.
+   * <p>
+   * An invocation of the form:
+   * 
+   * <pre>
+   * src.copyTo(b, offset)
+   * </pre>
+   * 
+   * Behaves in exactly the same way as the invocation:
+   * 
+   * <pre>
+   * int len = Math.min(src.length(), b.length - offset);
+   * for (int i = 0; i &lt; len; i++)
+   *   b[offset + i] = src.get(i);
+   * </pre>
+   * 
+   * Except that it is potentially much more efficient.
+   * 
+   * @param b
+   *          The byte array to which bytes are to be copied.
+   * @param offset
+   *          The offset within the array of the first byte to be
+   *          written; must be non-negative and no larger than b.length.
+   * @return The byte array.
+   * @throws IndexOutOfBoundsException
+   *           If {@code offset} is negative.
+   */
+  byte[] copyTo(byte[] b, int offset) throws IndexOutOfBoundsException;
+
+
+
+  /**
+   * Appends the entire contents of this byte sequence to the provided
+   * {@link ByteStringBuilder}.
+   * 
+   * @param builder
+   *          The builder to copy to.
+   * @return The builder.
+   */
+  ByteStringBuilder copyTo(ByteStringBuilder builder);
+
+
+
+  /**
+   * Copies the entire contents of this byte sequence to the provided
+   * {@code OutputStream}.
+   * 
+   * @param stream
+   *          The {@code OutputStream} to copy to.
+   * @return The {@code OutputStream}.
+   * @throws IOException
+   *           If an error occurs while writing to the {@code
+   *           OutputStream}.
+   */
+  OutputStream copyTo(OutputStream stream) throws IOException;
+
+
+
+  /**
+   * Indicates whether the provided byte array sub-sequence is equal to
+   * this byte sequence. In order for it to be considered equal, the
+   * provided byte array sub-sequence must contain the same bytes in the
+   * same order.
+   * 
+   * @param b
+   *          The byte array for which to make the determination.
+   * @param offset
+   *          The offset of the sub-sequence in the byte array to be
+   *          compared; must be non-negative and no larger than {@code
+   *          b.length} .
+   * @param length
+   *          The length of the sub-sequence in the byte array to be
+   *          compared; must be non-negative and no larger than {@code
+   *          b.length - offset}.
+   * @return {@code true} if the content of the provided byte array
+   *         sub-sequence is equal to that of this byte sequence, or
+   *         {@code false} if not.
+   * @throws IndexOutOfBoundsException
+   *           If {@code offset} is negative or if {@code length} is
+   *           negative or if {@code offset + length} is greater than
+   *           {@code b.length}.
+   */
+  boolean equals(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException;
+
+
+
+  /**
+   * Indicates whether the provided object is equal to this byte
+   * sequence. In order for it to be considered equal, the provided
+   * object must be a byte sequence containing the same bytes in the
+   * same order.
+   * 
+   * @param o
+   *          The object for which to make the determination.
+   * @return {@code true} if the provided object is a byte sequence
+   *         whose content is equal to that of this byte sequence, or
+   *         {@code false} if not.
+   */
+  boolean equals(Object o);
+
+
+
+  /**
+   * Returns a hash code for this byte sequence. It will be the sum of
+   * all of the bytes contained in the byte sequence.
+   * 
+   * @return A hash code for this byte sequence.
+   */
+  int hashCode();
+
+
+
+  /**
+   * Returns the length of this byte sequence.
+   * 
+   * @return The length of this byte sequence.
+   */
+  int length();
+
+
+
+  /**
+   * Returns a new byte sequence that is a subsequence of this byte
+   * sequence.
+   * <p>
+   * The subsequence starts with the byte value at the specified {@code
+   * start} index and ends with the byte value at index {@code end - 1}.
+   * The length (in bytes) of the returned sequence is {@code end -
+   * start}, so if {@code start == end} then an empty sequence is
+   * returned.
+   * <p>
+   * <b>NOTE:</b> changes to the underlying byte sequence (if mutable)
+   * may render the returned sub-sequence invalid.
+   * 
+   * @param start
+   *          The start index, inclusive.
+   * @param end
+   *          The end index, exclusive.
+   * @return The newly created byte subsequence.
+   * @throws IndexOutOfBoundsException
+   *           If {@code start} or {@code end} are negative, if {@code
+   *           end} is greater than {@code length()}, or if {@code
+   *           start} is greater than {@code end}.
+   */
+  ByteSequence subSequence(int start, int end)
+      throws IndexOutOfBoundsException;
+
+
+
+  /**
+   * Returns a byte array containing the bytes in this sequence in the
+   * same order as this sequence. The length of the byte array will be
+   * the length of this sequence.
+   * <p>
+   * An invocation of the form:
+   * 
+   * <pre>
+   * src.toByteArray()
+   * </pre>
+   * 
+   * Behaves in exactly the same way as the invocation:
+   * 
+   * <pre>
+   * src.copyTo(new byte[src.length()]);
+   * </pre>
+   * 
+   * @return A byte array consisting of exactly this sequence of bytes.
+   */
+  byte[] toByteArray();
+
+
+
+  /**
+   * Returns the {@link ByteString} representation of this byte
+   * sequence.
+   * 
+   * @return The {@link ByteString} representation of this byte
+   *         sequence.
+   */
+  ByteString toByteString();
+
+
+
+  /**
+   * Returns the UTF-8 decoded string representation of this byte
+   * sequence. If UTF-8 decoding fails, the platform's default encoding
+   * will be used.
+   * 
+   * @return The string representation of this byte sequence.
+   */
+  String toString();
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java b/sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java
new file mode 100644
index 0000000..7f77f30
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteSequenceOutputStream.java
@@ -0,0 +1,113 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2008 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An adapter class that allows writing to an byte string builder
+ * with the outputstream interface.
+ */
+public final class ByteSequenceOutputStream extends OutputStream {
+
+  private final ByteStringBuilder buffer;
+
+  /**
+   * Creates a new byte string builder output stream.
+   *
+   * @param buffer
+   *          The underlying byte string builder.
+   */
+  public ByteSequenceOutputStream(ByteStringBuilder buffer)
+  {
+    this.buffer = buffer;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void write(int i) throws IOException {
+    buffer.append(((byte) (i & 0xFF)));
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void write(byte[] bytes) throws IOException {
+    buffer.append(bytes);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void write(byte[] bytes, int i, int i1) throws IOException {
+    buffer.append(bytes, i, i1);
+  }
+
+  /**
+   * Gets the length of the underlying byte string builder.
+   *
+   * @return The length of the underlying byte string builder.
+   */
+  public int length() {
+    return buffer.length();
+  }
+
+
+
+  /**
+   * Writes the content of the underlying byte string builder to the
+   * provided output stream.
+   *
+   * @param stream
+   *          The output stream.
+   * @throws IOException
+   *           If an I/O error occurs. In particular, an {@code
+   *           IOException} is thrown if the output stream is closed.
+   */
+  public void writeTo(OutputStream stream) throws IOException
+  {
+    buffer.copyTo(stream);
+  }
+
+  /**
+   * Resets this output stream such that the underlying byte string
+   * builder is empty.
+   */
+  public void reset()
+  {
+    buffer.clear();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException {
+    buffer.clear();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteSequenceReader.java b/sdk/src/org/opends/sdk/util/ByteSequenceReader.java
new file mode 100755
index 0000000..35ca336
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteSequenceReader.java
@@ -0,0 +1,510 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+/**
+ * An interface for iteratively reading date from a {@link ByteSequence}
+ * . {@code ByteSequenceReader} must be created using the associated
+ * {@code ByteSequence}'s {@code asReader()} method.
+ */
+public final class ByteSequenceReader
+{
+
+  // The current position in the byte sequence.
+  private int pos = 0;
+
+  // The underlying byte sequence.
+  private final ByteSequence sequence;
+
+
+
+  /**
+   * Creates a new byte sequence reader whose source is the provided
+   * byte sequence.
+   * <p>
+   * <b>NOTE:</b> any concurrent changes to the underlying byte sequence
+   * (if mutable) may cause subsequent reads to overrun and fail.
+   * <p>
+   * This constructor is package private: construction must be performed
+   * using {@link ByteSequence#asReader()}.
+   * 
+   * @param sequence
+   *          The byte sequence to be read.
+   */
+  ByteSequenceReader(ByteSequence sequence)
+  {
+    this.sequence = sequence;
+  }
+
+
+
+  /**
+   * Relative get method. Reads the byte at the current position.
+   * 
+   * @return The byte at this reader's current position.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < 1}.
+   */
+  public byte get() throws IndexOutOfBoundsException
+  {
+    final byte b = sequence.byteAt(pos);
+    pos++;
+    return b;
+  }
+
+
+
+  /**
+   * Relative bulk get method. This method transfers bytes from this
+   * reader into the given destination array. An invocation of this
+   * method of the form:
+   * 
+   * <pre>
+   * src.get(b);
+   * </pre>
+   * 
+   * Behaves in exactly the same way as the invocation:
+   * 
+   * <pre>
+   * src.get(b, 0, b.length);
+   * </pre>
+   * 
+   * @param b
+   *          The byte array into which bytes are to be written.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < b.length}.
+   */
+  public void get(byte[] b) throws IndexOutOfBoundsException
+  {
+    get(b, 0, b.length);
+  }
+
+
+
+  /**
+   * Relative bulk get method. Copies {@code length} bytes from this
+   * reader into the given array, starting at the current position of
+   * this reader and at the given {@code offset} in the array. The
+   * position of this reader is then incremented by {@code length}. In
+   * other words, an invocation of this method of the form:
+   * 
+   * <pre>
+   * src.get(b, offset, length);
+   * </pre>
+   * 
+   * Has exactly the same effect as the loop:
+   * 
+   * <pre>
+   * for (int i = offset; i &lt; offset + length; i++)
+   *   b[i] = src.get();
+   * </pre>
+   * 
+   * Except that it first checks that there are sufficient bytes in this
+   * buffer and it is potentially much more efficient.
+   * 
+   * @param b
+   *          The byte array into which bytes are to be written.
+   * @param offset
+   *          The offset within the array of the first byte to be
+   *          written; must be non-negative and no larger than {@code
+   *          b.length}.
+   * @param length
+   *          The number of bytes to be written to the given array; must
+   *          be non-negative and no larger than {@code b.length} .
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < length}.
+   */
+  public void get(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    if (offset < 0 || length < 0 || offset + length > b.length
+        || length > remaining())
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    sequence.subSequence(pos, pos + length).copyTo(b, offset);
+    pos += length;
+  }
+
+
+
+  /**
+   * Relative get method for reading a multi-byte BER length. Reads the
+   * next one to five bytes at this reader's current position, composing
+   * them into a integer value and then increments the position by the
+   * number of bytes read.
+   * 
+   * @return The integer value representing the length at this reader's
+   *         current position.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request.
+   */
+  public int getBERLength() throws IndexOutOfBoundsException
+  {
+    // Make sure we have at least one byte to read.
+    int newPos = pos + 1;
+    if (newPos > sequence.length())
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    int length = sequence.byteAt(pos) & 0x7F;
+    if (length != sequence.byteAt(pos))
+    {
+      // Its a multi-byte length
+      final int numLengthBytes = length;
+      newPos = pos + 1 + numLengthBytes;
+      // Make sure we have the bytes needed
+      if (numLengthBytes > 4 || newPos > sequence.length())
+      {
+        // Shouldn't have more than 4 bytes
+        throw new IndexOutOfBoundsException();
+      }
+
+      length = 0x00;
+      for (int i = pos + 1; i < newPos; i++)
+      {
+        length = length << 8 | sequence.byteAt(i) & 0xFF;
+      }
+    }
+
+    pos = newPos;
+    return length;
+  }
+
+
+
+  /**
+   * Relative bulk get method. Returns a {@link ByteSequence} whose
+   * content is the next {@code length} bytes from this reader, starting
+   * at the current position of this reader. The position of this reader
+   * is then incremented by {@code length}.
+   * <p>
+   * <b>NOTE:</b> The value returned from this method should NEVER be
+   * cached as it prevents the contents of the underlying byte stream
+   * from being garbage collected.
+   * 
+   * @param length
+   *          The length of the byte sequence to be returned.
+   * @return The byte sequence whose content is the next {@code length}
+   *         bytes from this reader.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < length}.
+   */
+  public ByteSequence getByteSequence(int length)
+      throws IndexOutOfBoundsException
+  {
+    final int newPos = pos + length;
+    final ByteSequence subSequence = sequence.subSequence(pos, newPos);
+    pos = newPos;
+    return subSequence;
+  }
+
+
+
+  /**
+   * Relative bulk get method. Returns a {@link ByteString} whose
+   * content is the next {@code length} bytes from this reader, starting
+   * at the current position of this reader. The position of this reader
+   * is then incremented by {@code length}.
+   * <p>
+   * An invocation of this method of the form:
+   * 
+   * <pre>
+   * src.getByteString(length);
+   * </pre>
+   * 
+   * Has exactly the same effect as:
+   * 
+   * <pre>
+   * src.getByteSequence(length).toByteString();
+   * </pre>
+   * 
+   * <b>NOTE:</b> The value returned from this method should NEVER be
+   * cached as it prevents the contents of the underlying byte stream
+   * from being garbage collected.
+   * 
+   * @param length
+   *          The length of the byte string to be returned.
+   * @return The byte string whose content is the next {@code length}
+   *         bytes from this reader.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < length}.
+   */
+  public ByteString getByteString(int length)
+      throws IndexOutOfBoundsException
+  {
+    return getByteSequence(length).toByteString();
+  }
+
+
+
+  /**
+   * Relative get method for reading an integer value. Reads the next
+   * four bytes at this reader's current position, composing them into
+   * an integer value according to big-endian byte order, and then
+   * increments the position by four.
+   * 
+   * @return The integer value at this reader's current position.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < 4}.
+   */
+  public int getInt() throws IndexOutOfBoundsException
+  {
+    if (remaining() < 4)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    int v = 0;
+    for (int i = 0; i < 4; i++)
+    {
+      v <<= 8;
+      v |= sequence.byteAt(pos++) & 0xFF;
+    }
+
+    return v;
+  }
+
+
+
+  /**
+   * Relative get method for reading a long value. Reads the next eight
+   * bytes at this reader's current position, composing them into a long
+   * value according to big-endian byte order, and then increments the
+   * position by eight.
+   * 
+   * @return The long value at this reader's current position.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < 8}.
+   */
+  public long getLong() throws IndexOutOfBoundsException
+  {
+    if (remaining() < 8)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    long v = 0;
+    for (int i = 0; i < 8; i++)
+    {
+      v <<= 8;
+      v |= sequence.byteAt(pos++) & 0xFF;
+    }
+
+    return v;
+  }
+
+
+
+  /**
+   * Relative get method for reading an short value. Reads the next 2
+   * bytes at this reader's current position, composing them into an
+   * short value according to big-endian byte order, and then increments
+   * the position by two.
+   * 
+   * @return The integer value at this reader's current position.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < 2}.
+   */
+  public short getShort() throws IndexOutOfBoundsException
+  {
+    if (remaining() < 2)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    short v = 0;
+    for (int i = 0; i < 2; i++)
+    {
+      v <<= 8;
+      v |= sequence.byteAt(pos++) & 0xFF;
+    }
+
+    return v;
+  }
+
+
+
+  /**
+   * Relative get method for reading a UTF-8 encoded string. Reads the
+   * next number of specified bytes at this reader's current position,
+   * decoding them into a string using UTF-8 and then increments the
+   * position by the number of bytes read. If UTF-8 decoding fails, the
+   * platform's default encoding will be used.
+   * 
+   * @param length
+   *          The number of bytes to read and decode.
+   * @return The string value at the reader's current position.
+   * @throws IndexOutOfBoundsException
+   *           If there are fewer bytes remaining in this reader than
+   *           are required to satisfy the request, that is, if {@code
+   *           remaining() < length}.
+   */
+  public String getString(int length) throws IndexOutOfBoundsException
+  {
+    if (remaining() < length)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    final int newPos = pos + length;
+    final String str =
+        sequence.subSequence(pos, pos + length).toString();
+    pos = newPos;
+    return str;
+  }
+
+
+
+  /**
+   * Returns this reader's position.
+   * 
+   * @return The position of this reader.
+   */
+  public int position()
+  {
+    return pos;
+  }
+
+
+
+  /**
+   * Sets this reader's position.
+   * 
+   * @param pos
+   *          The new position value; must be non-negative and no larger
+   *          than the length of the underlying byte sequence.
+   * @throws IndexOutOfBoundsException
+   *           If the position is negative or larger than the length of
+   *           the underlying byte sequence.
+   */
+  public void position(int pos) throws IndexOutOfBoundsException
+  {
+    if (pos > sequence.length() || pos < 0)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    this.pos = pos;
+  }
+
+
+
+  /**
+   * Returns the number of bytes between the current position and the
+   * end of the underlying byte sequence.
+   * 
+   * @return The number of bytes between the current position and the
+   *         end of the underlying byte sequence.
+   */
+  public int remaining()
+  {
+    return sequence.length() - pos;
+  }
+
+
+
+  /**
+   * Rewinds this reader's position to zero.
+   * <p>
+   * An invocation of this method of the form:
+   * 
+   * <pre>
+   * src.rewind();
+   * </pre>
+   * 
+   * Has exactly the same effect as:
+   * 
+   * <pre>
+   * src.position(0);
+   * </pre>
+   */
+  public void rewind()
+  {
+    position(0);
+  }
+
+
+
+  /**
+   * Skips the given number of bytes. Negative values are allowed.
+   * <p>
+   * An invocation of this method of the form:
+   * 
+   * <pre>
+   * src.skip(length);
+   * </pre>
+   * 
+   * Has exactly the same effect as:
+   * 
+   * <pre>
+   * src.position(position() + length);
+   * </pre>
+   * 
+   * @param length
+   *          The number of bytes to skip.
+   * @throws IndexOutOfBoundsException
+   *           If the new position is less than 0 or greater than the
+   *           length of the underlying byte sequence.
+   */
+  public void skip(int length) throws IndexOutOfBoundsException
+  {
+    position(pos + length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return sequence.toString();
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteString.java b/sdk/src/org/opends/sdk/util/ByteString.java
new file mode 100755
index 0000000..c626b2d
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteString.java
@@ -0,0 +1,681 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.logging.Level;
+
+
+
+/**
+ * An immutable sequence of bytes backed by a byte array.
+ */
+public final class ByteString implements ByteSequence
+{
+
+  // Singleton empty byte string.
+  private static final ByteString EMPTY = wrap(new byte[0]);
+
+
+
+  /**
+   * Returns an empty byte string.
+   * 
+   * @return An empty byte string.
+   */
+  public static ByteString empty()
+  {
+    return EMPTY;
+  }
+
+
+
+  /**
+   * Returns a byte string containing the big-endian encoded bytes of
+   * the provided integer.
+   * 
+   * @param i
+   *          The integer to encode.
+   * @return The byte string containing the big-endian encoded bytes of
+   *         the provided integer.
+   */
+  public static ByteString valueOf(int i)
+  {
+    final byte[] bytes = new byte[4];
+    for (int j = 3; j >= 0; j--)
+    {
+      bytes[j] = (byte) (i & 0xFF);
+      i >>>= 8;
+    }
+    return wrap(bytes);
+  }
+
+
+
+  /**
+   * Returns a byte string containing the big-endian encoded bytes of
+   * the provided long.
+   * 
+   * @param l
+   *          The long to encode.
+   * @return The byte string containing the big-endian encoded bytes of
+   *         the provided long.
+   */
+  public static ByteString valueOf(long l)
+  {
+    final byte[] bytes = new byte[8];
+    for (int i = 7; i >= 0; i--)
+    {
+      bytes[i] = (byte) (l & 0xFF);
+      l >>>= 8;
+    }
+    return wrap(bytes);
+  }
+
+
+
+  /**
+   * Returns a byte string containing the provided object. If the object
+   * is an instance of {@code ByteSequence} then it is converted to a
+   * byte string using the {@code toByteString()} method. Otherwise a
+   * new byte string is created containing the UTF-8 encoded bytes of
+   * the string representation of the provided object.
+   * 
+   * @param o
+   *          The object to use.
+   * @return The byte string containing the provided object.
+   */
+  public static ByteString valueOf(Object o)
+  {
+    if (o instanceof ByteSequence)
+    {
+      return ((ByteSequence) o).toByteString();
+    }
+    else
+    {
+      return wrap(StaticUtils.getBytes(o.toString()));
+    }
+  }
+
+
+
+  /**
+   * Returns a byte string containing the UTF-8 encoded bytes of the
+   * provided string.
+   * 
+   * @param s
+   *          The string to use.
+   * @return The byte string with the encoded bytes of the provided
+   *         string.
+   */
+  public static ByteString valueOf(String s)
+  {
+    return wrap(StaticUtils.getBytes(s));
+  }
+
+
+
+  /**
+   * Returns a byte string that wraps the provided byte array.
+   * <p>
+   * <b>NOTE:</b> this method takes ownership of the provided byte array
+   * and, therefore, the byte array MUST NOT be altered directly after
+   * this method returns.
+   * 
+   * @param b
+   *          The byte array to wrap.
+   * @return The byte string that wraps the given byte array.
+   */
+  public static ByteString wrap(byte[] b)
+  {
+    return new ByteString(b, 0, b.length);
+  }
+
+
+
+  /**
+   * Returns a byte string that wraps a subsequence of the provided byte
+   * array.
+   * <p>
+   * <b>NOTE:</b> this method takes ownership of the provided byte array
+   * and, therefore, the byte array MUST NOT be altered directly after
+   * this method returns.
+   * 
+   * @param b
+   *          The byte array to wrap.
+   * @param offset
+   *          The offset of the byte array to be used; must be
+   *          non-negative and no larger than {@code b.length} .
+   * @param length
+   *          The length of the byte array to be used; must be
+   *          non-negative and no larger than {@code b.length - offset}.
+   * @return The byte string that wraps the given byte array.
+   * @throws IndexOutOfBoundsException
+   *           If {@code offset} is negative or if {@code length} is
+   *           negative or if {@code offset + length} is greater than
+   *           {@code b.length}.
+   */
+  public static ByteString wrap(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    checkArrayBounds(b, offset, length);
+    return new ByteString(b, offset, length);
+  }
+
+
+
+  /**
+   * Checks the array bounds of the provided byte array sub-sequence,
+   * throwing an {@code IndexOutOfBoundsException} if they are illegal.
+   * 
+   * @param b
+   *          The byte array.
+   * @param offset
+   *          The offset of the byte array to be checked; must be
+   *          non-negative and no larger than {@code b.length}.
+   * @param length
+   *          The length of the byte array to be checked; must be
+   *          non-negative and no larger than {@code b.length - offset}.
+   * @throws IndexOutOfBoundsException
+   *           If {@code offset} is negative or if {@code length} is
+   *           negative or if {@code offset + length} is greater than
+   *           {@code b.length}.
+   */
+  static void checkArrayBounds(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    if (offset < 0 || offset > b.length || length < 0
+        || offset + length > b.length || offset + length < 0)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+  }
+
+
+
+  /**
+   * Compares two byte array sub-sequences and returns a value that
+   * indicates their relative order.
+   * 
+   * @param b1
+   *          The byte array containing the first sub-sequence.
+   * @param offset1
+   *          The offset of the first byte array sub-sequence.
+   * @param length1
+   *          The length of the first byte array sub-sequence.
+   * @param b2
+   *          The byte array containing the second sub-sequence.
+   * @param offset2
+   *          The offset of the second byte array sub-sequence.
+   * @param length2
+   *          The length of the second byte array sub-sequence.
+   * @return A negative integer if first byte array sub-sequence should
+   *         come before the second byte array sub-sequence in ascending
+   *         order, a positive integer if the first byte array
+   *         sub-sequence should come after the byte array sub-sequence
+   *         in ascending order, or zero if there is no difference
+   *         between the two byte array sub-sequences with regard to
+   *         ordering.
+   */
+  static int compareTo(byte[] b1, int offset1, int length1, byte[] b2,
+      int offset2, int length2)
+  {
+    int count = Math.min(length1, length2);
+    int i = offset1;
+    int j = offset2;
+    while (count-- != 0)
+    {
+      final int firstByte = 0xFF & b1[i++];
+      final int secondByte = 0xFF & b2[j++];
+      if (firstByte != secondByte)
+      {
+        return firstByte - secondByte;
+      }
+    }
+    return length1 - length2;
+  }
+
+
+
+  /**
+   * Indicates whether two byte array sub-sequences are equal. In order
+   * for them to be considered equal, they must contain the same bytes
+   * in the same order.
+   * 
+   * @param b1
+   *          The byte array containing the first sub-sequence.
+   * @param offset1
+   *          The offset of the first byte array sub-sequence.
+   * @param length1
+   *          The length of the first byte array sub-sequence.
+   * @param b2
+   *          The byte array containing the second sub-sequence.
+   * @param offset2
+   *          The offset of the second byte array sub-sequence.
+   * @param length2
+   *          The length of the second byte array sub-sequence.
+   * @return {@code true} if the two byte array sub-sequences have the
+   *         same content, or {@code false} if not.
+   */
+  static boolean equals(byte[] b1, int offset1, int length1, byte[] b2,
+      int offset2, int length2)
+  {
+    if (length1 != length2)
+    {
+      return false;
+    }
+
+    int i = offset1;
+    int j = offset2;
+    int count = length1;
+    while (count-- != 0)
+    {
+      if (b1[i++] != b2[j++])
+      {
+        return false;
+      }
+    }
+
+    return true;
+  }
+
+
+
+  /**
+   * Returns a hash code for the provided byte array sub-sequence.
+   * 
+   * @param b
+   *          The byte array.
+   * @param offset
+   *          The offset of the byte array sub-sequence.
+   * @param length
+   *          The length of the byte array sub-sequence.
+   * @return A hash code for the provided byte array sub-sequence.
+   */
+  static int hashCode(byte[] b, int offset, int length)
+  {
+    int hashCode = 1;
+    int i = offset;
+    int count = length;
+    while (count-- != 0)
+    {
+      hashCode = 31 * hashCode + b[i++];
+    }
+    return hashCode;
+  }
+
+
+
+  /**
+   * Returns the UTF-8 decoded string representation of the provided
+   * byte array sub-sequence. If UTF-8 decoding fails, the platform's
+   * default encoding will be used.
+   * 
+   * @param b
+   *          The byte array.
+   * @param offset
+   *          The offset of the byte array sub-sequence.
+   * @param length
+   *          The length of the byte array sub-sequence.
+   * @return The string representation of the byte array sub-sequence.
+   */
+  static String toString(byte[] b, int offset, int length)
+  {
+    String stringValue;
+    try
+    {
+      stringValue = new String(b, offset, length, "UTF-8");
+    }
+    catch (final Exception e)
+    {
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+      {
+        StaticUtils.DEBUG_LOG.warning("Unable to decode ByteString "
+            + "bytes as UTF-8 string: " + e.toString());
+      }
+
+      stringValue = new String(b, offset, length);
+    }
+
+    return stringValue;
+  }
+
+  // These are package private so that compression and crypto
+  // functionality may directly access the fields.
+
+  // The buffer where data is stored.
+  final byte[] buffer;
+
+  // The number of bytes to expose from the buffer.
+  final int length;
+
+  // The start index of the range of bytes to expose through this byte
+  // string.
+  final int offset;
+
+
+
+  /**
+   * Creates a new byte string that wraps a subsequence of the provided
+   * byte array.
+   * <p>
+   * <b>NOTE:</b> this method takes ownership of the provided byte array
+   * and, therefore, the byte array MUST NOT be altered directly after
+   * this method returns.
+   * 
+   * @param b
+   *          The byte array to wrap.
+   * @param offset
+   *          The offset of the byte array to be used; must be
+   *          non-negative and no larger than {@code b.length} .
+   * @param length
+   *          The length of the byte array to be used; must be
+   *          non-negative and no larger than {@code b.length - offset}.
+   */
+  private ByteString(byte[] b, int offset, int length)
+  {
+    this.buffer = b;
+    this.offset = offset;
+    this.length = length;
+  }
+
+
+
+  /**
+   * Returns a {@link ByteSequenceReader} which can be used to
+   * incrementally read and decode data from this byte string.
+   * 
+   * @return The {@link ByteSequenceReader} which can be used to
+   *         incrementally read and decode data from this byte string.
+   */
+  public ByteSequenceReader asReader()
+  {
+    return new ByteSequenceReader(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte byteAt(int index) throws IndexOutOfBoundsException
+  {
+    if (index >= length || index < 0)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+    return buffer[offset + index];
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    checkArrayBounds(b, offset, length);
+    return compareTo(this.buffer, this.offset, this.length, b, offset,
+        length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(ByteSequence o)
+  {
+    if (this == o)
+    {
+      return 0;
+    }
+    return -o.compareTo(buffer, offset, length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte[] copyTo(byte[] b)
+  {
+    copyTo(b, 0);
+    return b;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte[] copyTo(byte[] b, int offset)
+      throws IndexOutOfBoundsException
+  {
+    if (offset < 0)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+    System.arraycopy(buffer, this.offset, b, offset, Math.min(length,
+        b.length - offset));
+    return b;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteStringBuilder copyTo(ByteStringBuilder builder)
+  {
+    builder.append(buffer, offset, length);
+    return builder;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public OutputStream copyTo(OutputStream stream) throws IOException
+  {
+    stream.write(buffer, offset, length);
+    return stream;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    checkArrayBounds(b, offset, length);
+    return equals(this.buffer, this.offset, this.length, b, offset,
+        length);
+  }
+
+
+
+  /**
+   * Indicates whether the provided object is equal to this byte string.
+   * In order for it to be considered equal, the provided object must be
+   * a byte sequence containing the same bytes in the same order.
+   * 
+   * @param o
+   *          The object for which to make the determination.
+   * @return {@code true} if the provided object is a byte sequence
+   *         whose content is equal to that of this byte string, or
+   *         {@code false} if not.
+   */
+  @Override
+  public boolean equals(Object o)
+  {
+    if (this == o)
+    {
+      return true;
+    }
+    else if (o instanceof ByteSequence)
+    {
+      final ByteSequence other = (ByteSequence) o;
+      return other.equals(buffer, offset, length);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Returns a hash code for this byte string. It will be the sum of all
+   * of the bytes contained in the byte string.
+   * 
+   * @return A hash code for this byte string.
+   */
+  @Override
+  public int hashCode()
+  {
+    return hashCode(buffer, offset, length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int length()
+  {
+    return length;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString subSequence(int start, int end)
+      throws IndexOutOfBoundsException
+  {
+    if (start < 0 || start > end || end > length)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+    return new ByteString(buffer, offset + start, end - start);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte[] toByteArray()
+  {
+    return copyTo(new byte[length]);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteString toByteString()
+  {
+    return this;
+  }
+
+
+
+  /**
+   * Returns the integer value represented by the first four bytes of
+   * this byte string in big-endian order.
+   * 
+   * @return The integer value represented by the first four bytes of
+   *         this byte string in big-endian order.
+   * @throws IndexOutOfBoundsException
+   *           If this byte string has less than four bytes.
+   */
+  public int toInt() throws IndexOutOfBoundsException
+  {
+    if (length < 4)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    int v = 0;
+    for (int i = 0; i < 4; i++)
+    {
+      v <<= 8;
+      v |= buffer[offset + i] & 0xFF;
+    }
+    return v;
+  }
+
+
+
+  /**
+   * Returns the long value represented by the first eight bytes of this
+   * byte string in big-endian order.
+   * 
+   * @return The long value represented by the first eight bytes of this
+   *         byte string in big-endian order.
+   * @throws IndexOutOfBoundsException
+   *           If this byte string has less than eight bytes.
+   */
+  public long toLong() throws IndexOutOfBoundsException
+  {
+    if (length < 8)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    long v = 0;
+    for (int i = 0; i < 8; i++)
+    {
+      v <<= 8;
+      v |= buffer[offset + i] & 0xFF;
+    }
+    return v;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return toString(buffer, offset, length);
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ByteStringBuilder.java b/sdk/src/org/opends/sdk/util/ByteStringBuilder.java
new file mode 100755
index 0000000..efb5ed0
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ByteStringBuilder.java
@@ -0,0 +1,1108 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.logging.Level;
+
+
+
+/**
+ * A mutable sequence of bytes backed by a byte array.
+ */
+public final class ByteStringBuilder implements ByteSequence
+{
+
+  /**
+   * A sub-sequence of the parent byte string builder. The sub-sequence
+   * will be robust against all updates to the byte string builder
+   * except for invocations of the method {@code clear()}.
+   */
+  private final class SubSequence implements ByteSequence
+  {
+
+    // The length of the sub-sequence.
+    private final int subLength;
+
+    // The offset of the sub-sequence.
+    private final int subOffset;
+
+
+
+    /**
+     * Creates a new sub-sequence.
+     * 
+     * @param offset
+     *          The offset of the sub-sequence.
+     * @param length
+     *          The length of the sub-sequence.
+     */
+    private SubSequence(int offset, int length)
+    {
+      this.subOffset = offset;
+      this.subLength = length;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public ByteSequenceReader asReader()
+    {
+      return new ByteSequenceReader(this);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public byte byteAt(int index) throws IndexOutOfBoundsException
+    {
+      if (index >= subLength || index < 0)
+      {
+        throw new IndexOutOfBoundsException();
+      }
+
+      // Protect against reallocation: use builder's buffer.
+      return buffer[subOffset + index];
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(byte[] b, int offset, int length)
+        throws IndexOutOfBoundsException
+    {
+      ByteString.checkArrayBounds(b, offset, length);
+
+      // Protect against reallocation: use builder's buffer.
+      return ByteString.compareTo(buffer, subOffset, subLength, b,
+          offset, length);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int compareTo(ByteSequence o)
+    {
+      if (this == o)
+      {
+        return 0;
+      }
+
+      // Protect against reallocation: use builder's buffer.
+      return -o.compareTo(buffer, subOffset, subLength);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public byte[] copyTo(byte[] b)
+    {
+      copyTo(b, 0);
+      return b;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public byte[] copyTo(byte[] b, int offset)
+        throws IndexOutOfBoundsException
+    {
+      if (offset < 0)
+      {
+        throw new IndexOutOfBoundsException();
+      }
+
+      // Protect against reallocation: use builder's buffer.
+      System.arraycopy(buffer, subOffset, b, offset, Math.min(
+          subLength, b.length - offset));
+      return b;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public ByteStringBuilder copyTo(ByteStringBuilder builder)
+    {
+      // Protect against reallocation: use builder's buffer.
+      return builder.append(buffer, subOffset, subLength);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public OutputStream copyTo(OutputStream stream) throws IOException
+    {
+      // Protect against reallocation: use builder's buffer.
+      stream.write(buffer, subOffset, subLength);
+      return stream;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean equals(byte[] b, int offset, int length)
+        throws IndexOutOfBoundsException
+    {
+      ByteString.checkArrayBounds(b, offset, length);
+
+      // Protect against reallocation: use builder's buffer.
+      return ByteString.equals(buffer, subOffset, subLength, b, offset,
+          length);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object o)
+    {
+      if (this == o)
+      {
+        return true;
+      }
+      else if (o instanceof ByteSequence)
+      {
+        final ByteSequence other = (ByteSequence) o;
+
+        // Protect against reallocation: use builder's buffer.
+        return other.equals(buffer, subOffset, subLength);
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode()
+    {
+      // Protect against reallocation: use builder's buffer.
+      return ByteString.hashCode(buffer, subOffset, subLength);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public int length()
+    {
+      return subLength;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public ByteSequence subSequence(int start, int end)
+        throws IndexOutOfBoundsException
+    {
+      if (start < 0 || start > end || end > subLength)
+      {
+        throw new IndexOutOfBoundsException();
+      }
+
+      return new SubSequence(subOffset + start, end - start);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public byte[] toByteArray()
+    {
+      return copyTo(new byte[subLength]);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public ByteString toByteString()
+    {
+      // Protect against reallocation: use builder's buffer.
+      final byte[] b = new byte[subLength];
+      System.arraycopy(buffer, subOffset, b, 0, subLength);
+      return ByteString.wrap(b);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString()
+    {
+      // Protect against reallocation: use builder's buffer.
+      return ByteString.toString(buffer, subOffset, subLength);
+    }
+  }
+
+  // These are package private so that compression and crypto
+  // functionality may directly access the fields.
+
+  // The buffer where data is stored.
+  byte[] buffer;
+
+  // The number of bytes to expose from the buffer.
+  int length;
+
+
+
+  /**
+   * Creates a new byte string builder with an initial capacity of 32
+   * bytes.
+   */
+  public ByteStringBuilder()
+  {
+    // Initially create a 32 byte buffer.
+    this(32);
+  }
+
+
+
+  /**
+   * Creates a new byte string builder with the specified initial
+   * capacity.
+   * 
+   * @param capacity
+   *          The initial capacity.
+   * @throws IllegalArgumentException
+   *           If the {@code capacity} is negative.
+   */
+  public ByteStringBuilder(int capacity)
+      throws IllegalArgumentException
+  {
+    if (capacity < 0)
+    {
+      throw new IllegalArgumentException();
+    }
+
+    this.buffer = new byte[capacity];
+    this.length = 0;
+  }
+
+
+
+  /**
+   * Appends the provided byte to this byte string builder.
+   * 
+   * @param b
+   *          The byte to be appended to this byte string builder.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder append(byte b)
+  {
+    ensureAdditionalCapacity(1);
+    buffer[length++] = b;
+    return this;
+  }
+
+
+
+  /**
+   * Appends the provided byte array to this byte string builder.
+   * <p>
+   * An invocation of the form:
+   * 
+   * <pre>
+   * src.append(b)
+   * </pre>
+   * 
+   * Behaves in exactly the same way as the invocation:
+   * 
+   * <pre>
+   * src.append(b, 0, b.length);
+   * </pre>
+   * 
+   * @param b
+   *          The byte array to be appended to this byte string builder.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder append(byte[] b)
+  {
+    return append(b, 0, b.length);
+  }
+
+
+
+  /**
+   * Appends the provided byte array to this byte string builder.
+   * 
+   * @param b
+   *          The byte array to be appended to this byte string builder.
+   * @param offset
+   *          The offset of the byte array to be used; must be
+   *          non-negative and no larger than {@code b.length} .
+   * @param length
+   *          The length of the byte array to be used; must be
+   *          non-negative and no larger than {@code b.length - offset}.
+   * @return This byte string builder.
+   * @throws IndexOutOfBoundsException
+   *           If {@code offset} is negative or if {@code length} is
+   *           negative or if {@code offset + length} is greater than
+   *           {@code b.length}.
+   */
+  public ByteStringBuilder append(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    ByteString.checkArrayBounds(b, offset, length);
+
+    if (length != 0)
+    {
+      ensureAdditionalCapacity(length);
+      System.arraycopy(b, offset, buffer, this.length, length);
+      this.length += length;
+    }
+
+    return this;
+  }
+
+
+
+  /**
+   * Appends the provided {@code ByteBuffer} to this byte string
+   * builder.
+   * 
+   * @param buffer
+   *          The byte buffer to be appended to this byte string
+   *          builder.
+   * @param length
+   *          The number of bytes to be appended from {@code buffer}.
+   * @return This byte string builder.
+   * @throws IndexOutOfBoundsException
+   *           If {@code length} is less than zero or greater than
+   *           {@code buffer.remaining()}.
+   */
+  public ByteStringBuilder append(ByteBuffer buffer, int length)
+      throws IndexOutOfBoundsException
+  {
+    if (length < 0 || length > buffer.remaining())
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    if (length != 0)
+    {
+      ensureAdditionalCapacity(length);
+      buffer.get(this.buffer, this.length, length);
+      this.length += length;
+    }
+
+    return this;
+  }
+
+
+
+  /**
+   * Appends the provided {@link ByteSequence} to this byte string
+   * builder.
+   * 
+   * @param bytes
+   *          The byte sequence to be appended to this byte string
+   *          builder.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder append(ByteSequence bytes)
+  {
+    return bytes.copyTo(this);
+  }
+
+
+
+  /**
+   * Appends the provided {@link ByteSequenceReader} to this byte string
+   * builder.
+   * 
+   * @param reader
+   *          The byte sequence reader to be appended to this byte
+   *          string builder.
+   * @param length
+   *          The number of bytes to be appended from {@code reader}.
+   * @return This byte string builder.
+   * @throws IndexOutOfBoundsException
+   *           If {@code length} is less than zero or greater than
+   *           {@code reader.remaining()}.
+   */
+  public ByteStringBuilder append(ByteSequenceReader reader, int length)
+      throws IndexOutOfBoundsException
+  {
+    if (length < 0 || length > reader.remaining())
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    if (length != 0)
+    {
+      ensureAdditionalCapacity(length);
+      reader.get(buffer, this.length, length);
+      this.length += length;
+    }
+
+    return this;
+  }
+
+
+
+  /**
+   * Appends the provided {@code InputStream} to this byte string
+   * builder.
+   * 
+   * @param stream
+   *          The input stream to be appended to this byte string
+   *          builder.
+   * @param length
+   *          The maximum number of bytes to be appended from {@code
+   *          buffer}.
+   * @return The number of bytes read from the input stream, or {@code
+   *         -1} if the end of the input stream has been reached.
+   * @throws IndexOutOfBoundsException
+   *           If {@code length} is less than zero.
+   * @throws IOException
+   *           If an I/O error occurs.
+   */
+  public int append(InputStream stream, int length)
+      throws IndexOutOfBoundsException, IOException
+  {
+    if (length < 0)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    ensureAdditionalCapacity(length);
+    final int bytesRead = stream.read(buffer, this.length, length);
+    if (bytesRead > 0)
+    {
+      this.length += bytesRead;
+    }
+
+    return bytesRead;
+  }
+
+
+
+  /**
+   * Appends the big-endian encoded bytes of the provided integer to
+   * this byte string builder.
+   * 
+   * @param i
+   *          The integer whose big-endian encoding is to be appended to
+   *          this byte string builder.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder append(int i)
+  {
+    ensureAdditionalCapacity(4);
+    for (int j = length + 3; j >= length; j--)
+    {
+      buffer[j] = (byte) (i & 0xFF);
+      i >>>= 8;
+    }
+    length += 4;
+    return this;
+  }
+
+
+
+  /**
+   * Appends the big-endian encoded bytes of the provided long to this
+   * byte string builder.
+   * 
+   * @param l
+   *          The long whose big-endian encoding is to be appended to
+   *          this byte string builder.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder append(long l)
+  {
+    ensureAdditionalCapacity(8);
+    for (int i = length + 7; i >= length; i--)
+    {
+      buffer[i] = (byte) (l & 0xFF);
+      l >>>= 8;
+    }
+    length += 8;
+    return this;
+  }
+
+
+
+  /**
+   * Appends the big-endian encoded bytes of the provided short to this
+   * byte string builder.
+   * 
+   * @param i
+   *          The short whose big-endian encoding is to be appended to
+   *          this byte string builder.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder append(short i)
+  {
+    ensureAdditionalCapacity(2);
+    for (int j = length + 1; j >= length; j--)
+    {
+      buffer[j] = (byte) (i & 0xFF);
+      i >>>= 8;
+    }
+    length += 2;
+    return this;
+  }
+
+
+
+  /**
+   * Appends the UTF-8 encoded bytes of the provided string to this byte
+   * string builder.
+   * 
+   * @param s
+   *          The string whose UTF-8 encoding is to be appended to this
+   *          byte string builder.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder append(String s)
+  {
+    if (s == null)
+    {
+      return this;
+    }
+
+    // Assume that each char is 1 byte
+    final int len = s.length();
+    ensureAdditionalCapacity(len);
+
+    for (int i = 0; i < len; i++)
+    {
+      final char c = s.charAt(i);
+      final byte b = (byte) (c & 0x0000007F);
+
+      if (c == b)
+      {
+        buffer[this.length + i] = b;
+      }
+      else
+      {
+        // There is a multi-byte char. Defer to JDK
+        try
+        {
+          return append(s.getBytes("UTF-8"));
+        }
+        catch (final Exception e)
+        {
+          if (StaticUtils.DEBUG_LOG.isLoggable(Level.WARNING))
+          {
+            StaticUtils.DEBUG_LOG.warning("Unable to encode String "
+                + "to UTF-8 bytes: " + e.toString());
+          }
+
+          return append(s.getBytes());
+        }
+      }
+    }
+
+    // The 1 byte char assumption was correct
+    this.length += len;
+    return this;
+  }
+
+
+
+  /**
+   * Appends the ASN.1 BER length encoding representation of the
+   * provided integer to this byte string builder.
+   * 
+   * @param length
+   *          The value to encode using the BER length encoding rules.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder appendBERLength(int length)
+  {
+    if ((length & 0x0000007F) == length)
+    {
+      ensureAdditionalCapacity(1);
+
+      buffer[this.length++] = (byte) (length & 0xFF);
+    }
+    else if ((length & 0x000000FF) == length)
+    {
+      ensureAdditionalCapacity(2);
+
+      buffer[this.length++] = (byte) 0x81;
+      buffer[this.length++] = (byte) (length & 0xFF);
+    }
+    else if ((length & 0x0000FFFF) == length)
+    {
+      ensureAdditionalCapacity(3);
+
+      buffer[this.length++] = (byte) 0x82;
+      buffer[this.length++] = (byte) (length >> 8 & 0xFF);
+      buffer[this.length++] = (byte) (length & 0xFF);
+    }
+    else if ((length & 0x00FFFFFF) == length)
+    {
+      ensureAdditionalCapacity(4);
+
+      buffer[this.length++] = (byte) 0x83;
+      buffer[this.length++] = (byte) (length >> 16 & 0xFF);
+      buffer[this.length++] = (byte) (length >> 8 & 0xFF);
+      buffer[this.length++] = (byte) (length & 0xFF);
+    }
+    else
+    {
+      ensureAdditionalCapacity(5);
+
+      buffer[this.length++] = (byte) 0x84;
+      buffer[this.length++] = (byte) (length >> 24 & 0xFF);
+      buffer[this.length++] = (byte) (length >> 16 & 0xFF);
+      buffer[this.length++] = (byte) (length >> 8 & 0xFF);
+      buffer[this.length++] = (byte) (length & 0xFF);
+    }
+    return this;
+  }
+
+
+
+  /**
+   * Returns a {@link ByteSequenceReader} which can be used to
+   * incrementally read and decode data from this byte string builder.
+   * <p>
+   * <b>NOTE:</b> all concurrent updates to this byte string builder are
+   * supported with the exception of {@link #clear()}. Any invocations
+   * of {@link #clear()} must be accompanied by a subsequent call to
+   * {@code ByteSequenceReader.rewind()}.
+   * 
+   * @return The {@link ByteSequenceReader} which can be used to
+   *         incrementally read and decode data from this byte string
+   *         builder.
+   * @see #clear()
+   */
+  public ByteSequenceReader asReader()
+  {
+    return new ByteSequenceReader(this);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte byteAt(int index) throws IndexOutOfBoundsException
+  {
+    if (index >= length || index < 0)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+    return buffer[index];
+  }
+
+
+
+  /**
+   * Sets the length of this byte string builder to zero.
+   * <p>
+   * <b>NOTE:</b> if this method is called, then {@code
+   * ByteSequenceReader.rewind()} must also be called on any associated
+   * byte sequence readers in order for them to remain valid.
+   * 
+   * @return This byte string builder.
+   * @see #asReader()
+   */
+  public ByteStringBuilder clear()
+  {
+    length = 0;
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    ByteString.checkArrayBounds(b, offset, length);
+    return ByteString.compareTo(this.buffer, 0, this.length, b, offset,
+        length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(ByteSequence o)
+  {
+    if (this == o)
+    {
+      return 0;
+    }
+    return -o.compareTo(buffer, 0, length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte[] copyTo(byte[] b)
+  {
+    copyTo(b, 0);
+    return b;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte[] copyTo(byte[] b, int offset)
+      throws IndexOutOfBoundsException
+  {
+    if (offset < 0)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+    System.arraycopy(buffer, 0, b, offset, Math.min(length, b.length
+        - offset));
+    return b;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public ByteStringBuilder copyTo(ByteStringBuilder builder)
+  {
+    builder.append(buffer, 0, length);
+    return builder;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public OutputStream copyTo(OutputStream stream) throws IOException
+  {
+    stream.write(buffer, 0, length);
+    return stream;
+  }
+
+
+
+  /**
+   * Ensures that the specified number of additional bytes will fit in
+   * this byte string builder and resizes it if necessary.
+   * 
+   * @param size
+   *          The number of additional bytes.
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder ensureAdditionalCapacity(int size)
+  {
+    final int newCount = this.length + size;
+    if (newCount > buffer.length)
+    {
+      final byte[] newbuffer =
+          new byte[Math.max(buffer.length << 1, newCount)];
+      System.arraycopy(buffer, 0, newbuffer, 0, buffer.length);
+      buffer = newbuffer;
+    }
+    return this;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean equals(byte[] b, int offset, int length)
+      throws IndexOutOfBoundsException
+  {
+    ByteString.checkArrayBounds(b, offset, length);
+    return ByteString.equals(this.buffer, 0, this.length, b, offset,
+        length);
+  }
+
+
+
+  /**
+   * Indicates whether the provided object is equal to this byte string
+   * builder. In order for it to be considered equal, the provided
+   * object must be a byte sequence containing the same bytes in the
+   * same order.
+   * 
+   * @param o
+   *          The object for which to make the determination.
+   * @return {@code true} if the provided object is a byte sequence
+   *         whose content is equal to that of this byte string builder,
+   *         or {@code false} if not.
+   */
+  @Override
+  public boolean equals(Object o)
+  {
+    if (this == o)
+    {
+      return true;
+    }
+    else if (o instanceof ByteSequence)
+    {
+      final ByteSequence other = (ByteSequence) o;
+      return other.equals(buffer, 0, length);
+    }
+    else
+    {
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Returns the byte array that backs this byte string builder.
+   * Modifications to this byte string builder's content may cause the
+   * returned array's content to be modified, and vice versa.
+   * <p>
+   * Note that the length of the returned array is only guaranteed to be
+   * the same as the length of this byte string builder immediately
+   * after a call to {@link #trimToSize()}.
+   * <p>
+   * In addition, subsequent modifications to this byte string builder
+   * may cause the backing byte array to be reallocated thus decoupling
+   * the returned byte array from this byte string builder.
+   * 
+   * @return The byte array that backs this byte string builder.
+   */
+  public byte[] getBackingArray()
+  {
+    return buffer;
+  }
+
+
+
+  /**
+   * Returns a hash code for this byte string builder. It will be the
+   * sum of all of the bytes contained in the byte string builder.
+   * <p>
+   * <b>NOTE:</b> subsequent changes to this byte string builder will
+   * invalidate the returned hash code.
+   * 
+   * @return A hash code for this byte string builder.
+   */
+  @Override
+  public int hashCode()
+  {
+    return ByteString.hashCode(buffer, 0, length);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public int length()
+  {
+    return length;
+  }
+
+
+
+  /**
+   * Sets the length of this byte string builder.
+   * <p>
+   * If the <code>newLength</code> argument is less than the current
+   * length, the length is changed to the specified length.
+   * <p>
+   * If the <code>newLength</code> argument is greater than or equal to
+   * the current length, then the capacity is increased and sufficient
+   * null bytes are appended so that length becomes the
+   * <code>newLength</code> argument.
+   * <p>
+   * The <code>newLength</code> argument must be greater than or equal
+   * to <code>0</code>.
+   * 
+   * @param newLength
+   *          The new length.
+   * @return This byte string builder.
+   * @throws IndexOutOfBoundsException
+   *           If the <code>newLength</code> argument is negative.
+   */
+  public ByteStringBuilder setLength(int newLength)
+  {
+    if (newLength < 0)
+    {
+      throw new IndexOutOfBoundsException("Negative newLength: "
+          + newLength);
+    }
+
+    if (newLength > length)
+    {
+      ensureAdditionalCapacity(newLength - length);
+
+      // Pad with zeros.
+      for (int i = length; i < newLength; i++)
+      {
+        buffer[i] = 0;
+      }
+    }
+    length = newLength;
+
+    return this;
+  }
+
+
+
+  /**
+   * Returns a new byte sequence that is a subsequence of this byte
+   * sequence.
+   * <p>
+   * The subsequence starts with the byte value at the specified {@code
+   * start} index and ends with the byte value at index {@code end - 1}.
+   * The length (in bytes) of the returned sequence is {@code end -
+   * start}, so if {@code start == end} then an empty sequence is
+   * returned.
+   * <p>
+   * <b>NOTE:</b> the returned sub-sequence will be robust against all
+   * updates to the byte string builder except for invocations of the
+   * method {@link #clear()}. If a permanent immutable byte sequence is
+   * required then callers should invoke {@code toByteString()} on the
+   * returned byte sequence.
+   * 
+   * @param start
+   *          The start index, inclusive.
+   * @param end
+   *          The end index, exclusive.
+   * @return The newly created byte subsequence.
+   * @throws IndexOutOfBoundsException
+   *           If {@code start} or {@code end} are negative, if {@code
+   *           end} is greater than {@code length()}, or if {@code
+   *           start} is greater than {@code end}.
+   */
+  public ByteSequence subSequence(int start, int end)
+      throws IndexOutOfBoundsException
+  {
+    if (start < 0 || start > end || end > length)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    return new SubSequence(start, end - start);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public byte[] toByteArray()
+  {
+    return copyTo(new byte[length]);
+  }
+
+
+
+  /**
+   * Returns the {@link ByteString} representation of this byte string
+   * builder. Subsequent changes to this byte string builder will not
+   * modify the returned {@link ByteString}.
+   * 
+   * @return The {@link ByteString} representation of this byte
+   *         sequence.
+   */
+  public ByteString toByteString()
+  {
+    final byte[] b = new byte[length];
+    System.arraycopy(buffer, 0, b, 0, length);
+    return ByteString.wrap(b);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString()
+  {
+    return ByteString.toString(buffer, 0, length);
+  }
+
+
+
+  /**
+   * Attempts to reduce storage used for this byte string builder. If
+   * the buffer is larger than necessary to hold its current sequence of
+   * bytes, then it may be resized to become more space efficient.
+   * 
+   * @return This byte string builder.
+   */
+  public ByteStringBuilder trimToSize()
+  {
+    if (buffer.length > length)
+    {
+      final byte[] newBuffer = new byte[length];
+      System.arraycopy(buffer, 0, newBuffer, 0, length);
+      buffer = newBuffer;
+    }
+    return this;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/Function.java b/sdk/src/org/opends/sdk/util/Function.java
new file mode 100644
index 0000000..bb8157e
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Function.java
@@ -0,0 +1,58 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * Functions transform input values of type {@code M} to output values
+ * of type {@code N}.
+ *
+ * @param <M>
+ *          The type of input values transformed by this function.
+ * @param <N>
+ *          The type of output values return by this function.
+ * @param <P>
+ *          The type of the additional parameter to this function's
+ *          {@code apply} method. Use {@link java.lang.Void} for
+ *          functions that do not need an additional parameter.
+ */
+public interface Function<M, N, P>
+{
+  /**
+   * Applies this function to the provided input value of type {@code M}
+   * , returning an output value of type {@code N}.
+   *
+   * @param value
+   *          The value to be transformed.
+   * @param p
+   *          A function specified parameter.
+   * @return The result of the transformation.
+   */
+  N apply(M value, P p);
+}
diff --git a/sdk/src/org/opends/sdk/util/Functions.java b/sdk/src/org/opends/sdk/util/Functions.java
new file mode 100644
index 0000000..02bbea9
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Functions.java
@@ -0,0 +1,345 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import org.opends.sdk.AttributeDescription;
+import org.opends.sdk.DN;
+import org.opends.sdk.schema.Schema;
+
+
+
+/**
+ * Common {@link Function} implementations.
+ */
+public final class Functions
+{
+
+  private static final class FixedFunction<M, N, P> implements
+      Function<M, N, Void>
+  {
+    private final Function<M, N, P> function;
+    private final P parameter;
+
+
+
+    private FixedFunction(Function<M, N, P> function, P p)
+    {
+      this.function = function;
+      this.parameter = p;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public N apply(M value, Void p)
+    {
+      return function.apply(value, parameter);
+    }
+
+  }
+
+  private static final Function<ByteString, AttributeDescription, Schema> BYTESTRING_TO_ATTRIBUTE_DESCRIPTION =
+      new Function<ByteString, AttributeDescription, Schema>()
+      {
+
+        public AttributeDescription apply(ByteString value, Schema p)
+        {
+          // FIXME: what should we do if parsing fails?
+          return AttributeDescription.valueOf(value.toString(), p);
+        }
+      };
+
+  private static final Function<ByteString, Boolean, Void> BYTESTRING_TO_BOOLEAN =
+      new Function<ByteString, Boolean, Void>()
+      {
+
+        public Boolean apply(ByteString value, Void p)
+        {
+          String valueString =
+              StaticUtils.toLowerCase(value.toString());
+
+          if (valueString.equals("true") || valueString.equals("yes")
+              || valueString.equals("on") || valueString.equals("1"))
+          {
+            return Boolean.TRUE;
+          }
+          else if (valueString.equals("false")
+              || valueString.equals("no") || valueString.equals("off")
+              || valueString.equals("0"))
+          {
+            return Boolean.FALSE;
+          }
+          else
+          {
+            throw new NumberFormatException("Invalid boolean value \""
+                + valueString + "\"");
+          }
+        }
+      };
+
+  private static final Function<ByteString, DN, Schema> BYTESTRING_TO_DN =
+      new Function<ByteString, DN, Schema>()
+      {
+
+        public DN apply(ByteString value, Schema p)
+        {
+          // FIXME: what should we do if parsing fails?
+
+          // FIXME: we should have a ByteString valueOf implementation.
+          return DN.valueOf(value.toString(), p);
+        }
+      };
+
+  private static final Function<ByteString, Integer, Void> BYTESTRING_TO_INTEGER =
+      new Function<ByteString, Integer, Void>()
+      {
+
+        public Integer apply(ByteString value, Void p)
+        {
+          // We do not use ByteString.toInt() as we are string based.
+          return Integer.valueOf(value.toString());
+        }
+      };
+
+  private static final Function<ByteString, Long, Void> BYTESTRING_TO_LONG =
+      new Function<ByteString, Long, Void>()
+      {
+
+        public Long apply(ByteString value, Void p)
+        {
+          // We do not use ByteString.toLong() as we are string based.
+          return Long.valueOf(value.toString());
+        }
+      };
+
+  private static final Function<ByteString, String, Void> BYTESTRING_TO_STRING =
+      new Function<ByteString, String, Void>()
+      {
+
+        public String apply(ByteString value, Void p)
+        {
+          return value.toString();
+        }
+      };
+
+  private static final Function<String, String, Void> NORMALIZE_STRING =
+      new Function<String, String, Void>()
+      {
+
+        public String apply(String value, Void p)
+        {
+          return StaticUtils.toLowerCase(value).trim();
+        }
+      };
+
+
+
+  /**
+   * Returns a function which which always invokes {@code function} with
+   * {@code p}.
+   *
+   * @param <M>
+   *          The type of input values transformed by this function.
+   * @param <N>
+   *          The type of output values return by this function.
+   * @param <P>
+   *          The type of the additional parameter to this function's
+   *          {@code apply} method. Use {@link java.lang.Void} for
+   *          functions that do not need an additional parameter.
+   * @param function
+   *          The function to wrap.
+   * @param p
+   *          The parameter which will always be passed to {@code
+   *          function}.
+   * @return A function which which always invokes {@code function} with
+   *         {@code p}.
+   */
+  public static <M, N, P> Function<M, N, Void> fixedFunction(
+      Function<M, N, P> function, P p)
+  {
+    return new FixedFunction<M, N, P>(function, p);
+  }
+
+
+
+  /**
+   * Returns a function which converts a {@code String} to lower case
+   * using {@link StaticUtils#toLowerCase} and then trims it.
+   *
+   * @return A function which converts a {@code String} to lower case
+   *         using {@link StaticUtils#toLowerCase} and then trims it.
+   */
+  public static Function<String, String, Void> normalizeString()
+  {
+    return NORMALIZE_STRING;
+  }
+
+
+
+  /**
+   * Returns a function which parses the string representation of a
+   * {@code ByteString} as an {@code AttributeDescription} using the
+   * default schema. Invalid values will result in a {@code
+   * LocalizedIllegalArgumentException}.
+   *
+   * @return A function which parses the string representation of a
+   *         {@code ByteString} as an {@code AttributeDescription}.
+   */
+  public static Function<ByteString, AttributeDescription, Void> valueToAttributeDescription()
+  {
+    return fixedFunction(BYTESTRING_TO_ATTRIBUTE_DESCRIPTION, Schema
+        .getDefaultSchema());
+  }
+
+
+
+  /**
+   * Returns a function which parses the string representation of a
+   * {@code ByteString} as an {@code AttributeDescription} using the
+   * provided schema. Invalid values will result in a {@code
+   * LocalizedIllegalArgumentException}.
+   *
+   * @param schema
+   *          The schema to use for decoding attribute descriptions.
+   * @return A function which parses the string representation of a
+   *         {@code ByteString} as an {@code AttributeDescription}.
+   */
+  public static Function<ByteString, AttributeDescription, Void> valueToAttributeDescription(
+      Schema schema)
+  {
+    return fixedFunction(BYTESTRING_TO_ATTRIBUTE_DESCRIPTION, schema);
+  }
+
+
+
+  /**
+   * Returns a function which parses the string representation of a
+   * {@code ByteString} to a {@code Boolean}. The function will accept
+   * the values {@code 0}, {@code false}, {@code no}, {@code off},
+   * {@code 1}, {@code true}, {@code yes}, {@code on}. All other values
+   * will result in a {@code NumberFormatException}.
+   *
+   * @return A function which transforms a {@code ByteString} to a
+   *         {@code Boolean}.
+   */
+  public static Function<ByteString, Boolean, Void> valueToBoolean()
+  {
+    return BYTESTRING_TO_BOOLEAN;
+  }
+
+
+
+  /**
+   * Returns a function which parses the string representation of a
+   * {@code ByteString} as a {@code DN} using the default schema.
+   * Invalid values will result in a {@code
+   * LocalizedIllegalArgumentException}.
+   *
+   * @return A function which parses the string representation of a
+   *         {@code ByteString} as an {@code DN}.
+   */
+  public static Function<ByteString, DN, Void> valueToDN()
+  {
+    return fixedFunction(BYTESTRING_TO_DN, Schema.getDefaultSchema());
+  }
+
+
+
+  /**
+   * Returns a function which parses the string representation of a
+   * {@code ByteString} as a {@code DN} using the provided schema.
+   * Invalid values will result in a {@code
+   * LocalizedIllegalArgumentException}.
+   *
+   * @param schema
+   *          The schema to use for decoding DNs.
+   * @return A function which parses the string representation of a
+   *         {@code ByteString} as an {@code DN}.
+   */
+  public static Function<ByteString, DN, Void> valueToDN(Schema schema)
+  {
+    return fixedFunction(BYTESTRING_TO_DN, schema);
+  }
+
+
+
+  /**
+   * Returns a function which parses the string representation of a
+   * {@code ByteString} as an {@code Integer}. Invalid values will
+   * result in a {@code NumberFormatException}.
+   *
+   * @return A function which parses the string representation of a
+   *         {@code ByteString} as an {@code Integer}.
+   */
+  public static Function<ByteString, Integer, Void> valueToInteger()
+  {
+    return BYTESTRING_TO_INTEGER;
+  }
+
+
+
+  /**
+   * Returns a function which parses the string representation of a
+   * {@code ByteString} as a {@code Long}. Invalid values will result in
+   * a {@code NumberFormatException}.
+   *
+   * @return A function which parses the string representation of a
+   *         {@code ByteString} as a {@code Long}.
+   */
+  public static Function<ByteString, Long, Void> valueToLong()
+  {
+    return BYTESTRING_TO_LONG;
+  }
+
+
+
+  /**
+   * Returns a function which parses a {@code ByteString} as a UTF-8
+   * encoded {@code String}.
+   *
+   * @return A function which parses the string representation of a
+   *         {@code ByteString} as a UTF-8 encoded {@code String}.
+   */
+  public static Function<ByteString, String, Void> valueToString()
+  {
+    return BYTESTRING_TO_STRING;
+  }
+
+
+
+  // Prevent instantiation
+  private Functions()
+  {
+    // Do nothing.
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/Iterables.java b/sdk/src/org/opends/sdk/util/Iterables.java
new file mode 100644
index 0000000..0313cdc
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Iterables.java
@@ -0,0 +1,397 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import java.util.Iterator;
+
+
+
+/**
+ * Utility methods for manipulating {@link Iterable}s.
+ */
+public final class Iterables
+{
+  private static final class EmptyIterable<M> implements Iterable<M>
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<M> iterator()
+    {
+      return Iterators.empty();
+    }
+
+  }
+
+
+
+  private static final class FilteredIterable<M, P> implements
+      Iterable<M>
+  {
+
+    private final Iterable<M> iterable;
+    private final P parameter;
+    private final Predicate<? super M, P> predicate;
+
+
+
+    // Constructed via factory methods.
+    private FilteredIterable(Iterable<M> iterable,
+        Predicate<? super M, P> predicate, P p)
+    {
+      this.iterable = iterable;
+      this.predicate = predicate;
+      this.parameter = p;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<M> iterator()
+    {
+      return Iterators
+          .filter(iterable.iterator(), predicate, parameter);
+    }
+
+  }
+
+
+
+  private static final class SingletonIterable<M> implements
+      Iterable<M>
+  {
+
+    private final M value;
+
+
+
+    // Constructed via factory methods.
+    private SingletonIterable(M value)
+    {
+      this.value = value;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<M> iterator()
+    {
+      return Iterators.singleton(value);
+    }
+
+  }
+
+
+
+  private static final class ArrayIterable<M> implements Iterable<M>
+  {
+
+    private final M[] a;
+
+
+
+    // Constructed via factory methods.
+    private ArrayIterable(M[] a)
+    {
+      this.a = a;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<M> iterator()
+    {
+      return Iterators.arrayIterator(a);
+    }
+
+  }
+
+
+
+  private static final class TransformedIterable<M, N, P> implements
+      Iterable<N>
+  {
+
+    private final Function<? super M, ? extends N, P> function;
+    private final Iterable<M> iterable;
+    private final P parameter;
+
+
+
+    // Constructed via factory methods.
+    private TransformedIterable(Iterable<M> iterable,
+        Function<? super M, ? extends N, P> function, P p)
+    {
+      this.iterable = iterable;
+      this.function = function;
+      this.parameter = p;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<N> iterator()
+    {
+      return Iterators.transform(iterable.iterator(), function,
+          parameter);
+    }
+
+  }
+
+
+
+  private static final class UnmodifiableIterable<M> implements
+      Iterable<M>
+  {
+
+    private final Iterable<M> iterable;
+
+
+
+    // Constructed via factory methods.
+    private UnmodifiableIterable(Iterable<M> iterable)
+    {
+      this.iterable = iterable;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Iterator<M> iterator()
+    {
+      return Iterators.unmodifiable(iterable.iterator());
+    }
+
+  }
+
+  private static final Iterable<Object> EMPTY_ITERABLE =
+      new EmptyIterable<Object>();
+
+
+
+  /**
+   * Returns an immutable empty iterable.
+   *
+   * @param <M>
+   *          The required type of the empty iterable.
+   * @return An immutable empty iterable.
+   */
+  @SuppressWarnings("unchecked")
+  public static <M> Iterable<M> empty()
+  {
+    return (Iterable<M>) EMPTY_ITERABLE;
+  }
+
+
+
+  /**
+   * Returns a filtered view of {@code iterable} containing only those
+   * elements which match {@code predicate}. The returned iterable's
+   * iterator supports element removal via the {@code remove()} method
+   * subject to any constraints imposed by {@code iterable}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterable}.
+   * @param <P>
+   *          The type of the additional parameter to the predicate's
+   *          {@code matches} method. Use {@link java.lang.Void} for
+   *          predicates that do not need an additional parameter.
+   * @param iterable
+   *          The iterable to be filtered.
+   * @param predicate
+   *          The predicate.
+   * @param p
+   *          A predicate specified parameter.
+   * @return A filtered view of {@code iterable} containing only those
+   *         elements which match {@code predicate}.
+   */
+  public static <M, P> Iterable<M> filter(Iterable<M> iterable,
+      Predicate<? super M, P> predicate, P p)
+  {
+    return new FilteredIterable<M, P>(iterable, predicate, p);
+  }
+
+
+
+  /**
+   * Returns a filtered view of {@code iterable} containing only those
+   * elements which match {@code predicate}. The returned iterable's
+   * iterator supports element removal via the {@code remove()} method
+   * subject to any constraints imposed by {@code iterable}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterable}.
+   * @param iterable
+   *          The iterable to be filtered.
+   * @param predicate
+   *          The predicate.
+   * @return A filtered view of {@code iterable} containing only those
+   *         elements which match {@code predicate}.
+   */
+  public static <M> Iterable<M> filter(Iterable<M> iterable,
+      Predicate<? super M, Void> predicate)
+  {
+    return new FilteredIterable<M, Void>(iterable, predicate, null);
+  }
+
+
+
+  /**
+   * Returns an iterable containing the single element {@code value}.
+   * The returned iterable's iterator does not support element removal
+   * via the {@code remove()} method.
+   *
+   * @param <M>
+   *          The type of the single element {@code value}.
+   * @param value
+   *          The single element.
+   * @return An iterable containing the single element {@code value}.
+   */
+  public static <M> Iterable<M> singleton(M value)
+  {
+    return new SingletonIterable<M>(value);
+  }
+
+
+
+  /**
+   * Returns an iterable containing the elements of {@code a}. The
+   * returned iterable's iterator does not support element removal via
+   * the {@code remove()} method.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code a}.
+   * @param a
+   *          The array of elements.
+   * @return An iterable containing the elements of {@code a}.
+   */
+  public static <M> Iterable<M> arrayIterable(M[] a)
+  {
+    return new ArrayIterable<M>(a);
+  }
+
+
+
+  /**
+   * Returns a view of {@code iterable} whose values have been mapped to
+   * elements of type {@code N} using {@code function}. The returned
+   * iterable's iterator supports element removal via the {@code
+   * remove()} method subject to any constraints imposed by {@code
+   * iterable}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterable}.
+   * @param <N>
+   *          The type of elements contained in the returned iterable.
+   * @param <P>
+   *          The type of the additional parameter to the function's
+   *          {@code apply} method. Use {@link java.lang.Void} for
+   *          functions that do not need an additional parameter.
+   * @param iterable
+   *          The iterable to be transformed.
+   * @param function
+   *          The function.
+   * @param p
+   *          A predicate specified parameter.
+   * @return A view of {@code iterable} whose values have been mapped to
+   *         elements of type {@code N} using {@code function}.
+   */
+  public static <M, N, P> Iterable<N> transform(Iterable<M> iterable,
+      Function<? super M, ? extends N, P> function, P p)
+  {
+    return new TransformedIterable<M, N, P>(iterable, function, p);
+  }
+
+
+
+  /**
+   * Returns a view of {@code iterable} whose values have been mapped to
+   * elements of type {@code N} using {@code function}. The returned
+   * iterable's iterator supports element removal via the {@code
+   * remove()} method subject to any constraints imposed by {@code
+   * iterable}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterable}.
+   * @param <N>
+   *          The type of elements contained in the returned iterable.
+   * @param iterable
+   *          The iterable to be transformed.
+   * @param function
+   *          The function.
+   * @return A view of {@code iterable} whose values have been mapped to
+   *         elements of type {@code N} using {@code function}.
+   */
+  public static <M, N> Iterable<N> transform(Iterable<M> iterable,
+      Function<? super M, ? extends N, Void> function)
+  {
+    return new TransformedIterable<M, N, Void>(iterable, function, null);
+  }
+
+
+
+  /**
+   * Returns a read-only view of {@code iterable} whose iterator does
+   * not support element removal via the {@code remove()}. Attempts to
+   * use the {@code remove()} method will result in a {@code
+   * UnsupportedOperationException}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterable}.
+   * @param iterable
+   *          The iterable to be made read-only.
+   * @return A read-only view of {@code iterable} whose iterator does
+   *         not support element removal via the {@code remove()}.
+   */
+  public static <M> Iterable<M> unmodifiable(Iterable<M> iterable)
+  {
+    return new UnmodifiableIterable<M>(iterable);
+  }
+
+
+
+  // Prevent instantiation
+  private Iterables()
+  {
+    // Do nothing.
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/Iterators.java b/sdk/src/org/opends/sdk/util/Iterators.java
new file mode 100644
index 0000000..785c278
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Iterators.java
@@ -0,0 +1,549 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+
+
+/**
+ * Utility methods for manipulating {@link Iterator}s.
+ */
+public final class Iterators
+{
+  private static final class EmptyIterator<M> implements Iterator<M>
+  {
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+      return false;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public M next()
+    {
+      throw new NoSuchElementException();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+      throw new UnsupportedOperationException();
+    }
+  };
+
+
+
+  private static final class FilteredIterator<M, P> implements
+      Iterator<M>
+  {
+
+    private boolean hasNextMustIterate = true;
+    private final Iterator<M> iterator;
+    private M next = null;
+
+    private final P parameter;
+    private final Predicate<? super M, P> predicate;
+
+
+
+    // Constructed via factory methods.
+    private FilteredIterator(Iterator<M> iterator,
+        Predicate<? super M, P> predicate, P p)
+    {
+      this.iterator = iterator;
+      this.predicate = predicate;
+      this.parameter = p;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+      if (hasNextMustIterate)
+      {
+        hasNextMustIterate = false;
+        while (iterator.hasNext())
+        {
+          next = iterator.next();
+          if (predicate.matches(next, parameter))
+          {
+            return true;
+          }
+        }
+        next = null;
+        return false;
+      }
+      else
+      {
+        return next != null;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public M next()
+    {
+      if (!hasNext())
+      {
+        throw new NoSuchElementException();
+      }
+      hasNextMustIterate = true;
+      return next;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+      iterator.remove();
+    }
+
+  }
+
+
+
+  private static final class SingletonIterator<M> implements
+      Iterator<M>
+  {
+    private M value;
+
+
+
+    // Constructed via factory methods.
+    private SingletonIterator(M value)
+    {
+      this.value = value;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+      return value != null;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public M next()
+    {
+      if (value != null)
+      {
+        M tmp = value;
+        value = null;
+        return tmp;
+      }
+      else
+      {
+        throw new NoSuchElementException();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+  }
+
+
+
+  private static final class ArrayIterator<M> implements Iterator<M>
+  {
+    private int i = 0;
+    private final M[] a;
+
+
+
+    // Constructed via factory methods.
+    private ArrayIterator(M[] a)
+    {
+      this.a = a;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+      return i < a.length;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public M next()
+    {
+      if (hasNext())
+      {
+        return a[i++];
+      }
+      else
+      {
+        throw new NoSuchElementException();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+      throw new UnsupportedOperationException();
+    }
+
+  }
+
+
+
+  private static final class TransformedIterator<M, N, P> implements
+      Iterator<N>
+  {
+
+    private final Function<? super M, ? extends N, P> function;
+    private final Iterator<M> iterator;
+    private final P parameter;
+
+
+
+    // Constructed via factory methods.
+    private TransformedIterator(Iterator<M> iterator,
+        Function<? super M, ? extends N, P> function, P p)
+    {
+      this.iterator = iterator;
+      this.function = function;
+      this.parameter = p;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+      return iterator.hasNext();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public N next()
+    {
+      return function.apply(iterator.next(), parameter);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+      iterator.remove();
+    }
+
+  }
+
+
+
+  private static final class UnmodifiableIterator<M> implements
+      Iterator<M>
+  {
+    private final Iterator<M> iterator;
+
+
+
+    private UnmodifiableIterator(Iterator<M> iterator)
+    {
+      this.iterator = iterator;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean hasNext()
+    {
+      return iterator.hasNext();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public M next()
+    {
+      return iterator.next();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void remove()
+    {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private static final Iterator<Object> EMPTY_ITERATOR =
+      new EmptyIterator<Object>();
+
+
+
+  /**
+   * Returns an immutable empty iterator.
+   *
+   * @param <M>
+   *          The required type of the empty iterator.
+   * @return An immutable empty iterator.
+   */
+  @SuppressWarnings("unchecked")
+  public static <M> Iterator<M> empty()
+  {
+    return (Iterator<M>) EMPTY_ITERATOR;
+  }
+
+
+
+  /**
+   * Returns a filtered view of {@code iterator} containing only those
+   * elements which match {@code predicate}. The returned iterator
+   * supports element removal via the {@code remove()} method subject to
+   * any constraints imposed by {@code iterator}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterator}.
+   * @param <P>
+   *          The type of the additional parameter to the predicate's
+   *          {@code matches} method. Use {@link java.lang.Void} for
+   *          predicates that do not need an additional parameter.
+   * @param iterator
+   *          The iterator to be filtered.
+   * @param predicate
+   *          The predicate.
+   * @param p
+   *          A predicate specified parameter.
+   * @return A filtered view of {@code iterator} containing only those
+   *         elements which match {@code predicate}.
+   */
+  public static <M, P> Iterator<M> filter(Iterator<M> iterator,
+      Predicate<? super M, P> predicate, P p)
+  {
+    return new FilteredIterator<M, P>(iterator, predicate, p);
+  }
+
+
+
+  /**
+   * Returns a filtered view of {@code iterator} containing only those
+   * elements which match {@code predicate}. The returned iterator
+   * supports element removal via the {@code remove()} method subject to
+   * any constraints imposed by {@code iterator}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterator}.
+   * @param iterator
+   *          The iterator to be filtered.
+   * @param predicate
+   *          The predicate.
+   * @return A filtered view of {@code iterator} containing only those
+   *         elements which match {@code predicate}.
+   */
+  public static <M> Iterator<M> filter(Iterator<M> iterator,
+      Predicate<? super M, Void> predicate)
+  {
+    return new FilteredIterator<M, Void>(iterator, predicate, null);
+  }
+
+
+
+  /**
+   * Returns an iterator containing the single element {@code value}.
+   * The returned iterator does not support element removal via the
+   * {@code remove()} method.
+   *
+   * @param <M>
+   *          The type of the single element {@code value}.
+   * @param value
+   *          The single element to be returned by the iterator.
+   * @return An iterator containing the single element {@code value}.
+   */
+  public static <M> Iterator<M> singleton(M value)
+  {
+    return new SingletonIterator<M>(value);
+  }
+
+
+
+  /**
+   * Returns an iterator over the elements contained in {@code a}. The
+   * returned iterator does not support element removal via the {@code
+   * remove()} method.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code a}.
+   * @param a
+   *          The array of elements to be returned by the iterator.
+   * @return An iterator over the elements contained in {@code a}.
+   */
+  public static <M> Iterator<M> arrayIterator(M[] a)
+  {
+    return new ArrayIterator<M>(a);
+  }
+
+
+
+  /**
+   * Returns a view of {@code iterator} whose values have been mapped to
+   * elements of type {@code N} using {@code function}. The returned
+   * iterator supports element removal via the {@code remove()} method
+   * subject to any constraints imposed by {@code iterator}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterator}.
+   * @param <N>
+   *          The type of elements contained in the returned iterator.
+   * @param <P>
+   *          The type of the additional parameter to the function's
+   *          {@code apply} method. Use {@link java.lang.Void} for
+   *          functions that do not need an additional parameter.
+   * @param iterator
+   *          The iterator to be transformed.
+   * @param function
+   *          The function.
+   * @param p
+   *          A predicate specified parameter.
+   * @return A view of {@code iterator} whose values have been mapped to
+   *         elements of type {@code N} using {@code function}.
+   */
+  public static <M, N, P> Iterator<N> transform(Iterator<M> iterator,
+      Function<? super M, ? extends N, P> function, P p)
+  {
+    return new TransformedIterator<M, N, P>(iterator, function, p);
+  }
+
+
+
+  /**
+   * Returns a view of {@code iterator} whose values have been mapped to
+   * elements of type {@code N} using {@code function}. The returned
+   * iterator supports element removal via the {@code remove()} method
+   * subject to any constraints imposed by {@code iterator}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterator}.
+   * @param <N>
+   *          The type of elements contained in the returned iterator.
+   * @param iterator
+   *          The iterator to be transformed.
+   * @param function
+   *          The function.
+   * @return A view of {@code iterator} whose values have been mapped to
+   *         elements of type {@code N} using {@code function}.
+   */
+  public static <M, N> Iterator<N> transform(Iterator<M> iterator,
+      Function<? super M, ? extends N, Void> function)
+  {
+    return new TransformedIterator<M, N, Void>(iterator, function, null);
+  }
+
+
+
+  /**
+   * Returns a read-only view of {@code iterator} which does not support
+   * element removal via the {@code remove()}. Attempts to use the
+   * {@code remove()} method will result in a {@code
+   * UnsupportedOperationException}.
+   *
+   * @param <M>
+   *          The type of elements contained in {@code iterator}.
+   * @param iterator
+   *          The iterator to be made read-only.
+   * @return A read-only view of {@code iterator} which does not support
+   *         element removal via the {@code remove()}.
+   */
+  public static <M> Iterator<M> unmodifiable(Iterator<M> iterator)
+  {
+    return new UnmodifiableIterator<M>(iterator);
+  }
+
+
+
+  // Prevent instantiation
+  private Iterators()
+  {
+    // Do nothing.
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/LocalizableException.java b/sdk/src/org/opends/sdk/util/LocalizableException.java
new file mode 100644
index 0000000..edbc792
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/LocalizableException.java
@@ -0,0 +1,48 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * This interface should be implemented by any exception interfaces that
+ * expose a localizable error message.
+ */
+public interface LocalizableException
+{
+  /**
+   * Returns the message that explains the problem that occurred.
+   * 
+   * @return The message that explains the problem that occurred.
+   */
+  Message getMessageObject();
+
+}
diff --git a/sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java b/sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java
new file mode 100644
index 0000000..5687293
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/LocalizedIllegalArgumentException.java
@@ -0,0 +1,101 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import org.opends.messages.Message;
+
+
+
+/**
+ * Thrown to indicate that a method has been passed an illegal or
+ * inappropriate argument.
+ * <p>
+ * A {@code LocalizedIllegalArgumentException} contains a localized
+ * error message which maybe used to provide the user with detailed
+ * diagnosis information. The localized message can be retrieved using
+ * the {@link #getMessageObject} method.
+ * <p>
+ * A {@code LocalizedIllegalArgumentException} is typically used to
+ * indicate problems parsing values such as distinguished names and
+ * filters.
+ */
+@SuppressWarnings("serial")
+public class LocalizedIllegalArgumentException extends
+    IllegalArgumentException implements LocalizableException
+{
+  // The I18N message associated with this exception.
+  private final Message message;
+
+
+
+  /**
+   * Creates a new localized illegal argument exception with the
+   * provided message.
+   *
+   * @param message
+   *          The message that explains the problem that occurred.
+   */
+  public LocalizedIllegalArgumentException(Message message)
+  {
+    super(String.valueOf(message));
+    this.message = message;
+  }
+
+
+
+  /**
+   * Creates a new localized illegal argument exception with the
+   * provided message and cause.
+   *
+   * @param message
+   *          The message that explains the problem that occurred.
+   * @param cause
+   *          The cause which may be later retrieved by the
+   *          {@link #getCause} method. A {@code null} value is
+   *          permitted, and indicates that the cause is nonexistent or
+   *          unknown.
+   */
+  public LocalizedIllegalArgumentException(Message message,
+      Throwable cause)
+  {
+    super(String.valueOf(message), cause);
+    this.message = message;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Message getMessageObject()
+  {
+    return this.message;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/Platform.java b/sdk/src/org/opends/sdk/util/Platform.java
new file mode 100644
index 0000000..8440dc1
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Platform.java
@@ -0,0 +1,650 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+import static org.opends.messages.UtilityMessages.*;
+
+import java.io.*;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.security.*;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+import org.opends.messages.Message;
+
+/**
+ * Provides a wrapper class that collects all of the JVM vendor
+ * and JDK version specific code in a single place.
+ *
+ */
+public final class Platform {
+
+   //Prefix that determines which security package to use.
+    private static String pkgPrefix;
+
+    //IBM security package doesn't appear to support PCKS10, this flags turns
+    //off support for that.
+    private static boolean certReqAllowed;
+
+    //The two security package prefixes (IBM and SUN).
+    private static final String IBM_SEC = "com.ibm.security";
+    private static final String SUN_SEC = "sun.security";
+
+    private static final PlatformIMPL IMPL;
+
+    static {
+     String vendor = System.getProperty("java.vendor");
+     String ver = System.getProperty("java.version");
+
+      if(vendor.startsWith("IBM"))
+      {
+        pkgPrefix = IBM_SEC;
+        certReqAllowed = false;
+        if(ver.startsWith("1.5"))
+        {
+          IMPL = new IBM5PlatformIMPL();
+        }
+        else
+        {
+          IMPL = new DefaultPlatformIMPL();
+        }
+      }
+      else
+      {
+        pkgPrefix = SUN_SEC;
+        certReqAllowed = true;
+        if(ver.startsWith("1.5"))
+        {
+         IMPL = new Sun5PlatformIMPL();
+        }
+        else
+        {
+          IMPL = new DefaultPlatformIMPL();
+        }
+      }
+    }
+
+   /**
+    * Platform base class. Performs all of the certificate management functions.
+    */
+    private abstract static class PlatformIMPL {
+
+        //Key size, key algorithm and signature algorithms used.
+        private static final  int KEY_SIZE = 1024;
+        private static final String KEY_ALGORITHM = "rsa";
+        private static final String SIG_ALGORITHM = "SHA1WithRSA";
+
+        //Time values used in validity calculations.
+        private static final int SEC_IN_DAY = 24 * 60 * 60;
+        private static final int DEFAULT_VALIDITY = 90 * SEC_IN_DAY;
+
+        //These two are used to build certificate request files.
+        private static final String TMPFILE_PREFIX = "CertificateManager-";
+        private static final String TMPFILE_EXT = ".csr";
+
+        //Methods pulled from the classes.
+        private static final String ENCODE_SIGN_METHOD = "encodeAndSign";
+        private static final String GENERATE_METHOD = "generate";
+        private static final String GET_PRIVATE_KEY_METHOD = "getPrivateKey";
+        private static final String GET_SELFSIGNED_CERT_METHOD =
+                                                          "getSelfCertificate";
+        private static final String PRINT_METHOD = "print";
+
+        //Classes needed to manage certificates.
+        private static Class<?> certKeyGenClass, X500NameClass,
+                                X500SignerClass, PKCS10Class;
+
+        //Constructors for each of the above classes.
+        private static Constructor<?> certKeyGenCons, X500NameCons,
+                                      X500SignerCons, pkcs10Cons;
+
+        static {
+          String x509pkg = pkgPrefix + ".x509";
+          String pkcs10Pkg = pkgPrefix + ".pkcs";
+          String certAndKeyGen=  x509pkg + ".CertAndKeyGen";
+          String X500Name =  x509pkg + ".X500Name";
+          String X500Signer = x509pkg + ".X500Signer";
+          try {
+            certKeyGenClass = Class.forName(certAndKeyGen);
+            X500NameClass = Class.forName(X500Name);
+            X500SignerClass = Class.forName(X500Signer);
+            if(certReqAllowed) {
+              String pkcs10 = pkcs10Pkg + ".PKCS10";
+              PKCS10Class = Class.forName(pkcs10);
+              pkcs10Cons = PKCS10Class.getConstructor(PublicKey.class);
+            }
+            certKeyGenCons =
+                    certKeyGenClass.getConstructor(String.class, String.class);
+            X500NameCons = X500NameClass.getConstructor(String.class);
+            X500SignerCons =
+                 X500SignerClass.getConstructor(Signature.class, X500NameClass);
+          } catch (ClassNotFoundException e) {
+            Message msg = ERR_CERTMGR_CLASS_NOT_FOUND.get(e.getMessage());
+            throw new ExceptionInInitializerError(msg.toString());
+          } catch (SecurityException e) {
+            Message msg = ERR_CERTMGR_SECURITY.get(e.getMessage());
+            throw new ExceptionInInitializerError(msg.toString());
+          } catch (NoSuchMethodException e) {
+            Message msg = ERR_CERTMGR_NO_METHOD.get(e.getMessage());
+            throw new ExceptionInInitializerError(msg.toString());
+          }
+        }
+
+        protected PlatformIMPL() {}
+
+        /**
+         * Generate a certificate request. Note that this methods checks if
+         * the certificate request generation is allowed and throws an
+         * exception if it isn't supported. Some vendors JDKs aren't compatible
+         * with Sun's certificate request generation classes so they aren't
+         * supported.
+         *
+         * @param ks The keystore to use in the request creation.
+         * @param ksType The keystore type.
+         * @param ksPath The path to the keystore.
+         * @param alias The alias to use in the request generation.
+         * @param pwd The keystore password to use.
+         * @param dn A dn string to use as the certificate subject.
+         *
+         * @return A file object pointing at the created certificate request.
+         * @throws KeyStoreException If the certificate request failed.
+         */
+        public final File
+        generateCertificateRequest(KeyStore ks, String ksType, String ksPath,
+            String alias, char[] pwd, String dn) throws KeyStoreException {
+          if(!certReqAllowed) {
+            String vendor = System.getProperty("java.vendor");
+            Message msg =
+              ERR_CERTMGR_CERT_SIGN_REQ_NOT_SUPPORTED.get(vendor);
+            throw new KeyStoreException(msg.toString());
+          }
+          KeyStore keyStore = generateSelfSignedCertificate(ks, ksType, ksPath,
+                                            alias, pwd, dn, DEFAULT_VALIDITY);
+          File csrFile;
+          try {
+            csrFile = File.createTempFile(TMPFILE_PREFIX, TMPFILE_EXT);
+            csrFile.deleteOnExit();
+            PrintStream printStream =
+              new PrintStream(new FileOutputStream(csrFile.getAbsolutePath()));
+            if(keyStore == null) {
+              Message msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get();
+              throw new KeyStoreException(msg.toString());
+            }
+            PrivateKey privateKey = getPrivateKey(keyStore, alias, pwd);
+            if(privateKey == null) {
+              Message msg =  ERR_CERTMGR_PRIVATE_KEY.get(alias);
+              throw new KeyStoreException(msg.toString());
+            }
+            Certificate cert = keyStore.getCertificate(alias);
+            if(cert == null) {
+              Message msg = ERR_CERTMGR_ALIAS_NO_CERTIFICATE.get(alias);
+              throw new KeyStoreException(msg.toString());
+            }
+            Signature signature = Signature.getInstance(SIG_ALGORITHM);
+            signature.initSign(privateKey);
+            Object request = pkcs10Cons.newInstance(cert.getPublicKey());
+            Object subject = X500NameCons.newInstance(dn);
+            Object signer =
+              X500SignerCons.newInstance(signature, subject);
+            Method encodeAndSign =
+              PKCS10Class.getMethod(ENCODE_SIGN_METHOD, X500SignerClass);
+            Method print =
+              PKCS10Class.getMethod(PRINT_METHOD, PrintStream.class);
+            encodeAndSign.invoke(request, signer);
+            print.invoke(request, printStream);
+            printStream.close();
+          } catch (Exception e) {
+            Message msg = ERR_CERTMGR_CERT_REQUEST.get(alias,e.getMessage());
+            throw new KeyStoreException(msg.toString());
+          }
+          return csrFile;
+        }
+
+        /**
+         * Delete the specified alias from the specified keystore.
+         *
+         * @param ks The keystore to delete the alias from.
+         * @param ksPath The path to the keystore.
+         * @param alias The alias to use in the request generation.
+         * @param pwd The keystore password to use.
+         *
+         * @throws KeyStoreException If an error occurred deleting the alias.
+         */
+        public final void deleteAlias(KeyStore ks, String ksPath,
+            String alias, char[] pwd) throws KeyStoreException {
+              try {
+                  if(ks == null) {
+                      Message msg = ERR_CERTMGR_KEYSTORE_NONEXISTANT.get();
+                      throw new KeyStoreException(msg.toString());
+                  }
+                  ks.deleteEntry(alias);
+                  FileOutputStream fs = new FileOutputStream(ksPath);
+                  ks.store(fs, pwd);
+                  fs.close();
+              } catch (Exception e) {
+                  Message msg =
+                      ERR_CERTMGR_DELETE_ALIAS.get(alias,e.getMessage());
+                  throw new KeyStoreException(msg.toString());
+              }
+        }
+
+        /**
+         * Add the certificate in the specified path to the specified keystore,
+         * creating the keystore using the specified type and path if it the
+         * keystore doesn't exist.
+         *
+         * @param ks The keystore to add the certificate to, may be null if it
+         *           doesn't exist.
+         * @param ksType The type to use if the keystore is created.
+         * @param ksPath The path to the keystore if it is created.
+         * @param alias The alias to store the certificate under.
+         * @param pwd The password to use in saving the certificate.
+         * @param certPath The path to the file containing the certificate.
+         * @throws KeyStoreException If an error occurred adding the
+         *                           certificate to the keystore.
+         */
+        public final void addCertificate(KeyStore ks, String ksType,
+            String ksPath, String alias, char[] pwd, String certPath)
+        throws KeyStoreException {
+          try {
+            CertificateFactory cf = CertificateFactory.getInstance("X509");
+            InputStream inStream = new FileInputStream(certPath);
+            if(ks == null) {
+              ks = KeyStore.getInstance(ksType);
+              ks.load(null, pwd);
+            }
+            //Do not support certificate replies.
+            if (ks.entryInstanceOf(alias ,KeyStore.PrivateKeyEntry.class)) {
+              Message msg = ERR_CERTMGR_CERT_REPLIES_INVALID.get(alias);
+              throw new KeyStoreException(msg.toString());
+            } else if(!ks.containsAlias(alias) ||
+                ks.entryInstanceOf(alias,
+                    KeyStore.TrustedCertificateEntry.class))
+              trustedCert(alias, cf, ks, inStream);
+            else {
+              Message msg = ERR_CERTMGR_ALIAS_INVALID.get(alias);
+              throw new KeyStoreException(msg.toString());
+            }
+            FileOutputStream fileOutStream = new FileOutputStream(ksPath);
+            ks.store(fileOutStream, pwd);
+            fileOutStream.close();
+            inStream.close();
+          } catch (Exception e) {
+            Message msg =
+              ERR_CERTMGR_ADD_CERT.get(alias, e.getMessage());
+            throw new KeyStoreException(msg.toString());
+          }
+        }
+
+        /**
+         * Generate a self-signed certificate using the specified alias, dn
+         * string and validity period. If the keystore does not exist, create it
+         * using the specified type and path.
+         *
+         * @param ks The keystore to save the certificate in. May be null if it
+         *           does not exist.
+         * @param ksType The keystore type to use if the keystore is created.
+         * @param ksPath The path to the keystore if the keystore is created.
+         * @param alias The alias to store the certificate under.
+         * @param pwd The password to us in saving the certificate.
+         * @param dn The dn string used as the certificate subject.
+         * @param validity The validity of the certificate in days.
+         * @return The keystore that the self-signed certificate was stored in.
+         *
+         * @throws KeyStoreException If the self-signed certificate cannot be
+         *                           generated.
+         */
+        public final
+        KeyStore generateSelfSignedCertificate(KeyStore ks, String ksType,
+            String ksPath, String alias, char[] pwd, String dn, int validity)
+        throws KeyStoreException {
+          try {
+            if(ks == null) {
+              ks = KeyStore.getInstance(ksType);
+              ks.load(null, pwd);
+            } else if(ks.containsAlias(alias)) {
+              Message msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
+              throw new KeyStoreException(msg.toString());
+            }
+            Object keypair =
+              certKeyGenCons.newInstance(KEY_ALGORITHM, SIG_ALGORITHM);
+            Object subject = X500NameCons.newInstance(dn);
+            Method certAndKeyGenGenerate =
+              certKeyGenClass.getMethod(GENERATE_METHOD, int.class);
+            certAndKeyGenGenerate.invoke(keypair, KEY_SIZE);
+            Method certAndKeyGetPrivateKey =
+              certKeyGenClass.getMethod(GET_PRIVATE_KEY_METHOD);
+            PrivateKey privatevKey =
+              (PrivateKey) certAndKeyGetPrivateKey.invoke(keypair);
+            Certificate[] certificateChain = new Certificate[1];
+            Method getSelfCertificate =
+              certKeyGenClass.getMethod(GET_SELFSIGNED_CERT_METHOD,
+                                        X500NameClass,long.class);
+            int days = validity * SEC_IN_DAY;
+            certificateChain[0] =
+              (Certificate) getSelfCertificate.invoke(keypair, subject, days);
+            ks.setKeyEntry(alias, privatevKey, pwd, certificateChain);
+            FileOutputStream fileOutStream = new FileOutputStream(ksPath);
+            ks.store(fileOutStream, pwd);
+            fileOutStream.close();
+          } catch (Exception e) {
+            Message msg =
+                   ERR_CERTMGR_GEN_SELF_SIGNED_CERT.get(alias, e.getMessage());
+            throw new KeyStoreException(msg.toString());
+          }
+          return ks;
+        }
+
+        /**
+         * Generate a x509 certificate from the input stream. Verification is
+         * done only if it is self-signed.
+         *
+         * @param alias The alias to save the certificate under.
+         * @param cf The x509 certificate factory.
+         * @param ks The keystore to add the certificate in.
+         * @param in The input stream to read the certificate from.
+         * @throws KeyStoreException If the alias exists already in the
+         *         keystore, if the self-signed certificate didn't verify, or
+         *         the certificate could not be stored.
+         */
+        private void trustedCert(String alias, CertificateFactory cf,
+             KeyStore ks, InputStream in) throws KeyStoreException {
+          try {
+            if (ks.containsAlias(alias) == true) {
+              Message msg = ERR_CERTMGR_ALIAS_ALREADY_EXISTS.get(alias);
+              throw new KeyStoreException(msg.toString());
+            }
+            X509Certificate cert = (X509Certificate) cf.generateCertificate(in);
+            if (isSelfSigned(cert))
+              cert.verify(cert.getPublicKey());
+            ks.setCertificateEntry(alias, cert);
+          } catch (Exception e) {
+            Message msg =
+              ERR_CERTMGR_TRUSTED_CERT.get(alias,e.getMessage());
+            throw new KeyStoreException(msg.toString());
+          }
+        }
+
+        /**
+         * Check that the issuer and subject DNs match.
+         *
+         * @param cert The certificate to examine.
+         * @return {@code true} if the certificate is self-signed.
+         */
+        private boolean isSelfSigned(X509Certificate cert) {
+          return cert.getSubjectDN().equals(cert.getIssuerDN());
+        }
+
+        /**
+         * Returns the private key associated with specified alias and keystore.
+         * The keystore was already checked for existance.
+         *
+         * @param ks The keystore to get the private key from, it must exist.
+         * @param alias The alias to get the private key of.
+         * @param pwd The password used to get the key from the keystore.
+         * @return The private key of related to the alias.
+         *
+         * @throws KeyStoreException If the alias is not in the keystore, the
+         *    entry related to the alias is not of
+         */
+        private PrivateKey getPrivateKey(KeyStore ks, String alias, char[] pwd)
+        throws KeyStoreException  {
+            PrivateKey key = null;
+            try {
+                if(!ks.containsAlias(alias)) {
+                    Message msg = ERR_CERTMGR_ALIAS_DOES_NOT_EXIST.get(alias);
+                    throw new KeyStoreException(msg.toString());
+                }
+                if(!ks.entryInstanceOf(alias, KeyStore.PrivateKeyEntry.class) &&
+                  !ks.entryInstanceOf(alias, KeyStore.SecretKeyEntry.class)) {
+                    Message msg =
+                                ERR_CERTMGR_ALIAS_INVALID_ENTRY_TYPE.get(alias);
+                    throw new KeyStoreException(msg.toString());
+                }
+                key = (PrivateKey)ks.getKey(alias, pwd);
+            } catch (Exception  e) {
+                Message msg =
+                    ERR_CERTMGR_GET_KEY.get(alias,e.getMessage());
+                throw new KeyStoreException(msg.toString());
+            }
+            return key;
+        }
+
+        /**
+         * Normalize the data in the specified buffer.
+         *
+         * @param buffer The buffer to normalize.
+         */
+         public abstract void normalize(StringBuilder buffer);
+    }
+
+    //Prevent instantiation.
+    private Platform() {}
+
+    /**
+     * Add the certificate in the specified path to the provided keystore;
+     * creating the keystore with the provided type and path if it doesn't
+     * exist.
+     *
+     * @param ks The keystore to add the certificate to, may be null if it
+     *           doesn't exist.
+     * @param ksType The type to use if the keystore is created.
+     * @param ksPath The path to the keystore if it is created.
+     * @param alias The alias to store the certificate under.
+     * @param pwd The password to use in saving the certificate.
+     * @param certPath The path to the file containing the certificate.
+     *
+     * @throws KeyStoreException If an error occurred adding the
+     *                           certificate to the keystore.
+     */
+    public static void addCertificate(KeyStore ks, String ksType, String ksPath,
+        String alias, char[] pwd, String certPath) throws KeyStoreException {
+        IMPL.addCertificate(ks,ksType, ksPath, alias, pwd, certPath);
+    }
+
+
+    /**
+     * Delete the specified alias from the provided keystore.
+     *
+     * @param ks The keystore to delete the alias from.
+     * @param ksPath The path to the keystore.
+     * @param alias The alias to use in the request generation.
+     * @param pwd The keystore password to use.
+     *
+     * @throws KeyStoreException If an error occurred deleting the alias.
+     */
+    public static void deleteAlias(KeyStore ks, String ksPath, String alias,
+        char[] pwd) throws KeyStoreException {
+        IMPL.deleteAlias(ks, ksPath, alias, pwd);
+    }
+
+
+    /**
+     * Generate a certificate request using the specified parameters.
+     *
+     * @param ks The keystore to use in the request creation.
+     * @param ksType The keystore type.
+     * @param ksPath The path to the keystore.
+     * @param alias The alias to use in the request generation.
+     * @param pwd The keystore password to use.
+     * @param dn A dn string to use as the certificate subject.
+     * @return A file object pointing at the created certificate request.
+     *
+     * @throws KeyStoreException If the certificate request failed.
+     */
+    public static File generateCertificateRequest(KeyStore ks, String ksType,
+        String ksPath, String alias, char[] pwd, String dn)
+    throws KeyStoreException {
+        return IMPL.generateCertificateRequest(ks, ksType, ksPath, alias,
+                                               pwd, dn);
+    }
+
+
+    /**
+     * Generate a self-signed certificate using the specified alias, dn
+     * string and validity period. If the keystore does not exist, it will be
+     * created using the specified keystore type and path.
+     *
+     * @param ks The keystore to save the certificate in. May be null if it
+     *           does not exist.
+     * @param ksType The keystore type to use if the keystore is created.
+     * @param ksPath The path to the keystore if the keystore is created.
+     * @param alias The alias to store the certificate under.
+     * @param pwd The password to us in saving the certificate.
+     * @param dn The dn string used as the certificate subject.
+     * @param validity The validity of the certificate in days.
+     *
+     * @throws KeyStoreException If the self-signed certificate cannot be
+     *                           generated.
+     */
+    public static void generateSelfSignedCertificate(KeyStore ks, String ksType,
+        String ksPath, String alias, char[] pwd, String dn, int validity)
+    throws KeyStoreException {
+        IMPL.generateSelfSignedCertificate(ks, ksType, ksPath, alias, pwd, dn,
+                                      validity);
+    }
+
+
+
+    /**
+     * Sun 5 JDK platform class.
+     */
+    private static class Sun5PlatformIMPL extends PlatformIMPL {
+       //normalize method.
+      private static final Method NORMALIZE;
+      //Normalized form method.
+      private static final Object FORM_NFKC;
+
+      static {
+        Method normalize = null;
+        Object formNFKC = null;
+        try {
+          Class<?> normalizer = Class.forName("sun.text.Normalizer");
+          formNFKC = normalizer.getField("DECOMP_COMPAT").get(null);
+          Class<?> normalizerForm = Class.forName("sun.text.Normalizer$Mode");
+          normalize = normalizer.getMethod("normalize", String.class,
+                 normalizerForm, Integer.TYPE);
+        }
+        catch (Exception ex) {
+        // Do not use Normalizer. The values are already set to null.
+        }
+      NORMALIZE = normalize;
+      FORM_NFKC = formNFKC;
+     }
+
+
+      @Override
+      public void normalize(StringBuilder buffer) {
+        try {
+          String normal =
+               (String) NORMALIZE.invoke(null, buffer.toString(), FORM_NFKC,0);
+          buffer.replace(0,buffer.length(),normal);
+        }
+        catch(Exception ex) {
+          //Don't do anything. buffer should be used.
+        }
+      }
+   }
+
+    /**
+     * Default platform class.
+     */
+     private static class DefaultPlatformIMPL extends PlatformIMPL {
+       //normalize method.
+      private static final Method NORMALIZE;
+      //Normalized form method.
+      private static final Object FORM_NFKC;
+
+      static {
+
+        Method normalize = null;
+        Object formNFKC = null;
+        try {
+          Class<?> normalizer = Class.forName("java.text.Normalizer");
+          Class<?> normalizerForm = Class.forName("java.text.Normalizer$Form");
+          normalize = normalizer.getMethod("normalize", CharSequence.class,
+                normalizerForm);
+          formNFKC = normalizerForm.getField("NFKD").get(null);
+        }
+        catch (Exception ex) {
+        // Do not use Normalizer. The values are already set to null.
+        }
+        NORMALIZE = normalize;
+        FORM_NFKC = formNFKC;
+     }
+
+
+      @Override
+      public void normalize(StringBuilder buffer) {
+        try {
+          String normal = (String) NORMALIZE.invoke(null, buffer, FORM_NFKC);
+          buffer.replace(0,buffer.length(),normal);
+        }
+        catch(Exception ex) {
+          //Don't do anything. buffer should be used.
+        }
+      }
+   }
+
+   /**
+    * IBM JDK 5 platform class.
+    */
+   private static class IBM5PlatformIMPL extends PlatformIMPL {
+
+    @Override
+    public void normalize(StringBuilder buffer) {
+      //No implementation.
+    }
+   }
+
+   /**
+    * Normalize the specified buffer.
+    *
+    * @param buffer The buffer to normalize.
+    */
+   public static void normalize(StringBuilder buffer) {
+     IMPL.normalize(buffer);
+   }
+
+   /**
+    * Test if a platform java vendor property starts with the specified
+    * vendor string.
+    *
+    * @param vendor The vendor to check for.
+    * @return {@code true} if the java vendor starts with the specified vendor
+    *         string.
+    */
+   public static boolean isVendor(String vendor) {
+     String javaVendor = System.getProperty("java.vendor");
+     return javaVendor.startsWith(vendor);
+   }
+}
+
diff --git a/sdk/src/org/opends/sdk/util/Predicate.java b/sdk/src/org/opends/sdk/util/Predicate.java
new file mode 100644
index 0000000..a7058f6
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Predicate.java
@@ -0,0 +1,57 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * Predicates transform input values of type {@code M} to a boolean
+ * output value and are typically used for performing filtering.
+ *
+ * @param <M>
+ *          The type of input values matched by this predicate.
+ * @param <P>
+ *          The type of the additional parameter to this predicate's
+ *          {@code matches} method. Use {@link java.lang.Void} for
+ *          predicates that do not need an additional parameter.
+ */
+public interface Predicate<M, P>
+{
+  /**
+   * Indicates whether or not this predicate matches the provided input
+   * value of type {@code M}.
+   *
+   * @param value
+   *          The input value for which to make the determination.
+   * @param p
+   *          A predicate specified parameter.
+   * @return {@code true} if this predicate matches {@code value},
+   *         otherwise {@code false}.
+   */
+  boolean matches(M value, P p);
+}
diff --git a/sdk/src/org/opends/sdk/util/SSLUtils.java b/sdk/src/org/opends/sdk/util/SSLUtils.java
new file mode 100644
index 0000000..a6cb1ba
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/SSLUtils.java
@@ -0,0 +1,47 @@
+package org.opends.sdk.util;
+
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: boli
+ * Date: Oct 22, 2009
+ * Time: 3:10:13 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SSLUtils
+{
+  private static String PROVIDER =
+        System.getProperty("org.opends.security.provider");
+  private static String KEY_STORE =
+      System.getProperty("javax.net.ssl.keyStore");
+
+  {
+
+  }
+  public static SSLContext getSSLContext(TrustManager trustManager,
+                                         KeyManager keyManager)
+      throws KeyManagementException, NoSuchAlgorithmException {
+    TrustManager[] tm = null;
+    if (trustManager != null)
+    {
+      tm = new TrustManager[] {trustManager};
+    }
+
+    KeyManager[] km = null;
+    if (keyManager != null)
+    {
+      km = new KeyManager[] {keyManager};
+    }
+
+    SSLContext sslContext = SSLContext.getInstance("TLSv1");
+    sslContext.init(km, tm, null);
+
+    return sslContext;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/SizeLimitInputStream.java b/sdk/src/org/opends/sdk/util/SizeLimitInputStream.java
new file mode 100644
index 0000000..16eca77
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/SizeLimitInputStream.java
@@ -0,0 +1,182 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2006-2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An implementation of input stream that enforces an read size limit.
+ */
+public class SizeLimitInputStream extends InputStream
+{
+  private int bytesRead;
+  private int markBytesRead;
+  private int readLimit;
+  private InputStream parentStream;
+
+  /**
+   * Creates a new a new size limit input stream.
+   *
+   * @param parentStream
+   *          The parent stream.
+   * @param readLimit
+   *          The size limit.
+   */
+  public SizeLimitInputStream(InputStream parentStream, int readLimit)
+  {
+    this.parentStream = parentStream;
+    this.readLimit = readLimit;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int available() throws IOException
+  {
+    int streamAvail = parentStream.available();
+    int limitedAvail = readLimit - bytesRead;
+    return limitedAvail < streamAvail ? limitedAvail : streamAvail;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public synchronized void mark(int readlimit)
+  {
+    parentStream.mark(readlimit);
+    markBytesRead = bytesRead;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int read() throws IOException
+  {
+    if(bytesRead >= readLimit)
+    {
+      return -1;
+    }
+
+    int b = parentStream.read();
+    if (b != -1)
+    {
+      ++bytesRead;
+    }
+    return b;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int read(byte b[], int off, int len) throws IOException
+  {
+    if(off < 0 || len < 0 || off+len > b.length)
+    {
+      throw new IndexOutOfBoundsException();
+    }
+
+    if(len == 0)
+    {
+      return 0;
+    }
+
+    if(bytesRead >= readLimit)
+    {
+      return -1;
+    }
+
+    if(bytesRead + len > readLimit)
+    {
+      len = readLimit - bytesRead;
+    }
+
+    int readLen = parentStream.read(b, off, len);
+    if(readLen > 0)
+    {
+      bytesRead += readLen;
+    }
+    return readLen;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public synchronized void reset() throws IOException
+  {
+    parentStream.reset();
+    bytesRead = markBytesRead;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public long skip(long n) throws IOException
+  {
+    if(bytesRead + n > readLimit)
+    {
+      n = readLimit - bytesRead;
+    }
+
+    bytesRead += n;
+    return parentStream.skip(n);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public boolean markSupported() {
+    return parentStream.markSupported();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void close() throws IOException {
+    parentStream.close();
+  }
+
+  /**
+   * Retrieves the number of bytes read from this stream.
+   *
+   * @return The number of bytes read from this stream.
+   */
+  public int getBytesRead()
+  {
+    return bytesRead;
+  }
+
+  /**
+   * Retrieves the size limit of this stream.
+   *
+   * @return The size limit of this stream.
+   */
+  public int getSizeLimit()
+  {
+    return readLimit;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/StaticUtils.java b/sdk/src/org/opends/sdk/util/StaticUtils.java
new file mode 100644
index 0000000..b7fb4ad
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/StaticUtils.java
@@ -0,0 +1,1998 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+import static org.opends.messages.UtilityMessages.ERR_HEX_DECODE_INVALID_CHARACTER;
+import static org.opends.messages.UtilityMessages.ERR_HEX_DECODE_INVALID_LENGTH;
+import static org.opends.messages.UtilityMessages.ERR_INVALID_ESCAPE_CHAR;
+
+import java.lang.reflect.InvocationTargetException;
+import java.text.ParseException;
+import java.util.*;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.zip.DataFormatException;
+import java.util.zip.Deflater;
+import java.util.zip.Inflater;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.messages.MessageDescriptor;
+import org.opends.sdk.DecodeException;
+
+
+
+/**
+ * Common utility methods.
+ */
+public final class StaticUtils
+{
+  public static final Logger DEBUG_LOG = Logger
+      .getLogger("org.opends.sdk");
+
+  /**
+   * The end-of-line character for this platform.
+   */
+  public static final String EOL = System.getProperty("line.separator");
+
+  // The name of the time zone for universal coordinated time (UTC).
+  private static final String TIME_ZONE_UTC = "UTC";
+
+  // UTC TimeZone is assumed to never change over JVM lifetime
+  private static final TimeZone TIME_ZONE_UTC_OBJ = TimeZone
+      .getTimeZone(TIME_ZONE_UTC);
+
+
+
+  /**
+   * Retrieves a string representation of the provided byte in
+   * hexadecimal.
+   *
+   * @param b
+   *          The byte for which to retrieve the hexadecimal string
+   *          representation.
+   * @return The string representation of the provided byte in
+   *         hexadecimal.
+   */
+  public static String byteToHex(byte b)
+  {
+    switch (b & 0xFF)
+    {
+    case 0x00:
+      return "00";
+    case 0x01:
+      return "01";
+    case 0x02:
+      return "02";
+    case 0x03:
+      return "03";
+    case 0x04:
+      return "04";
+    case 0x05:
+      return "05";
+    case 0x06:
+      return "06";
+    case 0x07:
+      return "07";
+    case 0x08:
+      return "08";
+    case 0x09:
+      return "09";
+    case 0x0A:
+      return "0A";
+    case 0x0B:
+      return "0B";
+    case 0x0C:
+      return "0C";
+    case 0x0D:
+      return "0D";
+    case 0x0E:
+      return "0E";
+    case 0x0F:
+      return "0F";
+    case 0x10:
+      return "10";
+    case 0x11:
+      return "11";
+    case 0x12:
+      return "12";
+    case 0x13:
+      return "13";
+    case 0x14:
+      return "14";
+    case 0x15:
+      return "15";
+    case 0x16:
+      return "16";
+    case 0x17:
+      return "17";
+    case 0x18:
+      return "18";
+    case 0x19:
+      return "19";
+    case 0x1A:
+      return "1A";
+    case 0x1B:
+      return "1B";
+    case 0x1C:
+      return "1C";
+    case 0x1D:
+      return "1D";
+    case 0x1E:
+      return "1E";
+    case 0x1F:
+      return "1F";
+    case 0x20:
+      return "20";
+    case 0x21:
+      return "21";
+    case 0x22:
+      return "22";
+    case 0x23:
+      return "23";
+    case 0x24:
+      return "24";
+    case 0x25:
+      return "25";
+    case 0x26:
+      return "26";
+    case 0x27:
+      return "27";
+    case 0x28:
+      return "28";
+    case 0x29:
+      return "29";
+    case 0x2A:
+      return "2A";
+    case 0x2B:
+      return "2B";
+    case 0x2C:
+      return "2C";
+    case 0x2D:
+      return "2D";
+    case 0x2E:
+      return "2E";
+    case 0x2F:
+      return "2F";
+    case 0x30:
+      return "30";
+    case 0x31:
+      return "31";
+    case 0x32:
+      return "32";
+    case 0x33:
+      return "33";
+    case 0x34:
+      return "34";
+    case 0x35:
+      return "35";
+    case 0x36:
+      return "36";
+    case 0x37:
+      return "37";
+    case 0x38:
+      return "38";
+    case 0x39:
+      return "39";
+    case 0x3A:
+      return "3A";
+    case 0x3B:
+      return "3B";
+    case 0x3C:
+      return "3C";
+    case 0x3D:
+      return "3D";
+    case 0x3E:
+      return "3E";
+    case 0x3F:
+      return "3F";
+    case 0x40:
+      return "40";
+    case 0x41:
+      return "41";
+    case 0x42:
+      return "42";
+    case 0x43:
+      return "43";
+    case 0x44:
+      return "44";
+    case 0x45:
+      return "45";
+    case 0x46:
+      return "46";
+    case 0x47:
+      return "47";
+    case 0x48:
+      return "48";
+    case 0x49:
+      return "49";
+    case 0x4A:
+      return "4A";
+    case 0x4B:
+      return "4B";
+    case 0x4C:
+      return "4C";
+    case 0x4D:
+      return "4D";
+    case 0x4E:
+      return "4E";
+    case 0x4F:
+      return "4F";
+    case 0x50:
+      return "50";
+    case 0x51:
+      return "51";
+    case 0x52:
+      return "52";
+    case 0x53:
+      return "53";
+    case 0x54:
+      return "54";
+    case 0x55:
+      return "55";
+    case 0x56:
+      return "56";
+    case 0x57:
+      return "57";
+    case 0x58:
+      return "58";
+    case 0x59:
+      return "59";
+    case 0x5A:
+      return "5A";
+    case 0x5B:
+      return "5B";
+    case 0x5C:
+      return "5C";
+    case 0x5D:
+      return "5D";
+    case 0x5E:
+      return "5E";
+    case 0x5F:
+      return "5F";
+    case 0x60:
+      return "60";
+    case 0x61:
+      return "61";
+    case 0x62:
+      return "62";
+    case 0x63:
+      return "63";
+    case 0x64:
+      return "64";
+    case 0x65:
+      return "65";
+    case 0x66:
+      return "66";
+    case 0x67:
+      return "67";
+    case 0x68:
+      return "68";
+    case 0x69:
+      return "69";
+    case 0x6A:
+      return "6A";
+    case 0x6B:
+      return "6B";
+    case 0x6C:
+      return "6C";
+    case 0x6D:
+      return "6D";
+    case 0x6E:
+      return "6E";
+    case 0x6F:
+      return "6F";
+    case 0x70:
+      return "70";
+    case 0x71:
+      return "71";
+    case 0x72:
+      return "72";
+    case 0x73:
+      return "73";
+    case 0x74:
+      return "74";
+    case 0x75:
+      return "75";
+    case 0x76:
+      return "76";
+    case 0x77:
+      return "77";
+    case 0x78:
+      return "78";
+    case 0x79:
+      return "79";
+    case 0x7A:
+      return "7A";
+    case 0x7B:
+      return "7B";
+    case 0x7C:
+      return "7C";
+    case 0x7D:
+      return "7D";
+    case 0x7E:
+      return "7E";
+    case 0x7F:
+      return "7F";
+    case 0x80:
+      return "80";
+    case 0x81:
+      return "81";
+    case 0x82:
+      return "82";
+    case 0x83:
+      return "83";
+    case 0x84:
+      return "84";
+    case 0x85:
+      return "85";
+    case 0x86:
+      return "86";
+    case 0x87:
+      return "87";
+    case 0x88:
+      return "88";
+    case 0x89:
+      return "89";
+    case 0x8A:
+      return "8A";
+    case 0x8B:
+      return "8B";
+    case 0x8C:
+      return "8C";
+    case 0x8D:
+      return "8D";
+    case 0x8E:
+      return "8E";
+    case 0x8F:
+      return "8F";
+    case 0x90:
+      return "90";
+    case 0x91:
+      return "91";
+    case 0x92:
+      return "92";
+    case 0x93:
+      return "93";
+    case 0x94:
+      return "94";
+    case 0x95:
+      return "95";
+    case 0x96:
+      return "96";
+    case 0x97:
+      return "97";
+    case 0x98:
+      return "98";
+    case 0x99:
+      return "99";
+    case 0x9A:
+      return "9A";
+    case 0x9B:
+      return "9B";
+    case 0x9C:
+      return "9C";
+    case 0x9D:
+      return "9D";
+    case 0x9E:
+      return "9E";
+    case 0x9F:
+      return "9F";
+    case 0xA0:
+      return "A0";
+    case 0xA1:
+      return "A1";
+    case 0xA2:
+      return "A2";
+    case 0xA3:
+      return "A3";
+    case 0xA4:
+      return "A4";
+    case 0xA5:
+      return "A5";
+    case 0xA6:
+      return "A6";
+    case 0xA7:
+      return "A7";
+    case 0xA8:
+      return "A8";
+    case 0xA9:
+      return "A9";
+    case 0xAA:
+      return "AA";
+    case 0xAB:
+      return "AB";
+    case 0xAC:
+      return "AC";
+    case 0xAD:
+      return "AD";
+    case 0xAE:
+      return "AE";
+    case 0xAF:
+      return "AF";
+    case 0xB0:
+      return "B0";
+    case 0xB1:
+      return "B1";
+    case 0xB2:
+      return "B2";
+    case 0xB3:
+      return "B3";
+    case 0xB4:
+      return "B4";
+    case 0xB5:
+      return "B5";
+    case 0xB6:
+      return "B6";
+    case 0xB7:
+      return "B7";
+    case 0xB8:
+      return "B8";
+    case 0xB9:
+      return "B9";
+    case 0xBA:
+      return "BA";
+    case 0xBB:
+      return "BB";
+    case 0xBC:
+      return "BC";
+    case 0xBD:
+      return "BD";
+    case 0xBE:
+      return "BE";
+    case 0xBF:
+      return "BF";
+    case 0xC0:
+      return "C0";
+    case 0xC1:
+      return "C1";
+    case 0xC2:
+      return "C2";
+    case 0xC3:
+      return "C3";
+    case 0xC4:
+      return "C4";
+    case 0xC5:
+      return "C5";
+    case 0xC6:
+      return "C6";
+    case 0xC7:
+      return "C7";
+    case 0xC8:
+      return "C8";
+    case 0xC9:
+      return "C9";
+    case 0xCA:
+      return "CA";
+    case 0xCB:
+      return "CB";
+    case 0xCC:
+      return "CC";
+    case 0xCD:
+      return "CD";
+    case 0xCE:
+      return "CE";
+    case 0xCF:
+      return "CF";
+    case 0xD0:
+      return "D0";
+    case 0xD1:
+      return "D1";
+    case 0xD2:
+      return "D2";
+    case 0xD3:
+      return "D3";
+    case 0xD4:
+      return "D4";
+    case 0xD5:
+      return "D5";
+    case 0xD6:
+      return "D6";
+    case 0xD7:
+      return "D7";
+    case 0xD8:
+      return "D8";
+    case 0xD9:
+      return "D9";
+    case 0xDA:
+      return "DA";
+    case 0xDB:
+      return "DB";
+    case 0xDC:
+      return "DC";
+    case 0xDD:
+      return "DD";
+    case 0xDE:
+      return "DE";
+    case 0xDF:
+      return "DF";
+    case 0xE0:
+      return "E0";
+    case 0xE1:
+      return "E1";
+    case 0xE2:
+      return "E2";
+    case 0xE3:
+      return "E3";
+    case 0xE4:
+      return "E4";
+    case 0xE5:
+      return "E5";
+    case 0xE6:
+      return "E6";
+    case 0xE7:
+      return "E7";
+    case 0xE8:
+      return "E8";
+    case 0xE9:
+      return "E9";
+    case 0xEA:
+      return "EA";
+    case 0xEB:
+      return "EB";
+    case 0xEC:
+      return "EC";
+    case 0xED:
+      return "ED";
+    case 0xEE:
+      return "EE";
+    case 0xEF:
+      return "EF";
+    case 0xF0:
+      return "F0";
+    case 0xF1:
+      return "F1";
+    case 0xF2:
+      return "F2";
+    case 0xF3:
+      return "F3";
+    case 0xF4:
+      return "F4";
+    case 0xF5:
+      return "F5";
+    case 0xF6:
+      return "F6";
+    case 0xF7:
+      return "F7";
+    case 0xF8:
+      return "F8";
+    case 0xF9:
+      return "F9";
+    case 0xFA:
+      return "FA";
+    case 0xFB:
+      return "FB";
+    case 0xFC:
+      return "FC";
+    case 0xFD:
+      return "FD";
+    case 0xFE:
+      return "FE";
+    case 0xFF:
+      return "FF";
+    default:
+      return "??";
+    }
+  }
+
+
+
+  /**
+   * Attempts to compress the data in the provided source array into the
+   * given destination array. If the compressed data will fit into the
+   * destination array, then this method will return the number of bytes
+   * of compressed data in the array. Otherwise, it will return -1 to
+   * indicate that the compression was not successful. Note that if -1
+   * is returned, then the data in the destination array should be
+   * considered invalid.
+   *
+   * @param src
+   *          The array containing the raw data to compress.
+   * @param srcOff
+   *          The start offset of the source data.
+   * @param srcLen
+   *          The maximum number of source data bytes to compress.
+   * @param dst
+   *          The array into which the compressed data should be
+   *          written.
+   * @param dstOff
+   *          The start offset of the compressed data.
+   * @param dstLen
+   *          The maximum number of bytes of compressed data.
+   * @return The number of bytes of compressed data, or -1 if it was not
+   *         possible to actually compress the data.
+   */
+  public static int compress(byte[] src, int srcOff, int srcLen,
+      byte[] dst, int dstOff, int dstLen)
+  {
+    final Deflater deflater = new Deflater();
+    try
+    {
+      deflater.setInput(src, srcOff, srcLen);
+      deflater.finish();
+
+      final int compressedLength = deflater
+          .deflate(dst, dstOff, dstLen);
+      if (deflater.finished())
+      {
+        return compressedLength;
+      }
+      else
+      {
+        return -1;
+      }
+    }
+    finally
+    {
+      deflater.end();
+    }
+  }
+
+
+
+  /**
+   * Attempts to compress the data in the provided byte sequence into
+   * the provided byte string builder. Note that if compression was not
+   * successful, then the byte string builder will be left unchanged.
+   *
+   * @param input
+   *          The source data to be compressed.
+   * @param output
+   *          The destination buffer to which the compressed data will
+   *          be appended.
+   * @return <code>true</code> if compression was successful or
+   *         <code>false</code> otherwise.
+   */
+  public static boolean compress(ByteSequence input,
+      ByteStringBuilder output)
+  {
+    // Avoid extra copies if possible.
+    byte[] inputBuffer;
+    int inputOffset;
+    final int inputLength = input.length();
+
+    if (input instanceof ByteString)
+    {
+      final ByteString byteString = (ByteString) input;
+      inputBuffer = byteString.buffer;
+      inputOffset = byteString.offset;
+    }
+    else if (input instanceof ByteStringBuilder)
+    {
+      final ByteStringBuilder builder = (ByteStringBuilder) input;
+      inputBuffer = builder.buffer;
+      inputOffset = 0;
+    }
+    else
+    {
+      inputBuffer = new byte[inputLength];
+      inputOffset = 0;
+      input.copyTo(inputBuffer);
+    }
+
+    // Make sure the free space in the destination buffer is at least
+    // as big as this.
+    output.ensureAdditionalCapacity(inputLength);
+
+    final int compressedSize = compress(inputBuffer, inputOffset,
+        inputLength, output.buffer, output.length, output.buffer.length
+            - output.length);
+
+    if (compressedSize != -1)
+    {
+      if (StaticUtils.DEBUG_LOG.isLoggable(Level.FINE))
+      {
+        StaticUtils.DEBUG_LOG.fine(String.format("Compression %d/%d%n",
+            compressedSize, inputLength));
+      }
+
+      output.length += compressedSize;
+      return true;
+    }
+
+    return false;
+  }
+
+
+
+  public static ByteString evaluateEscapes(SubstringReader reader,
+      char[] escapeChars, boolean trim) throws DecodeException
+  {
+    return evaluateEscapes(reader, escapeChars, escapeChars, trim);
+  }
+
+
+
+  public static ByteString evaluateEscapes(SubstringReader reader,
+      char[] escapeChars, char[] delimiterChars, boolean trim)
+      throws DecodeException
+  {
+    int length = 0;
+    int lengthWithoutSpace = 0;
+    char c;
+    ByteStringBuilder valueBuffer = null;
+
+    if (trim)
+    {
+      reader.skipWhitespaces();
+    }
+
+    reader.mark();
+    while (reader.remaining() > 0)
+    {
+      c = reader.read();
+      if (c == 0x5C) // The backslash character
+      {
+        if (valueBuffer == null)
+        {
+          valueBuffer = new ByteStringBuilder();
+        }
+        valueBuffer.append(reader.read(length));
+        valueBuffer.append(evaluateEscapedChar(reader, escapeChars));
+        reader.mark();
+        length = lengthWithoutSpace = 0;
+      }
+      if (delimiterChars != null)
+      {
+        for (final char delimiterChar : delimiterChars)
+        {
+          if (c == delimiterChar)
+          {
+            reader.reset();
+            if (valueBuffer != null)
+            {
+              if (trim)
+              {
+                valueBuffer.append(reader.read(lengthWithoutSpace));
+              }
+              else
+              {
+                valueBuffer.append(reader.read(length));
+              }
+              return valueBuffer.toByteString();
+            }
+            else
+            {
+              if (trim)
+              {
+                if (lengthWithoutSpace > 0)
+                {
+                  return ByteString.valueOf(reader
+                      .read(lengthWithoutSpace));
+                }
+                return ByteString.empty();
+              }
+              if (length > 0)
+              {
+                return ByteString.valueOf(reader.read(length));
+              }
+              return ByteString.empty();
+            }
+          }
+        }
+      }
+      length++;
+      if (c != ' ')
+      {
+        lengthWithoutSpace = length;
+      }
+      else
+      {
+        lengthWithoutSpace++;
+      }
+    }
+
+    reader.reset();
+    if (valueBuffer != null)
+    {
+      if (trim)
+      {
+        valueBuffer.append(reader.read(lengthWithoutSpace));
+      }
+      else
+      {
+        valueBuffer.append(reader.read(length));
+      }
+      return valueBuffer.toByteString();
+    }
+    else
+    {
+      if (trim)
+      {
+        if (lengthWithoutSpace > 0)
+        {
+          return ByteString.valueOf(reader.read(lengthWithoutSpace));
+        }
+        return ByteString.empty();
+      }
+      if (length > 0)
+      {
+        return ByteString.valueOf(reader.read(length));
+      }
+      return ByteString.empty();
+    }
+  }
+
+
+
+  /**
+   * Returns a string containing provided date formatted using the
+   * generalized time syntax.
+   *
+   * @param date
+   *          The date to be formated.
+   * @return The string containing provided date formatted using the
+   *         generalized time syntax.
+   * @throws NullPointerException
+   *           If {@code date} was {@code null}.
+   */
+  public static String formatAsGeneralizedTime(Date date)
+  {
+    return formatAsGeneralizedTime(date.getTime());
+  }
+
+
+
+  /**
+   * Returns a string containing provided date formatted using the
+   * generalized time syntax.
+   *
+   * @param date
+   *          The date to be formated.
+   * @return The string containing provided date formatted using the
+   *         generalized time syntax.
+   * @throws IllegalArgumentException
+   *           If {@code date} was invalid.
+   */
+  public static String formatAsGeneralizedTime(long date)
+  {
+    // 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(date);
+
+    // Format the year yyyy.
+    int n = calendar.get(Calendar.YEAR);
+    if (n < 0)
+    {
+      final IllegalArgumentException e = new IllegalArgumentException(
+          "Year cannot be < 0:" + n);
+      StaticUtils.DEBUG_LOG.throwing("GeneralizedTimeSyntax", "format",
+          e);
+      throw e;
+    }
+    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 sb.toString();
+  }
+
+
+
+  /**
+   * Construct a byte array containing the UTF-8 encoding of the
+   * provided string. This is significantly faster than calling
+   * {@link String#getBytes(String)} for ASCII strings.
+   *
+   * @param s
+   *          The string to convert to a UTF-8 byte array.
+   * @return Returns a byte array containing the UTF-8 encoding of the
+   *         provided string.
+   */
+  public static byte[] getBytes(String s)
+  {
+    if (s == null)
+    {
+      return null;
+    }
+
+    try
+    {
+      char c;
+      final int length = s.length();
+      final byte[] returnArray = new byte[length];
+      for (int i = 0; i < length; i++)
+      {
+        c = s.charAt(i);
+        returnArray[i] = (byte) (c & 0x0000007F);
+        if (c != returnArray[i])
+        {
+          return s.getBytes("UTF-8");
+        }
+      }
+
+      return returnArray;
+    }
+    catch (final Exception e)
+    {
+      DEBUG_LOG.warning("Unable to encode UTF-8 string " + s);
+
+      return s.getBytes();
+    }
+  }
+
+
+
+  /**
+   * Retrieves the best human-readable message for the provided
+   * exception. For exceptions defined in the OpenDS project, it will
+   * attempt to use the message (combining it with the message ID if
+   * available). For some exceptions that use encapsulation (e.g.,
+   * InvocationTargetException), it will be unwrapped and the cause will
+   * be treated. For all others, the
+   *
+   * @param t
+   *          The {@code Throwable} object for which to retrieve the
+   *          message.
+   * @return The human-readable message generated for the provided
+   *         exception.
+   */
+  public static Message getExceptionMessage(Throwable t)
+  {
+    if (t instanceof LocalizableException)
+    {
+      final LocalizableException ie = (LocalizableException) t;
+
+      final StringBuilder message = new StringBuilder();
+      message.append(ie.getMessageObject());
+      message.append(" (id=");
+      final Message ieMsg = ie.getMessageObject();
+      if (ieMsg != null)
+      {
+        message.append(ieMsg.getDescriptor().getId());
+      }
+      else
+      {
+        message.append(MessageDescriptor.NULL_ID);
+      }
+      message.append(")");
+      return Message.raw(message.toString());
+    }
+    else if (t instanceof NullPointerException)
+    {
+      final StackTraceElement[] stackElements = t.getStackTrace();
+
+      final MessageBuilder message = new MessageBuilder();
+      message.append("NullPointerException(");
+      message.append(stackElements[0].getFileName());
+      message.append(":");
+      message.append(stackElements[0].getLineNumber());
+      message.append(")");
+      return message.toMessage();
+    }
+    else if (t instanceof InvocationTargetException
+        && t.getCause() != null)
+    {
+      return getExceptionMessage(t.getCause());
+    }
+    else
+    {
+      final StringBuilder message = new StringBuilder();
+
+      final String className = t.getClass().getName();
+      final int periodPos = className.lastIndexOf('.');
+      if (periodPos > 0)
+      {
+        message.append(className.substring(periodPos + 1));
+      }
+      else
+      {
+        message.append(className);
+      }
+
+      message.append("(");
+      if (t.getMessage() == null)
+      {
+        final StackTraceElement[] stackElements = t.getStackTrace();
+        message.append(stackElements[0].getFileName());
+        message.append(":");
+        message.append(stackElements[0].getLineNumber());
+
+        // FIXME Temporary to debug issue 2256.
+        if (t instanceof IllegalStateException)
+        {
+          for (int i = 1; i < stackElements.length; i++)
+          {
+            message.append(' ');
+            message.append(stackElements[i].getFileName());
+            message.append(":");
+            message.append(stackElements[i].getLineNumber());
+          }
+        }
+      }
+      else
+      {
+        message.append(t.getMessage());
+      }
+
+      message.append(")");
+
+      return Message.raw(message.toString());
+    }
+  }
+
+
+
+  /**
+   * Converts the provided hexadecimal string to a byte array.
+   *
+   * @param hexString
+   *          The hexadecimal string to convert to a byte array.
+   * @return The byte array containing the binary representation of the
+   *         provided hex string.
+   * @throws java.text.ParseException
+   *           If the provided string contains invalid hexadecimal
+   *           digits or does not contain an even number of digits.
+   */
+  public static byte[] hexStringToByteArray(String hexString)
+      throws ParseException
+  {
+    int length;
+    if (hexString == null || (length = hexString.length()) == 0)
+    {
+      return new byte[0];
+    }
+
+    if (length % 2 == 1)
+    {
+      final Message message = ERR_HEX_DECODE_INVALID_LENGTH
+          .get(hexString);
+      throw new ParseException(message.toString(), 0);
+    }
+
+    final int arrayLength = length / 2;
+    final byte[] returnArray = new byte[arrayLength];
+    for (int i = 0; i < arrayLength; i++)
+    {
+      returnArray[i] = hexToByte(hexString.charAt(i * 2), hexString
+          .charAt(i * 2 + 1));
+    }
+
+    return returnArray;
+  }
+
+
+
+  /**
+   * Converts the provided pair of characters to a byte.
+   *
+   * @param c1
+   *          The first hexadecimal character.
+   * @param c2
+   *          The second hexadecimal character.
+   * @return The byte containing the binary representation of the
+   *         provided hex characters.
+   * @throws ParseException
+   *           If the provided string contains invalid hexadecimal
+   *           digits or does not contain an even number of digits.
+   */
+  public static byte hexToByte(char c1, char c2) throws ParseException
+  {
+    byte b;
+    switch (c1)
+    {
+    case '0':
+      b = 0x00;
+      break;
+    case '1':
+      b = 0x10;
+      break;
+    case '2':
+      b = 0x20;
+      break;
+    case '3':
+      b = 0x30;
+      break;
+    case '4':
+      b = 0x40;
+      break;
+    case '5':
+      b = 0x50;
+      break;
+    case '6':
+      b = 0x60;
+      break;
+    case '7':
+      b = 0x70;
+      break;
+    case '8':
+      b = (byte) 0x80;
+      break;
+    case '9':
+      b = (byte) 0x90;
+      break;
+    case 'A':
+    case 'a':
+      b = (byte) 0xA0;
+      break;
+    case 'B':
+    case 'b':
+      b = (byte) 0xB0;
+      break;
+    case 'C':
+    case 'c':
+      b = (byte) 0xC0;
+      break;
+    case 'D':
+    case 'd':
+      b = (byte) 0xD0;
+      break;
+    case 'E':
+    case 'e':
+      b = (byte) 0xE0;
+      break;
+    case 'F':
+    case 'f':
+      b = (byte) 0xF0;
+      break;
+    default:
+      final Message message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
+          new String(new char[] { c1, c2 }), c1);
+      throw new ParseException(message.toString(), 0);
+    }
+
+    switch (c2)
+    {
+    case '0':
+      // No action required.
+      break;
+    case '1':
+      b |= 0x01;
+      break;
+    case '2':
+      b |= 0x02;
+      break;
+    case '3':
+      b |= 0x03;
+      break;
+    case '4':
+      b |= 0x04;
+      break;
+    case '5':
+      b |= 0x05;
+      break;
+    case '6':
+      b |= 0x06;
+      break;
+    case '7':
+      b |= 0x07;
+      break;
+    case '8':
+      b |= 0x08;
+      break;
+    case '9':
+      b |= 0x09;
+      break;
+    case 'A':
+    case 'a':
+      b |= 0x0A;
+      break;
+    case 'B':
+    case 'b':
+      b |= 0x0B;
+      break;
+    case 'C':
+    case 'c':
+      b |= 0x0C;
+      break;
+    case 'D':
+    case 'd':
+      b |= 0x0D;
+      break;
+    case 'E':
+    case 'e':
+      b |= 0x0E;
+      break;
+    case 'F':
+    case 'f':
+      b |= 0x0F;
+      break;
+    default:
+      final Message message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
+          new String(new char[] { c1, c2 }), c1);
+      throw new ParseException(message.toString(), 0);
+    }
+
+    return b;
+  }
+
+
+
+  /**
+   * Indicates whether the provided character is an ASCII alphabetic
+   * character.
+   *
+   * @param c
+   *          The character for which to make the determination.
+   * @return <CODE>true</CODE> if the provided value is an uppercase or
+   *         lowercase ASCII alphabetic character, or <CODE>false</CODE>
+   *         if it is not.
+   */
+  public static boolean isAlpha(char c)
+  {
+    final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+    return cp != null ? cp.isLetter() : false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided character is a numeric digit.
+   *
+   * @param c
+   *          The character for which to make the determination.
+   * @return <CODE>true</CODE> if the provided character represents a
+   *         numeric digit, or <CODE>false</CODE> if not.
+   */
+  public static boolean isDigit(char c)
+  {
+    final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+    return cp != null ? cp.isDigit() : false;
+  }
+
+
+
+  /**
+   * Indicates whether the provided character is a hexadecimal digit.
+   *
+   * @param c
+   *          The character for which to make the determination.
+   * @return <CODE>true</CODE> if the provided character represents a
+   *         hexadecimal digit, or <CODE>false</CODE> if not.
+   */
+  public static boolean isHexDigit(char c)
+  {
+    final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+    return cp != null ? cp.isHexDigit() : false;
+  }
+
+
+
+  /**
+   * Returns a string representation of the contents of the provided
+   * byte sequence using hexadecimal characters and a space between each
+   * byte.
+   *
+   * @param bytes
+   *          The byte sequence.
+   * @return A string representation of the contents of the provided
+   *         byte sequence using hexadecimal characters.
+   */
+  public static String toHex(ByteSequence bytes)
+  {
+    return toHex(bytes, new StringBuilder((bytes.length() - 1) * 3 + 2))
+        .toString();
+  }
+
+
+
+  /**
+   * Appends the string representation of the contents of the provided
+   * byte sequence to a string builder using hexadecimal characters and
+   * a space between each byte.
+   *
+   * @param bytes
+   *          The byte sequence.
+   * @param builder
+   *          The string builder to which the hexadecimal representation
+   *          of {@code bytes} should be appended.
+   * @return The string builder.
+   */
+  public static StringBuilder toHex(ByteSequence bytes,
+      StringBuilder builder)
+  {
+    final int length = bytes.length();
+    builder.ensureCapacity(builder.length() + (length - 1) * 3 + 2);
+    builder.append(StaticUtils.byteToHex(bytes.byteAt(0)));
+    for (int i = 1; i < length; i++)
+    {
+      builder.append(" ");
+      builder.append(StaticUtils.byteToHex(bytes.byteAt(i)));
+    }
+    return builder;
+  }
+
+
+
+  /**
+   * Appends a string representation of the data in the provided byte
+   * sequence to the given string builder using the specified indent.
+   * <p>
+   * The data will be formatted with sixteen hex bytes in a row followed
+   * by the ASCII representation, then wrapping to a new line as
+   * necessary. The state of the byte buffer is not changed.
+   *
+   * @param bytes
+   *          The byte sequence.
+   * @param builder
+   *          The string builder to which the information is to be
+   *          appended.
+   * @param indent
+   *          The number of spaces to indent the output.
+   * @return The string builder.
+   */
+  public static StringBuilder toHexPlusAscii(ByteSequence bytes,
+      StringBuilder builder, int indent)
+  {
+    final StringBuilder indentBuf = new StringBuilder(indent);
+    for (int i = 0; i < indent; i++)
+    {
+      indentBuf.append(' ');
+    }
+
+    final int length = bytes.length();
+    int pos = 0;
+    while (length - pos >= 16)
+    {
+      final StringBuilder asciiBuf = new StringBuilder(17);
+
+      byte currentByte = bytes.byteAt(pos);
+      builder.append(indentBuf);
+      builder.append(StaticUtils.byteToHex(currentByte));
+      asciiBuf.append(byteToASCII(currentByte));
+      pos++;
+
+      for (int i = 1; i < 16; i++, pos++)
+      {
+        currentByte = bytes.byteAt(pos);
+        builder.append(' ');
+        builder.append(StaticUtils.byteToHex(currentByte));
+        asciiBuf.append(byteToASCII(currentByte));
+
+        if (i == 7)
+        {
+          builder.append("  ");
+          asciiBuf.append(' ');
+        }
+      }
+
+      builder.append("  ");
+      builder.append(asciiBuf);
+      builder.append(EOL);
+    }
+
+    final int remaining = length - pos;
+    if (remaining > 0)
+    {
+      final StringBuilder asciiBuf = new StringBuilder(remaining + 1);
+
+      byte currentByte = bytes.byteAt(pos);
+      builder.append(indentBuf);
+      builder.append(StaticUtils.byteToHex(currentByte));
+      asciiBuf.append(byteToASCII(currentByte));
+      pos++;
+
+      for (int i = 1; i < 16; i++, pos++)
+      {
+        builder.append(' ');
+
+        if (i < remaining)
+        {
+          currentByte = bytes.byteAt(pos);
+          builder.append(StaticUtils.byteToHex(currentByte));
+          asciiBuf.append(byteToASCII(currentByte));
+        }
+        else
+        {
+          builder.append("  ");
+        }
+
+        if (i == 7)
+        {
+          builder.append("  ");
+
+          if (i < remaining)
+          {
+            asciiBuf.append(' ');
+          }
+        }
+      }
+
+      builder.append("  ");
+      builder.append(asciiBuf);
+      builder.append(EOL);
+    }
+
+    return builder;
+  }
+
+
+
+  /**
+   * Appends a lowercase string representation of the contents of the
+   * given byte array to the provided buffer. This implementation
+   * presumes that the provided string will contain only ASCII
+   * characters and is optimized for that case. However, if a non-ASCII
+   * character is encountered it will fall back on a more expensive
+   * algorithm that will work properly for non-ASCII characters.
+   *
+   * @param b
+   *          The byte array for which to obtain the lowercase string
+   *          representation.
+   * @param builder
+   *          The buffer to which the lowercase form of the string
+   *          should be appended.
+   * @return The updated {@code StringBuilder}.
+   */
+  public static StringBuilder toLowerCase(ByteSequence b,
+      StringBuilder builder)
+  {
+    Validator.ensureNotNull(b, builder);
+
+    // FIXME: What locale should we use for non-ASCII characters? I
+    // think we should use default to the Unicode StringPrep.
+
+    final int origBufferLen = builder.length();
+    final int length = b.length();
+
+    for (int i = 0; i < length; i++)
+    {
+      final int c = b.byteAt(i);
+
+      if (c < 0)
+      {
+        builder.replace(origBufferLen, builder.length(), b.toString()
+            .toLowerCase(Locale.ENGLISH));
+        return builder;
+      }
+
+      // At this point 0 <= 'c' <= 128.
+      final ASCIICharProp cp = ASCIICharProp.valueOf(c);
+      builder.append(cp.toLowerCase());
+    }
+
+    return builder;
+  }
+
+
+
+  /**
+   * Retrieves a lower-case representation of the given string. This
+   * implementation presumes that the provided string will contain only
+   * ASCII characters and is optimized for that case. However, if a
+   * non-ASCII character is encountered it will fall back on a more
+   * expensive algorithm that will work properly for non-ASCII
+   * characters.
+   *
+   * @param s
+   *          The string for which to obtain the lower-case
+   *          representation.
+   * @return The lower-case representation of the given string.
+   */
+  public static String toLowerCase(String s)
+  {
+    Validator.ensureNotNull(s);
+
+    // FIXME: What locale should we use for non-ASCII characters? I
+    // think we should use default to the Unicode StringPrep.
+
+    // This code is optimized for the case where the input string 's'
+    // has already been converted to lowercase.
+    final int length = s.length();
+    int i = 0;
+    ASCIICharProp cp = null;
+
+    // Scan for non lowercase ASCII.
+    while (i < length)
+    {
+      cp = ASCIICharProp.valueOf(s.charAt(i));
+      if (cp == null || cp.isUpperCase()) break;
+      i++;
+    }
+
+    if (i == length)
+    {
+      // String was already lowercase ASCII.
+      return s;
+    }
+
+    // Found non lowercase ASCII.
+    final StringBuilder builder = new StringBuilder(length);
+    builder.append(s, 0, i);
+
+    if (cp != null)
+    {
+      // Upper-case ASCII.
+      builder.append(cp.toLowerCase());
+      i++;
+      while (i < length)
+      {
+        cp = ASCIICharProp.valueOf(s.charAt(i));
+        if (cp == null) break;
+        builder.append(cp.toLowerCase());
+        i++;
+      }
+    }
+
+    if (i < length)
+    {
+      builder.append(s.substring(i).toLowerCase(Locale.ENGLISH));
+    }
+
+    return builder.toString();
+  }
+
+
+
+  /**
+   * Appends a lower-case representation of the given string to the
+   * provided buffer. This implementation presumes that the provided
+   * string will contain only ASCII characters and is optimized for that
+   * case. However, if a non-ASCII character is encountered it will fall
+   * back on a more expensive algorithm that will work properly for
+   * non-ASCII characters.
+   *
+   * @param s
+   *          The string for which to obtain the lower-case
+   *          representation.
+   * @param builder
+   *          The {@code StringBuilder} to which the lower-case form of
+   *          the string should be appended.
+   * @return The updated {@code StringBuilder}.
+   */
+  public static StringBuilder toLowerCase(String s,
+      StringBuilder builder)
+  {
+    Validator.ensureNotNull(s, builder);
+
+    // FIXME: What locale should we use for non-ASCII characters? I
+    // think we should use default to the Unicode StringPrep.
+
+    final int length = s.length();
+    builder.ensureCapacity(builder.length() + length);
+
+    for (int i = 0; i < length; i++)
+    {
+      final ASCIICharProp cp = ASCIICharProp.valueOf(s.charAt(i));
+      if (cp != null)
+      {
+        builder.append(cp.toLowerCase());
+      }
+      else
+      {
+        // Non-ASCII.
+        builder.append(s.substring(i).toLowerCase(Locale.ENGLISH));
+        return builder;
+      }
+    }
+
+    return builder;
+  }
+
+
+
+  /**
+   * Attempts to uncompress the data in the provided source array into
+   * the given destination array. If the uncompressed data will fit into
+   * the given destination array, then this method will return the
+   * number of bytes of uncompressed data written into the destination
+   * buffer. Otherwise, it will return a negative value to indicate that
+   * the destination buffer was not large enough. The absolute value of
+   * that negative return value will indicate the buffer size required
+   * to fully decompress the data. Note that if a negative value is
+   * returned, then the data in the destination array should be
+   * considered invalid.
+   *
+   * @param src
+   *          The array containing the raw data to compress.
+   * @param srcOff
+   *          The start offset of the source data.
+   * @param srcLen
+   *          The maximum number of source data bytes to compress.
+   * @param dst
+   *          The array into which the compressed data should be
+   *          written.
+   * @param dstOff
+   *          The start offset of the compressed data.
+   * @param dstLen
+   *          The maximum number of bytes of compressed data.
+   * @return A positive value containing the number of bytes of
+   *         uncompressed data written into the destination buffer, or a
+   *         negative value whose absolute value is the size of the
+   *         destination buffer required to fully decompress the
+   *         provided data.
+   * @throws java.util.zip.DataFormatException
+   *           If a problem occurs while attempting to uncompress the
+   *           data.
+   */
+  public static int uncompress(byte[] src, int srcOff, int srcLen,
+      byte[] dst, int dstOff, int dstLen) throws DataFormatException
+  {
+    final Inflater inflater = new Inflater();
+    try
+    {
+      inflater.setInput(src, srcOff, srcLen);
+
+      final int decompressedLength = inflater.inflate(dst, dstOff,
+          dstLen);
+      if (inflater.finished())
+      {
+        return decompressedLength;
+      }
+      else
+      {
+        int totalLength = decompressedLength;
+
+        while (!inflater.finished())
+        {
+          totalLength += inflater.inflate(dst, dstOff, dstLen);
+        }
+
+        return -totalLength;
+      }
+    }
+    finally
+    {
+      inflater.end();
+    }
+  }
+
+
+
+  /**
+   * Attempts to uncompress the data in the provided byte sequence into
+   * the provided byte string builder. Note that if uncompression was
+   * not successful, then the data in the destination buffer should be
+   * considered invalid.
+   *
+   * @param input
+   *          The source data to be uncompressed.
+   * @param output
+   *          The destination buffer to which the uncompressed data will
+   *          be appended.
+   * @param uncompressedSize
+   *          The uncompressed size of the data if known or 0 otherwise.
+   * @return <code>true</code> if decompression was successful or
+   *         <code>false</code> otherwise.
+   * @throws java.util.zip.DataFormatException
+   *           If a problem occurs while attempting to uncompress the
+   *           data.
+   */
+  public static boolean uncompress(ByteSequence input,
+      ByteStringBuilder output, int uncompressedSize)
+      throws DataFormatException
+  {
+    // Avoid extra copies if possible.
+    byte[] inputBuffer;
+    int inputOffset;
+    final int inputLength = input.length();
+
+    if (input instanceof ByteString)
+    {
+      final ByteString byteString = (ByteString) input;
+      inputBuffer = byteString.buffer;
+      inputOffset = byteString.offset;
+    }
+    else if (input instanceof ByteStringBuilder)
+    {
+      final ByteStringBuilder builder = (ByteStringBuilder) input;
+      inputBuffer = builder.buffer;
+      inputOffset = 0;
+    }
+    else
+    {
+      inputBuffer = new byte[inputLength];
+      inputOffset = 0;
+      input.copyTo(inputBuffer);
+    }
+
+    // Resize destination buffer if a uncompressed size was provided.
+    if (uncompressedSize > 0)
+    {
+      output.ensureAdditionalCapacity(uncompressedSize);
+    }
+
+    int decompressResult = uncompress(inputBuffer, inputOffset,
+        inputLength, output.buffer, output.length, output.buffer.length
+            - output.length);
+
+    if (decompressResult < 0)
+    {
+      // The destination buffer wasn't big enough. Resize and retry.
+      output.ensureAdditionalCapacity(-decompressResult);
+      decompressResult = uncompress(inputBuffer, inputOffset,
+          inputLength, output.buffer, output.length,
+          output.buffer.length - output.length);
+    }
+
+    if (decompressResult >= 0)
+    {
+      // It was successful.
+      output.length += decompressResult;
+      return true;
+    }
+
+    // Still unsuccessful. Give up.
+    return false;
+  }
+
+
+
+  /**
+   * Retrieves the printable ASCII representation of the provided byte.
+   *
+   * @param b
+   *          The byte for which to retrieve the printable ASCII
+   *          representation.
+   * @return The printable ASCII representation of the provided byte, or
+   *         a space if the provided byte does not have printable ASCII
+   *         representation.
+   */
+  private static char byteToASCII(byte b)
+  {
+    if (b >= 32 && b <= 126)
+    {
+      return (char) b;
+    }
+
+    return ' ';
+  }
+
+
+
+  private static char evaluateEscapedChar(SubstringReader reader,
+      char[] escapeChars) throws DecodeException
+  {
+    final char c1 = reader.read();
+    byte b;
+    switch (c1)
+    {
+    case '0':
+      b = 0x00;
+      break;
+    case '1':
+      b = 0x10;
+      break;
+    case '2':
+      b = 0x20;
+      break;
+    case '3':
+      b = 0x30;
+      break;
+    case '4':
+      b = 0x40;
+      break;
+    case '5':
+      b = 0x50;
+      break;
+    case '6':
+      b = 0x60;
+      break;
+    case '7':
+      b = 0x70;
+      break;
+    case '8':
+      b = (byte) 0x80;
+      break;
+    case '9':
+      b = (byte) 0x90;
+      break;
+    case 'A':
+    case 'a':
+      b = (byte) 0xA0;
+      break;
+    case 'B':
+    case 'b':
+      b = (byte) 0xB0;
+      break;
+    case 'C':
+    case 'c':
+      b = (byte) 0xC0;
+      break;
+    case 'D':
+    case 'd':
+      b = (byte) 0xD0;
+      break;
+    case 'E':
+    case 'e':
+      b = (byte) 0xE0;
+      break;
+    case 'F':
+    case 'f':
+      b = (byte) 0xF0;
+      break;
+    default:
+      if (c1 == 0x5C)
+      {
+        return c1;
+      }
+      if (escapeChars != null)
+      {
+        for (final char escapeChar : escapeChars)
+        {
+          if (c1 == escapeChar)
+          {
+            return c1;
+          }
+        }
+      }
+      final Message message = ERR_INVALID_ESCAPE_CHAR.get(reader
+          .getString(), c1);
+      throw DecodeException.error(message);
+    }
+
+    // The two positions must be the hex characters that
+    // comprise the escaped value.
+    if (reader.remaining() == 0)
+    {
+      final Message message = ERR_HEX_DECODE_INVALID_LENGTH.get(reader
+          .getString());
+
+      throw DecodeException.error(message);
+    }
+
+    final char c2 = reader.read();
+    switch (c2)
+    {
+    case '0':
+      // No action required.
+      break;
+    case '1':
+      b |= 0x01;
+      break;
+    case '2':
+      b |= 0x02;
+      break;
+    case '3':
+      b |= 0x03;
+      break;
+    case '4':
+      b |= 0x04;
+      break;
+    case '5':
+      b |= 0x05;
+      break;
+    case '6':
+      b |= 0x06;
+      break;
+    case '7':
+      b |= 0x07;
+      break;
+    case '8':
+      b |= 0x08;
+      break;
+    case '9':
+      b |= 0x09;
+      break;
+    case 'A':
+    case 'a':
+      b |= 0x0A;
+      break;
+    case 'B':
+    case 'b':
+      b |= 0x0B;
+      break;
+    case 'C':
+    case 'c':
+      b |= 0x0C;
+      break;
+    case 'D':
+    case 'd':
+      b |= 0x0D;
+      break;
+    case 'E':
+    case 'e':
+      b |= 0x0E;
+      break;
+    case 'F':
+    case 'f':
+      b |= 0x0F;
+      break;
+    default:
+      final Message message = ERR_HEX_DECODE_INVALID_CHARACTER.get(
+          new String(new char[] { c1, c2 }), c1);
+      throw DecodeException.error(message);
+    }
+    return (char) b;
+  }
+
+
+
+  // Prevent instantiation.
+  private StaticUtils()
+  {
+    // No implementation required.
+  }
+
+}
diff --git a/sdk/src/org/opends/sdk/util/StringPrepProfile.java b/sdk/src/org/opends/sdk/util/StringPrepProfile.java
new file mode 100644
index 0000000..e8b9abe
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/StringPrepProfile.java
@@ -0,0 +1,696 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+package org.opends.sdk.util;
+
+
+
+import static org.opends.sdk.util.Validator.ensureNotNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+
+
+
+/**
+ * This class defines the "stringprep" profile as defined in RFC 4518.
+ * It must be used by all the matching rules that support unicode
+ * characters. For a complete list of such rules, refer to Section 4.2,
+ * RFC 4517.
+ */
+public final class StringPrepProfile
+{
+  /**
+   * A Table defining the mapped code-points as per RFC 3454.
+   */
+  private static class MappingTable
+  {
+    // Set of chars which are deleted from the incoming value.
+    private final static HashSet<Character> map2null =
+        new HashSet<Character>();
+    // Set of chars which are replaced by a SPACE when found.
+    private final static HashSet<Character> map2space =
+        new HashSet<Character>();
+    // Table for case-folding. Map of Character and String containing
+    // uppercase
+    // and lowercase value as the key-value pair.
+    private final static HashMap<Character, String> caseMappingTable =
+        new HashMap<Character, String>();
+
+    static
+    {
+      // Appendix B.1 RFC 3454.
+      final char[][] mapped2null =
+          new char[][] { { '\u0000', '\u0008' },
+              { '\u000E', '\u001F' }, { '\u007F', '\u0084' },
+              { '\u0086', '\u009F' }, { '\u00AD' }, { '\u034F' },
+              { '\u06DD' }, { '\u070F' }, { '\u1806' },
+              { '\u180B', '\u180E' }, { '\u200C', '\u200F' },
+              { '\u202A', '\u202E' }, { '\u2060', '\u2063' },
+              { '\u206A', '\u206F' }, { '\uFE00', '\uFE0F' },
+              { '\uFEFF' }, { '\uFFF9', '\uFFFC' } };
+
+      for (final char[] element : mapped2null)
+      {
+        if (element.length == 1)
+        {
+          map2null.add(element[0]);
+        }
+        else
+        {
+          // Contains a range of values.
+          for (char c = element[0]; c <= element[1]; c++)
+          {
+            map2null.add(c);
+          }
+        }
+      }
+
+      final char[] mapped2Space =
+          new char[] { '\u0009', 0xA, '\u000B', '\u000C', 0xD,
+              '\u0085', '\u00A0', '\u1680', '\u2000', '\u2001',
+              '\u2002', '\u2003', '\u2004', '\u2005', '\u2006',
+              '\u2007', '\u2008', '\u2009', '\u200A', '\u2028',
+              '\u2029', '\u202F', '\u205F', '\u3000' };
+
+      for (final char c : mapped2Space)
+      {
+        map2space.add(c);
+      }
+
+      // Appendix B.2 RFC 3454.
+      // Build an uppercase array and a lowercase array and create a map
+      // of both
+      // values.
+      final char[] upperCaseArr =
+          new char[] { '\u0041', '\u0042', '\u0043', '\u0044',
+              '\u0045', '\u0046', '\u0047', '\u0048', '\u0049',
+              '\u004A', '\u004B', '\u004C', '\u004D', '\u004E',
+              '\u004F', '\u0050', '\u0051', '\u0052', '\u0053',
+              '\u0054', '\u0055', '\u0056', '\u0057', '\u0058',
+              '\u0059', '\u005A', '\u00B5', '\u00C0', '\u00C1',
+              '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6',
+              '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB',
+              '\u00CC', '\u00CD', '\u00CE', '\u00CF', '\u00D0',
+              '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5',
+              '\u00D6', '\u00D8', '\u00D9', '\u00DA', '\u00DB',
+              '\u00DC', '\u00DD', '\u00DE', '\u00DF', '\u0100',
+              '\u0102', '\u0104', '\u0106', '\u0108', '\u010A',
+              '\u010C', '\u010E', '\u0110', '\u0112', '\u0114',
+              '\u0116', '\u0118', '\u011A', '\u011C', '\u011E',
+              '\u0120', '\u0122', '\u0124', '\u0126', '\u0128',
+              '\u012A', '\u012C', '\u012E', '\u0130', '\u0132',
+              '\u0134', '\u0136', '\u0139', '\u013B', '\u013D',
+              '\u013F', '\u0141', '\u0143', '\u0145', '\u0147',
+              '\u0149', '\u014A', '\u014C', '\u014E', '\u0150',
+              '\u0152', '\u0154', '\u0156', '\u0158', '\u015A',
+              '\u015C', '\u015E', '\u0160', '\u0162', '\u0164',
+              '\u0166', '\u0168', '\u016A', '\u016C', '\u016E',
+              '\u0170', '\u0172', '\u0174', '\u0176', '\u0178',
+              '\u0179', '\u017B', '\u017D', '\u017F', '\u0181',
+              '\u0182', '\u0184', '\u0186', '\u0187', '\u0189',
+              '\u018A', '\u018B', '\u018E', '\u018F', '\u0190',
+              '\u0191', '\u0193', '\u0194', '\u0196', '\u0197',
+              '\u0198', '\u019C', '\u019D', '\u019F', '\u01A0',
+              '\u01A2', '\u01A4', '\u01A6', '\u01A7', '\u01A9',
+              '\u01AC', '\u01AE', '\u01AF', '\u01B1', '\u01B2',
+              '\u01B3', '\u01B5', '\u01B7', '\u01B8', '\u01BC',
+              '\u01C4', '\u01C5', '\u01C7', '\u01C8', '\u01CA',
+              '\u01CB', '\u01CD', '\u01CF', '\u01D1', '\u01D3',
+              '\u01D5', '\u01D7', '\u01D9', '\u01DB', '\u01DE',
+              '\u01E0', '\u01E2', '\u01E4', '\u01E6', '\u01E8',
+              '\u01EA', '\u01EC', '\u01EE', '\u01F0', '\u01F1',
+              '\u01F2', '\u01F4', '\u01F6', '\u01F7', '\u01F8',
+              '\u01FA', '\u01FC', '\u01FE', '\u0200', '\u0202',
+              '\u0204', '\u0206', '\u0208', '\u020A', '\u020C',
+              '\u020E', '\u0210', '\u0212', '\u0214', '\u0216',
+              '\u0218', '\u021A', '\u021C', '\u021E', '\u0220',
+              '\u0222', '\u0224', '\u0226', '\u0228', '\u022A',
+              '\u022C', '\u022E', '\u0230', '\u0232', '\u0345',
+              '\u037A', '\u0386', '\u0388', '\u0389', '\u038A',
+              '\u038C', '\u038E', '\u038F', '\u0390', '\u0391',
+              '\u0392', '\u0393', '\u0394', '\u0395', '\u0396',
+              '\u0397', '\u0398', '\u0399', '\u039A', '\u039B',
+              '\u039C', '\u039D', '\u039E', '\u039F', '\u03A0',
+              '\u03A1', '\u03A3', '\u03A4', '\u03A5', '\u03A6',
+              '\u03A7', '\u03A8', '\u03A9', '\u03AA', '\u03AB',
+              '\u03B0', '\u03C2', '\u03D0', '\u03D1', '\u03D2',
+              '\u03D3', '\u03D4', '\u03D5', '\u03D6', '\u03D8',
+              '\u03DA', '\u03DC', '\u03DE', '\u03E0', '\u03E2',
+              '\u03E4', '\u03E6', '\u03E8', '\u03EA', '\u03EC',
+              '\u03EE', '\u03F0', '\u03F1', '\u03F2', '\u03F4',
+              '\u03F5', '\u0400', '\u0401', '\u0402', '\u0403',
+              '\u0404', '\u0405', '\u0406', '\u0407', '\u0408',
+              '\u0409', '\u040A', '\u040B', '\u040C', '\u040D',
+              '\u040E', '\u040F', '\u0410', '\u0411', '\u0412',
+              '\u0413', '\u0414', '\u0415', '\u0416', '\u0417',
+              '\u0418', '\u0419', '\u041A', '\u041B', '\u041C',
+              '\u041D', '\u041E', '\u041F', '\u0420', '\u0421',
+              '\u0422', '\u0423', '\u0424', '\u0425', '\u0426',
+              '\u0427', '\u0428', '\u0429', '\u042A', '\u042B',
+              '\u042C', '\u042D', '\u042E', '\u042F', '\u0460',
+              '\u0462', '\u0464', '\u0466', '\u0468', '\u046A',
+              '\u046C', '\u046E', '\u0470', '\u0472', '\u0474',
+              '\u0476', '\u0478', '\u047A', '\u047C', '\u047E',
+              '\u0480', '\u048A', '\u048C', '\u048E', '\u0490',
+              '\u0492', '\u0494', '\u0496', '\u0498', '\u049A',
+              '\u049C', '\u049E', '\u04A0', '\u04A2', '\u04A4',
+              '\u04A6', '\u04A8', '\u04AA', '\u04AC', '\u04AE',
+              '\u04B0', '\u04B2', '\u04B4', '\u04B6', '\u04B8',
+              '\u04BA', '\u04BC', '\u04BE', '\u04C1', '\u04C3',
+              '\u04C5', '\u04C7', '\u04C9', '\u04CB', '\u04CD',
+              '\u04D0', '\u04D2', '\u04D4', '\u04D6', '\u04D8',
+              '\u04DA', '\u04DC', '\u04DE', '\u04E0', '\u04E2',
+              '\u04E4', '\u04E6', '\u04E8', '\u04EA', '\u04EC',
+              '\u04EE', '\u04F0', '\u04F2', '\u04F4', '\u04F8',
+              '\u0500', '\u0502', '\u0504', '\u0506', '\u0508',
+              '\u050A', '\u050C', '\u050E', '\u0531', '\u0532',
+              '\u0533', '\u0534', '\u0535', '\u0536', '\u0537',
+              '\u0538', '\u0539', '\u053A', '\u053B', '\u053C',
+              '\u053D', '\u053E', '\u053F', '\u0540', '\u0541',
+              '\u0542', '\u0543', '\u0544', '\u0545', '\u0546',
+              '\u0547', '\u0548', '\u0549', '\u054A', '\u054B',
+              '\u054C', '\u054D', '\u054E', '\u054F', '\u0550',
+              '\u0551', '\u0552', '\u0553', '\u0554', '\u0555',
+              '\u0556', '\u0587', '\u1E00', '\u1E02', '\u1E04',
+              '\u1E06', '\u1E08', '\u1E0A', '\u1E0C', '\u1E0E',
+              '\u1E10', '\u1E12', '\u1E14', '\u1E16', '\u1E18',
+              '\u1E1A', '\u1E1C', '\u1E1E', '\u1E20', '\u1E22',
+              '\u1E24', '\u1E26', '\u1E28', '\u1E2A', '\u1E2C',
+              '\u1E2E', '\u1E30', '\u1E32', '\u1E34', '\u1E36',
+              '\u1E38', '\u1E3A', '\u1E3C', '\u1E3E', '\u1E40',
+              '\u1E42', '\u1E44', '\u1E46', '\u1E48', '\u1E4A',
+              '\u1E4C', '\u1E4E', '\u1E50', '\u1E52', '\u1E54',
+              '\u1E56', '\u1E58', '\u1E5A', '\u1E5C', '\u1E5E',
+              '\u1E60', '\u1E62', '\u1E64', '\u1E66', '\u1E68',
+              '\u1E6A', '\u1E6C', '\u1E6E', '\u1E70', '\u1E72',
+              '\u1E74', '\u1E76', '\u1E78', '\u1E7A', '\u1E7C',
+              '\u1E7E', '\u1E80', '\u1E82', '\u1E84', '\u1E86',
+              '\u1E88', '\u1E8A', '\u1E8C', '\u1E8E', '\u1E90',
+              '\u1E92', '\u1E94', '\u1E96', '\u1E97', '\u1E98',
+              '\u1E99', '\u1E9A', '\u1E9B', '\u1EA0', '\u1EA2',
+              '\u1EA4', '\u1EA6', '\u1EA8', '\u1EAA', '\u1EAC',
+              '\u1EAE', '\u1EB0', '\u1EB2', '\u1EB4', '\u1EB6',
+              '\u1EB8', '\u1EBA', '\u1EBC', '\u1EBE', '\u1EC0',
+              '\u1EC2', '\u1EC4', '\u1EC6', '\u1EC8', '\u1ECA',
+              '\u1ECC', '\u1ECE', '\u1ED0', '\u1ED2', '\u1ED4',
+              '\u1ED6', '\u1ED8', '\u1EDA', '\u1EDC', '\u1EDE',
+              '\u1EE0', '\u1EE2', '\u1EE4', '\u1EE6', '\u1EE8',
+              '\u1EEA', '\u1EEC', '\u1EEE', '\u1EF0', '\u1EF2',
+              '\u1EF4', '\u1EF6', '\u1EF8', '\u1F08', '\u1F09',
+              '\u1F0A', '\u1F0B', '\u1F0C', '\u1F0D', '\u1F0E',
+              '\u1F0F', '\u1F18', '\u1F19', '\u1F1A', '\u1F1B',
+              '\u1F1C', '\u1F1D', '\u1F28', '\u1F29', '\u1F2A',
+              '\u1F2B', '\u1F2C', '\u1F2D', '\u1F2E', '\u1F2F',
+              '\u1F38', '\u1F39', '\u1F3A', '\u1F3B', '\u1F3C',
+              '\u1F3D', '\u1F3E', '\u1F3F', '\u1F48', '\u1F49',
+              '\u1F4A', '\u1F4B', '\u1F4C', '\u1F4D', '\u1F50',
+              '\u1F52', '\u1F54', '\u1F56', '\u1F59', '\u1F5B',
+              '\u1F5D', '\u1F5F', '\u1F68', '\u1F69', '\u1F6A',
+              '\u1F6B', '\u1F6C', '\u1F6D', '\u1F6E', '\u1F6F',
+              '\u1F80', '\u1F81', '\u1F82', '\u1F83', '\u1F84',
+              '\u1F85', '\u1F86', '\u1F87', '\u1F88', '\u1F89',
+              '\u1F8A', '\u1F8B', '\u1F8C', '\u1F8D', '\u1F8E',
+              '\u1F8F', '\u1F90', '\u1F91', '\u1F92', '\u1F93',
+              '\u1F94', '\u1F95', '\u1F96', '\u1F97', '\u1F98',
+              '\u1F99', '\u1F9A', '\u1F9B', '\u1F9C', '\u1F9D',
+              '\u1F9E', '\u1F9F', '\u1FA0', '\u1FA1', '\u1FA2',
+              '\u1FA3', '\u1FA4', '\u1FA5', '\u1FA6', '\u1FA7',
+              '\u1FA8', '\u1FA9', '\u1FAA', '\u1FAB', '\u1FAC',
+              '\u1FAD', '\u1FAE', '\u1FAF', '\u1FB2', '\u1FB3',
+              '\u1FB4', '\u1FB6', '\u1FB7', '\u1FB8', '\u1FB9',
+              '\u1FBA', '\u1FBB', '\u1FBC', '\u1FBE', '\u1FC2',
+              '\u1FC3', '\u1FC4', '\u1FC6', '\u1FC7', '\u1FC8',
+              '\u1FC9', '\u1FCA', '\u1FCB', '\u1FCC', '\u1FD2',
+              '\u1FD3', '\u1FD6', '\u1FD7', '\u1FD8', '\u1FD9',
+              '\u1FDA', '\u1FDB', '\u1FE2', '\u1FE3', '\u1FE4',
+              '\u1FE6', '\u1FE7', '\u1FE8', '\u1FE9', '\u1FEA',
+              '\u1FEB', '\u1FEC', '\u1FF2', '\u1FF3', '\u1FF4',
+              '\u1FF6', '\u1FF7', '\u1FF8', '\u1FF9', '\u1FFA',
+              '\u1FFB', '\u1FFC', '\u20A8', '\u2102', '\u2103',
+              '\u2107', '\u2109', '\u210B', '\u210C', '\u210D',
+              '\u2110', '\u2111', '\u2112', '\u2115', '\u2116',
+              '\u2119', '\u211A', '\u211B', '\u211C', '\u211D',
+              '\u2120', '\u2121', '\u2122', '\u2124', '\u2126',
+              '\u2128', '\u212A', '\u212B', '\u212C', '\u212D',
+              '\u2130', '\u2131', '\u2133', '\u213E', '\u213F',
+              '\u2145', '\u2160', '\u2161', '\u2162', '\u2163',
+              '\u2164', '\u2165', '\u2166', '\u2167', '\u2168',
+              '\u2169', '\u216A', '\u216B', '\u216C', '\u216D',
+              '\u216E', '\u216F', '\u24B6', '\u24B7', '\u24B8',
+              '\u24B9', '\u24BA', '\u24BB', '\u24BC', '\u24BD',
+              '\u24BE', '\u24BF', '\u24C0', '\u24C1', '\u24C2',
+              '\u24C3', '\u24C4', '\u24C5', '\u24C6', '\u24C7',
+              '\u24C8', '\u24C9', '\u24CA', '\u24CB', '\u24CC',
+              '\u24CD', '\u24CE', '\u24CF', '\u3371', '\u3373',
+              '\u3375', '\u3380', '\u3381', '\u3382', '\u3383',
+              '\u3384', '\u3385', '\u3386', '\u3387', '\u338A',
+              '\u338B', '\u338C', '\u3390', '\u3391', '\u3392',
+              '\u3393', '\u3394', '\u33A9', '\u33AA', '\u33AB',
+              '\u33AC', '\u33B4', '\u33B5', '\u33B6', '\u33B7',
+              '\u33B8', '\u33B9', '\u33BA', '\u33BB', '\u33BC',
+              '\u33BD', '\u33BE', '\u33BF', '\u33C0', '\u33C1',
+              '\u33C3', '\u33C6', '\u33C7', '\u33C8', '\u33C9',
+              '\u33CB', '\u33CD', '\u33CE', '\u33D7', '\u33D9',
+              '\u33DA', '\u33DC', '\u33DD', '\uFB00', '\uFB01',
+              '\uFB02', '\uFB03', '\uFB04', '\uFB05', '\uFB06',
+              '\uFB13', '\uFB14', '\uFB15', '\uFB16', '\uFB17',
+              '\uFF21', '\uFF22', '\uFF23', '\uFF24', '\uFF25',
+              '\uFF26', '\uFF27', '\uFF28', '\uFF29', '\uFF2A',
+              '\uFF2B', '\uFF2C', '\uFF2D', '\uFF2E', '\uFF2F',
+              '\uFF30', '\uFF31', '\uFF32', '\uFF33', '\uFF34',
+              '\uFF35', '\uFF36', '\uFF37', '\uFF38', '\uFF39',
+              '\uFF3A' };
+      final String[] lowerCaseFoldedArr =
+          new String[] { "\u0061", "\u0062", "\u0063", "\u0064",
+              "\u0065", "\u0066", "\u0067", "\u0068", "\u0069",
+              "\u006A", "\u006B", "\u006C", "\u006D", "\u006E",
+              "\u006F", "\u0070", "\u0071", "\u0072", "\u0073",
+              "\u0074", "\u0075", "\u0076", "\u0077", "\u0078",
+              "\u0079", "\u007A", "\u03BC", "\u00E0", "\u00E1",
+              "\u00E2", "\u00E3", "\u00E4", "\u00E5", "\u00E6",
+              "\u00E7", "\u00E8", "\u00E9", "\u00EA", "\u00EB",
+              "\u00EC", "\u00ED", "\u00EE", "\u00EF", "\u00F0",
+              "\u00F1", "\u00F2", "\u00F3", "\u00F4", "\u00F5",
+              "\u00F6", "\u00F8", "\u00F9", "\u00FA", "\u00FB",
+              "\u00FC", "\u00FD", "\u00FE", "\u0073\u0073", "\u0101",
+              "\u0103", "\u0105", "\u0107", "\u0109", "\u010B",
+              "\u010D", "\u010F", "\u0111", "\u0113", "\u0115",
+              "\u0117", "\u0119", "\u011B", "\u011D", "\u011F",
+              "\u0121", "\u0123", "\u0125", "\u0127", "\u0129",
+              "\u012B", "\u012D", "\u012F", "\u0069\u0307", "\u0133",
+              "\u0135", "\u0137", "\u013A", "\u013C", "\u013E",
+              "\u0140", "\u0142", "\u0144", "\u0146", "\u0148",
+              "\u02BC\u006E", "\u014B", "\u014D", "\u014F", "\u0151",
+              "\u0153", "\u0155", "\u0157", "\u0159", "\u015B",
+              "\u015D", "\u015F", "\u0161", "\u0163", "\u0165",
+              "\u0167", "\u0169", "\u016B", "\u016D", "\u016F",
+              "\u0171", "\u0173", "\u0175", "\u0177", "\u00FF",
+              "\u017A", "\u017C", "\u017E", "\u0073", "\u0253",
+              "\u0183", "\u0185", "\u0254", "\u0188", "\u0256",
+              "\u0257", "\u018C", "\u01DD", "\u0259", "\u025B",
+              "\u0192", "\u0260", "\u0263", "\u0269", "\u0268",
+              "\u0199", "\u026F", "\u0272", "\u0275", "\u01A1",
+              "\u01A3", "\u01A5", "\u0280", "\u01A8", "\u0283",
+              "\u01AD", "\u0288", "\u01B0", "\u028A", "\u028B",
+              "\u01B4", "\u01B6", "\u0292", "\u01B9", "\u01BD",
+              "\u01C6", "\u01C6", "\u01C9", "\u01C9", "\u01CC",
+              "\u01CC", "\u01CE", "\u01D0", "\u01D2", "\u01D4",
+              "\u01D6", "\u01D8", "\u01DA", "\u01DC", "\u01DF",
+              "\u01E1", "\u01E3", "\u01E5", "\u01E7", "\u01E9",
+              "\u01EB", "\u01ED", "\u01EF", "\u006A\u030C", "\u01F3",
+              "\u01F3", "\u01F5", "\u0195", "\u01BF", "\u01F9",
+              "\u01FB", "\u01FD", "\u01FF", "\u0201", "\u0203",
+              "\u0205", "\u0207", "\u0209", "\u020B", "\u020D",
+              "\u020F", "\u0211", "\u0213", "\u0215", "\u0217",
+              "\u0219", "\u021B", "\u021D", "\u021F", "\u019E",
+              "\u0223", "\u0225", "\u0227", "\u0229", "\u022B",
+              "\u022D", "\u022F", "\u0231", "\u0233", "\u03B9",
+              "\u0020\u03B9", "\u03AC", "\u03AD", "\u03AE", "\u03AF",
+              "\u03CC", "\u03CD", "\u03CE", "\u03B9\u0308\u0301",
+              "\u03B1", "\u03B2", "\u03B3", "\u03B4", "\u03B5",
+              "\u03B6", "\u03B7", "\u03B8", "\u03B9", "\u03BA",
+              "\u03BB", "\u03BC", "\u03BD", "\u03BE", "\u03BF",
+              "\u03C0", "\u03C1", "\u03C3", "\u03C4", "\u03C5",
+              "\u03C6", "\u03C7", "\u03C8", "\u03C9", "\u03CA",
+              "\u03CB", "\u03C5\u0308\u0301", "\u03C3", "\u03B2",
+              "\u03B8", "\u03C5", "\u03CD", "\u03CB", "\u03C6",
+              "\u03C0", "\u03D9", "\u03DB", "\u03DD", "\u03DF",
+              "\u03E1", "\u03E3", "\u03E5", "\u03E7", "\u03E9",
+              "\u03EB", "\u03ED", "\u03EF", "\u03BA", "\u03C1",
+              "\u03C3", "\u03B8", "\u03B5", "\u0450", "\u0451",
+              "\u0452", "\u0453", "\u0454", "\u0455", "\u0456",
+              "\u0457", "\u0458", "\u0459", "\u045A", "\u045B",
+              "\u045C", "\u045D", "\u045E", "\u045F", "\u0430",
+              "\u0431", "\u0432", "\u0433", "\u0434", "\u0435",
+              "\u0436", "\u0437", "\u0438", "\u0439", "\u043A",
+              "\u043B", "\u043C", "\u043D", "\u043E", "\u043F",
+              "\u0440", "\u0441", "\u0442", "\u0443", "\u0444",
+              "\u0445", "\u0446", "\u0447", "\u0448", "\u0449",
+              "\u044A", "\u044B", "\u044C", "\u044D", "\u044E",
+              "\u044F", "\u0461", "\u0463", "\u0465", "\u0467",
+              "\u0469", "\u046B", "\u046D", "\u046F", "\u0471",
+              "\u0473", "\u0475", "\u0477", "\u0479", "\u047B",
+              "\u047D", "\u047F", "\u0481", "\u048B", "\u048D",
+              "\u048F", "\u0491", "\u0493", "\u0495", "\u0497",
+              "\u0499", "\u049B", "\u049D", "\u049F", "\u04A1",
+              "\u04A3", "\u04A5", "\u04A7", "\u04A9", "\u04AB",
+              "\u04AD", "\u04AF", "\u04B1", "\u04B3", "\u04B5",
+              "\u04B7", "\u04B9", "\u04BB", "\u04BD", "\u04BF",
+              "\u04C2", "\u04C4", "\u04C6", "\u04C8", "\u04CA",
+              "\u04CC", "\u04CE", "\u04D1", "\u04D3", "\u04D5",
+              "\u04D7", "\u04D9", "\u04DB", "\u04DD", "\u04DF",
+              "\u04E1", "\u04E3", "\u04E5", "\u04E7", "\u04E9",
+              "\u04EB", "\u04ED", "\u04EF", "\u04F1", "\u04F3",
+              "\u04F5", "\u04F9", "\u0501", "\u0503", "\u0505",
+              "\u0507", "\u0509", "\u050B", "\u050D", "\u050F",
+              "\u0561", "\u0562", "\u0563", "\u0564", "\u0565",
+              "\u0566", "\u0567", "\u0568", "\u0569", "\u056A",
+              "\u056B", "\u056C", "\u056D", "\u056E", "\u056F",
+              "\u0570", "\u0571", "\u0572", "\u0573", "\u0574",
+              "\u0575", "\u0576", "\u0577", "\u0578", "\u0579",
+              "\u057A", "\u057B", "\u057C", "\u057D", "\u057E",
+              "\u057F", "\u0580", "\u0581", "\u0582", "\u0583",
+              "\u0584", "\u0585", "\u0586", "\u0565\u0582", "\u1E01",
+              "\u1E03", "\u1E05", "\u1E07", "\u1E09", "\u1E0B",
+              "\u1E0D", "\u1E0F", "\u1E11", "\u1E13", "\u1E15",
+              "\u1E17", "\u1E19", "\u1E1B", "\u1E1D", "\u1E1F",
+              "\u1E21", "\u1E23", "\u1E25", "\u1E27", "\u1E29",
+              "\u1E2B", "\u1E2D", "\u1E2F", "\u1E31", "\u1E33",
+              "\u1E35", "\u1E37", "\u1E39", "\u1E3B", "\u1E3D",
+              "\u1E3F", "\u1E41", "\u1E43", "\u1E45", "\u1E47",
+              "\u1E49", "\u1E4B", "\u1E4D", "\u1E4F", "\u1E51",
+              "\u1E53", "\u1E55", "\u1E57", "\u1E59", "\u1E5B",
+              "\u1E5D", "\u1E5F", "\u1E61", "\u1E63", "\u1E65",
+              "\u1E67", "\u1E69", "\u1E6B", "\u1E6D", "\u1E6F",
+              "\u1E71", "\u1E73", "\u1E75", "\u1E77", "\u1E79",
+              "\u1E7B", "\u1E7D", "\u1E7F", "\u1E81", "\u1E83",
+              "\u1E85", "\u1E87", "\u1E89", "\u1E8B", "\u1E8D",
+              "\u1E8F", "\u1E91", "\u1E93", "\u1E95", "\u0068\u0331",
+              "\u0074\u0308", "\u0077\u030A", "\u0079\u030A",
+              "\u0061\u02BE", "\u1E61", "\u1EA1", "\u1EA3", "\u1EA5",
+              "\u1EA7", "\u1EA9", "\u1EAB", "\u1EAD", "\u1EAF",
+              "\u1EB1", "\u1EB3", "\u1EB5", "\u1EB7", "\u1EB9",
+              "\u1EBB", "\u1EBD", "\u1EBF", "\u1EC1", "\u1EC3",
+              "\u1EC5", "\u1EC7", "\u1EC9", "\u1ECB", "\u1ECD",
+              "\u1ECF", "\u1ED1", "\u1ED3", "\u1ED5", "\u1ED7",
+              "\u1ED9", "\u1EDB", "\u1EDD", "\u1EDF", "\u1EE1",
+              "\u1EE3", "\u1EE5", "\u1EE7", "\u1EE9", "\u1EEB",
+              "\u1EED", "\u1EEF", "\u1EF1", "\u1EF3", "\u1EF5",
+              "\u1EF7", "\u1EF9", "\u1F00", "\u1F01", "\u1F02",
+              "\u1F03", "\u1F04", "\u1F05", "\u1F06", "\u1F07",
+              "\u1F10", "\u1F11", "\u1F12", "\u1F13", "\u1F14",
+              "\u1F15", "\u1F20", "\u1F21", "\u1F22", "\u1F23",
+              "\u1F24", "\u1F25", "\u1F26", "\u1F27", "\u1F30",
+              "\u1F31", "\u1F32", "\u1F33", "\u1F34", "\u1F35",
+              "\u1F36", "\u1F37", "\u1F40", "\u1F41", "\u1F42",
+              "\u1F43", "\u1F44", "\u1F45", "\u03C5\u0313",
+              "\u03C5\u0313\u0300", "\u03C5\u0313\u0301",
+              "\u03C5\u0313\u0342", "\u1F51", "\u1F53", "\u1F55",
+              "\u1F57", "\u1F60", "\u1F61", "\u1F62", "\u1F63",
+              "\u1F64", "\u1F65", "\u1F66", "\u1F67", "\u1F00\u03B9",
+              "\u1F01\u03B9", "\u1F02\u03B9", "\u1F03\u03B9",
+              "\u1F04\u03B9", "\u1F05\u03B9", "\u1F06\u03B9",
+              "\u1F07\u03B9", "\u1F00\u03B9", "\u1F01\u03B9",
+              "\u1F02\u03B9", "\u1F03\u03B9", "\u1F04\u03B9",
+              "\u1F05\u03B9", "\u1F06\u03B9", "\u1F07\u03B9",
+              "\u1F20\u03B9", "\u1F21\u03B9", "\u1F22\u03B9",
+              "\u1F23\u03B9", "\u1F24\u03B9", "\u1F25\u03B9",
+              "\u1F26\u03B9", "\u1F27\u03B9", "\u1F20\u03B9",
+              "\u1F21\u03B9", "\u1F22\u03B9", "\u1F23\u03B9",
+              "\u1F24\u03B9", "\u1F25\u03B9", "\u1F26\u03B9",
+              "\u1F27\u03B9", "\u1F60\u03B9", "\u1F61\u03B9",
+              "\u1F62\u03B9", "\u1F63\u03B9", "\u1F64\u03B9",
+              "\u1F65\u03B9", "\u1F66\u03B9", "\u1F67\u03B9",
+              "\u1F60\u03B9", "\u1F61\u03B9", "\u1F62\u03B9",
+              "\u1F63\u03B9", "\u1F64\u03B9", "\u1F65\u03B9",
+              "\u1F66\u03B9", "\u1F67\u03B9", "\u1F70\u03B9",
+              "\u03B1\u03B9", "\u03AC\u03B9", "\u03B1\u0342",
+              "\u03B1\u0342\u03B9", "\u1FB0", "\u1FB1", "\u1F70",
+              "\u1F71", "\u03B1\u03B9", "\u03B9", "\u1F74\u03B9",
+              "\u03B7\u03B9", "\u03AE\u03B9", "\u03B7\u0342",
+              "\u03B7\u0342\u03B9", "\u1F72", "\u1F73", "\u1F74",
+              "\u1F75", "\u03B7\u03B9", "\u03B9\u0308\u0300",
+              "\u03B9\u0308\u0301", "\u03B9\u0342",
+              "\u03B9\u0308\u0342", "\u1FD0", "\u1FD1", "\u1F76",
+              "\u1F77", "\u03C5\u0308\u0300", "\u03C5\u0308\u0301",
+              "\u03C1\u0313", "\u03C5\u0342", "\u03C5\u0308\u0342",
+              "\u1FE0", "\u1FE1", "\u1F7A", "\u1F7B", "\u1FE5",
+              "\u1F7C\u03B9", "\u03C9\u03B9", "\u03CE\u03B9",
+              "\u03C9\u0342", "\u03C9\u0342\u03B9", "\u1F78", "\u1F79",
+              "\u1F7C", "\u1F7D", "\u03C9\u03B9", "\u0072\u0073",
+              "\u0063", "\u00B0\u0063", "\u025B", "\u00B0\u0066",
+              "\u0068", "\u0068", "\u0068", "\u0069", "\u0069",
+              "\u006C", "\u006E", "\u006E\u006F", "\u0070", "\u0071",
+              "\u0072", "\u0072", "\u0072", "\u0073\u006D",
+              "\u0074\u0065\u006C", "\u0074\u006D", "\u007A", "\u03C9",
+              "\u007A", "\u006B", "\u00E5", "\u0062", "\u0063",
+              "\u0065", "\u0066", "\u006D", "\u03B3", "\u03C0",
+              "\u0064", "\u2170", "\u2171", "\u2172", "\u2173",
+              "\u2174", "\u2175", "\u2176", "\u2177", "\u2178",
+              "\u2179", "\u217A", "\u217B", "\u217C", "\u217D",
+              "\u217E", "\u217F", "\u24D0", "\u24D1", "\u24D2",
+              "\u24D3", "\u24D4", "\u24D5", "\u24D6", "\u24D7",
+              "\u24D8", "\u24D9", "\u24DA", "\u24DB", "\u24DC",
+              "\u24DD", "\u24DE", "\u24DF", "\u24E0", "\u24E1",
+              "\u24E2", "\u24E3", "\u24E4", "\u24E5", "\u24E6",
+              "\u24E7", "\u24E8", "\u24E9", "\u0068\u0070\u0061",
+              "\u0061\u0075", "\u006F\u0076", "\u0070\u0061",
+              "\u006E\u0061", "\u03BC\u0061", "\u006D\u0061",
+              "\u006B\u0061", "\u006B\u0062", "\u006D\u0062",
+              "\u0067\u0062", "\u0070\u0066", "\u006E\u0066",
+              "\u03BC\u0066", "\u0068\u007A", "\u006B\u0068\u007A",
+              "\u006D\u0068\u007A", "\u0067\u0068\u007A",
+              "\u0074\u0068\u007A", "\u0070\u0061",
+              "\u006B\u0070\u0061", "\u006D\u0070\u0061",
+              "\u0067\u0070\u0061", "\u0070\u0076", "\u006E\u0076",
+              "\u03BC\u0076", "\u006D\u0076", "\u006B\u0076",
+              "\u006D\u0076", "\u0070\u0077", "\u006E\u0077",
+              "\u03BC\u0077", "\u006D\u0077", "\u006B\u0077",
+              "\u006D\u0077", "\u006B\u03C9", "\u006D\u03C9",
+              "\u0062\u0071", "\u0063\u2215\u006B\u0067",
+              "\u0063\u006F\u002E", "\u0064\u0062", "\u0067\u0079",
+              "\u0068\u0070", "\u006B\u006B", "\u006B\u006D",
+              "\u0070\u0068", "\u0070\u0070\u006D", "\u0070\u0072",
+              "\u0073\u0076", "\u0077\u0062", "\u0066\u0066",
+              "\u0066\u0069", "\u0066\u006C", "\u0066\u0066\u0069",
+              "\u0066\u0066\u006C", "\u0073\u0074", "\u0073\u0074",
+              "\u0574\u0576", "\u0574\u0565", "\u0574\u056B",
+              "\u057E\u0576", "\u0574\u056D", "\uFF41", "\uFF42",
+              "\uFF43", "\uFF44", "\uFF45", "\uFF46", "\uFF47",
+              "\uFF48", "\uFF49", "\uFF4A", "\uFF4B", "\uFF4C",
+              "\uFF4D", "\uFF4E", "\uFF4F", "\uFF50", "\uFF51",
+              "\uFF52", "\uFF53", "\uFF54", "\uFF55", "\uFF56",
+              "\uFF57", "\uFF58", "\uFF59", "\uFF5A" };
+      for (int count = 0; count < upperCaseArr.length; count++)
+      {
+        caseMappingTable.put(upperCaseArr[count],
+            lowerCaseFoldedArr[count]);
+      }
+    }
+
+
+
+    // Gets the mapped String.
+    private static void map(StringBuilder buffer,
+        ByteSequence sequence, boolean trim, boolean foldCase)
+    {
+      final String value = sequence.toString();
+      for (int i = 0; i < value.length(); i++)
+      {
+        final char c = value.charAt(i);
+        if (map2null.contains(c))
+        {
+          continue;
+        }
+
+        if (map2space.contains(c))
+        {
+          final int buffLen = buffer.length();
+          if (trim && buffLen == 0 || buffLen > 0
+              && buffer.charAt(buffLen - 1) == SPACE_CHAR)
+          {
+            /**
+             * Do not map this character into a space if: a . trimming
+             * is wanted and this was the first char. b. The last
+             * character was a space.
+             **/
+            continue;
+          }
+          buffer.append(SPACE_CHAR);
+          continue;
+        }
+
+        if (foldCase)
+        {
+          final String mapping = caseMappingTable.get(c);
+          if (mapping != null)
+          {
+            buffer.append(mapping);
+            continue;
+          }
+        }
+        // It came here so no match was found.
+        buffer.append(c);
+      }
+    }
+  }
+
+  /**
+   * Defines SPACE character.
+   */
+  private static final char SPACE_CHAR = '\u0020';
+
+  /**
+   * Indicates whether case should be folded during string preparation.
+   */
+  public static final boolean CASE_FOLD = true;
+
+  /**
+   * Indicates whether case should not be folded during string
+   * preparation.
+   */
+  public static final boolean NO_CASE_FOLD = false;
+
+  /**
+   * Indicates whether leading and trailing spaces should be trimmed
+   * during string preparation.
+   */
+  public static final boolean TRIM = true;
+
+
+
+  /**
+   * Prepares an attribute or assertion value as per stringprep
+   * algorithm defined in RFC 4518.
+   * 
+   * @param buffer
+   *          The buffer to which the prepared form of the string should
+   *          be appended.
+   * @param sequence
+   *          The {@link org.opends.sdk.util.ByteSequence} that
+   *          needs preparation.
+   * @param trim
+   *          Indicates whether leading and trailing spaces should be
+   *          omitted from the string representation.
+   * @param foldCase
+   *          Indicates whether the case will be folded during mapping.
+   * @see <a href="http://www.rfc-editor.org/rfc/rfc4518.txt">
+   *      Internationalized String Preparation</a>
+   */
+  public static void prepareUnicode(StringBuilder buffer,
+      ByteSequence sequence, boolean trim, boolean foldCase)
+  {
+    ensureNotNull(buffer);
+    ensureNotNull(sequence);
+    // Optimize in the case of purely ascii characters which is the most
+    // common
+    // case.
+    final int length = sequence.length();
+    for (int i = 0; i < length; i++)
+    {
+      if ((sequence.byteAt(i) & 0x7F) != sequence.byteAt(i))
+      {
+        // Map the attribute value.
+        map(buffer, sequence.subSequence(i, length), trim, foldCase);
+        // Normalize the attribute value.
+        normalize(buffer);
+        break;
+      }
+      int buffLen = buffer.length();
+      switch (sequence.byteAt(i))
+      {
+      case ' ':
+        if (trim && buffLen == 0 || buffLen > 0
+            && buffer.charAt(buffLen - 1) == SPACE_CHAR)
+        {
+          break;
+        }
+        buffer.append(' ');
+        break;
+      default:
+        final byte b = sequence.byteAt(i);
+        // Perform mapping.
+        if (b >= '\u0009' && b < '\u000E')
+        {
+          // These characters are mapped to a SPACE.
+          buffLen = buffer.length();
+          if (trim && buffLen == 0 || buffLen > 0
+              && buffer.charAt(buffLen - 1) == ' ')
+          {
+            /**
+             * Do not map this character into a space if: a . trimming
+             * is desired and this was the leading char. b. The last
+             * character was a space.
+             **/
+            break;
+          }
+          else
+          {
+            buffer.append(SPACE_CHAR);
+          }
+        }
+        else if (b >= '\u0000' && b <= '\u0008' || b >= '\u000E'
+            && b <= '\u001F' || b == '\u007F')
+        {
+          // These characters are mapped to nothing and hence not copied
+          // over..
+          break;
+        }
+        else if (foldCase && b >= 65 && b <= 90)
+        {
+          // If case-folding is allowed then map to the lower case.
+          buffer.append((char) (b + 32));
+        }
+        else
+        {
+          buffer.append((char) b);
+        }
+        break;
+      }
+    }
+    if (trim)
+    {
+      // Strip off any trailing spaces.
+      for (int i = buffer.length() - 1; i > 0; i--)
+      {
+        if (buffer.charAt(i) == SPACE_CHAR)
+        {
+          buffer.delete(i, i + 1);
+        }
+        else
+        {
+          break;
+        }
+      }
+    }
+  }
+
+
+
+  // Checks each character and replaces it with its mapping.
+  private static void map(StringBuilder buffer, ByteSequence value,
+      boolean trim, boolean foldCase)
+  {
+    MappingTable.map(buffer, value, trim, foldCase);
+  }
+
+
+
+  // Normalizes the input string with NFKC Form.
+  private static void normalize(StringBuilder buffer)
+  {
+    Platform.normalize(buffer);
+  }
+
+
+
+  // Prevent instantiation.
+  private StringPrepProfile()
+  {
+    // Nothing to do.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/SubstringReader.java b/sdk/src/org/opends/sdk/util/SubstringReader.java
new file mode 100644
index 0000000..1efcd07
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/SubstringReader.java
@@ -0,0 +1,84 @@
+package org.opends.sdk.util;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: boli
+ * Date: Jul 13, 2009
+ * Time: 3:11:50 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public class SubstringReader
+{
+  private String source;
+  private int pos;
+  private int mark;
+
+  public SubstringReader(String s) {
+    Validator.ensureNotNull(s);
+    source = s;
+    pos = 0;
+    mark = 0;
+  }
+
+  /**
+   * Attemps to read a substring of the specified length.
+   * 
+   * @param length The number of characters to read.
+   * @return The substring.
+   */
+  public String read(int length)
+  {
+    String substring = source.substring(pos, pos + length);
+    pos += length;
+    return substring;
+  }
+
+  public char read()
+  {
+    return source.charAt(pos++);
+  }
+
+  public String getString()
+  {
+    return source;
+  }
+
+  public int pos()
+  {
+    return pos;
+  }
+
+  public int remaining()
+  {
+    return source.length() - pos;
+  }
+
+  /**
+   * Marks the present position in the stream. Subsequent calls
+   * to reset() will reposition the stream to this point.
+   */
+  public void mark()
+  {
+    mark = pos;
+  }
+
+  /**
+   * Resets the stream to the most recent mark, or to the beginning
+   * of the string if it has never been marked.
+   */
+  public void reset()
+  {
+    pos = mark; 
+  }
+
+  public int skipWhitespaces()
+  {
+    int skipped = 0;
+    while(pos < source.length() && source.charAt(pos) == ' ')
+    {
+      skipped++;
+      pos++;
+    }
+    return skipped;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/Validator.java b/sdk/src/org/opends/sdk/util/Validator.java
new file mode 100644
index 0000000..47fbaaa
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/Validator.java
@@ -0,0 +1,215 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2009 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util;
+
+
+
+/**
+ * Common methods for validating method arguments.
+ */
+public final class Validator
+{
+
+  /**
+   * Throws a {@code NullPointerException} if the provided argument is
+   * {@code null}. This method returns a reference to its single
+   * parameter so that it can be easily used in constructors.
+   *
+   * @param <T>
+   *          The type of {@code o1}.
+   * @param o1
+   *          The object to test.
+   * @return A reference to {@code o1}.
+   * @throws NullPointerException
+   *           If the provided argument is {@code null}.
+   */
+  public static <T> T ensureNotNull(T o1) throws NullPointerException
+  {
+    if (o1 == null)
+    {
+      throw new NullPointerException();
+    }
+    return o1;
+  }
+
+
+
+  /**
+   * Throws a {@code NullPointerException} if any of the provided
+   * arguments are {@code null}.
+   *
+   * @param o1
+   *          The first object to test.
+   * @param o2
+   *          The second object to test.
+   * @throws NullPointerException
+   *           If any of the provided arguments are {@code null}.
+   */
+  public static void ensureNotNull(Object o1, Object o2)
+      throws NullPointerException
+  {
+    if (o1 == null || o2 == null)
+    {
+      throw new NullPointerException();
+    }
+  }
+
+
+
+  /**
+   * Throws a {@code NullPointerException} if any of the provided
+   * arguments are {@code null}.
+   *
+   * @param o1
+   *          The first object to test.
+   * @param o2
+   *          The second object to test.
+   * @param o3
+   *          The third object to test.
+   * @throws NullPointerException
+   *           If any of the provided arguments are {@code null}.
+   */
+  public static void ensureNotNull(Object o1, Object o2, Object o3)
+      throws NullPointerException
+  {
+    if (o1 == null || o2 == null || o3 == null)
+    {
+      throw new NullPointerException();
+    }
+  }
+
+
+
+  /**
+   * Throws a {@code NullPointerException} if any of the provided
+   * arguments are {@code null}.
+   *
+   * @param o1
+   *          The first object to test.
+   * @param o2
+   *          The second object to test.
+   * @param o3
+   *          The third object to test.
+   * @param o4
+   *          The fourth object to test.
+   * @throws NullPointerException
+   *           If any of the provided arguments are {@code null}.
+   */
+  public static void ensureNotNull(Object o1, Object o2, Object o3,
+      Object o4) throws NullPointerException
+  {
+    if (o1 == null || o2 == null || o3 == null || o4 == null)
+    {
+      throw new NullPointerException();
+    }
+  }
+
+
+
+  /**
+   * Throws a {@code NullPointerException} if any of the provided
+   * arguments are {@code null}.
+   *
+   * @param o1
+   *          The first object to test.
+   * @param o2
+   *          The second object to test.
+   * @param o3
+   *          The third object to test.
+   * @param o4
+   *          The fourth object to test.
+   * @param o5
+   *          The fifth object to test.
+   * @throws NullPointerException
+   *           If any of the provided arguments are {@code null}.
+   */
+  public static void ensureNotNull(Object o1, Object o2, Object o3,
+      Object o4, Object o5) throws NullPointerException
+  {
+    if (o1 == null || o2 == null || o3 == null || o4 == null
+        || o5 == null)
+    {
+      throw new NullPointerException();
+    }
+  }
+
+
+
+  /**
+   * Throws a {@code NullPointerException} if any of the provided
+   * arguments are {@code null}.
+   *
+   * @param objects
+   *          The objects to test.
+   * @throws NullPointerException
+   *           If any of the provided arguments are {@code null}.
+   */
+  public static void ensureNotNull(Object... objects)
+      throws NullPointerException
+  {
+    for (Object o : objects)
+    {
+      if (o == null)
+      {
+        throw new NullPointerException();
+      }
+    }
+  }
+
+
+
+  /**
+   * Throws an {@code IllegalArgumentException} if the provided
+   * condition is {@code false}.
+   *
+   * @param condition
+   *          The condition, which must be {@code true} to avoid an
+   *          exception.
+   * @param message
+   *          The error message to include in the exception if it is
+   *          thrown.
+   * @throws IllegalArgumentException
+   *           If {@code condition} was {@code false}.
+   */
+  public static void ensureTrue(boolean condition, String message)
+      throws IllegalArgumentException
+  {
+    if (!condition)
+    {
+      throw new IllegalArgumentException(message);
+    }
+  }
+
+
+
+  // Prevent instantiation.
+  private Validator()
+  {
+    // No implementation required.
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java
new file mode 100644
index 0000000..3b1ac39
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/DistrustAllTrustManager.java
@@ -0,0 +1,66 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util.ssl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * An X509TrustManager which trusts everything.
+ */
+public class DistrustAllTrustManager implements X509TrustManager {
+
+  /**
+   * {@inheritDoc}
+   */
+  public void checkClientTrusted(X509Certificate[] chain, String authType)
+  throws CertificateException
+  {
+    throw new CertificateException();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void checkServerTrusted(X509Certificate[] chain, String authType)
+  throws CertificateException
+  {
+    throw new CertificateException();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public X509Certificate[] getAcceptedIssuers()
+  {
+    return new X509Certificate[0];
+  }
+}
+
diff --git a/sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java b/sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java
new file mode 100644
index 0000000..f6153af
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/HostnameMismatchCertificateException.java
@@ -0,0 +1,67 @@
+package org.opends.sdk.util.ssl;
+
+
+
+import java.security.cert.CertificateException;
+
+
+
+/**
+ * The certificate's subject DN's value and the host name we tried to
+ * connect to do not match.
+ */
+@SuppressWarnings("serial")
+public class HostnameMismatchCertificateException extends
+    CertificateException
+{
+  private String expectedHostname;
+
+  private String certificateHostname;
+
+
+
+  public HostnameMismatchCertificateException(String expectedHostname,
+      String certificateHostname)
+  {
+    this.expectedHostname = expectedHostname;
+    this.certificateHostname = certificateHostname;
+  }
+
+
+
+  public HostnameMismatchCertificateException(String msg,
+      String expectedHostname, String certificateHostname)
+  {
+    super(msg);
+    this.expectedHostname = expectedHostname;
+    this.certificateHostname = certificateHostname;
+  }
+
+
+
+  public String getExpectedHostname()
+  {
+    return expectedHostname;
+  }
+
+
+
+  public void setExpectedHostname(String expectedHostname)
+  {
+    this.expectedHostname = expectedHostname;
+  }
+
+
+
+  public String getCertificateHostname()
+  {
+    return certificateHostname;
+  }
+
+
+
+  public void setCertificateHostname(String certificateHostname)
+  {
+    this.certificateHostname = certificateHostname;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java
new file mode 100644
index 0000000..77ee67f
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/PromptingTrustManager.java
@@ -0,0 +1,412 @@
+package org.opends.sdk.util.ssl;
+
+import static org.opends.messages.UtilityMessages.*;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.opends.messages.Message;
+import org.opends.sdk.util.Validator;
+import org.opends.server.util.cli.*;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: boli
+ * Date: Oct 16, 2009
+ * Time: 10:38:50 AM
+ * To change this template use File | Settings | File Templates.
+ */
+public class PromptingTrustManager implements X509TrustManager
+{
+  static private final Logger LOG =
+      Logger.getLogger(PromptingTrustManager.class.getName());
+  static private final String DEFAULT_PATH =
+      System.getProperty("user.home") + File.separator + ".opends" +
+          File.separator + "keystore";
+  static private final char[] DEFAULT_PASSWORD = "OpenDS".toCharArray();
+
+  /**
+   * Enumeration description server certificate trust option.
+   */
+  private enum TrustOption
+  {
+    UNTRUSTED(1, INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_NO.get()),
+    SESSION(2,INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_SESSION.get()),
+    PERMAMENT(3,INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION_ALWAYS.get()),
+    CERTIFICATE_DETAILS(4,
+        INFO_LDAP_CONN_PROMPT_SECURITY_CERTIFICATE_DETAILS.get());
+
+    private Integer choice;
+
+    private Message msg;
+
+    /**
+     * Private constructor.
+     *
+     * @param i
+     *          the menu return value.
+     * @param msg
+     *          the message message.
+     */
+    private TrustOption(int i, Message msg)
+    {
+      choice = i;
+      this.msg = msg;
+    }
+
+    /**
+     * Returns the choice number.
+     *
+     * @return the attribute name.
+     */
+    public Integer getChoice()
+    {
+      return choice;
+    }
+
+    /**
+     * Return the menu message.
+     *
+     * @return the menu message.
+     */
+    public Message getMenuMessage()
+    {
+      return msg;
+    }
+  }
+
+  private final KeyStore inMemoryTrustStore;
+  private final KeyStore onDiskTrustStore;
+
+  private final X509TrustManager inMemoryTrustManager;
+  private final X509TrustManager onDiskTrustManager;
+
+  private final X509TrustManager nestedTrustManager;
+  private final ConsoleApplication app;
+
+  public PromptingTrustManager(ConsoleApplication app,
+                               X509TrustManager sourceTrustManager)
+      throws KeyStoreException, IOException, NoSuchAlgorithmException,
+      CertificateException
+  {
+    this(app, DEFAULT_PATH, sourceTrustManager);
+  }
+  public PromptingTrustManager(ConsoleApplication app, String acceptedStorePath,
+                               X509TrustManager sourceTrustManager)
+      throws KeyStoreException, IOException, NoSuchAlgorithmException,
+      CertificateException
+  {
+    Validator.ensureNotNull(app, acceptedStorePath);
+    this.app = app;
+    this.nestedTrustManager = sourceTrustManager;
+    inMemoryTrustStore =
+        KeyStore.getInstance(KeyStore.getDefaultType());
+    onDiskTrustStore =
+        KeyStore.getInstance(KeyStore.getDefaultType());
+
+    File onDiskTrustStorePath = new File(acceptedStorePath);
+    inMemoryTrustStore.load(null, null);
+    if(!onDiskTrustStorePath.exists())
+    {
+      onDiskTrustStore.load(null, null);
+    }
+    else
+    {
+      FileInputStream fos = new FileInputStream(onDiskTrustStorePath);
+      onDiskTrustStore.load(fos, DEFAULT_PASSWORD);
+    }
+    TrustManagerFactory tmf = TrustManagerFactory.getInstance(
+        TrustManagerFactory.getDefaultAlgorithm());
+
+    tmf.init(inMemoryTrustStore);
+    X509TrustManager x509tm = null;
+    for(TrustManager tm : tmf.getTrustManagers())
+    {
+      if(tm instanceof X509TrustManager)
+      {
+        x509tm = (X509TrustManager)tm;
+        break;
+      }
+    }
+    if(x509tm == null)
+    {
+      throw new NoSuchAlgorithmException();
+    }
+    this.inMemoryTrustManager = x509tm;
+
+    tmf.init(onDiskTrustStore);
+    x509tm = null;
+    for(TrustManager tm : tmf.getTrustManagers())
+    {
+      if(tm instanceof X509TrustManager)
+      {
+        x509tm = (X509TrustManager)tm;
+        break;
+      }
+    }
+    if(x509tm == null)
+    {
+      throw new NoSuchAlgorithmException();
+    }
+    this.onDiskTrustManager = x509tm;
+  }
+
+  public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
+      throws CertificateException
+  {
+    try
+    {
+      inMemoryTrustManager.checkClientTrusted(x509Certificates, s);
+    }
+    catch(Exception ce1)
+    {
+      try
+      {
+        onDiskTrustManager.checkClientTrusted(x509Certificates, s);
+      }
+      catch(Exception ce2)
+      {
+        if(nestedTrustManager != null)
+        {
+          try
+          {
+          nestedTrustManager.checkClientTrusted(x509Certificates, s);
+          }
+          catch(Exception ce3)
+          {
+            checkManuallyTrusted(x509Certificates, ce3);
+          }
+        }
+        else
+        {
+          checkManuallyTrusted(x509Certificates, ce1);
+        }
+      }
+    }
+  }
+
+  public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
+      throws CertificateException
+  {
+    try
+    {
+      inMemoryTrustManager.checkServerTrusted(x509Certificates, s);
+    }
+    catch(Exception ce1)
+    {
+      try
+      {
+        onDiskTrustManager.checkServerTrusted(x509Certificates, s);
+      }
+      catch(Exception ce2)
+      {
+        if(nestedTrustManager != null)
+        {
+          try
+          {
+          nestedTrustManager.checkServerTrusted(x509Certificates, s);
+          }
+          catch(Exception ce3)
+          {
+            checkManuallyTrusted(x509Certificates, ce3);
+          }
+        }
+        else
+        {
+          checkManuallyTrusted(x509Certificates, ce1);
+        }
+      }
+    }
+  }
+
+  public X509Certificate[] getAcceptedIssuers() {
+    if(nestedTrustManager != null)
+    {
+      return nestedTrustManager.getAcceptedIssuers();
+    }
+    return new X509Certificate[0];
+  }
+
+  /**
+   * Indicate if the certificate chain can be trusted.
+   *
+   * @param chain The certificate chain to validate
+   * certificate.
+   */
+  private void checkManuallyTrusted(X509Certificate[] chain,
+                                    Exception exception)
+      throws CertificateException
+  {
+    app.println();
+    app.println(INFO_LDAP_CONN_PROMPT_SECURITY_SERVER_CERTIFICATE.get());
+    app.println();
+    for (int i = 0; i < chain.length; i++)
+    {
+      // Certificate DN
+      app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_USER_DN.get(
+          chain[i].getSubjectDN().toString()));
+
+      // certificate validity
+      app.println(
+          INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_VALIDITY.get(
+              chain[i].getNotBefore().toString(),
+              chain[i].getNotAfter().toString()));
+
+      // certificate Issuer
+      app.println(
+          INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE_ISSUER.get(
+              chain[i].getIssuerDN().toString()));
+
+      if (i+1 <chain.length)
+      {
+        app.println();
+        app.println();
+      }
+    }
+    MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
+    builder.setPrompt(INFO_LDAP_CONN_PROMPT_SECURITY_TRUST_OPTION.get());
+
+    TrustOption defaultTrustMethod = TrustOption.SESSION ;
+    for (TrustOption t : TrustOption.values())
+    {
+      int i = builder.addNumberedOption(t.getMenuMessage(), MenuResult
+          .success(t.getChoice()));
+      if (t.equals(defaultTrustMethod))
+      {
+        builder.setDefault(
+            INFO_LDAP_CONN_PROMPT_SECURITY_PROTOCOL_DEFAULT_CHOICE
+                .get(new Integer(i)), MenuResult.success(t.getChoice()));
+      }
+    }
+
+    app.println();
+    app.println();
+
+    Menu<Integer> menu = builder.toMenu();
+    while (true)
+    {
+      try
+      {
+        MenuResult<Integer> result = menu.run();
+        if (result.isSuccess())
+        {
+          if (result.getValue().equals(TrustOption.UNTRUSTED.getChoice()))
+          {
+            if(exception instanceof CertificateException)
+            {
+              throw (CertificateException)exception;
+            }
+            else
+            {
+              throw new CertificateException(exception);
+            }
+          }
+
+          if ((result.getValue().equals(TrustOption.CERTIFICATE_DETAILS
+              .getChoice())))
+          {
+            for (X509Certificate aChain : chain) {
+              app.println();
+              app.println(INFO_LDAP_CONN_SECURITY_SERVER_CERTIFICATE
+                  .get(aChain.toString()));
+            }
+            continue;
+          }
+
+          // Update the trust manager with the new certificate
+          acceptCertificate(chain,
+              result.getValue().equals(TrustOption.PERMAMENT.getChoice()));
+          break;
+        }
+        else
+        {
+          // Should never happen.
+          throw new RuntimeException();
+        }
+      }
+      catch (CLIException cliE)
+      {
+        throw new RuntimeException(cliE);
+      }
+    }
+  }
+
+  /**
+   * This method is called when the user accepted a certificate.
+   * @param chain the certificate chain accepted by the user.
+   * certificate.
+   */
+  public void acceptCertificate(X509Certificate[] chain, boolean permanent)
+  {
+    if(permanent)
+    {
+      LOG.log(Level.INFO, "Permanently accepting certificate chain to " +
+          "truststore");
+    }
+    else
+    {
+      LOG.log(Level.INFO, "Accepting certificate chain for this session");
+    }
+
+    for (X509Certificate aChain : chain) {
+      try
+      {
+        String alias = aChain.getSubjectDN().getName();
+        inMemoryTrustStore.setCertificateEntry(alias, aChain);
+        if(permanent)
+        {
+          onDiskTrustStore.setCertificateEntry(alias, aChain);
+        }
+      }
+      catch(Exception e)
+      {
+        LOG.log(Level.WARNING, "Error setting certificate to store: " + e +
+            "\nCert: " + aChain.toString());
+      }
+    }
+
+    if(permanent)
+    {
+      try
+      {
+        File truststoreFile = new File(DEFAULT_PATH);
+        if (!truststoreFile.exists())
+        {
+          createFile(truststoreFile);
+        }
+        FileOutputStream fos = new FileOutputStream(truststoreFile);
+        onDiskTrustStore.store(fos, DEFAULT_PASSWORD);
+        fos.close();
+      }
+      catch(Exception e)
+      {
+        LOG.log(Level.WARNING, "Error saving store to disk: " + e);
+      }
+    }
+  }
+
+  private boolean createFile(File f) throws IOException {
+    boolean success = false;
+    if (f != null) {
+      File parent = f.getParentFile();
+      if (!parent.exists()) {
+        parent.mkdirs();
+      }
+      success = f.createNewFile();
+    }
+    return success;
+  }
+}
diff --git a/sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java
new file mode 100644
index 0000000..17c0db5
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/TrustAllTrustManager.java
@@ -0,0 +1,64 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2008 Sun Microsystems, Inc.
+ */
+
+package org.opends.sdk.util.ssl;
+
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+
+import javax.net.ssl.X509TrustManager;
+
+/**
+ * An X509TrustManager which trusts everything.
+ */
+public class TrustAllTrustManager implements X509TrustManager {
+
+  /**
+   * {@inheritDoc}
+   */
+  public void checkClientTrusted(X509Certificate[] chain, String authType)
+  throws CertificateException
+  {
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void checkServerTrusted(X509Certificate[] chain, String authType)
+  throws CertificateException
+  {
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public X509Certificate[] getAcceptedIssuers()
+  {
+    return new X509Certificate[0];
+  }
+}
+
diff --git a/sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java b/sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java
new file mode 100644
index 0000000..9b3ad5b
--- /dev/null
+++ b/sdk/src/org/opends/sdk/util/ssl/TrustStoreTrustManager.java
@@ -0,0 +1,285 @@
+/*
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * 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
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  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 2008-2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2009 Parametric Technology Corporation (PTC)
+ */
+
+package org.opends.sdk.util.ssl;
+
+
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+
+import org.opends.sdk.DN;
+import org.opends.sdk.schema.Schema;
+import org.opends.sdk.util.Validator;
+
+
+
+/**
+ * This class is in charge of checking whether the certificates that are
+ * presented are trusted or not. This implementation tries to check also
+ * that the subject DN of the certificate corresponds to the host passed
+ * using the setHostName method. This implementation also checks to make
+ * sure the certificate is in the validity period. The constructor tries
+ * to use a default TrustManager from the system and if it cannot be
+ * retrieved this class will only accept the certificates explicitly
+ * accepted by the user (and specified by calling acceptCertificate).
+ */
+public class TrustStoreTrustManager implements X509TrustManager
+{
+  static private final Logger LOG = Logger
+      .getLogger(TrustStoreTrustManager.class.getName());
+
+  private final X509TrustManager trustManager;
+
+  private final KeyStore truststore;
+
+  private final File truststoreFile;
+
+  private final char[] truststorePassword;
+
+  private final String hostname;
+
+  private final boolean checkValidityDates;
+
+
+
+  /**
+   * The default constructor.
+   */
+  public TrustStoreTrustManager(String truststorePath,
+      String truststorePassword, String hostname,
+      boolean checkValidityDates) throws KeyStoreException,
+      IOException, NoSuchAlgorithmException, CertificateException
+  {
+    Validator.ensureNotNull(truststorePath);
+    this.truststoreFile = new File(truststorePath);
+    if (truststorePassword != null)
+    {
+      this.truststorePassword = truststorePassword.toCharArray();
+    }
+    else
+    {
+      this.truststorePassword = null;
+    }
+    truststore = KeyStore.getInstance(KeyStore.getDefaultType());
+
+    FileInputStream fos = new FileInputStream(truststoreFile);
+    truststore.load(fos, this.truststorePassword);
+    TrustManagerFactory tmf = TrustManagerFactory
+        .getInstance(TrustManagerFactory.getDefaultAlgorithm());
+
+    tmf.init(truststore);
+    X509TrustManager x509tm = null;
+    for (TrustManager tm : tmf.getTrustManagers())
+    {
+      if (tm instanceof X509TrustManager)
+      {
+        x509tm = (X509TrustManager) tm;
+        break;
+      }
+    }
+    if (x509tm == null)
+    {
+      throw new NoSuchAlgorithmException();
+    }
+    this.trustManager = x509tm;
+    this.hostname = hostname;
+    this.checkValidityDates = checkValidityDates;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void checkClientTrusted(X509Certificate[] chain,
+      String authType) throws CertificateException
+  {
+    verifyExpiration(chain);
+    verifyHostName(chain);
+    trustManager.checkClientTrusted(chain, authType);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public void checkServerTrusted(X509Certificate[] chain,
+      String authType) throws CertificateException
+  {
+    verifyExpiration(chain);
+    verifyHostName(chain);
+    trustManager.checkClientTrusted(chain, authType);
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public X509Certificate[] getAcceptedIssuers()
+  {
+    if (trustManager != null)
+    {
+      return trustManager.getAcceptedIssuers();
+    }
+    else
+    {
+      return new X509Certificate[0];
+    }
+  }
+
+
+
+  private void verifyExpiration(X509Certificate[] chain)
+      throws CertificateException
+  {
+    Date currentDate = new Date();
+    for (X509Certificate c : chain)
+    {
+      try
+      {
+        c.checkValidity(currentDate);
+      }
+      catch (CertificateExpiredException cee)
+      {
+        LOG.log(Level.WARNING, "Refusing to trust security"
+            + " certificate \"" + c.getSubjectDN().getName()
+            + "\" because it" + " expired on "
+            + String.valueOf(c.getNotAfter()));
+
+        throw cee;
+      }
+      catch (CertificateNotYetValidException cnyve)
+      {
+        LOG.log(Level.WARNING, "Refusing to trust security"
+            + " certificate \"" + c.getSubjectDN().getName()
+            + "\" because it" + " is not valid until "
+            + String.valueOf(c.getNotBefore()));
+
+        throw cnyve;
+      }
+    }
+  }
+
+
+
+  /**
+   * Verifies that the provided certificate chains subject DN
+   * corresponds to the host name specified with the setHost method.
+   *
+   * @param chain
+   *          the certificate chain to analyze.
+   * @throws HostnameMismatchCertificateException
+   *           if the subject DN of the certificate does not match with
+   *           the host name specified with the method setHost.
+   */
+  private void verifyHostName(X509Certificate[] chain)
+      throws HostnameMismatchCertificateException
+  {
+    if (hostname != null)
+    {
+      try
+      {
+        DN dn = DN.valueOf(
+            chain[0].getSubjectX500Principal().getName(), Schema
+                .getCoreSchema());
+        String value = dn.iterator().next().iterator().next()
+            .getAttributeValue().toString();
+        if (!hostMatch(value, hostname))
+        {
+          throw new HostnameMismatchCertificateException(
+              "Hostname mismatch between host name " + hostname
+                  + " and subject DN: "
+                  + chain[0].getSubjectX500Principal(), hostname,
+              chain[0].getSubjectX500Principal().getName());
+        }
+      }
+      catch (Throwable t)
+      {
+        LOG.log(Level.WARNING, "Error parsing subject dn: "
+            + chain[0].getSubjectX500Principal(), t);
+      }
+    }
+  }
+
+
+
+  /**
+   * Checks whether two host names match. It accepts the use of wildcard
+   * in the host name.
+   *
+   * @param host1
+   *          the first host name.
+   * @param host2
+   *          the second host name.
+   * @return <CODE>true</CODE> if the host match and <CODE>false</CODE>
+   *         otherwise.
+   */
+  private boolean hostMatch(String host1, String host2)
+  {
+    if (host1 == null)
+    {
+      throw new IllegalArgumentException(
+          "The host1 parameter cannot be null");
+    }
+    if (host2 == null)
+    {
+      throw new IllegalArgumentException(
+          "The host2 parameter cannot be null");
+    }
+    String[] h1 = host1.split("\\.");
+    String[] h2 = host2.split("\\.");
+
+    boolean hostMatch = h1.length == h2.length;
+    for (int i = 0; i < h1.length && hostMatch; i++)
+    {
+      if (!h1[i].equals("*") && !h2[i].equals("*"))
+      {
+        hostMatch = h1[i].equalsIgnoreCase(h2[i]);
+      }
+    }
+    return hostMatch;
+  }
+}

--
Gitblit v1.10.0