| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.directory.api.ldap.model.entry; |
| |
| |
| import java.io.IOException; |
| import java.io.ObjectInput; |
| import java.io.ObjectOutput; |
| import java.util.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<Value<?>>(); |
| |
| /** 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; |
| |
| |
| //------------------------------------------------------------------------- |
| // Helper methods |
| //------------------------------------------------------------------------- |
| private Value<String> createStringValue( AttributeType attributeType, String value ) |
| { |
| Value<String> stringValue = null; |
| |
| if ( attributeType != null ) |
| { |
| try |
| { |
| stringValue = new StringValue( attributeType, value ); |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| return null; |
| } |
| } |
| else |
| { |
| stringValue = new StringValue( value ); |
| } |
| |
| return stringValue; |
| } |
| |
| |
| private Value<byte[]> createBinaryValue( AttributeType attributeType, byte[] value ) |
| throws LdapInvalidAttributeValueException |
| { |
| Value<byte[]> binaryValue = null; |
| |
| if ( attributeType != null ) |
| { |
| binaryValue = new BinaryValue( attributeType, value ); |
| } |
| else |
| { |
| binaryValue = new BinaryValue( value ); |
| } |
| |
| return binaryValue; |
| } |
| |
| |
| //------------------------------------------------------------------------- |
| // 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, without ID nor value. |
| * Used by the serializer |
| */ |
| /* 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 |
| } |
| } |
| } |
| |
| |
| /** |
| * 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_04460_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 StringValue( ( String ) null ) ); |
| } |
| else |
| { |
| for ( Value<?> val : vals ) |
| { |
| if ( ( val instanceof StringValue ) || ( !val.isHumanReadable() ) ) |
| { |
| add( val ); |
| } |
| else |
| { |
| String message = I18n.err( I18n.ERR_04129, val.getClass().getName() ); |
| LOG.error( message ); |
| throw new IllegalStateException( message ); |
| } |
| } |
| } |
| |
| 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_04460_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_04460_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 |
| */ |
| 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_04460_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 |
| */ |
| 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; |
| |
| // We have to convert the value first |
| if ( clientValue instanceof StringValue ) |
| { |
| if ( isHR ) |
| { |
| serverValue = new StringValue( attributeType, clientValue.getString() ); |
| } |
| else |
| { |
| // We have to convert the value to a binary value first |
| serverValue = new BinaryValue( attributeType, |
| clientValue.getBytes() ); |
| } |
| } |
| else if ( clientValue instanceof BinaryValue ) |
| { |
| if ( isHR ) |
| { |
| // We have to convert the value to a String value first |
| serverValue = new StringValue( attributeType, |
| clientValue.getString() ); |
| } |
| else |
| { |
| serverValue = new BinaryValue( attributeType, clientValue.getBytes() ); |
| } |
| } |
| |
| add( serverValue ); |
| } |
| } |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public byte[] getBytes() throws LdapInvalidAttributeValueException |
| { |
| Value<?> value = get(); |
| |
| if ( !isHR && ( value != null ) ) |
| { |
| return value.getBytes(); |
| } |
| |
| String message = I18n.err( I18n.ERR_04130 ); |
| LOG.error( message ); |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getString() throws LdapInvalidAttributeValueException |
| { |
| Value<?> value = get(); |
| |
| if ( isHR && ( value != null ) ) |
| { |
| return value.getString(); |
| } |
| |
| String message = I18n.err( I18n.ERR_04131 ); |
| LOG.error( message ); |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, message ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getId() |
| { |
| return id; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String getUpId() |
| { |
| return upId; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void setUpId( String upId ) |
| { |
| setUpId( upId, attributeType ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void setUpId( byte[] upId ) |
| { |
| setUpId( upId, attributeType ); |
| } |
| |
| |
| /** |
| * Check that the upId is either a name or the OID of a given AT |
| */ |
| 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} |
| */ |
| public void setUpId( String upId, AttributeType attributeType ) |
| { |
| String trimmed = Strings.trim( upId ); |
| |
| if ( Strings.isEmpty( trimmed ) && ( attributeType == null ) ) |
| { |
| throw new IllegalArgumentException( "Cannot set a null ID with a null AttributeType" ); |
| } |
| |
| String newId = Strings.toLowerCase( trimmed ); |
| |
| setUpIdInternal( upId, newId, attributeType ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void setUpId( byte[] upId, AttributeType attributeType ) |
| { |
| byte[] trimmed = Strings.trim( upId ); |
| |
| if ( Strings.isEmpty( trimmed ) && ( attributeType == null ) ) |
| { |
| throw new IllegalArgumentException( "Cannot set a null ID with a null AttributeType" ); |
| } |
| |
| String newId = Strings.toLowerCase( trimmed ); |
| |
| setUpIdInternal( Strings.utf8ToString( upId ), newId, attributeType ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| 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( "ID '" + id + "' and AttributeType '" + attributeType.getName() |
| + "' are not compatible " ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean isHumanReadable() |
| { |
| return isHR != null ? isHR : false; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| 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.size() == 0 ) |
| { |
| 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} |
| */ |
| public int add( Value<?>... vals ) |
| { |
| int nbAdded = 0; |
| BinaryValue nullBinaryValue = null; |
| StringValue 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<String> nullSV = new StringValue( attributeType, ( String ) null ); |
| |
| if ( values.add( nullSV ) ) |
| { |
| nbAdded++; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| continue; |
| } |
| } |
| else if ( val instanceof StringValue ) |
| { |
| StringValue stringValue = ( StringValue ) val; |
| |
| try |
| { |
| if ( stringValue.getAttributeType() == null ) |
| { |
| stringValue.apply( attributeType ); |
| } |
| |
| 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_04451 ); |
| LOG.error( message ); |
| } |
| } |
| else |
| { |
| if ( val == null ) |
| { |
| if ( attributeType.getSyntax().getSyntaxChecker().isValidSyntax( val ) ) |
| { |
| try |
| { |
| Value<byte[]> nullSV = new BinaryValue( attributeType, ( byte[] ) null ); |
| |
| if ( values.add( nullSV ) ) |
| { |
| nbAdded++; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| String message = I18n.err( I18n.ERR_04452 ); |
| LOG.error( message ); |
| } |
| } |
| else |
| { |
| if ( val instanceof BinaryValue ) |
| { |
| BinaryValue binaryValue = ( BinaryValue ) val; |
| |
| try |
| { |
| if ( binaryValue.getAttributeType() == null ) |
| { |
| binaryValue = new BinaryValue( attributeType, val.getBytes() ); |
| } |
| |
| if ( values.add( binaryValue ) ) |
| { |
| nbAdded++; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException iae ) |
| { |
| continue; |
| } |
| } |
| else |
| { |
| String message = I18n.err( I18n.ERR_04452 ); |
| 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 BinaryValue( ( byte[] ) null ); |
| nullStringValue = new StringValue( ( String ) null ); |
| |
| values.add( nullBinaryValue ); |
| values.add( nullStringValue ); |
| nullValueAdded = true; |
| nbAdded++; |
| } |
| else if ( !isHR ) |
| { |
| // The attribute type is binary. |
| nullBinaryValue = new BinaryValue( ( 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 StringValue( ( 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 instanceof StringValue ) |
| { |
| // 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 BinaryValue |
| BinaryValue bv = new BinaryValue( 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 StringValue |
| StringValue sv = new StringValue( val.getString() ); |
| |
| 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} |
| */ |
| 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<String> value = createStringValue( attributeType, val ); |
| |
| if ( value == null ) |
| { |
| // The value can't be normalized : we don't add it. |
| LOG.error( I18n.err( I18n.ERR_04449, val ) ); |
| continue; |
| } |
| |
| // Call the add(Value) method, if not already present |
| if ( add( value ) == 1 ) |
| { |
| nbAdded++; |
| } |
| else |
| { |
| LOG.warn( I18n.err( I18n.ERR_04486_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<byte[]> 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_04487_ATTRIBUTE_IS_SINGLE_VALUED, attributeType.getName() ) ); |
| return 0; |
| } |
| |
| if ( isHR ) |
| { |
| for ( String val : valArray ) |
| { |
| Value<String> value = createStringValue( attributeType, val ); |
| |
| if ( value == null ) |
| { |
| // The value can't be normalized : we don't add it. |
| LOG.error( I18n.err( I18n.ERR_04449, val ) ); |
| continue; |
| } |
| |
| // Call the add(Value) method, if not already present |
| if ( add( value ) == 1 ) |
| { |
| nbAdded++; |
| } |
| else |
| { |
| LOG.warn( I18n.err( I18n.ERR_04486_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<byte[]> 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<byte[]> value = null; |
| |
| if ( attributeType == null ) |
| { |
| value = new BinaryValue( val ); |
| } |
| else |
| { |
| value = createBinaryValue( attributeType, val ); |
| } |
| |
| if ( add( value ) != 0 ) |
| { |
| nbAdded++; |
| } |
| else |
| { |
| LOG.warn( I18n.err( I18n.ERR_04486_VALUE_ALREADY_EXISTS, Strings.dumpBytes( val ), upId ) ); |
| } |
| } |
| } |
| else |
| { |
| // We can't add Binary values into a String Attribute |
| LOG.info( I18n.err( I18n.ERR_04451 ) ); |
| return 0; |
| } |
| |
| return nbAdded; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void clear() |
| { |
| values.clear(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| 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 instanceof StringValue ) |
| { |
| 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 StringValue( 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 BinaryValue( 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 instanceof StringValue ) |
| { |
| StringValue stringValue = ( StringValue ) val; |
| |
| try |
| { |
| if ( stringValue.getAttributeType() == null ) |
| { |
| stringValue.apply( attributeType ); |
| } |
| } |
| 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 instanceof BinaryValue ) |
| { |
| if ( !values.contains( val ) ) |
| { |
| return false; |
| } |
| } |
| else |
| { |
| // Not a Binary value |
| return false; |
| } |
| } |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| 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 StringValue( 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 BinaryValue( 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 |
| { |
| StringValue value = new StringValue( 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 BinaryValue( 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 StringValue( 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 |
| { |
| BinaryValue value = new BinaryValue( attributeType, val ); |
| |
| if ( !values.contains( value ) ) |
| { |
| return false; |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public Value<?> get() |
| { |
| if ( values.isEmpty() ) |
| { |
| return null; |
| } |
| |
| return values.iterator().next(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public int size() |
| { |
| return values.size(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean remove( Value<?>... vals ) |
| { |
| if ( ( isHR == null ) || ( values.size() == 0 ) ) |
| { |
| // 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 instanceof StringValue ) |
| { |
| removed &= values.remove( val ); |
| } |
| else |
| { |
| // Convert the binary value to a string value |
| byte[] binaryVal = val.getBytes(); |
| removed &= values.remove( new StringValue( 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 instanceof StringValue ) |
| { |
| StringValue stringValue = ( StringValue ) val; |
| |
| try |
| { |
| if ( stringValue.getAttributeType() == null ) |
| { |
| stringValue.apply( attributeType ); |
| } |
| |
| removed &= values.remove( stringValue ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| removed = false; |
| } |
| } |
| else |
| { |
| removed = false; |
| } |
| } |
| } |
| else |
| { |
| for ( Value<?> val : vals ) |
| { |
| if ( val instanceof BinaryValue ) |
| { |
| try |
| { |
| BinaryValue binaryValue = ( BinaryValue ) val; |
| |
| if ( binaryValue.getAttributeType() == null ) |
| { |
| binaryValue.apply( attributeType ); |
| } |
| |
| removed &= values.remove( binaryValue ); |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| removed = false; |
| } |
| } |
| else |
| { |
| removed = false; |
| } |
| } |
| } |
| } |
| |
| return removed; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean remove( byte[]... vals ) |
| { |
| if ( ( isHR == null ) || ( values.size() == 0 ) ) |
| { |
| // 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 ) |
| { |
| BinaryValue value = new BinaryValue( 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 ) |
| { |
| StringValue value = new StringValue( Strings.utf8ToString( val ) ); |
| removed &= values.remove( value ); |
| } |
| } |
| } |
| else |
| { |
| if ( !isHR ) |
| { |
| try |
| { |
| for ( byte[] val : vals ) |
| { |
| BinaryValue value = new BinaryValue( attributeType, val ); |
| removed &= values.remove( value ); |
| } |
| } |
| catch ( LdapInvalidAttributeValueException liave ) |
| { |
| removed = false; |
| } |
| } |
| else |
| { |
| removed = false; |
| } |
| } |
| |
| return removed; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean remove( String... vals ) |
| { |
| if ( ( isHR == null ) || ( values.size() == 0 ) ) |
| { |
| // 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 ) |
| { |
| StringValue value = new StringValue( 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 ) |
| { |
| BinaryValue value = new BinaryValue( Strings.getBytesUtf8( val ) ); |
| removed &= values.remove( value ); |
| } |
| } |
| } |
| else |
| { |
| if ( isHR ) |
| { |
| for ( String val : vals ) |
| { |
| try |
| { |
| StringValue value = new StringValue( 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. |
| */ |
| public Iterator<Value<?>> iterator() |
| { |
| return values.iterator(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public AttributeType getAttributeType() |
| { |
| return attributeType; |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public void apply( AttributeType attributeType ) throws LdapInvalidAttributeValueException |
| { |
| if ( attributeType == null ) |
| { |
| throw new IllegalArgumentException( "The AttributeType parameter should not be 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<Value<?>>( values.size() ); |
| |
| for ( Value<?> value : values ) |
| { |
| if ( value instanceof StringValue ) |
| { |
| newValues.add( new StringValue( attributeType, value.getString() ) ); |
| } |
| else |
| { |
| newValues.add( new BinaryValue( attributeType, value.getBytes() ) ); |
| } |
| } |
| |
| values = newValues; |
| } |
| |
| isHR = attributeType.getSyntax().isHumanReadable(); |
| |
| // Compute the hashCode |
| rehash(); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| 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() |
| { |
| h = 37; |
| |
| if ( isHR != null ) |
| { |
| h = h * 17 + isHR.hashCode(); |
| } |
| |
| if ( id != null ) |
| { |
| h = h * 17 + id.hashCode(); |
| } |
| |
| if ( attributeType != null ) |
| { |
| h = h * 17 + attributeType.hashCode(); |
| } |
| } |
| |
| |
| /** |
| * The hashCode is based on the id, the isHR flag and |
| * on the internal values. |
| * |
| * @see Object#hashCode() |
| * @return the instance's hashcode |
| */ |
| public int hashCode() |
| { |
| if ( h == 0 ) |
| { |
| rehash(); |
| } |
| |
| return h; |
| } |
| |
| |
| /** |
| * @see Object#equals(Object) |
| */ |
| 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} |
| */ |
| public Attribute clone() |
| { |
| try |
| { |
| DefaultAttribute attribute = ( DefaultAttribute ) super.clone(); |
| |
| if ( this.attributeType != null ) |
| { |
| attribute.id = attributeType.getOid(); |
| attribute.attributeType = attributeType; |
| } |
| |
| attribute.values = new LinkedHashSet<Value<?>>( 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} |
| */ |
| public void writeExternal( ObjectOutput out ) throws IOException |
| { |
| // Write the UPId (the id will be deduced from the upID) |
| out.writeUTF( upId ); |
| |
| // 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} |
| */ |
| public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException |
| { |
| // Read the ID and the UPId |
| upId = in.readUTF(); |
| |
| // Compute the id |
| setUpId( upId ); |
| |
| // 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 = null; |
| |
| if ( isHR ) |
| { |
| value = new StringValue( attributeType ); |
| } |
| else |
| { |
| value = new BinaryValue( attributeType ); |
| } |
| |
| value.readExternal( in ); |
| |
| values.add( value ); |
| } |
| } |
| } |
| |
| |
| /** |
| * @see Object#toString() |
| */ |
| public String toString() |
| { |
| return toString( "" ); |
| } |
| |
| |
| /** |
| * {@inheritDoc} |
| */ |
| public String toString( String tabs ) |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| if ( ( values != null ) && ( values.size() != 0 ) ) |
| { |
| 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(); |
| } |
| } |