blob: 9a66844cda0162fa7b7294a13a79425a8cdea4b0 [file] [log] [blame]
/*
* 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 );
}
}
}
}