| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| * |
| */ |
| package org.apache.directory.api.ldap.model.entry; |
| |
| |
| import java.io.Externalizable; |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.util.Arrays; |
| |
| import org.apache.directory.api.i18n.I18n; |
| import org.apache.directory.api.ldap.model.exception.LdapException; |
| import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; |
| import org.apache.directory.api.ldap.model.message.ResultCodeEnum; |
| import org.apache.directory.api.ldap.model.schema.AttributeType; |
| import org.apache.directory.api.ldap.model.schema.LdapComparator; |
| import org.apache.directory.api.ldap.model.schema.LdapSyntax; |
| import org.apache.directory.api.ldap.model.schema.MatchingRule; |
| import org.apache.directory.api.ldap.model.schema.Normalizer; |
| import org.apache.directory.api.ldap.model.schema.SyntaxChecker; |
| import org.apache.directory.api.ldap.model.schema.comparators.StringComparator; |
| import org.apache.directory.api.ldap.model.schema.normalizers.NoOpNormalizer; |
| import org.apache.directory.api.util.Serialize; |
| import org.apache.directory.api.util.Strings; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * A Class for wrapping attribute values stored into an Entry Attribute, or a AVA. |
| * |
| * We keep the value as byte[] unless we need to convert them to a String (if we have |
| * a HR Value). |
| * |
| * The serialized Value will be stored as : |
| * |
| * <pre> |
| * +---------+ |
| * | boolean | isHR flag |
| * +---------+ |
| * | boolean | TRUE if the value is not null, FALSE otherwise |
| * +---------+ |
| * [| int |] If the previous flag is TRUE, the length of the value |
| * [+---------+] |
| * [| byte[] |] The value itself |
| * [+---------+] |
| * | boolean | TRUE if we have a prepared String |
| * +---------+ |
| * [| String |] The prepared String if we have it |
| * [+---------+] |
| * </pre> |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class Value implements Cloneable, Externalizable, Comparable<Value> |
| { |
| /** Used for serialization */ |
| private static final long serialVersionUID = 2L; |
| |
| /** logger for reporting errors that might not be handled properly upstream */ |
| private static final Logger LOG = LoggerFactory.getLogger( Value.class ); |
| |
| /** reference to the attributeType associated with the value */ |
| private transient AttributeType attributeType; |
| |
| /** the User Provided value if it's a String */ |
| private String upValue; |
| |
| /** the prepared representation of the user provided value if it's a String */ |
| private String normValue; |
| |
| /** The computed hashcode. We don't want to compute it each time the hashcode() method is called */ |
| private volatile int h; |
| |
| /** The UTF-8 bytes for this value (we use the UP value) */ |
| private byte[] bytes; |
| |
| /** Two flags used to tell if the value is HR or not in serialization */ |
| private boolean isHR = true; |
| |
| /** A default comparator if we don't have an EQUALITY MR */ |
| private static StringComparator stringComparator = new StringComparator( null ); |
| |
| // ----------------------------------------------------------------------- |
| // Constructors |
| // ----------------------------------------------------------------------- |
| /** |
| * Creates a Value with an initial user provided String value. |
| * |
| * @param upValue the value to wrap. It can be null |
| */ |
| public Value( String upValue ) |
| { |
| this.upValue = upValue; |
| |
| // We can't normalize the value, we store it as is |
| normValue = upValue; |
| |
| if ( upValue != null ) |
| { |
| bytes = Strings.getBytesUtf8( upValue ); |
| } |
| |
| hashCode(); |
| } |
| |
| |
| /** |
| * Creates a Value with an initial user provided binary value. |
| * |
| * @param value the binary value to wrap which may be null, or a zero length byte array |
| */ |
| public Value( byte[] value ) |
| { |
| if ( value != null ) |
| { |
| bytes = new byte[value.length]; |
| System.arraycopy( value, 0, bytes, 0, value.length ); |
| } |
| else |
| { |
| bytes = null; |
| } |
| |
| isHR = false; |
| |
| hashCode(); |
| } |
| |
| |
| /** |
| * Creates a schema aware binary Value with an initial value. |
| * |
| * @param attributeType the schema type associated with this Value |
| * @param upValue the value to wrap |
| * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly |
| * to the schema |
| */ |
| public Value( AttributeType attributeType, byte[] upValue ) throws LdapInvalidAttributeValueException |
| { |
| init( attributeType ); |
| |
| if ( upValue != null ) |
| { |
| bytes = new byte[upValue.length]; |
| System.arraycopy( upValue, 0, bytes, 0, upValue.length ); |
| |
| if ( isHR ) |
| { |
| this.upValue = Strings.utf8ToString( upValue ); |
| } |
| } |
| else |
| { |
| bytes = null; |
| } |
| |
| if ( ( attributeType != null ) && !attributeType.isRelaxed() ) |
| { |
| // Check the value |
| SyntaxChecker syntaxChecker = attributeType.getSyntax().getSyntaxChecker(); |
| |
| if ( syntaxChecker != null ) |
| { |
| if ( !syntaxChecker.isValidSyntax( bytes ) ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, |
| I18n.err( I18n.ERR_13246_INVALID_VALUE_PER_SYNTAX ) ); |
| } |
| } |
| else |
| { |
| // We should always have a SyntaxChecker |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) ); |
| } |
| } |
| |
| hashCode(); |
| } |
| |
| |
| private void init( AttributeType attributeType ) |
| { |
| if ( attributeType != null ) |
| { |
| if ( attributeType.getSyntax() == null ) |
| { |
| // Some broken LDAP servers do not have proper syntax definitions, default to HR |
| // Log this on trace level only. Otherwise we get logs full of errors when working |
| // with AD and similar not-really-LDAP-compliant servers. |
| if ( LOG.isTraceEnabled() ) |
| { |
| LOG.trace( I18n.err( I18n.ERR_13225_NO_SYNTAX ) ); |
| } |
| |
| isHR = true; |
| } |
| else |
| { |
| isHR = attributeType.getSyntax().isHumanReadable(); |
| } |
| } |
| else |
| { |
| if ( LOG.isWarnEnabled() ) |
| { |
| LOG.warn( I18n.msg( I18n.MSG_13202_AT_IS_NULL ) ); |
| } |
| } |
| |
| this.attributeType = attributeType; |
| } |
| |
| |
| /** |
| * Creates a schema aware binary Value with an initial value. This method is |
| * only to be used by deserializers. |
| * |
| * @param attributeType the schema type associated with this Value |
| */ |
| /* Package protected*/ Value( AttributeType attributeType ) |
| { |
| init( attributeType ); |
| } |
| |
| |
| /** |
| * Creates a schema aware StringValue with an initial user provided String value. |
| * |
| * @param attributeType the schema type associated with this StringValue |
| * @param upValue the value to wrap |
| * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly |
| * to the schema |
| */ |
| public Value( AttributeType attributeType, String upValue ) throws LdapInvalidAttributeValueException |
| { |
| init( attributeType ); |
| this.upValue = upValue; |
| |
| if ( upValue != null ) |
| { |
| bytes = Strings.getBytesUtf8( upValue ); |
| } |
| else |
| { |
| bytes = null; |
| } |
| |
| try |
| { |
| computeNormValue(); |
| } |
| catch ( LdapException le ) |
| { |
| LOG.error( le.getMessage() ); |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13247_INVALID_VALUE_CANT_NORMALIZE, upValue ) ); |
| } |
| |
| if ( !attributeType.isRelaxed() ) |
| { |
| // Check the value |
| LdapSyntax syntax = attributeType.getSyntax(); |
| |
| if ( ( syntax != null ) && ( syntax.getSyntaxChecker() != null ) ) |
| { |
| if ( !attributeType.getSyntax().getSyntaxChecker().isValidSyntax( upValue ) ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, |
| I18n.err( I18n.ERR_13246_INVALID_VALUE_PER_SYNTAX ) ); |
| } |
| } |
| else |
| { |
| // We should always have a SyntaxChecker |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) ); |
| } |
| } |
| |
| hashCode(); |
| } |
| |
| |
| /** |
| * Creates a schema aware StringValue with an initial user provided String value and |
| * its normalized Value |
| * |
| * @param attributeType the schema type associated with this StringValue |
| * @param upValue the value to wrap |
| * @param normValue the normalized value to wrap |
| * @throws LdapInvalidAttributeValueException If the added value is invalid accordingly |
| * to the schema |
| */ |
| public Value( AttributeType attributeType, String upValue, String normValue ) throws LdapInvalidAttributeValueException |
| { |
| init( attributeType ); |
| this.upValue = upValue; |
| |
| if ( upValue != null ) |
| { |
| bytes = Strings.getBytesUtf8( upValue ); |
| } |
| else |
| { |
| bytes = null; |
| } |
| |
| this.normValue = normValue; |
| |
| if ( !attributeType.isRelaxed() ) |
| { |
| // Check the value |
| if ( attributeType.getSyntax().getSyntaxChecker() != null ) |
| { |
| if ( !attributeType.getSyntax().getSyntaxChecker().isValidSyntax( upValue ) ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, |
| I18n.err( I18n.ERR_13246_INVALID_VALUE_PER_SYNTAX ) ); |
| } |
| } |
| else |
| { |
| // We should always have a SyntaxChecker |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) ); |
| } |
| } |
| |
| hashCode(); |
| } |
| |
| |
| /** |
| * Creates a Value from an existing Value with an AttributeType |
| * |
| * @param attributeType the schema attribute type associated with this StringValue |
| * @param value the original Value |
| * @throws LdapInvalidAttributeValueException If the value is invalid |
| */ |
| public Value( AttributeType attributeType, Value value ) throws LdapInvalidAttributeValueException |
| { |
| init( attributeType ); |
| |
| if ( isHR ) |
| { |
| if ( value.isHR ) |
| { |
| this.upValue = value.upValue; |
| } |
| else |
| { |
| this.upValue = Strings.utf8ToString( value.bytes ); |
| } |
| } |
| |
| try |
| { |
| computeNormValue(); |
| } |
| catch ( LdapException le ) |
| { |
| LOG.error( le.getMessage() ); |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13247_INVALID_VALUE_CANT_NORMALIZE, upValue ) ); |
| } |
| |
| // Check the normValue |
| if ( !attributeType.isRelaxed() ) |
| { |
| // Check the value |
| if ( attributeType.getSyntax().getSyntaxChecker() != null ) |
| { |
| attributeType.getSyntax().getSyntaxChecker().isValidSyntax( value.normValue ); |
| } |
| else |
| { |
| // We should always have a SyntaxChecker |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, normValue ) ); |
| } |
| } |
| |
| // We have to copy the byte[], they are just referenced by super.clone() |
| if ( value.bytes != null ) |
| { |
| bytes = new byte[value.bytes.length]; |
| System.arraycopy( value.bytes, 0, bytes, 0, value.bytes.length ); |
| } |
| |
| hashCode(); |
| } |
| |
| |
| /** |
| * Create a Value with an AttributeType. It will not contain anything and will only be used by |
| * the deserializer. |
| * |
| * @param attributeType The ATttributeType to use |
| * @return An instance of value. |
| */ |
| public static Value createValue( AttributeType attributeType ) |
| { |
| return new Value( attributeType ); |
| } |
| |
| |
| /** |
| * Clone a Value |
| * |
| * @return A cloned value |
| */ |
| @Override |
| public Value clone() |
| { |
| try |
| { |
| Value clone = ( Value ) super.clone(); |
| |
| if ( isHR ) |
| { |
| return clone; |
| } |
| else |
| { |
| // We have to copy the byte[], they are just referenced by suoer.clone() |
| if ( bytes != null ) |
| { |
| clone.bytes = new byte[bytes.length]; |
| System.arraycopy( bytes, 0, clone.bytes, 0, bytes.length ); |
| } |
| } |
| |
| return clone; |
| } |
| catch ( CloneNotSupportedException cnse ) |
| { |
| // Do nothing |
| return null; |
| } |
| } |
| |
| |
| /** |
| * Check if the contained value is null or not |
| * |
| * @return <code>true</code> if the inner value is null. |
| */ |
| public boolean isNull() |
| { |
| if ( isHR ) |
| { |
| return upValue == null; |
| } |
| else |
| { |
| return bytes == null; |
| } |
| } |
| |
| |
| /** |
| * Get the associated AttributeType |
| * |
| * @return The AttributeType |
| */ |
| public AttributeType getAttributeType() |
| { |
| return attributeType; |
| } |
| |
| |
| /** |
| * Check if the value is stored into an instance of the given |
| * AttributeType, or one of its ascendant. |
| * |
| * For instance, if the Value is associated with a CommonName, |
| * checking for Name will match. |
| * |
| * @param attributeType The AttributeType we are looking at |
| * @return <code>true</code> if the value is associated with the given |
| * attributeType or one of its ascendant |
| */ |
| public boolean isInstanceOf( AttributeType attributeType ) |
| { |
| return ( attributeType != null ) |
| && ( this.attributeType.equals( attributeType ) || this.attributeType.isDescendantOf( attributeType ) ); |
| } |
| |
| |
| |
| |
| /** |
| * @return The value as an escaped String |
| */ |
| public String getEscaped() |
| { |
| if ( Strings.isEmpty( bytes ) ) |
| { |
| return Strings.EMPTY_STRING; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| boolean leadChar = true; |
| |
| for ( int pos = 0; pos < bytes.length; pos++ ) |
| { |
| boolean trailChar = pos == bytes.length - 1; |
| byte b = bytes[pos]; |
| |
| switch ( b ) |
| { |
| case 0x00 : |
| sb.append( "\\00" ); |
| break; |
| |
| case 0x01 : |
| case 0x02 : |
| case 0x03 : |
| case 0x04 : |
| case 0x05 : |
| case 0x06 : |
| case 0x07 : |
| case 0x08 : |
| case 0x09 : |
| case 0x0A : |
| case 0x0B : |
| case 0x0C : |
| case 0x0D : |
| case 0x0E : |
| case 0x0F : |
| case 0x10 : |
| case 0x11 : |
| case 0x12 : |
| case 0x13 : |
| case 0x14 : |
| case 0x15 : |
| case 0x16 : |
| case 0x17 : |
| case 0x18 : |
| case 0x19 : |
| case 0x1A : |
| case 0x1B : |
| case 0x1C : |
| case 0x1D : |
| case 0x1E : |
| case 0x1F : |
| sb.append( ( char ) b ); |
| break; |
| |
| case 0x20 : |
| if ( leadChar || trailChar ) |
| { |
| sb.append( "\\ " ); |
| } |
| else |
| { |
| sb.append( ( char ) b ); |
| } |
| |
| break; |
| |
| case 0x21 : |
| sb.append( ( char ) b ); |
| break; |
| |
| |
| case 0x22 : |
| sb.append( "\\\"" ); |
| break; |
| |
| case 0x23 : |
| if ( leadChar ) |
| { |
| sb.append( "\\#" ); |
| } |
| else |
| { |
| sb.append( '#' ); |
| } |
| |
| break; |
| |
| case 0x24 : |
| case 0x25 : |
| case 0x26 : |
| case 0x27 : |
| case 0x28 : |
| case 0x29 : |
| case 0x2A : |
| sb.append( ( char ) b ); |
| break; |
| |
| case 0x2B : |
| sb.append( "\\+" ); |
| break; |
| |
| case 0x2C : |
| sb.append( "\\," ); |
| break; |
| |
| case 0x2D : |
| case 0x2E : |
| case 0x2F : |
| case 0x30 : |
| case 0x31 : |
| case 0x32 : |
| case 0x33 : |
| case 0x34 : |
| case 0x35 : |
| case 0x36 : |
| case 0x37 : |
| case 0x38 : |
| case 0x39 : |
| case 0x3A : |
| sb.append( ( char ) b ); |
| break; |
| |
| case 0x3B : |
| sb.append( "\\;" ); |
| break; |
| |
| case 0x3C : |
| sb.append( "\\<" ); |
| break; |
| |
| case 0x3D : |
| sb.append( ( char ) b ); |
| break; |
| |
| case 0x3E : |
| sb.append( "\\>" ); |
| break; |
| |
| case 0x3F : |
| case 0x40 : |
| case 0x41 : |
| case 0x42 : |
| case 0x43 : |
| case 0x44 : |
| case 0x45 : |
| case 0x46 : |
| case 0x47 : |
| case 0x48 : |
| case 0x49 : |
| case 0x4A : |
| case 0x4B : |
| case 0x4C : |
| case 0x4D : |
| case 0x4E : |
| case 0x4F : |
| case 0x50 : |
| case 0x51 : |
| case 0x52 : |
| case 0x53 : |
| case 0x54 : |
| case 0x55 : |
| case 0x56 : |
| case 0x57 : |
| case 0x58 : |
| case 0x59 : |
| case 0x5A : |
| case 0x5B : |
| sb.append( ( char ) b ); |
| break; |
| |
| case 0x5C : |
| sb.append( "\\\\" ); |
| break; |
| |
| case 0x5D : |
| case 0x5E : |
| case 0x5F : |
| case 0x60 : |
| case 0x61 : |
| case 0x62 : |
| case 0x63 : |
| case 0x64 : |
| case 0x65 : |
| case 0x66 : |
| case 0x67 : |
| case 0x68 : |
| case 0x69 : |
| case 0x6A : |
| case 0x6B : |
| case 0x6C : |
| case 0x6D : |
| case 0x6E : |
| case 0x6F : |
| case 0x70 : |
| case 0x71 : |
| case 0x72 : |
| case 0x73 : |
| case 0x74 : |
| case 0x75 : |
| case 0x76 : |
| case 0x77 : |
| case 0x78 : |
| case 0x79 : |
| case 0x7A : |
| case 0x7B : |
| case 0x7C : |
| case 0x7D : |
| case 0x7E : |
| case 0x7F : |
| sb.append( ( char ) b ); |
| break; |
| |
| // Between 0x80 and 0xC1, this is an octet |
| case ( byte ) 0x80 : |
| case ( byte ) 0x81 : |
| case ( byte ) 0x82 : |
| case ( byte ) 0x83 : |
| case ( byte ) 0x84 : |
| case ( byte ) 0x85 : |
| case ( byte ) 0x86 : |
| case ( byte ) 0x87 : |
| case ( byte ) 0x88 : |
| case ( byte ) 0x89 : |
| case ( byte ) 0x8A : |
| case ( byte ) 0x8B : |
| case ( byte ) 0x8C : |
| case ( byte ) 0x8D : |
| case ( byte ) 0x8E : |
| case ( byte ) 0x8F : |
| case ( byte ) 0x90 : |
| case ( byte ) 0x91 : |
| case ( byte ) 0x92 : |
| case ( byte ) 0x93 : |
| case ( byte ) 0x94 : |
| case ( byte ) 0x95 : |
| case ( byte ) 0x96 : |
| case ( byte ) 0x97 : |
| case ( byte ) 0x98 : |
| case ( byte ) 0x99 : |
| case ( byte ) 0x9A : |
| case ( byte ) 0x9B : |
| case ( byte ) 0x9C : |
| case ( byte ) 0x9D : |
| case ( byte ) 0x9E : |
| case ( byte ) 0x9F : |
| case ( byte ) 0xA0 : |
| case ( byte ) 0xA1 : |
| case ( byte ) 0xA2 : |
| case ( byte ) 0xA3 : |
| case ( byte ) 0xA4 : |
| case ( byte ) 0xA5 : |
| case ( byte ) 0xA6 : |
| case ( byte ) 0xA7 : |
| case ( byte ) 0xA8 : |
| case ( byte ) 0xA9 : |
| case ( byte ) 0xAA : |
| case ( byte ) 0xAB : |
| case ( byte ) 0xAC : |
| case ( byte ) 0xAD : |
| case ( byte ) 0xAE : |
| case ( byte ) 0xAF : |
| case ( byte ) 0xB0 : |
| case ( byte ) 0xB1 : |
| case ( byte ) 0xB2 : |
| case ( byte ) 0xB3 : |
| case ( byte ) 0xB4 : |
| case ( byte ) 0xB5 : |
| case ( byte ) 0xB6 : |
| case ( byte ) 0xB7 : |
| case ( byte ) 0xB8 : |
| case ( byte ) 0xB9 : |
| case ( byte ) 0xBA : |
| case ( byte ) 0xBB : |
| case ( byte ) 0xBC : |
| case ( byte ) 0xBD : |
| case ( byte ) 0xBE : |
| case ( byte ) 0xBF : |
| case ( byte ) 0xC0 : |
| case ( byte ) 0xC1 : |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| break; |
| |
| // Between 0xC2 and 0xDF, we may have a UTF-2 char |
| case ( byte ) 0xC2 : |
| case ( byte ) 0xC3 : |
| case ( byte ) 0xC4 : |
| case ( byte ) 0xC5 : |
| case ( byte ) 0xC6 : |
| case ( byte ) 0xC7 : |
| case ( byte ) 0xC8 : |
| case ( byte ) 0xC9 : |
| case ( byte ) 0xCA : |
| case ( byte ) 0xCB : |
| case ( byte ) 0xCC : |
| case ( byte ) 0xCD : |
| case ( byte ) 0xCE : |
| case ( byte ) 0xCF : |
| case ( byte ) 0xD0 : |
| case ( byte ) 0xD1 : |
| case ( byte ) 0xD2 : |
| case ( byte ) 0xD3 : |
| case ( byte ) 0xD4 : |
| case ( byte ) 0xD5 : |
| case ( byte ) 0xD6 : |
| case ( byte ) 0xD7 : |
| case ( byte ) 0xD8 : |
| case ( byte ) 0xD9 : |
| case ( byte ) 0xDA : |
| case ( byte ) 0xDB : |
| case ( byte ) 0xDC : |
| case ( byte ) 0xDD : |
| case ( byte ) 0xDE : |
| case ( byte ) 0xDF : |
| // UTF2, if the following byte is in [0x80-0xBF] |
| if ( trailChar ) |
| { |
| // No next byte : this is an octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| else |
| { |
| int b2 = bytes[pos + 1] & 0x00FF; |
| |
| if ( ( b2 >= 0x0080 ) && ( b2 <= 0x00BF ) ) |
| { |
| // This is an UTF-2 char |
| sb.append( Strings.utf8ToString( bytes, pos, 2 ) ); |
| pos++; |
| } |
| else |
| { |
| // Not an UTF-2 |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| |
| break; |
| |
| case ( byte ) 0xE0 : |
| // May be an UTF-3, if the next byte is in [0xA0-0xBF], followed by a byte in [0x80-0xBF] |
| if ( trailChar ) |
| { |
| // No next byte : this is an octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| break; |
| } |
| |
| if ( pos == bytes.length - 2 ) |
| { |
| // We only have 2 bytes : not an UTF-3 |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| else |
| { |
| int b2 = bytes[pos + 1] & 0x00FF; |
| |
| if ( ( b2 >= 0x00A0 ) && ( b2 <= 0x00BF ) ) |
| { |
| int b3 = bytes[pos + 2] & 0x00FF; |
| |
| // Check that the third byte is in between 0x80-0xBF |
| if ( ( b3 >= 0x0080 ) && ( b3 <= 0x00BF ) ) |
| { |
| // UTF-3 |
| sb.append( Strings.utf8ToString( bytes, pos, 3 ) ); |
| pos += 2; |
| } |
| else |
| { |
| // Not an UTF-3, dump one bytes |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-3 : dump two byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| |
| break; |
| |
| |
| // Between E1 and EC, this may be an UTF-3 if the next two bytes are between 0x80 and 0xBF |
| case ( byte ) 0xE1 : |
| case ( byte ) 0xE2 : |
| case ( byte ) 0xE3 : |
| case ( byte ) 0xE4 : |
| case ( byte ) 0xE5 : |
| case ( byte ) 0xE6 : |
| case ( byte ) 0xE7 : |
| case ( byte ) 0xE8 : |
| case ( byte ) 0xE9 : |
| case ( byte ) 0xEA : |
| case ( byte ) 0xEB : |
| case ( byte ) 0xEC : |
| case ( byte ) 0xEE : |
| case ( byte ) 0xEF : |
| if ( trailChar ) |
| { |
| // No next byte : this is an octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| break; |
| } |
| |
| if ( pos == bytes.length - 2 ) |
| { |
| // We only have 2 bytes : not an UTF-3 |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| else |
| { |
| int b2 = bytes[pos + 1] & 0x00FF; |
| |
| if ( ( b2 >= 0x0080 ) && ( b2 <= 0x00BF ) ) |
| { |
| int b3 = bytes[pos + 2] & 0x00FF; |
| |
| // Check that the third byte is in between 0x80-0xBF |
| if ( ( b3 >= 0x0080 ) && ( b3 <= 0x00BF ) ) |
| { |
| // UTF-3 |
| sb.append( Strings.utf8ToString( bytes, pos, 3 ) ); |
| pos += 2; |
| } |
| else |
| { |
| // Not an UTF-3, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-3 : dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| pos++; |
| } |
| } |
| |
| break; |
| |
| case ( byte ) 0xED : |
| // May be an UTF-3 if the second byte is in [0x80-0x9F] and the third byte in [0x80-0xBF] |
| if ( trailChar ) |
| { |
| // No next byte : this is an octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| break; |
| } |
| |
| if ( pos == bytes.length - 2 ) |
| { |
| // We only have 2 bytes : not an UTF-3 |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| else |
| { |
| int b2 = bytes[pos + 1] & 0x00FF; |
| |
| if ( ( b2 >= 0x0080 ) && ( b2 <= 0x009F ) ) |
| { |
| int b3 = bytes[pos + 2] & 0x00FF; |
| |
| // Check that the third byte is in between 0x80-0xBF |
| if ( ( b3 >= 0x0080 ) && ( b3 <= 0x00BF ) ) |
| { |
| // UTF-3 |
| sb.append( Strings.utf8ToString( bytes, pos, 3 ) ); |
| pos += 2; |
| } |
| else |
| { |
| // Not an UTF-3, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-3 : dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| pos++; |
| } |
| } |
| |
| break; |
| |
| case ( byte ) 0xF0 : |
| // May be an UTF-4 if the second byte is in [0x90-0xBF] followed by two bytes in [0x80-0xBF] |
| if ( trailChar ) |
| { |
| // No next byte : this is an octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| break; |
| } |
| |
| if ( pos == bytes.length - 3 ) |
| { |
| // We only have 2 bytes : not an UTF-4 |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| else |
| { |
| int b2 = bytes[pos + 1] & 0x00FF; |
| |
| if ( ( b2 >= 0x0090 ) && ( b2 <= 0x00BF ) ) |
| { |
| int b3 = bytes[pos + 2] & 0x00FF; |
| |
| // Check that the third byte is in between 0x80-0xBF |
| if ( ( b3 >= 0x0080 ) && ( b3 <= 0x00BF ) ) |
| { |
| int b4 = bytes[pos + 3] & 0x00FF; |
| |
| // Check that the forth byte is in between 0x80-0xBF |
| if ( ( b4 >= 0x0080 ) && ( b4 <= 0x00BF ) ) |
| { |
| // UTF-4 |
| sb.append( Strings.utf8ToString( bytes, pos, 4 ) ); |
| pos += 3; |
| } |
| else |
| { |
| // Not an UTF-4, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-4, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-4 : dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| pos++; |
| } |
| } |
| |
| break; |
| |
| case ( byte ) 0xF1 : |
| case ( byte ) 0xF2 : |
| case ( byte ) 0xF3 : |
| // May be an UTF-4 |
| // May be an UTF-4 if it's followed by three bytes in [0x80-0xBF] |
| if ( trailChar ) |
| { |
| // No next byte : this is an octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| break; |
| } |
| |
| if ( pos == bytes.length - 3 ) |
| { |
| // We only have 2 bytes : not an UTF-4 |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| else |
| { |
| int b2 = bytes[pos + 1] & 0x00FF; |
| |
| if ( ( b2 >= 0x0080 ) && ( b2 <= 0x00BF ) ) |
| { |
| int b3 = bytes[pos + 2] & 0x00FF; |
| |
| // Check that the third byte is in between 0x80-0xBF |
| if ( ( b3 >= 0x0080 ) && ( b3 <= 0x00BF ) ) |
| { |
| int b4 = bytes[pos + 3] & 0x00FF; |
| |
| // Check that the forth byte is in between 0x80-0xBF |
| if ( ( b4 >= 0x0080 ) && ( b4 <= 0x00BF ) ) |
| { |
| // UTF-4 |
| sb.append( Strings.utf8ToString( bytes, pos, 4 ) ); |
| pos += 3; |
| } |
| else |
| { |
| // Not an UTF-4, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-4, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-4 : dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| pos++; |
| } |
| } |
| |
| break; |
| |
| case ( byte ) 0xF4 : |
| // May be an UTF-4 if the second byte is in [0x80-0x8F] followed by two bytes in [0x80-0xBF] |
| if ( trailChar ) |
| { |
| // No next byte : this is an octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| break; |
| } |
| |
| if ( pos == bytes.length - 3 ) |
| { |
| // We only have 2 bytes : not an UTF-4 |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| else |
| { |
| int b2 = bytes[pos + 1] & 0x00FF; |
| |
| if ( ( b2 >= 0x0080 ) && ( b2 <= 0x008F ) ) |
| { |
| int b3 = bytes[pos + 2] & 0x00FF; |
| |
| // Check that the third byte is in between 0x80-0xBF |
| if ( ( b3 >= 0x0080 ) && ( b3 <= 0x00BF ) ) |
| { |
| int b4 = bytes[pos + 3] & 0x00FF; |
| |
| // Check that the forth byte is in between 0x80-0xBF |
| if ( ( b4 >= 0x0080 ) && ( b4 <= 0x00BF ) ) |
| { |
| // UTF-4 |
| sb.append( Strings.utf8ToString( bytes, pos, 4 ) ); |
| pos += 3; |
| } |
| else |
| { |
| // Not an UTF-4, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-4, dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| } |
| } |
| else |
| { |
| // Not an UTF-4 : dump one byte |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| pos++; |
| } |
| } |
| |
| break; |
| |
| |
| default : |
| // octet |
| sb.append( '\\' ).append( Strings.byteToString( b ) ); |
| |
| break; |
| |
| } |
| |
| if ( leadChar ) |
| { |
| leadChar = false; |
| } |
| } |
| |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Get the User Provided value. If the value is Human Readable, it will return |
| * the stored String, otherwise it will returns a String based on the bytes - which may be |
| * invalid if the value is a pure binary -. |
| * |
| * @return The user provided value |
| */ |
| public String getString() |
| { |
| if ( isHR ) |
| { |
| return upValue; |
| } |
| else |
| { |
| return Strings.utf8ToString( bytes ); |
| } |
| } |
| |
| |
| /** |
| * Compute the normalized value |
| * |
| * @throws LdapException If we were'nt able to normalize the value |
| */ |
| private void computeNormValue() throws LdapException |
| { |
| if ( upValue == null ) |
| { |
| return; |
| } |
| |
| Normalizer normalizer; |
| |
| // We should have a Equality MatchingRule |
| MatchingRule equality = attributeType.getEquality(); |
| |
| if ( equality == null ) |
| { |
| // Let's try with the Substring MatchingRule |
| MatchingRule subString = attributeType.getSubstring(); |
| |
| if ( subString == null ) |
| { |
| // last chance : ordering matching rule |
| MatchingRule ordering = attributeType.getOrdering(); |
| |
| if ( ordering == null ) |
| { |
| // Ok, no luck. Use a NoOp normalizer |
| normalizer = new NoOpNormalizer(); |
| } |
| else |
| { |
| normalizer = ordering.getNormalizer(); |
| } |
| } |
| else |
| { |
| normalizer = subString.getNormalizer(); |
| } |
| } |
| else |
| { |
| normalizer = equality.getNormalizer(); |
| } |
| |
| if ( normalizer == null ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13220_NO_NORMALIZER ) ); |
| } |
| |
| // Now, normalize the upValue |
| normValue = normalizer.normalize( upValue ); |
| } |
| |
| |
| /** |
| * @return The normalized value |
| */ |
| public String getNormalized() |
| { |
| return normValue; |
| } |
| |
| |
| /** |
| * @return The User Provided value |
| */ |
| public String getUpValue() |
| { |
| if ( isHR ) |
| { |
| return upValue; |
| } |
| else |
| { |
| return getEscaped(); |
| } |
| } |
| |
| |
| /** |
| * Get the wrapped value as a byte[], if and only if the Value is binary, |
| * otherwise returns null. |
| * |
| * @return the wrapped value as a byte[] |
| */ |
| public byte[] getBytes() |
| { |
| if ( bytes == null ) |
| { |
| return null; |
| } |
| |
| if ( bytes.length == 0 ) |
| { |
| return Strings.EMPTY_BYTES; |
| } |
| |
| byte[] copy = new byte[bytes.length]; |
| System.arraycopy( bytes, 0, copy, 0, bytes.length ); |
| |
| return copy; |
| } |
| |
| |
| /** |
| * Tells if the value is schema aware or not. |
| * |
| * @return <code>true</code> if the value is sxhema aware |
| */ |
| public boolean isSchemaAware() |
| { |
| return attributeType != null; |
| } |
| |
| |
| /** |
| * Uses the syntaxChecker associated with the attributeType to check if the |
| * value is valid. |
| * |
| * @param syntaxChecker the SyntaxChecker to use to validate the value |
| * @return <code>true</code> if the value is valid |
| * @exception LdapInvalidAttributeValueException if the value cannot be validated |
| */ |
| public final boolean isValid( SyntaxChecker syntaxChecker ) throws LdapInvalidAttributeValueException |
| { |
| if ( syntaxChecker == null ) |
| { |
| String message = I18n.err( I18n.ERR_13219_NULL_SYNTAX_CHECKER, toString() ); |
| LOG.error( message ); |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message ); |
| } |
| |
| // No attributeType, or it's in relaxed mode |
| if ( isHR ) |
| { |
| // We need to prepare the String in this case |
| return syntaxChecker.isValidSyntax( getString() ); |
| } |
| else |
| { |
| return syntaxChecker.isValidSyntax( bytes ); |
| } |
| } |
| |
| |
| /** |
| * Tells if the current value is Human Readable |
| * |
| * @return <code>true</code> if the value is a String, <code>false</code> otherwise |
| */ |
| public boolean isHumanReadable() |
| { |
| return isHR; |
| } |
| |
| |
| /** |
| * @return The length of the interned value |
| */ |
| public int length() |
| { |
| if ( isHR ) |
| { |
| return upValue != null ? upValue.length() : 0; |
| } |
| else |
| { |
| return bytes != null ? bytes.length : 0; |
| } |
| } |
| |
| |
| /** |
| * Gets a comparator using getMatchingRule() to resolve the matching |
| * that the comparator is extracted from. |
| * |
| * @return a comparator associated with the attributeType or null if one cannot be found |
| */ |
| private LdapComparator<?> getLdapComparator() |
| { |
| if ( attributeType != null ) |
| { |
| MatchingRule mr = attributeType.getEquality(); |
| |
| if ( mr != null ) |
| { |
| return mr.getLdapComparator(); |
| } |
| } |
| |
| return null; |
| } |
| |
| |
| /** |
| * Serialize the Value into a buffer at the given position. |
| * |
| * @param buffer The buffer which will contain the serialized StringValue |
| * @param pos The position in the buffer for the serialized value |
| * @return The new position in the buffer |
| */ |
| public int serialize( byte[] buffer, int pos ) |
| { |
| // Compute the length : the isHR flag first, the value and prepared value presence flags |
| int length = 1; |
| byte[] preparedBytes = null; |
| |
| if ( isHR ) |
| { |
| if ( upValue != null ) |
| { |
| // The presence flag, the length and the value |
| length += 1 + 4 + bytes.length; |
| } |
| |
| if ( normValue != null ) |
| { |
| // The presence flag, the length and the value |
| preparedBytes = Strings.getBytesUtf8( normValue ); |
| length += 1 + 4 + preparedBytes.length; |
| } |
| } |
| else |
| { |
| if ( bytes != null ) |
| { |
| length = 1 + 1 + 4 + bytes.length; |
| } |
| else |
| { |
| length = 1 + 1; |
| } |
| } |
| |
| // Check that we will be able to store the data in the buffer |
| if ( buffer.length - pos < length ) |
| { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| |
| if ( isHR ) |
| { |
| buffer[pos++] = Serialize.TRUE; |
| |
| // Write the user provided value, if not null |
| if ( bytes != null ) |
| { |
| buffer[pos++] = Serialize.TRUE; |
| pos = Serialize.serialize( bytes, buffer, pos ); |
| } |
| else |
| { |
| buffer[pos++] = Serialize.FALSE; |
| } |
| |
| // Write the prepared value, if not null |
| if ( normValue != null ) |
| { |
| buffer[pos++] = Serialize.TRUE; |
| pos = Serialize.serialize( preparedBytes, buffer, pos ); |
| } |
| else |
| { |
| buffer[pos++] = Serialize.FALSE; |
| } |
| } |
| else |
| { |
| buffer[pos++] = Serialize.FALSE; |
| |
| if ( bytes != null ) |
| { |
| buffer[pos++] = Serialize.TRUE; |
| pos = Serialize.serialize( bytes, buffer, pos ); |
| } |
| else |
| { |
| buffer[pos++] = Serialize.FALSE; |
| } |
| } |
| |
| return pos; |
| } |
| |
| |
| /** |
| * Deserialize a Value. It will return a new Value instance. |
| * |
| * @param in The input stream |
| * @return A new Value instance |
| * @throws IOException If the stream can't be read |
| * @throws ClassNotFoundException If we can't instanciate a Value |
| */ |
| public static Value deserialize( ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| Value value = new Value( ( AttributeType ) null ); |
| value.readExternal( in ); |
| |
| return value; |
| } |
| |
| |
| /** |
| * Deserialize a Value. It will return a new Value instance. |
| * |
| * @param attributeType The AttributeType associated with the Value. Can be null |
| * @param in The input stream |
| * @return A new Value instance |
| * @throws IOException If the stream can't be read |
| * @throws ClassNotFoundException If we can't instanciate a Value |
| */ |
| public static Value deserialize( AttributeType attributeType, ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| Value value = new Value( attributeType ); |
| value.readExternal( in ); |
| |
| return value; |
| } |
| |
| |
| /** |
| * Deserialize a StringValue from a byte[], starting at a given position |
| * |
| * @param buffer The buffer containing the StringValue |
| * @param pos The position in the buffer |
| * @return The new position |
| * @throws IOException If the serialized value is not a StringValue |
| * @throws LdapInvalidAttributeValueException If the value is invalid |
| */ |
| public int deserialize( byte[] buffer, int pos ) throws IOException, LdapInvalidAttributeValueException |
| { |
| if ( ( pos < 0 ) || ( pos >= buffer.length ) ) |
| { |
| throw new ArrayIndexOutOfBoundsException(); |
| } |
| |
| // Read the isHR flag |
| isHR = Serialize.deserializeBoolean( buffer, pos ); |
| pos++; |
| |
| if ( isHR ) |
| { |
| // Read the user provided value, if it's not null |
| boolean hasValue = Serialize.deserializeBoolean( buffer, pos ); |
| pos++; |
| |
| if ( hasValue ) |
| { |
| bytes = Serialize.deserializeBytes( buffer, pos ); |
| pos += 4 + bytes.length; |
| |
| upValue = Strings.utf8ToString( bytes ); |
| } |
| |
| // Read the prepared value, if not null |
| boolean hasPreparedValue = Serialize.deserializeBoolean( buffer, pos ); |
| pos++; |
| |
| if ( hasPreparedValue ) |
| { |
| byte[] preparedBytes = Serialize.deserializeBytes( buffer, pos ); |
| pos += 4 + preparedBytes.length; |
| normValue = Strings.utf8ToString( preparedBytes ); |
| } |
| } |
| else |
| { |
| // Read the user provided value, if it's not null |
| boolean hasBytes = Serialize.deserializeBoolean( buffer, pos ); |
| pos++; |
| |
| if ( hasBytes ) |
| { |
| bytes = Serialize.deserializeBytes( buffer, pos ); |
| pos += 4 + bytes.length; |
| } |
| |
| } |
| |
| if ( attributeType != null ) |
| { |
| try |
| { |
| computeNormValue(); |
| } |
| catch ( LdapException le ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, le.getMessage() ); |
| } |
| } |
| |
| hashCode(); |
| |
| return pos; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| // Read the isHR flag |
| isHR = in.readBoolean(); |
| |
| if ( isHR ) |
| { |
| // Read the value if any |
| if ( in.readBoolean() ) |
| { |
| int length = in.readInt(); |
| bytes = new byte[length]; |
| |
| if ( length != 0 ) |
| { |
| in.readFully( bytes ); |
| } |
| |
| upValue = Strings.utf8ToString( bytes ); |
| } |
| |
| // Read the prepared String if any |
| if ( in.readBoolean() ) |
| { |
| normValue = in.readUTF(); |
| } |
| } |
| else |
| { |
| if ( in.readBoolean() ) |
| { |
| int length = in.readInt(); |
| bytes = new byte[length]; |
| |
| if ( length != 0 ) |
| { |
| in.readFully( bytes ); |
| } |
| } |
| } |
| |
| hashCode(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void writeExternal( ObjectOutput out ) throws IOException |
| { |
| // Write a boolean for the HR flag |
| out.writeBoolean( isHR ); |
| |
| if ( isHR ) |
| { |
| // Write the value if any |
| out.writeBoolean( upValue != null ); |
| |
| if ( upValue != null ) |
| { |
| // Write the value |
| out.writeInt( bytes.length ); |
| |
| if ( bytes.length > 0 ) |
| { |
| out.write( bytes ); |
| } |
| } |
| |
| // Write the prepared value if any |
| out.writeBoolean( normValue != null ); |
| |
| if ( normValue != null ) |
| { |
| // Write the value |
| out.writeUTF( normValue ); |
| } |
| } |
| else |
| { |
| // Just write the bytes if not null |
| out.writeBoolean( bytes != null ); |
| |
| if ( bytes != null ) |
| { |
| out.writeInt( bytes.length ); |
| |
| if ( bytes.length > 0 ) |
| { |
| out.write( bytes ); |
| } |
| } |
| } |
| |
| // and flush the data |
| out.flush(); |
| } |
| |
| |
| /** |
| * Compare the current value with a String. |
| * |
| * @param other the String we want to compare the current value with |
| * @return a positive value if the current value is above the provided String, a negative value |
| * if it's below, 0 if they are equal. |
| * @throws IllegalStateException on failures to extract the comparator, or the |
| * normalizers needed to perform the required comparisons based on the schema |
| */ |
| public int compareTo( String other ) |
| { |
| if ( !isHR ) |
| { |
| String msg = I18n.err( I18n.ERR_13224_FAILED_TO_COMPARE_NORM_VALUES, this, other ); |
| LOG.error( msg ); |
| throw new IllegalStateException( msg ); |
| } |
| |
| // Check if both value are null |
| if ( bytes == null ) |
| { |
| if ( other == null ) |
| { |
| return 0; |
| } |
| else |
| { |
| return -1; |
| } |
| } |
| else if ( other == null ) |
| { |
| return 1; |
| } |
| |
| // We have HR values. We may have an attributeType for the base Value |
| // It actually does not matter if the second value has an attributeType |
| // which is different |
| try |
| { |
| if ( attributeType != null ) |
| { |
| // No normalization. Use the base AttributeType to normalize |
| // the other value |
| String normalizedOther = attributeType.getEquality().getNormalizer().normalize( other ); |
| |
| return normValue.compareTo( normalizedOther ); |
| } |
| else |
| { |
| // No AtributeType... Compare the normValue |
| return normValue.compareTo( other ); |
| } |
| } |
| catch ( LdapException le ) |
| { |
| return -1; |
| } |
| } |
| |
| |
| /** |
| * Compare two values. We compare the stored bytes |
| * |
| * @param other the byte[] we want to compare the current value with |
| * @return a positive value if the current value is above the provided byte[], a negative value |
| * if it's below, 0 if they are equal. |
| * @throws IllegalStateException on failures to extract the comparator, or the |
| * normalizers needed to perform the required comparisons based on the schema |
| */ |
| public int compareTo( byte[] other ) |
| { |
| if ( isHR ) |
| { |
| String msg = I18n.err( I18n.ERR_13224_FAILED_TO_COMPARE_NORM_VALUES, this, other ); |
| LOG.error( msg ); |
| throw new IllegalStateException( msg ); |
| } |
| |
| // Check if both value are null |
| if ( bytes == null ) |
| { |
| if ( other == null ) |
| { |
| return 0; |
| } |
| else |
| { |
| return -1; |
| } |
| } |
| else if ( other == null ) |
| { |
| return 1; |
| } |
| |
| // Default : compare the bytes |
| return Strings.compare( bytes, other ); |
| } |
| |
| |
| /** |
| * Compare two values. We either compare the stored bytes, or we use the |
| * AttributeType Comparator, if we have an Ordered MatchingRule. |
| * |
| * @param other The other Value we want to compare the current value with |
| * @return a positive value if the current value is above the provided value, a negative value |
| * if it's below, 0 if they are equal. |
| * @throws IllegalStateException on failures to extract the comparator, or the |
| * normalizers needed to perform the required comparisons based on the schema |
| */ |
| @Override |
| public int compareTo( Value other ) |
| { |
| // The two values must have the same type |
| if ( isHR != other.isHR ) |
| { |
| String msg = I18n.err( I18n.ERR_13224_FAILED_TO_COMPARE_NORM_VALUES, this, other ); |
| LOG.error( msg ); |
| throw new IllegalStateException( msg ); |
| } |
| |
| // Check if both value are null |
| if ( bytes == null ) |
| { |
| if ( other.bytes == null ) |
| { |
| return 0; |
| } |
| else |
| { |
| return -1; |
| } |
| } |
| else if ( other.bytes == null ) |
| { |
| return 1; |
| } |
| |
| // Ok, neither this nor the other have null values. |
| |
| // Shortcut when the value are not HR |
| if ( !isHR ) |
| { |
| return Strings.compare( bytes, other.bytes ); |
| } |
| |
| // We have HR values. We may have an attributeType for the base Value |
| // It actually does not matter if the second value has an attributeType |
| // which is different |
| try |
| { |
| if ( attributeType != null ) |
| { |
| // Check if the other value has been normalized or not |
| if ( other.attributeType == null ) |
| { |
| // No normalization. Use the base AttributeType to normalize |
| // the other value |
| String normalizedOther = attributeType.getEquality().getNormalizer().normalize( other.upValue ); |
| |
| return normValue.compareTo( normalizedOther ); |
| } |
| else |
| { |
| return normValue.compareTo( other.normValue ); |
| } |
| } |
| else |
| { |
| if ( other.attributeType != null ) |
| { |
| // Normalize the current value with the other value normalizer |
| String normalizedThis = other.attributeType.getEquality().getNormalizer().normalize( upValue ); |
| |
| return normalizedThis.compareTo( other.normValue ); |
| } |
| else |
| { |
| // No AtributeType... Compare the normValue |
| return normValue.compareTo( other.normValue ); |
| } |
| } |
| } |
| catch ( LdapException le ) |
| { |
| return -1; |
| } |
| } |
| |
| |
| /** |
| * We compare two values using their Comparator, if any. |
| * |
| * @see Object#equals(Object) |
| */ |
| @Override |
| public boolean equals( Object obj ) |
| { |
| if ( this == obj ) |
| { |
| return true; |
| } |
| |
| if ( obj instanceof String ) |
| { |
| String other = ( String ) obj; |
| |
| if ( !isHR ) |
| { |
| return false; |
| } |
| |
| if ( attributeType == null ) |
| { |
| if ( upValue != null ) |
| { |
| return upValue.equals( other ); |
| } |
| else |
| { |
| return obj == null; |
| } |
| } |
| else |
| { |
| // Use the comparator |
| // We have an AttributeType, we use the associated comparator |
| try |
| { |
| LdapComparator<String> comparator = ( LdapComparator<String> ) getLdapComparator(); |
| |
| Normalizer normalizer = null; |
| |
| if ( attributeType.getEquality() != null ) |
| { |
| normalizer = attributeType.getEquality().getNormalizer(); |
| } |
| |
| if ( normalizer == null ) |
| { |
| if ( comparator == null ) |
| { |
| return normValue.equals( other ); |
| } |
| else |
| { |
| return comparator.compare( normValue, other ) == 0; |
| } |
| } |
| |
| String thisNormValue = normValue; |
| String otherNormValue = normalizer.normalize( other ); |
| |
| // Compare normalized values |
| if ( comparator == null ) |
| { |
| return thisNormValue.equals( otherNormValue ); |
| } |
| else |
| { |
| return comparator.compare( thisNormValue, otherNormValue ) == 0; |
| } |
| } |
| catch ( LdapException ne ) |
| { |
| return false; |
| } |
| } |
| } |
| |
| if ( !( obj instanceof Value ) ) |
| { |
| return false; |
| } |
| |
| Value other = ( Value ) obj; |
| |
| // Check if the values aren't of the same type |
| if ( isHR != other.isHR ) |
| { |
| // Both values must be HR or not HR |
| return false; |
| } |
| |
| if ( !isHR ) |
| { |
| // Shortcut for binary values |
| return Arrays.equals( bytes, other.bytes ); |
| } |
| |
| // HR values |
| if ( bytes == null ) |
| { |
| return other.bytes == null; |
| } |
| |
| // Special case |
| if ( other.bytes == null ) |
| { |
| return false; |
| } |
| |
| // Not null, but empty. We try to avoid a spurious String Preparation |
| if ( bytes.length == 0 ) |
| { |
| return other.bytes.length == 0; |
| } |
| else if ( other.bytes.length == 0 ) |
| { |
| return false; |
| } |
| |
| // Ok, now, let's see if we have an AttributeType at all. If both have one, |
| // and if they aren't equal, then we get out. If one of them has an AttributeType and |
| // not the other, we will assume that this is the AttributeType to use. |
| MatchingRule equalityMR; |
| |
| if ( attributeType == null ) |
| { |
| if ( other.attributeType != null ) |
| { |
| // Use the Other value AT |
| equalityMR = other.attributeType.getEquality(); |
| |
| // We may not have an Equality MR, and in tjis case, we compare the bytes |
| if ( equalityMR == null ) |
| { |
| return Arrays.equals( bytes, other.bytes ); |
| } |
| |
| LdapComparator<Object> ldapComparator = equalityMR.getLdapComparator(); |
| |
| if ( ldapComparator == null ) |
| { |
| // This is an error ! |
| LOG.error( I18n.err( I18n.ERR_13249_NO_COMPARATOR_FOR_AT, other.attributeType ) ); |
| |
| return false; |
| } |
| |
| return ldapComparator.compare( normValue, other.normValue ) == 0; |
| } |
| else |
| { |
| // Both are null. We will compare the prepared String if we have one, |
| // or the bytes otherwise. |
| if ( upValue != null ) |
| { |
| return upValue.equals( other.upValue ); |
| } |
| else |
| { |
| return Arrays.equals( bytes, other.bytes ); |
| } |
| } |
| } |
| else |
| { |
| if ( other.attributeType != null ) |
| { |
| // Both attributeType must be equal |
| if ( !attributeType.equals( other.attributeType ) ) |
| { |
| return false; |
| } |
| |
| // Use the comparator |
| // We have an AttributeType, we use the associated comparator |
| LdapComparator<String> comparator = ( LdapComparator<String> ) getLdapComparator(); |
| |
| if ( other.attributeType.getEquality() == null ) |
| { |
| // No equality ? Default to comparing using a String comparator |
| return stringComparator.compare( normValue, other.normValue ) == 0; |
| } |
| |
| |
| // Compare normalized values |
| if ( comparator == null ) |
| { |
| return normValue.equals( other.normValue ); |
| } |
| else |
| { |
| return comparator.compare( normValue, other.normValue ) == 0; |
| } |
| } |
| |
| // No attributeType |
| if ( normValue == null ) |
| { |
| return other.normValue == null; |
| } |
| else |
| { |
| return normValue.equals( other.normValue ); |
| } |
| } |
| } |
| |
| |
| /** |
| * @see Object#hashCode() |
| * @return the instance's hashcode |
| */ |
| @Override |
| public int hashCode() |
| { |
| if ( h == 0 ) |
| { |
| // return zero if the value is null so only one null value can be |
| // stored in an attribute - the binary version does the same |
| if ( isHR ) |
| { |
| if ( normValue != null ) |
| { |
| h = normValue.hashCode(); |
| } |
| else |
| { |
| h = 0; |
| } |
| } |
| else |
| { |
| h = Arrays.hashCode( bytes ); |
| } |
| } |
| |
| return h; |
| } |
| |
| |
| /** |
| * @see Object#toString() |
| */ |
| @Override |
| public String toString() |
| { |
| if ( isHR ) |
| { |
| return upValue == null ? "null" : upValue; |
| } |
| else |
| { |
| // Dumps binary in hex with label. |
| if ( bytes == null ) |
| { |
| return "null"; |
| } |
| else if ( bytes.length > 16 ) |
| { |
| // Just dump the first 16 bytes... |
| byte[] copy = new byte[16]; |
| |
| System.arraycopy( bytes, 0, copy, 0, 16 ); |
| |
| return Strings.dumpBytes( copy ) + "..."; |
| } |
| else |
| { |
| return Strings.dumpBytes( bytes ); |
| } |
| } |
| } |
| } |