blob: 6fa4bc94194d4cb6d50b49db076b1cedced42936 [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.shared.ldap.model.entry;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import org.apache.directory.shared.i18n.I18n;
import org.apache.directory.shared.ldap.model.exception.LdapException;
import org.apache.directory.shared.ldap.model.schema.MutableAttributeTypeImpl;
import org.apache.directory.shared.ldap.model.schema.AbstractLdapComparator;
import org.apache.directory.shared.ldap.model.schema.Normalizer;
import org.apache.directory.shared.ldap.model.schema.SchemaManager;
import org.apache.directory.shared.util.Strings;
import org.apache.directory.shared.util.Unicode;
import org.apache.directory.shared.util.exception.NotImplementedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 wrapped 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;
/** logger for reporting errors that might not be handled properly upstream */
private static final Logger LOG = LoggerFactory.getLogger( StringValue.class );
// -----------------------------------------------------------------------
// Constructors
// -----------------------------------------------------------------------
/**
* Creates a StringValue without an initial wrapped value.
*/
public StringValue()
{
normalized = false;
valid = null;
}
/**
* Creates a StringValue without an initial wrapped value.
*
* @param attributeType the schema type associated with this StringValue
*/
public StringValue( MutableAttributeTypeImpl attributeType )
{
if ( attributeType == null )
{
String message = I18n.err( I18n.ERR_04442_NULL_AT_NOT_ALLOWED );
LOG.error( message );
throw new IllegalArgumentException( message );
}
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 wrapped String value.
*
* @param value the value to wrap which can be null
*/
public StringValue( String value )
{
this.wrappedValue = value;
normalized = false;
valid = null;
}
/**
* Creates a StringValue with an initial wrapped String value.
*
* @param attributeType the schema type associated with this StringValue
* @param value the value to wrap which can be null
*/
public StringValue( MutableAttributeTypeImpl attributeType, String value )
{
this( attributeType );
this.wrappedValue = value;
}
// -----------------------------------------------------------------------
// Value<String> Methods
// -----------------------------------------------------------------------
/**
* Get a copy of the stored value.
*
* @return A copy of the stored value.
*/
public String get()
{
// The String is immutable, we can safely return the internal
// object without copying it.
return wrappedValue;
}
/**
* Gets the normalized (canonical) representation for the wrapped string.
* If the wrapped String is null, null is returned, otherwise the normalized
* form is returned. If the normalizedValue is null, then this method
* will attempt to generate it from the wrapped value: repeated calls to
* this method do not unnecessarily normalize the wrapped value. Only changes
* to the wrapped value result in attempts to normalize the wrapped value.
*
* @return gets the normalized value
*/
public String getNormalizedValue()
{
if ( isNull() )
{
normalized = true;
return null;
}
if ( !normalized )
{
try
{
normalize();
}
catch ( LdapException ne )
{
String message = "Cannot normalize the value :" + ne.getLocalizedMessage();
LOG.info( message );
normalized = false;
}
}
if ( normalizedValue == null )
{
return wrappedValue;
}
return normalizedValue;
}
/**
* Compute the normalized (canonical) representation for the wrapped string.
* If the wrapped String is null, the normalized form will be null too.
*
* @throws LdapException if the value cannot be properly normalized
*/
public void normalize() throws LdapException
{
// If the value is already normalized, get out.
if ( normalized )
{
return;
}
if ( attributeType != null )
{
Normalizer normalizer = getNormalizer();
if ( normalizer == null )
{
normalizedValue = wrappedValue;
}
else
{
normalizedValue = ( String ) normalizer.normalize( wrappedValue );
}
normalized = true;
}
}
/**
* Normalize the value. For a client String value, applies the given normalizer.
*
* It supposes that the client has access to the schema in order to select the
* appropriate normalizer.
*
* @param normalizer The normalizer to apply to the value
* @exception LdapException If the value cannot be normalized
*/
public final void normalize( Normalizer normalizer ) throws LdapException
{
if ( normalizer != null )
{
normalizedValue = (String)normalizer.normalize( wrappedValue );
normalized = true;
}
}
// -----------------------------------------------------------------------
// 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 getNormalizedValue().compareTo( stringValue.getNormalizedValue() );
}
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 getNormalizedValue().compareTo( stringValue.getNormalizedValue() );
}
try
{
return getLdapComparator().compare( getNormalizedValue(), stringValue.getNormalizedValue() );
}
catch ( LdapException e )
{
String msg = I18n.err( I18n.ERR_04443, this, value );
LOG.error( msg, e );
throw new IllegalStateException( msg, e );
}
}
// -----------------------------------------------------------------------
// Cloneable methods
// -----------------------------------------------------------------------
/**
* Get a clone of the Client Value
*
* @return a copy of the current value
*/
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 wrapped
// which cannot be null at this point.
// If the normalized value is null, will default to wrapped
// which cannot be null at this point.
String normalized = getNormalizedValue();
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;
if ( this.isNull() )
{
return other.isNull();
}
// If we have an attributeType, it must be equal
// We should also use the comparator if we have an AT
if ( attributeType != null )
{
if ( other.attributeType != null )
{
if ( !attributeType.equals( other.attributeType ) )
{
return false;
}
}
else
{
return this.getNormalizedValue().equals( other.getNormalizedValue() );
}
}
else if ( other.attributeType != null )
{
return this.getNormalizedValue().equals( other.getNormalizedValue() );
}
// Shortcut : compare the values without normalization
// If they are equal, we may avoid a normalization.
// Note : if two values are equal, then their normalized
// value are equal too if their attributeType are equal.
if ( getReference().equals( other.getReference() ) )
{
return true;
}
if ( attributeType != null )
{
try
{
AbstractLdapComparator<String> comparator = getLdapComparator();
// Compare normalized values
if ( comparator == null )
{
return getNormalizedValue().equals( other.getNormalizedValue() );
}
else
{
if ( isNormalized() )
{
return comparator.compare( getNormalizedValue(), other.getNormalizedValue() ) == 0;
}
else
{
Normalizer normalizer = attributeType.getEquality().getNormalizer();
return comparator.compare( normalizer.normalize( get() ), normalizer.normalize( other.get() ) ) == 0;
}
}
}
catch ( LdapException ne )
{
return false;
}
}
else
{
return this.getNormalizedValue().equals( other.getNormalizedValue() );
}
}
/**
* Tells if the current value is Binary or String
*
* @return <code>true</code> if the value is Binary, <code>false</code> otherwise
*/
public boolean isBinary()
{
return false;
}
/**
* @return The length of the interned value
*/
public int length()
{
return wrappedValue != null ? wrappedValue.length() : 0;
}
/**
* Get the wrapped value as a byte[].
* @return the wrapped value as a byte[]
*/
public byte[] getBytes()
{
return Strings.getBytesUtf8(wrappedValue);
}
/**
* Get the wrapped value as a String.
*
* @return the wrapped value as a String
*/
public String getString()
{
return wrappedValue != null ? wrappedValue : "";
}
/**
* {@inheritDoc}
*/
public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
{
// Read the wrapped value, if it's not null
if ( in.readBoolean() )
{
wrappedValue = Unicode.readUTF(in);
}
// Read the isNormalized flag
normalized = in.readBoolean();
if ( ( normalized ) && ( in.readBoolean() ) )
{
// Read the normalized value, if not null
normalizedValue = Unicode.readUTF(in);
}
h = 0;
}
/**
* {@inheritDoc}
*/
public void writeExternal( ObjectOutput out ) throws IOException
{
// Write the wrapped value, if it's not null
if ( wrappedValue != null )
{
out.writeBoolean( true );
Unicode.writeUTF(out, wrappedValue);
}
else
{
out.writeBoolean( false );
}
// Write the isNormalized flag
if ( normalized )
{
out.writeBoolean( true );
// Write the normalized value, if not null
if ( normalizedValue != null )
{
out.writeBoolean( true );
Unicode.writeUTF(out, normalizedValue);
}
else
{
out.writeBoolean( false );
}
}
else
{
out.writeBoolean( false );
}
// and flush the data
out.flush();
}
/**
* {@inheritDoc}
*/
public static StringValue deserialize( SchemaManager schemaManager, ObjectInput in ) throws IOException
{
return (StringValue)AbstractValue.deserialize( schemaManager, in );
}
/**
* @see Object#toString()
*/
public String toString()
{
return wrappedValue == null ? "null": wrappedValue;
}
}