blob: b7541d77733c22c712088a85dad454ac509dbf73 [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.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Comparator;
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.schema.AttributeType;
import org.apache.directory.api.ldap.model.schema.MatchingRule;
import org.apache.directory.api.ldap.model.schema.Normalizer;
import org.apache.directory.api.util.Serialize;
import org.apache.directory.api.util.Strings;
import org.apache.directory.api.util.exception.NotImplementedException;
/**
* A server side schema aware wrapper around a String attribute value.
* This value wrapper uses schema information to syntax check values,
* and to compare them for equality and ordering. It caches results
* and invalidates them when the user provided value changes.
*
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
*/
public class StringValue extends AbstractValue<String>
{
/** Used for serialization */
private static final long serialVersionUID = 2L;
/** The UTF-8 bytes for this value */
private byte[] bytes;
// -----------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------
/**
* Creates a StringValue without an initial user provided value.
*
* @param attributeType the schema attribute type associated with this StringValue
*/
public StringValue( AttributeType attributeType )
{
if ( attributeType != null )
{
// We must have a Syntax
if ( attributeType.getSyntax() == null )
{
throw new IllegalArgumentException( I18n.err( I18n.ERR_04445 ) );
}
if ( !attributeType.getSyntax().isHumanReadable() )
{
LOG.warn( "Treating a value of a binary attribute {} as a String: "
+ "\nthis could cause data corruption!", attributeType.getName() );
}
this.attributeType = attributeType;
}
}
/**
* Creates a StringValue with an initial user provided String value.
*
* @param value the value to wrap which can be null
*/
public StringValue( String value )
{
this.upValue = value;
this.normalizedValue = value;
bytes = Strings.getBytesUtf8( value );
}
/**
* Creates a StringValue with an initial user provided String value and a normalized value.
*
* @param value the user provided value to wrap which can be null
* @param normValue the normalized value to wrap which can be null
*/
public StringValue( String value, String normalizedValue )
{
this.upValue = value;
this.normalizedValue = normalizedValue;
bytes = Strings.getBytesUtf8( normalizedValue );
}
/**
* Creates a schema aware StringValue with an initial user provided String value.
*
* @param attributeType the schema type associated with this StringValue
* @param value the value to wrap
* @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
* to the schema
*/
public StringValue( AttributeType attributeType, String value ) throws LdapInvalidAttributeValueException
{
this( value, value );
apply( attributeType );
}
/**
* Creates a schema aware StringValue with an initial user provided String value.
*
* @param attributeType the schema type associated with this StringValue
* @param value the value to wrap
* @throws LdapInvalidAttributeValueException If the added value is invalid accordingly
* to the schema
*/
public StringValue( AttributeType attributeType, String value, String normValue ) throws LdapInvalidAttributeValueException
{
this( value, normValue );
apply( attributeType );
}
// -----------------------------------------------------------------------
// Value<String> Methods
// -----------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public String getValue()
{
// The String is immutable, we can safely return the internal
// object without copying it.
return upValue;
}
/**
* {@inheritDoc}
*/
public String getNormValue()
{
return normalizedValue;
}
// -----------------------------------------------------------------------
// Comparable<String> Methods
// -----------------------------------------------------------------------
/**
* @see ServerValue#compareTo(Value)
* @throws IllegalStateException on failures to extract the comparator, or the
* normalizers needed to perform the required comparisons based on the schema
*/
public int compareTo( Value<String> value )
{
if ( isNull() )
{
if ( ( value == null ) || value.isNull() )
{
return 0;
}
else
{
return -1;
}
}
else if ( ( value == null ) || value.isNull() )
{
return 1;
}
if ( !( value instanceof StringValue ) )
{
String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
LOG.error( message );
throw new NotImplementedException( message );
}
StringValue stringValue = ( StringValue ) value;
if ( attributeType != null )
{
if ( stringValue.getAttributeType() == null )
{
return getNormValue().compareTo( stringValue.getNormValue() );
}
else
{
if ( !attributeType.equals( stringValue.getAttributeType() ) )
{
String message = I18n.err( I18n.ERR_04128, toString(), value.getClass() );
LOG.error( message );
throw new NotImplementedException( message );
}
}
}
else
{
return getNormValue().compareTo( stringValue.getNormValue() );
}
try
{
return getLdapComparator().compare( getNormValue(), stringValue.getNormValue() );
}
catch ( LdapException e )
{
String msg = I18n.err( I18n.ERR_04443, this, value );
LOG.error( msg, e );
throw new IllegalStateException( msg, e );
}
}
// -----------------------------------------------------------------------
// Cloneable methods
// -----------------------------------------------------------------------
/**
* {@inheritDoc}
*/
public StringValue clone()
{
return ( StringValue ) super.clone();
}
// -----------------------------------------------------------------------
// Object Methods
// -----------------------------------------------------------------------
/**
* @see Object#hashCode()
* @return the instance's hashcode
*/
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 ( isNull() )
{
return 0;
}
// If the normalized value is null, will default to user provided
// which cannot be null at this point.
// If the normalized value is null, will default to user provided
// which cannot be null at this point.
String normalized = getNormValue();
if ( normalized != null )
{
h = normalized.hashCode();
}
else
{
h = 17;
}
}
return h;
}
/**
* Two StringValue are equals if their normalized values are equal
*
* @see Object#equals(Object)
*/
public boolean equals( Object obj )
{
if ( this == obj )
{
return true;
}
if ( !( obj instanceof StringValue ) )
{
return false;
}
StringValue other = ( StringValue ) obj;
// First check if we have an attrbuteType.
if ( attributeType != null )
{
// yes : check for the other value
if ( other.attributeType != null )
{
if ( attributeType.getOid().equals( other.getAttributeType().getOid() ) )
{
// Both AttributeType have the same OID, we can assume they are
// equals. We don't check any further, because the unicity of OID
// makes it unlikely that the two AT are different.
// The values may be both null
if ( isNull() )
{
return other.isNull();
}
// Shortcut : if we have an AT for both the values, check the
// already normalized values
if ( upValue.equals( other.upValue ) )
{
return true;
}
// We have an AttributeType, we use the associated comparator
try
{
Comparator<String> comparator = getLdapComparator();
// Compare normalized values
if ( comparator == null )
{
return getNormReference().equals( other.getNormReference() );
}
else
{
return comparator.compare( getNormReference(), other.getNormReference() ) == 0;
}
}
catch ( LdapException ne )
{
return false;
}
}
else
{
// We can't compare two values when the two ATs are different
return false;
}
}
else
{
// We only have one AT : we will assume that both values are for the
// same AT.
// The values may be both null
if ( isNull() )
{
return other.isNull();
}
// We have an AttributeType on the base value, we need to use its comparator
try
{
Comparator<String> comparator = getLdapComparator();
// Compare normalized values. We have to normalized the other value,
// as it has no AT
MatchingRule equality = getAttributeType().getEquality();
if ( equality == null )
{
// No matching rule : compare the raw values
return getNormReference().equals( other.getNormReference() );
}
Normalizer normalizer = equality.getNormalizer();
StringValue otherValue = ( StringValue ) normalizer.normalize( other );
if ( comparator == null )
{
return getNormReference().equals( otherValue.getNormReference() );
}
else
{
return comparator.compare( getNormReference(), otherValue.getNormReference() ) == 0;
}
}
catch ( LdapException ne )
{
return false;
}
}
}
else
{
// No : check for the other value
if ( other.attributeType != null )
{
// We only have one AT : we will assume that both values are for the
// same AT.
// The values may be both null
if ( isNull() )
{
return other.isNull();
}
try
{
Comparator<String> comparator = other.getLdapComparator();
// Compare normalized values. We have to normalized the other value,
// as it has no AT
MatchingRule equality = other.getAttributeType().getEquality();
if ( equality == null )
{
// No matching rule : compare the raw values
return getNormReference().equals( other.getNormReference() );
}
Normalizer normalizer = equality.getNormalizer();
StringValue thisValue = ( StringValue ) normalizer.normalize( this );
if ( comparator == null )
{
return thisValue.getNormReference().equals( other.getNormReference() );
}
else
{
return comparator.compare( thisValue.getNormReference(), other.getNormReference() ) == 0;
}
}
catch ( LdapException ne )
{
return false;
}
}
else
{
// The values may be both null
if ( isNull() )
{
return other.isNull();
}
// Now check the normalized values
return getNormReference().equals( other.getNormReference() );
}
}
}
/**
* {@inheritDoc}
*/
public boolean isHumanReadable()
{
return true;
}
/**
* @return The length of the interned value
*/
public int length()
{
return upValue != null ? upValue.length() : 0;
}
/**
* Get the user provided value as a byte[].
* @return the user provided value as a byte[]
*/
public byte[] getBytes()
{
return bytes;
}
/**
* Get the user provided value as a String.
*
* @return the user provided value as a String
*/
public String getString()
{
return upValue != null ? upValue : "";
}
/**
* Deserialize a StringValue. It will return a new StringValue instance.
*
* @param in The input stream
* @return A new StringValue instance
* @throws IOException If the stream can't be read
* @throws ClassNotFoundException If we can't instanciate a StringValue
*/
public static StringValue deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
{
StringValue value = new StringValue( ( AttributeType ) null );
value.readExternal( in );
return value;
}
/**
* Deserialize a schemaAware StringValue. It will return a new StringValue instance.
*
* @param attributeType The AttributeType associated with the Value. Can be null
* @param in The input stream
* @return A new StringValue instance
* @throws IOException If the stream can't be read
* @throws ClassNotFoundException If we can't instanciate a StringValue
*/
public static StringValue deserialize( AttributeType attributeType, ObjectInput in ) throws IOException,
ClassNotFoundException
{
StringValue value = new StringValue( attributeType );
value.readExternal( in );
return value;
}
/**
* {@inheritDoc}
*/
public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
{
// Read the STRING flag
boolean isHR = in.readBoolean();
if ( !isHR )
{
throw new IOException( "The serialized value is not a String value" );
}
// Read the user provided value, if it's not null
if ( in.readBoolean() )
{
upValue = in.readUTF();
bytes = Strings.getBytesUtf8( upValue );
}
// Read the isNormalized flag
boolean normalized = in.readBoolean();
if ( normalized )
{
// Read the normalized value, if not null
if ( in.readBoolean() )
{
normalizedValue = in.readUTF();
}
}
else
{
if ( attributeType != null )
{
try
{
MatchingRule equality = attributeType.getEquality();
if ( equality == null )
{
normalizedValue = upValue;
}
else
{
Normalizer normalizer = equality.getNormalizer();
if ( normalizer != null )
{
normalizedValue = normalizer.normalize( upValue );
}
else
{
normalizedValue = upValue;
}
}
}
catch ( LdapException le )
{
normalizedValue = upValue;
}
}
else
{
normalizedValue = upValue;
}
}
// The hashCoe
h = in.readInt();
}
/**
* Serialize the StringValue 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 value type, the user provided value presence flag,
// the normalizedValue presence flag and the hash length.
int length = 1 + 1 + 1 + 4;
byte[] upValueBytes = null;
byte[] normalizedValueBytes = null;
if ( upValue != null )
{
upValueBytes = Strings.getBytesUtf8( upValue );
length += 4 + upValueBytes.length;
}
if ( attributeType != null )
{
if ( normalizedValue != null )
{
normalizedValueBytes = Strings.getBytesUtf8( normalizedValue );
length += 1 + 4 + normalizedValueBytes.length;
}
else
{
length += 1;
}
}
// Check that we will be able to store the data in the buffer
if ( buffer.length - pos < length )
{
throw new ArrayIndexOutOfBoundsException();
}
// The STRING flag
buffer[pos] = Serialize.TRUE;
pos++;
// Write the user provided value, if it's not null
if ( upValue != null )
{
buffer[pos++] = Serialize.TRUE;
pos = Serialize.serialize( upValueBytes, buffer, pos );
}
else
{
buffer[pos++] = Serialize.FALSE;
}
// Write the isNormalized flag
if ( attributeType != null )
{
// This flag is present to tell that we have a normalized value different
// from the upValue
buffer[pos++] = Serialize.TRUE;
// Write the normalized value, if not null
if ( normalizedValue != null )
{
buffer[pos++] = Serialize.TRUE;
pos = Serialize.serialize( normalizedValueBytes, buffer, pos );
}
else
{
buffer[pos++] = Serialize.FALSE;
}
}
else
{
// No normalized value
buffer[pos++] = Serialize.FALSE;
}
// Write the hashCode
pos = Serialize.serialize( h, buffer, pos );
return pos;
}
/**
* 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
*/
public int deserialize( byte[] buffer, int pos ) throws IOException
{
if ( ( pos < 0 ) || ( pos >= buffer.length ) )
{
throw new ArrayIndexOutOfBoundsException();
}
// Read the STRING flag
boolean isHR = Serialize.deserializeBoolean( buffer, pos );
pos++;
if ( !isHR )
{
throw new IOException( "The serialized value is not a String value" );
}
// Read the user provided value, if it's not null
boolean hasWrappedValue = Serialize.deserializeBoolean( buffer, pos );
pos++;
if ( hasWrappedValue )
{
byte[] upValueBytes = Serialize.deserializeBytes( buffer, pos );
pos += 4 + upValueBytes.length;
upValue = Strings.utf8ToString( upValueBytes );
}
// Read the isNormalized flag
boolean hasAttributeType = Serialize.deserializeBoolean( buffer, pos );
pos++;
if ( hasAttributeType )
{
// Read the normalized value, if not null
boolean hasNormalizedValue = Serialize.deserializeBoolean( buffer, pos );
pos++;
if ( hasNormalizedValue )
{
byte[] normalizedValueBytes = Serialize.deserializeBytes( buffer, pos );
pos += 4 + normalizedValueBytes.length;
normalizedValue = Strings.utf8ToString( normalizedValueBytes );
}
}
else
{
if ( attributeType != null )
{
try
{
MatchingRule equality = attributeType.getEquality();
if ( equality == null )
{
normalizedValue = upValue;
}
else
{
Normalizer normalizer = equality.getNormalizer();
if ( normalizer != null )
{
normalizedValue = normalizer.normalize( upValue );
}
else
{
normalizedValue = upValue;
}
}
}
catch ( LdapException le )
{
normalizedValue = upValue;
}
}
else
{
normalizedValue = upValue;
}
}
// The hashCode
h = Serialize.deserializeInt( buffer, pos );
pos += 4;
return pos;
}
/**
* {@inheritDoc}
*/
public void writeExternal( ObjectOutput out ) throws IOException
{
// Write a boolean for the HR flag
out.writeBoolean( STRING );
// Write the user provided value, if it's not null
if ( upValue != null )
{
out.writeBoolean( true );
out.writeUTF( upValue );
}
else
{
out.writeBoolean( false );
}
// Write the isNormalized flag
if ( attributeType != null )
{
// This flag is present to tell that we have a normalized value different
// from the upValue
out.writeBoolean( true );
// Write the normalized value, if not null
if ( normalizedValue != null )
{
out.writeBoolean( true );
out.writeUTF( normalizedValue );
}
else
{
out.writeBoolean( false );
}
}
else
{
// No normalized value
out.writeBoolean( false );
}
// Write the hashCode
out.writeInt( h );
// and flush the data
out.flush();
}
/**
* @see Object#toString()
*/
public String toString()
{
return upValue == null ? "null" : upValue;
}
}