| /* |
| * 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 |
| * |
| * https://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.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.Set; |
| |
| import org.apache.directory.api.asn1.util.Oid; |
| 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.LdapSyntax; |
| import org.apache.directory.api.ldap.model.schema.SyntaxChecker; |
| import org.apache.directory.api.util.Strings; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| |
| /** |
| * An LDAP attribute.<p> |
| * To define the kind of data stored, the client must set the isHR flag, or inject an AttributeType. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public class DefaultAttribute implements Attribute, Cloneable |
| { |
| /** logger for reporting errors that might not be handled properly upstream */ |
| private static final Logger LOG = LoggerFactory.getLogger( DefaultAttribute.class ); |
| |
| /** The associated AttributeType */ |
| private AttributeType attributeType; |
| |
| /** The set of contained values */ |
| private Set<Value> values = new LinkedHashSet<>(); |
| |
| /** The User provided ID */ |
| private String upId; |
| |
| /** The normalized ID (will be the OID if we have a AttributeType) */ |
| private String id; |
| |
| /** Tells if the attribute is Human Readable or not. When not set, |
| * this flag is null. */ |
| private Boolean isHR; |
| |
| /** The computed hashcode. We don't want to compute it each time the hashcode() method is called */ |
| private volatile int h; |
| |
| |
| //------------------------------------------------------------------------- |
| // Constructors |
| //------------------------------------------------------------------------- |
| // maybe have some additional convenience constructors which take |
| // an initial value as a string or a byte[] |
| /** |
| * Create a new instance of a Attribute, without ID nor value. |
| * Used by the serializer |
| */ |
| /* No protection*/DefaultAttribute() |
| { |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute. |
| * Used by the serializer. |
| * |
| * @param attributeType The associated AttributeType |
| * @param upId The user provided ID |
| * @param normId the normalized ID |
| * @param isHR tells if the attributeType is human readable |
| * @param hashCode The Attribute pre-computed hashcode |
| * @param values The associated values |
| */ |
| /* No protection*/DefaultAttribute( AttributeType attributeType, String upId, String normId, boolean isHR, |
| int hashCode, Value... values ) |
| { |
| this.attributeType = attributeType; |
| this.upId = upId; |
| this.id = normId; |
| this.isHR = isHR; |
| this.h = hashCode; |
| |
| if ( values != null ) |
| { |
| for ( Value value : values ) |
| { |
| this.values.add( value ); |
| } |
| } |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, without ID nor value. |
| * |
| * @param attributeType the attributeType for the empty attribute added into the entry |
| */ |
| public DefaultAttribute( AttributeType attributeType ) |
| { |
| if ( attributeType != null ) |
| { |
| try |
| { |
| apply( attributeType ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| // Do nothing, it can't happen, there is no value |
| liave.printStackTrace(); |
| } |
| } |
| } |
| |
| |
| /** |
| * Create a new instance of an Attribute, without value. |
| * @param upId The user provided ID |
| */ |
| public DefaultAttribute( String upId ) |
| { |
| setUpId( upId ); |
| } |
| |
| |
| /** |
| * Create a new instance of an Attribute, without value. |
| * @param upId The user provided ID |
| */ |
| public DefaultAttribute( byte[] upId ) |
| { |
| setUpId( upId ); |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, without value. |
| * |
| * @param upId the ID for the added attributeType |
| * @param attributeType the added AttributeType |
| */ |
| public DefaultAttribute( String upId, AttributeType attributeType ) |
| { |
| if ( attributeType == null ) |
| { |
| String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED ); |
| LOG.error( message ); |
| throw new IllegalArgumentException( message ); |
| } |
| |
| try |
| { |
| apply( attributeType ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| // Do nothing, it can't happen, there is no value |
| } |
| |
| setUpId( upId, attributeType ); |
| } |
| |
| |
| /** |
| * Create a new instance of an Attribute, with some values, and a user provided ID.<br> |
| * If the value does not correspond to the same attributeType, then it's |
| * wrapped value is copied into a new ClientValue which uses the specified |
| * attributeType. |
| * <p> |
| * Otherwise, the value is stored, but as a reference. It's not a copy. |
| * </p> |
| * @param upId the attributeType ID |
| * @param vals an initial set of values for this attribute |
| */ |
| public DefaultAttribute( String upId, Value... vals ) |
| { |
| // The value can be null, this is a valid value. |
| if ( vals[0] == null ) |
| { |
| add( new Value( ( String ) null ) ); |
| } |
| else |
| { |
| for ( Value val : vals ) |
| { |
| add( val ); |
| } |
| } |
| |
| setUpId( upId ); |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, without ID but with some values. |
| * |
| * @param attributeType The attributeType added on creation |
| * @param vals The added value for this attribute |
| * @throws LdapInvalidAttributeValueException If any of the |
| * added values is not valid |
| */ |
| public DefaultAttribute( AttributeType attributeType, String... vals ) throws LdapInvalidAttributeValueException |
| { |
| this( null, attributeType, vals ); |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, with some values, and a user provided ID. |
| * |
| * @param upId the ID for the created attribute |
| * @param attributeType The attributeType added on creation |
| * @param vals the added values for this attribute |
| * @throws LdapInvalidAttributeValueException If any of the |
| * added values is not valid |
| */ |
| public DefaultAttribute( String upId, AttributeType attributeType, String... vals ) |
| throws LdapInvalidAttributeValueException |
| { |
| if ( attributeType == null ) |
| { |
| String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED ); |
| LOG.error( message ); |
| throw new IllegalArgumentException( message ); |
| } |
| |
| apply( attributeType ); |
| |
| if ( ( vals != null ) && ( vals.length > 0 ) ) |
| { |
| add( vals ); |
| } |
| |
| setUpId( upId, attributeType ); |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, with some values, and a user provided ID.<br> |
| * If the value does not correspond to the same attributeType, then it's |
| * wrapped value is copied into a new Value which uses the specified |
| * attributeType. |
| * <p> |
| * Otherwise, the value is stored, but as a reference. It's not a copy. |
| * </p> |
| * @param upId the ID of the created attribute |
| * @param attributeType the attribute type according to the schema |
| * @param vals an initial set of values for this attribute |
| * @throws LdapInvalidAttributeValueException If any of the |
| * added values is not valid |
| */ |
| public DefaultAttribute( String upId, AttributeType attributeType, Value... vals ) |
| throws LdapInvalidAttributeValueException |
| { |
| if ( attributeType == null ) |
| { |
| String message = I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED ); |
| LOG.error( message ); |
| throw new IllegalArgumentException( message ); |
| } |
| |
| apply( attributeType ); |
| setUpId( upId, attributeType ); |
| add( vals ); |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, with some values. |
| * <p> |
| * If the value does not correspond to the same attributeType, then it's |
| * wrapped value is copied into a new Value which uses the specified |
| * attributeType. |
| * </p> |
| * @param attributeType the attribute type according to the schema |
| * @param vals an initial set of values for this attribute |
| * @throws LdapInvalidAttributeValueException If one of the value is invalid |
| */ |
| public DefaultAttribute( AttributeType attributeType, Value... vals ) throws LdapInvalidAttributeValueException |
| { |
| this( null, attributeType, vals ); |
| } |
| |
| |
| /** |
| * Create a new instance of an Attribute, with some String values, and a user provided ID. |
| * |
| * @param upId the ID of the created attribute |
| * @param vals an initial set of String values for this attribute |
| */ |
| public DefaultAttribute( String upId, String... vals ) |
| { |
| try |
| { |
| add( vals ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| // Do nothing, it can't happen |
| } |
| |
| setUpId( upId ); |
| } |
| |
| |
| /** |
| * Create a new instance of an Attribute, with some binary values, and a user provided ID. |
| * |
| * @param upId the ID of the created attribute |
| * @param vals an initial set of binary values for this attribute |
| */ |
| public DefaultAttribute( String upId, byte[]... vals ) |
| { |
| try |
| { |
| add( vals ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| // Do nothing, this can't happen |
| } |
| |
| setUpId( upId ); |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, with some byte[] values. |
| * |
| * @param attributeType The attributeType added on creation |
| * @param vals The added binary values |
| * @throws LdapInvalidAttributeValueException If any of the |
| * added values is not valid |
| */ |
| public DefaultAttribute( AttributeType attributeType, byte[]... vals ) throws LdapInvalidAttributeValueException |
| { |
| this( null, attributeType, vals ); |
| } |
| |
| |
| /** |
| * Create a new instance of a schema aware Attribute, with some byte[] values, and |
| * a user provided ID. |
| * |
| * @param upId the ID for the added attribute |
| * @param attributeType the AttributeType to be added |
| * @param vals the binary values for the added attribute |
| * @throws LdapInvalidAttributeValueException If any of the |
| * added values is not valid |
| */ |
| public DefaultAttribute( String upId, AttributeType attributeType, byte[]... vals ) |
| throws LdapInvalidAttributeValueException |
| { |
| if ( attributeType == null ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13203_ATTRIBUTE_TYPE_NULL_NOT_ALLOWED ) ); |
| } |
| |
| apply( attributeType ); |
| add( vals ); |
| setUpId( upId, attributeType ); |
| } |
| |
| |
| /** |
| * Creates a new instance of schema aware Attribute, by copying another attribute. |
| * If the initial Attribute is not schema aware, the copy will be if the attributeType |
| * argument is not null. |
| * |
| * @param attributeType The attribute's type |
| * @param attribute The attribute to be copied |
| * @throws LdapException If the attribute can't be created |
| */ |
| public DefaultAttribute( AttributeType attributeType, Attribute attribute ) throws LdapException |
| { |
| // Copy the common values. isHR is only available on a ServerAttribute |
| this.attributeType = attributeType; |
| this.id = attribute.getId(); |
| this.upId = attribute.getUpId(); |
| |
| if ( attributeType == null ) |
| { |
| isHR = attribute.isHumanReadable(); |
| |
| // Copy all the values |
| for ( Value value : attribute ) |
| { |
| add( value.clone() ); |
| } |
| |
| if ( attribute.getAttributeType() != null ) |
| { |
| apply( attribute.getAttributeType() ); |
| } |
| } |
| else |
| { |
| |
| isHR = attributeType.getSyntax().isHumanReadable(); |
| |
| // Copy all the values |
| for ( Value clientValue : attribute ) |
| { |
| Value serverValue = null; |
| |
| if ( isHR ) |
| { |
| serverValue = new Value( attributeType, clientValue.getString() ); |
| } |
| else |
| { |
| // We have to convert the value to a binary value first |
| serverValue = new Value( attributeType, |
| clientValue.getBytes() ); |
| } |
| |
| add( serverValue ); |
| } |
| } |
| } |
| |
| |
| //------------------------------------------------------------------------- |
| // Helper methods |
| //------------------------------------------------------------------------- |
| private Value createStringValue( AttributeType attributeType, String value ) |
| { |
| Value newValue; |
| |
| if ( attributeType != null ) |
| { |
| try |
| { |
| newValue = new Value( attributeType, value ); |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| return null; |
| } |
| } |
| else |
| { |
| newValue = new Value( value ); |
| } |
| |
| return newValue; |
| } |
| |
| |
| private Value createBinaryValue( AttributeType attributeType, byte[] value ) |
| throws LdapInvalidAttributeValueException |
| { |
| Value binaryValue; |
| |
| if ( attributeType != null ) |
| { |
| binaryValue = new Value( attributeType, value ); |
| } |
| else |
| { |
| binaryValue = new Value( value ); |
| } |
| |
| return binaryValue; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public byte[] getBytes() throws LdapInvalidAttributeValueException |
| { |
| Value value = get(); |
| |
| if ( !isHumanReadable() && ( value != null ) ) |
| { |
| return value.getBytes(); |
| } |
| |
| String message = I18n.err( I18n.ERR_13214_VALUE_EXPECT_BYTES ); |
| LOG.error( message ); |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getString() throws LdapInvalidAttributeValueException |
| { |
| Value value = get(); |
| |
| if ( isHumanReadable() ) |
| { |
| if ( value != null ) |
| { |
| return value.getString(); |
| } |
| else |
| { |
| return ""; |
| } |
| } |
| |
| if ( attributeType == null ) |
| { |
| // Special case : the Attribute is not schema aware. |
| // The value is binary, we will try to convert it to a String |
| return Strings.utf8ToString( value.getBytes() ); |
| } |
| |
| String message = I18n.err( I18n.ERR_13215_VALUE_EXPECT_STRING ); |
| LOG.error( message ); |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getId() |
| { |
| return id; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String getUpId() |
| { |
| return upId; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setUpId( String upId ) |
| { |
| setUpId( upId, attributeType ); |
| } |
| |
| |
| /** |
| * Sets the User Provided ID as a byte[] |
| * |
| * @param upId The User Provided ID |
| */ |
| public void setUpId( byte[] upId ) |
| { |
| setUpId( upId, attributeType ); |
| } |
| |
| |
| /** |
| * Check that the id is either a name or the OID of a given AT |
| * |
| * @param id The ID to check |
| * @param attributeType The attributeType to verify |
| * @return <tt>true</tt> if the id is a name or an OID |
| */ |
| private boolean areCompatible( String id, AttributeType attributeType ) |
| { |
| // First, get rid of the options, if any |
| int optPos = id.indexOf( ';' ); |
| String idNoOption = id; |
| |
| if ( optPos != -1 ) |
| { |
| idNoOption = id.substring( 0, optPos ); |
| } |
| |
| // Check that we find the ID in the AT names |
| for ( String name : attributeType.getNames() ) |
| { |
| if ( name.equalsIgnoreCase( idNoOption ) ) |
| { |
| return true; |
| } |
| } |
| |
| // Not found in names, check the OID |
| return Oid.isOid( id ) && attributeType.getOid().equals( id ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void setUpId( String upId, AttributeType attributeType ) |
| { |
| String trimmed = Strings.trim( upId ); |
| |
| if ( Strings.isEmpty( trimmed ) && ( attributeType == null ) ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13235_NULL_ID_WITH_NULL_AT_NOT_ALLOWED ) ); |
| } |
| |
| String newId = Strings.toLowerCaseAscii( trimmed ); |
| |
| setUpIdInternal( upId, newId, attributeType ); |
| } |
| |
| |
| /** |
| * Sets the User Provided ID as a byte[] |
| * |
| * @param upId The User Provided ID |
| * @param attributeType The asscoiated AttributeType |
| */ |
| public void setUpId( byte[] upId, AttributeType attributeType ) |
| { |
| byte[] trimmed = Strings.trim( upId ); |
| |
| if ( Strings.isEmpty( trimmed ) && ( attributeType == null ) ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13235_NULL_ID_WITH_NULL_AT_NOT_ALLOWED ) ); |
| } |
| |
| String newId = Strings.toLowerCase( trimmed ); |
| |
| setUpIdInternal( Strings.utf8ToString( upId ), newId, attributeType ); |
| } |
| |
| |
| private void setUpIdInternal( String upId, String newId, AttributeType attributeType ) |
| { |
| if ( attributeType == null ) |
| { |
| if ( this.attributeType == null ) |
| { |
| this.upId = upId; |
| this.id = newId; |
| |
| // Compute the hashCode |
| rehash(); |
| |
| return; |
| } |
| else |
| { |
| if ( areCompatible( newId, this.attributeType ) ) |
| { |
| this.upId = upId; |
| this.id = this.attributeType.getOid(); |
| |
| // Compute the hashCode |
| rehash(); |
| |
| return; |
| } |
| else |
| { |
| return; |
| } |
| } |
| } |
| |
| if ( Strings.isEmpty( newId ) ) |
| { |
| this.attributeType = attributeType; |
| this.upId = attributeType.getName(); |
| this.id = attributeType.getOid(); |
| |
| // Compute the hashCode |
| rehash(); |
| |
| return; |
| } |
| |
| if ( areCompatible( newId, attributeType ) ) |
| { |
| this.upId = upId; |
| this.id = attributeType.getOid(); |
| this.attributeType = attributeType; |
| |
| // Compute the hashCode |
| rehash(); |
| |
| return; |
| } |
| |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13244_ID_AT_NOT_COMPATIBLE, id, attributeType.getName() ) ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isHumanReadable() |
| { |
| return isHR != null && isHR; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isValid( AttributeType attributeType ) throws LdapInvalidAttributeValueException |
| { |
| LdapSyntax syntax = attributeType.getSyntax(); |
| |
| if ( syntax == null ) |
| { |
| return false; |
| } |
| |
| SyntaxChecker syntaxChecker = syntax.getSyntaxChecker(); |
| |
| if ( syntaxChecker == null ) |
| { |
| return false; |
| } |
| |
| // Check that we can have no value for this attributeType |
| if ( values.isEmpty() ) |
| { |
| return syntaxChecker.isValidSyntax( null ); |
| } |
| |
| // Check that we can't have more than one value if the AT is single-value |
| if ( ( attributeType.isSingleValued() ) && ( values.size() > 1 ) ) |
| { |
| return false; |
| } |
| |
| // Now check the values |
| for ( Value value : values ) |
| { |
| try |
| { |
| if ( !value.isValid( syntaxChecker ) ) |
| { |
| return false; |
| } |
| } |
| catch ( LdapException le ) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int add( Value... vals ) |
| { |
| int nbAdded = 0; |
| Value nullBinaryValue = null; |
| Value nullStringValue = null; |
| boolean nullValueAdded = false; |
| Value[] valArray = vals; |
| |
| if ( vals == null ) |
| { |
| valArray = new Value[0]; |
| } |
| |
| if ( attributeType != null ) |
| { |
| for ( Value val : valArray ) |
| { |
| if ( attributeType.getSyntax().isHumanReadable() ) |
| { |
| if ( ( val == null ) || val.isNull() ) |
| { |
| try |
| { |
| Value nullSV = new Value( attributeType, ( String ) null ); |
| |
| if ( values.add( nullSV ) ) |
| { |
| nbAdded++; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| continue; |
| } |
| } |
| else if ( val.isHumanReadable() ) |
| { |
| try |
| { |
| if ( val.getAttributeType() == null ) |
| { |
| val = new Value( attributeType, val ); |
| } |
| |
| if ( values.contains( val ) ) |
| { |
| // Replace the value |
| values.remove( val ); |
| values.add( val ); |
| } |
| else if ( values.add( val ) ) |
| { |
| nbAdded++; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| String message = I18n.err( I18n.ERR_13213_VALUE_MUST_BE_A_STRING ); |
| LOG.error( message ); |
| } |
| } |
| else |
| { |
| if ( val == null ) |
| { |
| if ( attributeType.getSyntax().getSyntaxChecker().isValidSyntax( val ) ) |
| { |
| try |
| { |
| Value nullSV = new Value( attributeType, ( byte[] ) null ); |
| |
| if ( values.add( nullSV ) ) |
| { |
| nbAdded++; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| String message = I18n.err( I18n.ERR_13211_BYTE_VALUE_EXPECTED ); |
| LOG.error( message ); |
| } |
| } |
| else |
| { |
| if ( !val.isHumanReadable() ) |
| { |
| try |
| { |
| if ( val.getAttributeType() == null ) |
| { |
| val = new Value( attributeType, val.getBytes() ); |
| } |
| |
| if ( values.add( val ) ) |
| { |
| nbAdded++; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| String message = I18n.err( I18n.ERR_13211_BYTE_VALUE_EXPECTED ); |
| LOG.error( message ); |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| for ( Value val : valArray ) |
| { |
| if ( val == null ) |
| { |
| // We have a null value. If the HR flag is not set, we will consider |
| // that the attribute is not HR. We may change this later |
| if ( isHR == null ) |
| { |
| // This is the first value. Add both types, as we |
| // don't know yet the attribute type's, but we may |
| // know later if we add some new value. |
| // We have to do that because we are using a Set, |
| // and we can't remove the first element of the Set. |
| nullBinaryValue = new Value( ( byte[] ) null ); |
| nullStringValue = new Value( ( String ) null ); |
| |
| values.add( nullBinaryValue ); |
| values.add( nullStringValue ); |
| nullValueAdded = true; |
| nbAdded++; |
| } |
| else if ( !isHR ) |
| { |
| // The attribute type is binary. |
| nullBinaryValue = new Value( ( byte[] ) null ); |
| |
| // Don't add a value if it already exists. |
| if ( !values.contains( nullBinaryValue ) ) |
| { |
| values.add( nullBinaryValue ); |
| nbAdded++; |
| } |
| |
| } |
| else |
| { |
| // The attribute is HR |
| nullStringValue = new Value( ( String ) null ); |
| |
| // Don't add a value if it already exists. |
| if ( !values.contains( nullStringValue ) ) |
| { |
| values.add( nullStringValue ); |
| } |
| } |
| } |
| else |
| { |
| // Let's check the value type. |
| if ( val.isHumanReadable() ) |
| { |
| // We have a String value |
| if ( isHR == null ) |
| { |
| // The attribute type will be set to HR |
| isHR = true; |
| values.add( val ); |
| nbAdded++; |
| } |
| else if ( !isHR ) |
| { |
| // The attributeType is binary, convert the |
| // value to a Value |
| Value bv = new Value( val.getBytes() ); |
| |
| if ( !contains( bv ) ) |
| { |
| values.add( bv ); |
| nbAdded++; |
| } |
| } |
| else |
| { |
| // The attributeType is HR, simply add the value |
| if ( !contains( val ) ) |
| { |
| values.add( val ); |
| nbAdded++; |
| } |
| } |
| } |
| else |
| { |
| // We have a Binary value |
| if ( isHR == null ) |
| { |
| // The attribute type will be set to binary |
| isHR = false; |
| values.add( val ); |
| nbAdded++; |
| } |
| else if ( !isHR ) |
| { |
| // The attributeType is not HR, simply add the value if it does not already exist |
| if ( !contains( val ) ) |
| { |
| values.add( val ); |
| nbAdded++; |
| } |
| } |
| else |
| { |
| // The attribute Type is HR, convert the |
| // value to a Value |
| Value sv = new Value( Strings.utf8ToString( val.getBytes() ) ); |
| |
| if ( !contains( sv ) ) |
| { |
| values.add( sv ); |
| nbAdded++; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Last, not least, if a nullValue has been added, and if other |
| // values are all String, we have to keep the correct nullValue, |
| // and to remove the other |
| if ( nullValueAdded ) |
| { |
| if ( isHR ) |
| { |
| // Remove the Binary value |
| values.remove( nullBinaryValue ); |
| } |
| else |
| { |
| // Remove the String value |
| values.remove( nullStringValue ); |
| } |
| } |
| |
| return nbAdded; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int add( String... vals ) throws LdapInvalidAttributeValueException |
| { |
| int nbAdded = 0; |
| String[] valArray = vals; |
| |
| if ( vals == null ) |
| { |
| valArray = new String[0]; |
| } |
| |
| // First, if the isHR flag is not set, we assume that the |
| // attribute is HR, because we are asked to add some strings. |
| if ( isHR == null ) |
| { |
| isHR = true; |
| } |
| |
| // Check the attribute type. |
| if ( attributeType == null ) |
| { |
| if ( isHR ) |
| { |
| for ( String val : valArray ) |
| { |
| Value value = createStringValue( attributeType, val ); |
| |
| if ( value == null ) |
| { |
| // The value can't be normalized : we don't add it. |
| LOG.error( I18n.err( I18n.ERR_13200_VALUE_CANT_BE_NORMALIZED, val ) ); |
| continue; |
| } |
| |
| // Call the add(Value) method, if not already present |
| if ( add( value ) == 1 ) |
| { |
| nbAdded++; |
| } |
| else |
| { |
| if ( LOG.isWarnEnabled() ) |
| { |
| LOG.warn( I18n.err( I18n.ERR_13207_VALUE_ALREADY_EXISTS, val, upId ) ); |
| } |
| } |
| } |
| } |
| else |
| { |
| // The attribute is binary. Transform the String to byte[] |
| for ( String val : valArray ) |
| { |
| byte[] valBytes = null; |
| |
| if ( val != null ) |
| { |
| valBytes = Strings.getBytesUtf8( val ); |
| } |
| |
| Value value = createBinaryValue( attributeType, valBytes ); |
| |
| // Now call the add(Value) method |
| if ( add( value ) == 1 ) |
| { |
| nbAdded++; |
| } |
| } |
| } |
| } |
| else |
| { |
| if ( attributeType.isSingleValued() && ( values.size() + valArray.length > 1 ) ) |
| { |
| LOG.error( I18n.err( I18n.ERR_13208_ATTRIBUTE_IS_SINGLE_VALUED, attributeType.getName() ) ); |
| return 0; |
| } |
| |
| if ( isHR ) |
| { |
| for ( String val : valArray ) |
| { |
| Value value = createStringValue( attributeType, val ); |
| |
| if ( value == null ) |
| { |
| // The value can't be normalized : we don't add it. |
| LOG.error( I18n.err( I18n.ERR_13200_VALUE_CANT_BE_NORMALIZED, val ) ); |
| continue; |
| } |
| |
| // Call the add(Value) method, if not already present |
| if ( add( value ) == 1 ) |
| { |
| nbAdded++; |
| } |
| else |
| { |
| if ( LOG.isWarnEnabled() ) |
| { |
| LOG.warn( I18n.err( I18n.ERR_13207_VALUE_ALREADY_EXISTS, val, upId ) ); |
| } |
| } |
| } |
| } |
| else |
| { |
| // The attribute is binary. Transform the String to byte[] |
| for ( String val : valArray ) |
| { |
| byte[] valBytes = null; |
| |
| if ( val != null ) |
| { |
| valBytes = Strings.getBytesUtf8( val ); |
| } |
| |
| Value value = createBinaryValue( attributeType, valBytes ); |
| |
| // Now call the add(Value) method |
| if ( add( value ) == 1 ) |
| { |
| nbAdded++; |
| } |
| } |
| } |
| } |
| |
| return nbAdded; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public int add( byte[]... vals ) throws LdapInvalidAttributeValueException |
| { |
| int nbAdded = 0; |
| byte[][] valArray = vals; |
| |
| if ( vals == null ) |
| { |
| valArray = new byte[0][]; |
| } |
| |
| // First, if the isHR flag is not set, we assume that the |
| // attribute is not HR, because we are asked to add some byte[]. |
| if ( isHR == null ) |
| { |
| isHR = false; |
| } |
| |
| if ( !isHR ) |
| { |
| for ( byte[] val : valArray ) |
| { |
| Value value; |
| |
| if ( attributeType == null ) |
| { |
| value = new Value( val ); |
| } |
| else |
| { |
| value = createBinaryValue( attributeType, val ); |
| } |
| |
| if ( add( value ) != 0 ) |
| { |
| nbAdded++; |
| } |
| else |
| { |
| if ( LOG.isWarnEnabled() ) |
| { |
| LOG.warn( I18n.err( I18n.ERR_13207_VALUE_ALREADY_EXISTS, Strings.dumpBytes( val ), upId ) ); |
| } |
| } |
| } |
| } |
| else |
| { |
| // We can't add Binary values into a String Attribute |
| if ( LOG.isInfoEnabled() ) |
| { |
| LOG.info( I18n.err( I18n.ERR_13213_VALUE_MUST_BE_A_STRING ) ); |
| } |
| |
| return 0; |
| } |
| |
| return nbAdded; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void clear() |
| { |
| values.clear(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean contains( Value... vals ) |
| { |
| if ( isHR == null ) |
| { |
| // If this flag is null, then there is no values. |
| return false; |
| } |
| |
| if ( attributeType == null ) |
| { |
| if ( isHR ) |
| { |
| // Iterate through all the values, convert the Binary values |
| // to String values, and quit id any of the values is not |
| // contained in the object |
| for ( Value val : vals ) |
| { |
| if ( val.isHumanReadable() ) |
| { |
| if ( !values.contains( val ) ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| byte[] binaryVal = val.getBytes(); |
| |
| // We have to convert the binary value to a String |
| if ( !values.contains( new Value( Strings.utf8ToString( binaryVal ) ) ) ) |
| { |
| return false; |
| } |
| } |
| } |
| } |
| else |
| { |
| // Iterate through all the values, convert the String values |
| // to binary values, and quit id any of the values is not |
| // contained in the object |
| for ( Value val : vals ) |
| { |
| if ( val.isHumanReadable() ) |
| { |
| String stringVal = val.getString(); |
| |
| // We have to convert the binary value to a String |
| if ( !values.contains( new Value( Strings.getBytesUtf8( stringVal ) ) ) ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| if ( !values.contains( val ) ) |
| { |
| return false; |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| // Iterate through all the values, and quit if we |
| // don't find one in the values. We have to separate the check |
| // depending on the isHR flag value. |
| if ( isHR ) |
| { |
| for ( Value val : vals ) |
| { |
| if ( val.isHumanReadable() ) |
| { |
| try |
| { |
| if ( val.getAttributeType() == null ) |
| { |
| val = new Value( attributeType, val ); |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| return false; |
| } |
| |
| if ( !values.contains( val ) ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| // Not a String value |
| return false; |
| } |
| } |
| } |
| else |
| { |
| for ( Value val : vals ) |
| { |
| if ( !val.isHumanReadable() ) |
| { |
| if ( !values.contains( val ) ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| // Not a Binary value |
| return false; |
| } |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean contains( String... vals ) |
| { |
| if ( isHR == null ) |
| { |
| // If this flag is null, then there is no values. |
| return false; |
| } |
| |
| if ( attributeType == null ) |
| { |
| if ( isHR ) |
| { |
| for ( String val : vals ) |
| { |
| try |
| { |
| if ( !contains( new Value( val ) ) ) |
| { |
| return false; |
| } |
| } |
| catch ( IllegalArgumentException iae ) |
| { |
| return false; |
| } |
| } |
| } |
| else |
| { |
| // As the attribute type is binary, we have to convert |
| // the values before checking for them in the values |
| // Iterate through all the values, and quit if we |
| // don't find one in the values |
| for ( String val : vals ) |
| { |
| byte[] binaryVal = Strings.getBytesUtf8( val ); |
| |
| if ( !contains( new Value( binaryVal ) ) ) |
| { |
| return false; |
| } |
| } |
| } |
| } |
| else |
| { |
| if ( isHR ) |
| { |
| // Iterate through all the values, and quit if we |
| // don't find one in the values |
| for ( String val : vals ) |
| { |
| try |
| { |
| Value value = new Value( attributeType, val ); |
| |
| if ( !values.contains( value ) ) |
| { |
| return false; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean contains( byte[]... vals ) |
| { |
| if ( isHR == null ) |
| { |
| // If this flag is null, then there is no values. |
| return false; |
| } |
| |
| if ( attributeType == null ) |
| { |
| if ( !isHR ) |
| { |
| // Iterate through all the values, and quit if we |
| // don't find one in the values |
| for ( byte[] val : vals ) |
| { |
| if ( !contains( new Value( val ) ) ) |
| { |
| return false; |
| } |
| } |
| } |
| else |
| { |
| // As the attribute type is String, we have to convert |
| // the values before checking for them in the values |
| // Iterate through all the values, and quit if we |
| // don't find one in the values |
| for ( byte[] val : vals ) |
| { |
| String stringVal = Strings.utf8ToString( val ); |
| |
| if ( !contains( new Value( stringVal ) ) ) |
| { |
| return false; |
| } |
| } |
| } |
| } |
| else |
| { |
| if ( !isHR ) |
| { |
| // Iterate through all the values, and quit if we |
| // don't find one in the values |
| for ( byte[] val : vals ) |
| { |
| try |
| { |
| Value value = new Value( attributeType, val ); |
| |
| if ( !values.contains( value ) ) |
| { |
| return false; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Value get() |
| { |
| if ( values.isEmpty() ) |
| { |
| return null; |
| } |
| |
| return values.iterator().next(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public int size() |
| { |
| return values.size(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean remove( Value... vals ) |
| { |
| if ( ( isHR == null ) || ( values.isEmpty() ) ) |
| { |
| // Trying to remove a value from an empty list will fail |
| return false; |
| } |
| |
| boolean removed = true; |
| |
| if ( attributeType == null ) |
| { |
| if ( isHR ) |
| { |
| for ( Value val : vals ) |
| { |
| if ( val.isHumanReadable() ) |
| { |
| removed &= values.remove( val ); |
| } |
| else |
| { |
| // Convert the binary value to a string value |
| byte[] binaryVal = val.getBytes(); |
| removed &= values.remove( new Value( Strings.utf8ToString( binaryVal ) ) ); |
| } |
| } |
| } |
| else |
| { |
| for ( Value val : vals ) |
| { |
| removed &= values.remove( val ); |
| } |
| } |
| } |
| else |
| { |
| // Loop through all the values to remove. If one of |
| // them is not present, the method will return false. |
| // As the attribute may be HR or not, we have two separated treatments |
| if ( isHR ) |
| { |
| for ( Value val : vals ) |
| { |
| if ( val.isHumanReadable() ) |
| { |
| try |
| { |
| if ( val.getAttributeType() == null ) |
| { |
| val = new Value( attributeType, val ); |
| } |
| |
| removed &= values.remove( val ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| removed = false; |
| } |
| } |
| else |
| { |
| removed = false; |
| } |
| } |
| } |
| else |
| { |
| for ( Value val : vals ) |
| { |
| if ( !val.isHumanReadable() ) |
| { |
| try |
| { |
| if ( val.getAttributeType() == null ) |
| { |
| val = new Value( attributeType, val ); |
| } |
| |
| removed &= values.remove( val ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| removed = false; |
| } |
| } |
| else |
| { |
| removed = false; |
| } |
| } |
| } |
| } |
| |
| return removed; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean remove( byte[]... vals ) |
| { |
| if ( ( isHR == null ) || ( values.isEmpty() ) ) |
| { |
| // Trying to remove a value from an empty list will fail |
| return false; |
| } |
| |
| boolean removed = true; |
| |
| if ( attributeType == null ) |
| { |
| if ( !isHR ) |
| { |
| // The attribute type is not HR, we can directly process the values |
| for ( byte[] val : vals ) |
| { |
| Value value = new Value( val ); |
| removed &= values.remove( value ); |
| } |
| } |
| else |
| { |
| // The attribute type is String, we have to convert the values |
| // to String before removing them |
| for ( byte[] val : vals ) |
| { |
| Value value = new Value( Strings.utf8ToString( val ) ); |
| removed &= values.remove( value ); |
| } |
| } |
| } |
| else |
| { |
| if ( !isHR ) |
| { |
| try |
| { |
| for ( byte[] val : vals ) |
| { |
| Value value = new Value( attributeType, val ); |
| removed &= values.remove( value ); |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| removed = false; |
| } |
| } |
| else |
| { |
| removed = false; |
| } |
| } |
| |
| return removed; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean remove( String... vals ) |
| { |
| if ( ( isHR == null ) || ( values.isEmpty() ) ) |
| { |
| // Trying to remove a value from an empty list will fail |
| return false; |
| } |
| |
| boolean removed = true; |
| |
| if ( attributeType == null ) |
| { |
| if ( isHR ) |
| { |
| // The attribute type is HR, we can directly process the values |
| for ( String val : vals ) |
| { |
| Value value = new Value( val ); |
| removed &= values.remove( value ); |
| } |
| } |
| else |
| { |
| // The attribute type is binary, we have to convert the values |
| // to byte[] before removing them |
| for ( String val : vals ) |
| { |
| Value value = new Value( Strings.getBytesUtf8( val ) ); |
| removed &= values.remove( value ); |
| } |
| } |
| } |
| else |
| { |
| if ( isHR ) |
| { |
| for ( String val : vals ) |
| { |
| try |
| { |
| Value value = new Value( attributeType, val ); |
| removed &= values.remove( value ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| removed = false; |
| } |
| } |
| } |
| else |
| { |
| removed = false; |
| } |
| } |
| |
| return removed; |
| } |
| |
| |
| /** |
| * An iterator on top of the stored values. |
| * |
| * @return an iterator over the stored values. |
| */ |
| @Override |
| public Iterator<Value> iterator() |
| { |
| return values.iterator(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public AttributeType getAttributeType() |
| { |
| return attributeType; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void apply( AttributeType attributeType ) throws LdapInvalidAttributeValueException |
| { |
| if ( attributeType == null ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_13245_AT_PARAMETER_NULL ) ); |
| } |
| |
| this.attributeType = attributeType; |
| this.id = attributeType.getOid(); |
| |
| if ( Strings.isEmpty( this.upId ) ) |
| { |
| this.upId = attributeType.getName(); |
| } |
| else |
| { |
| if ( !areCompatible( this.upId, attributeType ) ) |
| { |
| this.upId = attributeType.getName(); |
| } |
| } |
| |
| if ( values != null ) |
| { |
| Set<Value> newValues = new LinkedHashSet<>( values.size() ); |
| |
| for ( Value value : values ) |
| { |
| if ( value.isSchemaAware() ) |
| { |
| newValues.add( value ); |
| } |
| else |
| { |
| if ( value.isHumanReadable() ) |
| { |
| newValues.add( new Value( attributeType, value.getString() ) ); |
| } |
| else |
| { |
| newValues.add( new Value( attributeType, value.getBytes() ) ); |
| } |
| } |
| } |
| |
| values = newValues; |
| } |
| |
| isHR = attributeType.getSyntax().isHumanReadable(); |
| |
| // Compute the hashCode |
| rehash(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public boolean isInstanceOf( AttributeType attributeType ) throws LdapInvalidAttributeValueException |
| { |
| return ( attributeType != null ) |
| && ( this.attributeType.equals( attributeType ) || this.attributeType.isDescendantOf( attributeType ) ); |
| } |
| |
| |
| //------------------------------------------------------------------------- |
| // Overloaded Object classes |
| //------------------------------------------------------------------------- |
| /** |
| * A helper method to rehash the hashCode |
| */ |
| private void rehash() |
| { |
| int hTmp = 37; |
| |
| if ( isHR != null ) |
| { |
| hTmp = hTmp * 17 + isHR.hashCode(); |
| } |
| |
| if ( id != null ) |
| { |
| hTmp = hTmp * 17 + id.hashCode(); |
| } |
| |
| if ( attributeType != null ) |
| { |
| hTmp = hTmp * 17 + attributeType.hashCode(); |
| } |
| |
| h = hTmp; |
| } |
| |
| |
| /** |
| * The hashCode is based on the id, the isHR flag and |
| * on the internal values. |
| * |
| * @see Object#hashCode() |
| * @return the instance's hashcode |
| */ |
| @Override |
| public int hashCode() |
| { |
| if ( h == 0 ) |
| { |
| rehash(); |
| } |
| |
| return h; |
| } |
| |
| |
| /** |
| * @see Object#equals(Object) |
| */ |
| @Override |
| public boolean equals( Object obj ) |
| { |
| if ( obj == this ) |
| { |
| return true; |
| } |
| |
| if ( !( obj instanceof Attribute ) ) |
| { |
| return false; |
| } |
| |
| Attribute other = ( Attribute ) obj; |
| |
| if ( id == null ) |
| { |
| if ( other.getId() != null ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| if ( other.getId() == null ) |
| { |
| return false; |
| } |
| else |
| { |
| if ( attributeType != null ) |
| { |
| if ( !attributeType.equals( other.getAttributeType() ) ) |
| { |
| return false; |
| } |
| } |
| else if ( !id.equals( other.getId() ) ) |
| { |
| return false; |
| } |
| } |
| } |
| |
| if ( isHumanReadable() != other.isHumanReadable() ) |
| { |
| return false; |
| } |
| |
| if ( values.size() != other.size() ) |
| { |
| return false; |
| } |
| |
| for ( Value val : values ) |
| { |
| if ( !other.contains( val ) ) |
| { |
| return false; |
| } |
| } |
| |
| if ( attributeType == null ) |
| { |
| return other.getAttributeType() == null; |
| } |
| |
| return attributeType.equals( other.getAttributeType() ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Attribute clone() |
| { |
| try |
| { |
| DefaultAttribute attribute = ( DefaultAttribute ) super.clone(); |
| |
| if ( this.attributeType != null ) |
| { |
| attribute.id = attributeType.getOid(); |
| attribute.attributeType = attributeType; |
| } |
| |
| attribute.values = new LinkedHashSet<>( values.size() ); |
| |
| for ( Value value : values ) |
| { |
| // No need to clone the value, it will never be changed |
| attribute.values.add( value ); |
| } |
| |
| return attribute; |
| } |
| catch ( CloneNotSupportedException cnse ) |
| { |
| return null; |
| } |
| } |
| |
| |
| /** |
| * This is the place where we serialize attributes, and all theirs |
| * elements. |
| * |
| * {@inheritDoc} |
| */ |
| @Override |
| public void writeExternal( ObjectOutput out ) throws IOException |
| { |
| // Write the UPId |
| out.writeUTF( upId ); |
| |
| // Write the ID |
| out.writeUTF( id ); |
| |
| // Write the HR flag, if not null |
| if ( isHR != null ) |
| { |
| out.writeBoolean( true ); |
| out.writeBoolean( isHR ); |
| } |
| else |
| { |
| out.writeBoolean( false ); |
| } |
| |
| // Write the number of values |
| out.writeInt( size() ); |
| |
| if ( size() > 0 ) |
| { |
| // Write each value |
| for ( Value value : values ) |
| { |
| // Write the value |
| value.writeExternal( out ); |
| } |
| } |
| |
| out.flush(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| // Read the ID and the UPId |
| upId = in.readUTF(); |
| |
| // Read the id |
| id = in.readUTF(); |
| |
| // Read the HR flag, if not null |
| if ( in.readBoolean() ) |
| { |
| isHR = in.readBoolean(); |
| } |
| |
| // Read the number of values |
| int nbValues = in.readInt(); |
| |
| if ( nbValues > 0 ) |
| { |
| for ( int i = 0; i < nbValues; i++ ) |
| { |
| Value value = new Value( attributeType ); |
| |
| value.readExternal( in ); |
| |
| values.add( value ); |
| } |
| } |
| } |
| |
| |
| /** |
| * @see Object#toString() |
| */ |
| @Override |
| public String toString() |
| { |
| return toString( "" ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public String toString( String tabs ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| if ( ( values != null ) && ( !values.isEmpty() ) ) |
| { |
| boolean isFirst = true; |
| |
| for ( Value value : values ) |
| { |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( '\n' ); |
| } |
| |
| sb.append( tabs ).append( upId ).append( ": " ); |
| |
| if ( value.isNull() ) |
| { |
| sb.append( "''" ); |
| } |
| else |
| { |
| sb.append( value ); |
| } |
| } |
| } |
| else |
| { |
| sb.append( tabs ).append( upId ).append( ": (null)" ); |
| } |
| |
| return sb.toString(); |
| } |
| } |