| /* |
| * 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.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, "Invalid upValue per syntax" ); |
| } |
| } |
| else |
| { |
| // We should always have a SyntaxChecker |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_04139_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.info( I18n.err( I18n.ERR_04445_NO_SYNTAX ) ); |
| isHR = true; |
| //throw new IllegalArgumentException( I18n.err( I18n.ERR_04445_NO_SYNTAX ) ); |
| } |
| else |
| { |
| isHR = attributeType.getSyntax().isHumanReadable(); |
| } |
| } |
| else |
| { |
| LOG.warn( "The attributeType 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 |
| * @param value the value to wrap |
| */ |
| /* 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( "Invalid upValue, it can't be normalized" ); |
| } |
| |
| if ( !attributeType.isRelaxed() ) |
| { |
| // Check the value |
| if ( attributeType.getSyntax().getSyntaxChecker() != null ) |
| { |
| if ( !attributeType.getSyntax().getSyntaxChecker().isValidSyntax( upValue ) ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, "Invalid upValue per syntax" ); |
| } |
| } |
| else |
| { |
| // We should always have a SyntaxChecker |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_04139_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, "Invalid upValue per syntax" ); |
| } |
| } |
| else |
| { |
| // We should always have a SyntaxChecker |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_04139_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 ) |
| { |
| this.upValue = value.upValue; |
| } |
| |
| try |
| { |
| computeNormValue(); |
| } |
| catch ( LdapException le ) |
| { |
| LOG.error( le.getMessage() ); |
| throw new IllegalArgumentException( "Invalid upValue, it can't be normalized" ); |
| } |
| |
| // 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_04139_NULL_SYNTAX_CHECKER, normValue ) ); |
| } |
| } |
| |
| // We have to copy the byte[], they are just referenced by suoer.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 ) ); |
| } |
| |
| |
| /** |
| * Get the User Provided value. If the value is Human Readable, it will return |
| * a String, otherwise it returns null. |
| * |
| * @return The user provided value |
| */ |
| public String getValue() |
| { |
| 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_04295_NO_NORMALIZER ) ); |
| } |
| |
| // Now, normalize the upValue |
| normValue = normalizer.normalize( upValue ); |
| } |
| |
| |
| /** |
| * @return The normalized value |
| */ |
| public String getNormalized() |
| { |
| return normValue; |
| } |
| |
| |
| /** |
| * Get the wrapped value as a byte[]. If the original value |
| * is binary, this method will return a copy of the wrapped byte[] |
| * |
| * @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_04139_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( getValue() ); |
| } |
| 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 |
| * @throws LdapException if resolution of schema entities fail |
| */ |
| private LdapComparator<?> getLdapComparator() throws LdapException |
| { |
| 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 |
| * @throws LdapInvalidAttributeValueException If the value is invalid |
| */ |
| public static Value deserialize( ObjectInput in ) throws IOException, ClassNotFoundException, LdapInvalidAttributeValueException |
| { |
| 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_04443, 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_04443, 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_04443, 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( "No comparator for the {} attributeType", 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 |
| try |
| { |
| 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; |
| } |
| |
| Normalizer normalizer = other.attributeType.getEquality().getNormalizer(); |
| |
| if ( normalizer == null ) |
| { |
| if ( comparator == null ) |
| { |
| return normValue.equals( other.normValue ); |
| } |
| else |
| { |
| return comparator.compare( normValue, other.normValue ) == 0; |
| } |
| } |
| |
| String thisNormValue = normalizer.normalize( normValue ); |
| |
| // Compare normalized values |
| if ( comparator == null ) |
| { |
| return thisNormValue.equals( other.normValue ); |
| } |
| else |
| { |
| return comparator.compare( thisNormValue, other.normValue ) == 0; |
| } |
| } |
| catch ( LdapException ne ) |
| { |
| return false; |
| } |
| } |
| |
| // 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 ); |
| } |
| } |
| } |
| } |