001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019 package org.apache.directory.shared.ldap.entry.client;
020
021
022 import java.io.IOException;
023 import java.io.ObjectInput;
024 import java.io.ObjectOutput;
025 import java.util.Iterator;
026 import java.util.LinkedHashSet;
027 import java.util.List;
028 import java.util.Set;
029
030 import javax.naming.NamingException;
031 import javax.naming.directory.InvalidAttributeValueException;
032
033 import org.apache.directory.shared.ldap.entry.EntryAttribute;
034 import org.apache.directory.shared.ldap.entry.Value;
035 import org.apache.directory.shared.ldap.schema.SyntaxChecker;
036 import org.apache.directory.shared.ldap.util.StringTools;
037 import org.slf4j.Logger;
038 import org.slf4j.LoggerFactory;
039
040
041 /**
042 * A client side entry attribute. The client is not aware of the schema,
043 * so we can't tell if the stored value will be String or Binary. We will
044 * default to Binary.<p>
045 * To define the kind of data stored, the client must set the isHR flag.
046 *
047 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
048 * @version $Rev$, $Date$
049 */
050 public class DefaultClientAttribute implements ClientAttribute
051 {
052 /** logger for reporting errors that might not be handled properly upstream */
053 private static final Logger LOG = LoggerFactory.getLogger( DefaultClientAttribute.class );
054
055
056 /** The set of contained values */
057 protected Set<Value<?>> values = new LinkedHashSet<Value<?>>();
058
059 /** The User provided ID */
060 protected String upId;
061
062 /** The normalized ID */
063 protected String id;
064
065 /** Tells if the attribute is Human Readable or not. When not set,
066 * this flag is null. */
067 protected Boolean isHR;
068
069
070 // maybe have some additional convenience constructors which take
071 // an initial value as a string or a byte[]
072 /**
073 * Create a new instance of a EntryAttribute, without ID nor value.
074 */
075 public DefaultClientAttribute()
076 {
077 }
078
079
080 /**
081 * Create a new instance of a EntryAttribute, without value.
082 */
083 public DefaultClientAttribute( String upId )
084 {
085 setUpId( upId );
086 }
087
088
089 /**
090 * If the value does not correspond to the same attributeType, then it's
091 * wrapped value is copied into a new ClientValue which uses the specified
092 * attributeType.
093 *
094 * Otherwise, the value is stored, but as a reference. It's not a copy.
095 *
096 * @param upId
097 * @param attributeType the attribute type according to the schema
098 * @param vals an initial set of values for this attribute
099 */
100 public DefaultClientAttribute( String upId, Value<?>... vals )
101 {
102 // The value can be null, this is a valid value.
103 if ( vals[0] == null )
104 {
105 add( new ClientStringValue() );
106 }
107 else
108 {
109 for ( Value<?> val:vals )
110 {
111 if ( ( val instanceof ClientStringValue ) || ( val.isBinary() ) )
112 {
113 add( val );
114 }
115 else
116 {
117 String message = "Unknown value type: " + val.getClass().getName();
118 LOG.error( message );
119 throw new IllegalStateException( message );
120 }
121 }
122 }
123
124 setUpId( upId );
125 }
126
127
128 /**
129 * Create a new instance of a EntryAttribute.
130 */
131 public DefaultClientAttribute( String upId, String... vals )
132 {
133 add( vals );
134 setUpId( upId );
135 }
136
137
138 /**
139 * Create a new instance of a EntryAttribute, with some byte[] values.
140 */
141 public DefaultClientAttribute( String upId, byte[]... vals )
142 {
143 add( vals );
144 setUpId( upId );
145 }
146
147
148 /**
149 * <p>
150 * Get the byte[] value, if and only if the value is known to be Binary,
151 * otherwise a InvalidAttributeValueException will be thrown
152 * </p>
153 * <p>
154 * Note that this method returns the first value only.
155 * </p>
156 *
157 * @return The value as a byte[]
158 * @throws InvalidAttributeValueException If the value is a String
159 */
160 public byte[] getBytes() throws InvalidAttributeValueException
161 {
162 Value<?> value = get();
163
164 if ( value.isBinary() )
165 {
166 return value.getBytes();
167 }
168 else
169 {
170 String message = "The value is expected to be a byte[]";
171 LOG.error( message );
172 throw new InvalidAttributeValueException( message );
173 }
174 }
175
176
177 /**
178 * <p>
179 * Get the String value, if and only if the value is known to be a String,
180 * otherwise a InvalidAttributeValueException will be thrown
181 * </p>
182 * <p>
183 * Note that this method returns the first value only.
184 * </p>
185 *
186 * @return The value as a String
187 * @throws InvalidAttributeValueException If the value is a byte[]
188 */
189 public String getString() throws InvalidAttributeValueException
190 {
191 Value<?> value = get();
192
193 if ( value instanceof ClientStringValue )
194 {
195 return value.getString();
196 }
197 else
198 {
199 String message = "The value is expected to be a String";
200 LOG.error( message );
201 throw new InvalidAttributeValueException( message );
202 }
203 }
204
205
206 /**
207 * Get's the attribute identifier. Its value is the same than the
208 * user provided ID.
209 *
210 * @return the attribute's identifier
211 */
212 public String getId()
213 {
214 return id;
215 }
216
217
218 /**
219 * <p>
220 * Set the attribute to Human Readable or to Binary.
221 * </p>
222 * @param isHR <code>true</code> for a Human Readable attribute,
223 * <code>false</code> for a Binary attribute.
224 */
225 public void setHR( boolean isHR )
226 {
227 this.isHR = isHR;
228 //TODO : deal with the values, we may have to convert them.
229 }
230
231
232 /**
233 * Set the normalized ID. The ID will be lowercased, and spaces
234 * will be trimmed.
235 *
236 * @param id The attribute ID
237 * @throws IllegalArgumentException If the ID is empty or null or
238 * resolve to an empty value after being trimmed
239 */
240 public void setId( String id )
241 {
242 this.id = StringTools.trim( StringTools.lowerCaseAscii( id ) );
243
244 if ( this.id.length() == 0 )
245 {
246 this.id = null;
247 throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" +
248 " value when trimmed" );
249 }
250 }
251
252
253 /**
254 * Get's the user provided identifier for this entry. This is the value
255 * that will be used as the identifier for the attribute within the
256 * entry. If this is a commonName attribute for example and the user
257 * provides "COMMONname" instead when adding the entry then this is
258 * the format the user will have that entry returned by the directory
259 * server. To do so we store this value as it was given and track it
260 * in the attribute using this property.
261 *
262 * @return the user provided identifier for this attribute
263 */
264 public String getUpId()
265 {
266 return upId;
267 }
268
269
270 /**
271 * Set the user provided ID. It will also set the ID, normalizing
272 * the upId (removing spaces before and after, and lowercasing it)
273 *
274 * @param upId The attribute ID
275 * @throws IllegalArgumentException If the ID is empty or null or
276 * resolve to an empty value after being trimmed
277 */
278 public void setUpId( String upId )
279 {
280 this.upId = StringTools.trim( upId );
281
282 if ( this.upId.length() == 0 )
283 {
284 this.upId = null;
285 throw new IllegalArgumentException( "An ID cannnot be null, empty, or resolved to an emtpy" +
286 " value when trimmed" );
287 }
288
289 this.id = StringTools.lowerCaseAscii( this.upId );
290 }
291
292
293 /**
294 * <p>
295 * Tells if the attribute is Human Readable.
296 * </p>
297 * <p>This flag is set by the caller, or implicitly when adding String
298 * values into an attribute which is not yet declared as Binary.
299 * </p>
300 * @return
301 */
302 public boolean isHR()
303 {
304 return isHR != null ? isHR : false;
305 }
306
307
308 /**
309 * Checks to see if this attribute is valid along with the values it contains.
310 *
311 * @return true if the attribute and it's values are valid, false otherwise
312 * @throws NamingException if there is a failure to check syntaxes of values
313 */
314 public boolean isValid() throws NamingException
315 {
316 for ( Value<?> value:values )
317 {
318 if ( !value.isValid() )
319 {
320 return false;
321 }
322 }
323
324 return true;
325 }
326
327
328 /**
329 * Checks to see if this attribute is valid along with the values it contains.
330 *
331 * @return true if the attribute and it's values are valid, false otherwise
332 * @throws NamingException if there is a failure to check syntaxes of values
333 */
334 public boolean isValid( SyntaxChecker checker ) throws NamingException
335 {
336 for ( Value<?> value : values )
337 {
338 if ( !value.isValid( checker ) )
339 {
340 return false;
341 }
342 }
343
344 return true;
345 }
346
347
348 /**
349 * Adds some values to this attribute. If the new values are already present in
350 * the attribute values, the method has no effect.
351 * <p>
352 * The new values are added at the end of list of values.
353 * </p>
354 * <p>
355 * This method returns the number of values that were added.
356 * </p>
357 * <p>
358 * If the value's type is different from the attribute's type,
359 * a conversion is done. For instance, if we try to set some
360 * StringValue into a Binary attribute, we just store the UTF-8
361 * byte array encoding for this StringValue.
362 * </p>
363 * <p>
364 * If we try to store some BinaryValue in a HR attribute, we try to
365 * convert those BinaryValue assuming they represent an UTF-8 encoded
366 * String. Of course, if it's not the case, the stored value will
367 * be incorrect.
368 * </p>
369 * <p>
370 * It's the responsibility of the caller to check if the stored
371 * values are consistent with the attribute's type.
372 * </p>
373 * <p>
374 * The caller can set the HR flag in order to enforce a type for
375 * the current attribute, otherwise this type will be set while
376 * adding the first value, using the value's type to set the flag.
377 * </p>
378 * <p>
379 * <b>Note : </b>If the entry contains no value, and the unique added value
380 * is a null length value, then this value will be considered as
381 * a binary value.
382 * </p>
383 * @param val some new values to be added which may be null
384 * @return the number of added values, or 0 if none has been added
385 */
386 public int add( Value<?>... vals )
387 {
388 int nbAdded = 0;
389 ClientBinaryValue nullBinaryValue = null;
390 ClientStringValue nullStringValue = null;
391 boolean nullValueAdded = false;
392
393 for ( Value<?> val:vals )
394 {
395 if ( val == null )
396 {
397 // We have a null value. If the HR flag is not set, we will consider
398 // that the attribute is not HR. We may change this later
399 if ( isHR == null )
400 {
401 // This is the first value. Add both types, as we
402 // don't know yet the attribute type's, but we may
403 // know later if we add some new value.
404 // We have to do that because we are using a Set,
405 // and we can't remove the first element of the Set.
406 nullBinaryValue = new ClientBinaryValue( null );
407 nullStringValue = new ClientStringValue( null );
408
409 values.add( nullBinaryValue );
410 values.add( nullStringValue );
411 nullValueAdded = true;
412 nbAdded++;
413 }
414 else if ( !isHR )
415 {
416 // The attribute type is binary.
417 nullBinaryValue = new ClientBinaryValue( null );
418
419 // Don't add a value if it already exists.
420 if ( !values.contains( nullBinaryValue ) )
421 {
422 values.add( nullBinaryValue );
423 nbAdded++;
424 }
425
426 }
427 else
428 {
429 // The attribute is HR
430 nullStringValue = new ClientStringValue( null );
431
432 // Don't add a value if it already exists.
433 if ( !values.contains( nullStringValue ) )
434 {
435 values.add( nullStringValue );
436 }
437 }
438 }
439 else
440 {
441 // Let's check the value type.
442 if ( val instanceof ClientStringValue )
443 {
444 // We have a String value
445 if ( isHR == null )
446 {
447 // The attribute type will be set to HR
448 isHR = true;
449 values.add( val );
450 nbAdded++;
451 }
452 else if ( !isHR )
453 {
454 // The attributeType is binary, convert the
455 // value to a BinaryValue
456 ClientBinaryValue cbv = new ClientBinaryValue();
457 cbv.set( val.getBytes() );
458
459 if ( !contains( cbv ) )
460 {
461 values.add( cbv );
462 nbAdded++;
463 }
464 }
465 else
466 {
467 // The attributeType is HR, simply add the value
468 if ( !contains( val ) )
469 {
470 values.add( val );
471 nbAdded++;
472 }
473 }
474 }
475 else
476 {
477 // We have a Binary value
478 if ( isHR == null )
479 {
480 // The attribute type will be set to binary
481 isHR = false;
482 values.add( val );
483 nbAdded++;
484 }
485 else if ( !isHR )
486 {
487 // The attributeType is not HR, simply add the value if it does not already exist
488 if ( !contains( val ) )
489 {
490 values.add( val );
491 nbAdded++;
492 }
493 }
494 else
495 {
496 // The attribute Type is HR, convert the
497 // value to a StringValue
498 ClientStringValue csv = new ClientStringValue();
499 csv.set( val.getString() );
500
501 if ( !contains( csv ) )
502 {
503 values.add( csv );
504 nbAdded++;
505 }
506 }
507 }
508 }
509 }
510
511 // Last, not least, if a nullValue has been added, and if other
512 // values are all String, we have to keep the correct nullValue,
513 // and to remove the other
514 if ( nullValueAdded )
515 {
516 if ( isHR )
517 {
518 // Remove the Binary value
519 values.remove( nullBinaryValue );
520 }
521 else
522 {
523 // Remove the String value
524 values.remove( nullStringValue );
525 }
526 }
527
528 return nbAdded;
529 }
530
531
532 /**
533 * @see EntryAttribute#add(String...)
534 */
535 public int add( String... vals )
536 {
537 int nbAdded = 0;
538
539 // First, if the isHR flag is not set, we assume that the
540 // attribute is HR, because we are asked to add some strings.
541 if ( isHR == null )
542 {
543 isHR = true;
544 }
545
546 // Check the attribute type.
547 if ( isHR )
548 {
549 for ( String val:vals )
550 {
551 // Call the add(Value) method, if not already present
552 if ( !contains( val ) )
553 {
554 if ( add( new ClientStringValue( val ) ) == 1 )
555 {
556 nbAdded++;
557 }
558 }
559 }
560 }
561 else
562 {
563 // The attribute is binary. Transform the String to byte[]
564 for ( String val:vals )
565 {
566 byte[] valBytes = null;
567
568 if ( val != null )
569 {
570 valBytes = StringTools.getBytesUtf8( val );
571 }
572
573 // Now call the add(Value) method
574 if ( add( new ClientBinaryValue( valBytes ) ) == 1 )
575 {
576 nbAdded++;
577 }
578 }
579 }
580
581 return nbAdded;
582 }
583
584
585 /**
586 * Adds some values to this attribute. If the new values are already present in
587 * the attribute values, the method has no effect.
588 * <p>
589 * The new values are added at the end of list of values.
590 * </p>
591 * <p>
592 * This method returns the number of values that were added.
593 * </p>
594 * If the value's type is different from the attribute's type,
595 * a conversion is done. For instance, if we try to set some String
596 * into a Binary attribute, we just store the UTF-8 byte array
597 * encoding for this String.
598 * If we try to store some byte[] in a HR attribute, we try to
599 * convert those byte[] assuming they represent an UTF-8 encoded
600 * String. Of course, if it's not the case, the stored value will
601 * be incorrect.
602 * <br>
603 * It's the responsibility of the caller to check if the stored
604 * values are consistent with the attribute's type.
605 * <br>
606 * The caller can set the HR flag in order to enforce a type for
607 * the current attribute, otherwise this type will be set while
608 * adding the first value, using the value's type to set the flag.
609 *
610 * @param val some new values to be added which may be null
611 * @return the number of added values, or 0 if none has been added
612 */
613 public int add( byte[]... vals )
614 {
615 int nbAdded = 0;
616
617 // First, if the isHR flag is not set, we assume that the
618 // attribute is not HR, because we are asked to add some byte[].
619 if ( isHR == null )
620 {
621 isHR = false;
622 }
623
624 // Check the attribute type.
625 if ( isHR )
626 {
627 // The attribute is HR. Transform the byte[] to String
628 for ( byte[] val:vals )
629 {
630 String valString = null;
631
632 if ( val != null )
633 {
634 valString = StringTools.utf8ToString( val );
635 }
636
637 // Now call the add(Value) method, if not already present
638 if ( !contains( val ) )
639 {
640 if ( add( new ClientStringValue( valString ) ) == 1 )
641 {
642 nbAdded++;
643 }
644 }
645 }
646 }
647 else
648 {
649 for ( byte[] val:vals )
650 {
651 if ( add( new ClientBinaryValue( val ) ) == 1 )
652 {
653 nbAdded++;
654 }
655 }
656 }
657
658 return nbAdded;
659 }
660
661
662 /**
663 * Remove all the values from this attribute.
664 */
665 public void clear()
666 {
667 values.clear();
668 }
669
670
671 /**
672 * <p>
673 * Indicates whether the specified values are some of the attribute's values.
674 * </p>
675 * <p>
676 * If the Attribute is HR, the binary values will be converted to String before
677 * being checked.
678 * </p>
679 *
680 * @param vals the values
681 * @return true if this attribute contains all the values, otherwise false
682 */
683 public boolean contains( Value<?>... vals )
684 {
685 if ( isHR == null )
686 {
687 // If this flag is null, then there is no values.
688 return false;
689 }
690
691 if ( isHR )
692 {
693 // Iterate through all the values, convert the Binary values
694 // to String values, and quit id any of the values is not
695 // contained in the object
696 for ( Value<?> val:vals )
697 {
698 if ( val instanceof ClientStringValue )
699 {
700 if ( !values.contains( val ) )
701 {
702 return false;
703 }
704 }
705 else
706 {
707 byte[] binaryVal = val.getBytes();
708
709 // We have to convert the binary value to a String
710 if ( ! values.contains( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) ) )
711 {
712 return false;
713 }
714 }
715 }
716 }
717 else
718 {
719 // Iterate through all the values, convert the String values
720 // to binary values, and quit id any of the values is not
721 // contained in the object
722 for ( Value<?> val:vals )
723 {
724 if ( val.isBinary() )
725 {
726 if ( !values.contains( val ) )
727 {
728 return false;
729 }
730 }
731 else
732 {
733 String stringVal = val.getString();
734
735 // We have to convert the binary value to a String
736 if ( ! values.contains( new ClientBinaryValue( StringTools.getBytesUtf8( stringVal ) ) ) )
737 {
738 return false;
739 }
740 }
741 }
742 }
743
744 return true;
745 }
746
747
748 /**
749 * <p>
750 * Indicates whether the specified values are some of the attribute's values.
751 * </p>
752 * <p>
753 * If the Attribute is not HR, the values will be converted to byte[]
754 * </p>
755 *
756 * @param vals the values
757 * @return true if this attribute contains all the values, otherwise false
758 */
759 public boolean contains( String... vals )
760 {
761 if ( isHR == null )
762 {
763 // If this flag is null, then there is no values.
764 return false;
765 }
766
767 if ( isHR )
768 {
769 // Iterate through all the values, and quit if we
770 // don't find one in the values
771 for ( String val:vals )
772 {
773 if ( !contains( new ClientStringValue( val ) ) )
774 {
775 return false;
776 }
777 }
778 }
779 else
780 {
781 // As the attribute type is binary, we have to convert
782 // the values before checking for them in the values
783 // Iterate through all the values, and quit if we
784 // don't find one in the values
785 for ( String val:vals )
786 {
787 byte[] binaryVal = StringTools.getBytesUtf8( val );
788
789 if ( !contains( new ClientBinaryValue( binaryVal ) ) )
790 {
791 return false;
792 }
793 }
794 }
795
796 return true;
797 }
798
799
800 /**
801 * <p>
802 * Indicates whether the specified values are some of the attribute's values.
803 * </p>
804 * <p>
805 * If the Attribute is HR, the values will be converted to String
806 * </p>
807 *
808 * @param vals the values
809 * @return true if this attribute contains all the values, otherwise false
810 */
811 public boolean contains( byte[]... vals )
812 {
813 if ( isHR == null )
814 {
815 // If this flag is null, then there is no values.
816 return false;
817 }
818
819 if ( !isHR )
820 {
821 // Iterate through all the values, and quit if we
822 // don't find one in the values
823 for ( byte[] val:vals )
824 {
825 if ( !contains( new ClientBinaryValue( val ) ) )
826 {
827 return false;
828 }
829 }
830 }
831 else
832 {
833 // As the attribute type is String, we have to convert
834 // the values before checking for them in the values
835 // Iterate through all the values, and quit if we
836 // don't find one in the values
837 for ( byte[] val:vals )
838 {
839 String stringVal = StringTools.utf8ToString( val );
840
841 if ( !contains( new ClientStringValue( stringVal ) ) )
842 {
843 return false;
844 }
845 }
846 }
847
848 return true;
849 }
850
851
852 /**
853 * @see EntryAttribute#contains(Object...)
854 */
855 public boolean contains( Object... vals )
856 {
857 boolean isHR = true;
858 boolean seen = false;
859
860 // Iterate through all the values, and quit if we
861 // don't find one in the values
862 for ( Object val:vals )
863 {
864 if ( ( val instanceof String ) )
865 {
866 if ( !seen )
867 {
868 isHR = true;
869 seen = true;
870 }
871
872 if ( isHR )
873 {
874 if ( !contains( (String)val ) )
875 {
876 return false;
877 }
878 }
879 else
880 {
881 return false;
882 }
883 }
884 else
885 {
886 if ( !seen )
887 {
888 isHR = false;
889 seen = true;
890 }
891
892 if ( !isHR )
893 {
894 if ( !contains( (byte[])val ) )
895 {
896 return false;
897 }
898 }
899 else
900 {
901 return false;
902 }
903 }
904 }
905
906 return true;
907 }
908
909
910 /**
911 * <p>
912 * Get the first value of this attribute. If there is none,
913 * null is returned.
914 * </p>
915 * <p>
916 * Note : even if we are storing values into a Set, one can assume
917 * the values are ordered following the insertion order.
918 * </p>
919 * <p>
920 * This method is meant to be used if the attribute hold only one value.
921 * </p>
922 *
923 * @return The first value for this attribute.
924 */
925 public Value<?> get()
926 {
927 if ( values.isEmpty() )
928 {
929 return null;
930 }
931
932 return values.iterator().next();
933 }
934
935
936 /**
937 * <p>
938 * Get the nth value of this attribute. If there is none,
939 * null is returned.
940 * </p>
941 * <p>
942 * Note : even if we are storing values into a Set, one can assume
943 * the values are ordered following the insertion order.
944 * </p>
945 * <p>
946 *
947 * @param i the index of the value to get
948 * @return The nth value for this attribute.
949 */
950 public Value<?> get( int i )
951 {
952 if ( values.size() < i )
953 {
954 return null;
955 }
956 else
957 {
958 int n = 0;
959
960 for ( Value<?> value:values )
961 {
962 if ( n == i )
963 {
964 return value;
965 }
966
967 n++;
968 }
969 }
970
971 // fallback to
972 return null;
973 }
974
975
976 /**
977 * Returns an iterator over all the attribute's values.
978 * <p>
979 * The effect on the returned enumeration of adding or removing values of
980 * the attribute is not specified.
981 * </p>
982 * <p>
983 * This method will throw any <code>NamingException</code> that occurs.
984 * </p>
985 *
986 * @return an enumeration of all values of the attribute
987 */
988 public Iterator<Value<?>> getAll()
989 {
990 return iterator();
991 }
992
993
994 /**
995 * Retrieves the number of values in this attribute.
996 *
997 * @return the number of values in this attribute, including any values
998 * wrapping a null value if there is one
999 */
1000 public int size()
1001 {
1002 return values.size();
1003 }
1004
1005
1006 /**
1007 * <p>
1008 * Removes all the values that are equal to the given values.
1009 * </p>
1010 * <p>
1011 * Returns true if all the values are removed.
1012 * </p>
1013 * <p>
1014 * If the attribute type is HR and some value which are not String, we
1015 * will convert the values first (same thing for a non-HR attribute).
1016 * </p>
1017 *
1018 * @param vals the values to be removed
1019 * @return true if all the values are removed, otherwise false
1020 */
1021 public boolean remove( Value<?>... vals )
1022 {
1023 if ( ( isHR == null ) || ( values.size() == 0 ) )
1024 {
1025 // Trying to remove a value from an empty list will fail
1026 return false;
1027 }
1028
1029 boolean removed = true;
1030
1031 if ( isHR )
1032 {
1033 for ( Value<?> val:vals )
1034 {
1035 if ( val instanceof ClientStringValue )
1036 {
1037 removed &= values.remove( val );
1038 }
1039 else
1040 {
1041 // Convert the binary value to a string value
1042 byte[] binaryVal = val.getBytes();
1043 removed &= values.remove( new ClientStringValue( StringTools.utf8ToString( binaryVal ) ) );
1044 }
1045 }
1046 }
1047 else
1048 {
1049 for ( Value<?> val:vals )
1050 {
1051 removed &= values.remove( val );
1052 }
1053 }
1054
1055 return removed;
1056 }
1057
1058
1059 /**
1060 * <p>
1061 * Removes all the values that are equal to the given values.
1062 * </p>
1063 * <p>
1064 * Returns true if all the values are removed.
1065 * </p>
1066 * <p>
1067 * If the attribute type is HR, then the values will be first converted
1068 * to String
1069 * </p>
1070 *
1071 * @param vals the values to be removed
1072 * @return true if all the values are removed, otherwise false
1073 */
1074 public boolean remove( byte[]... vals )
1075 {
1076 if ( ( isHR == null ) || ( values.size() == 0 ) )
1077 {
1078 // Trying to remove a value from an empty list will fail
1079 return false;
1080 }
1081
1082 boolean removed = true;
1083
1084 if ( !isHR )
1085 {
1086 // The attribute type is not HR, we can directly process the values
1087 for ( byte[] val:vals )
1088 {
1089 ClientBinaryValue value = new ClientBinaryValue( val );
1090 removed &= values.remove( value );
1091 }
1092 }
1093 else
1094 {
1095 // The attribute type is String, we have to convert the values
1096 // to String before removing them
1097 for ( byte[] val:vals )
1098 {
1099 ClientStringValue value = new ClientStringValue( StringTools.utf8ToString( val ) );
1100 removed &= values.remove( value );
1101 }
1102 }
1103
1104 return removed;
1105 }
1106
1107
1108 /**
1109 * Removes all the values that are equal to the given values.
1110 * <p>
1111 * Returns true if all the values are removed.
1112 * </p>
1113 * <p>
1114 * If the attribute type is not HR, then the values will be first converted
1115 * to byte[]
1116 * </p>
1117 *
1118 * @param vals the values to be removed
1119 * @return true if all the values are removed, otherwise false
1120 */
1121 public boolean remove( String... vals )
1122 {
1123 if ( ( isHR == null ) || ( values.size() == 0 ) )
1124 {
1125 // Trying to remove a value from an empty list will fail
1126 return false;
1127 }
1128
1129 boolean removed = true;
1130
1131 if ( isHR )
1132 {
1133 // The attribute type is HR, we can directly process the values
1134 for ( String val:vals )
1135 {
1136 ClientStringValue value = new ClientStringValue( val );
1137 removed &= values.remove( value );
1138 }
1139 }
1140 else
1141 {
1142 // The attribute type is binary, we have to convert the values
1143 // to byte[] before removing them
1144 for ( String val:vals )
1145 {
1146 ClientBinaryValue value = new ClientBinaryValue( StringTools.getBytesUtf8( val ) );
1147 removed &= values.remove( value );
1148 }
1149 }
1150
1151 return removed;
1152 }
1153
1154
1155 /**
1156 * An iterator on top of the stored values.
1157 *
1158 * @return an iterator over the stored values.
1159 */
1160 public Iterator<Value<?>> iterator()
1161 {
1162 return values.iterator();
1163 }
1164
1165
1166 /**
1167 * Puts some values to this attribute.
1168 * <p>
1169 * The new values will replace the previous values.
1170 * </p>
1171 * <p>
1172 * This method returns the number of values that were put.
1173 * </p>
1174 *
1175 * @param val some values to be put which may be null
1176 * @return the number of added values, or 0 if none has been added
1177 */
1178 public int put( String... vals )
1179 {
1180 values.clear();
1181 return add( vals );
1182 }
1183
1184
1185 /**
1186 * Puts some values to this attribute.
1187 * <p>
1188 * The new values will replace the previous values.
1189 * </p>
1190 * <p>
1191 * This method returns the number of values that were put.
1192 * </p>
1193 *
1194 * @param val some values to be put which may be null
1195 * @return the number of added values, or 0 if none has been added
1196 */
1197 public int put( byte[]... vals )
1198 {
1199 values.clear();
1200 return add( vals );
1201 }
1202
1203
1204 /**
1205 * Puts some values to this attribute.
1206 * <p>
1207 * The new values are replace the previous values.
1208 * </p>
1209 * <p>
1210 * This method returns the number of values that were put.
1211 * </p>
1212 *
1213 * @param val some values to be put which may be null
1214 * @return the number of added values, or 0 if none has been added
1215 */
1216 public int put( Value<?>... vals )
1217 {
1218 values.clear();
1219 return add( vals );
1220 }
1221
1222
1223 /**
1224 * <p>
1225 * Puts a list of values into this attribute.
1226 * </p>
1227 * <p>
1228 * The new values will replace the previous values.
1229 * </p>
1230 * <p>
1231 * This method returns the number of values that were put.
1232 * </p>
1233 *
1234 * @param vals the values to be put
1235 * @return the number of added values, or 0 if none has been added
1236 */
1237 public int put( List<Value<?>> vals )
1238 {
1239 values.clear();
1240
1241 // Transform the List to an array
1242 Value<?>[] valArray = new Value<?>[vals.size()];
1243 return add( vals.toArray( valArray ) );
1244 }
1245
1246 //-------------------------------------------------------------------------
1247 // Overloaded Object classes
1248 //-------------------------------------------------------------------------
1249 /**
1250 * The hashCode is based on the id, the isHR flag and
1251 * on the internal values.
1252 *
1253 * @see Object#hashCode()
1254 * @return the instance's hashcode
1255 */
1256 public int hashCode()
1257 {
1258 int h = 37;
1259
1260 if ( isHR != null )
1261 {
1262 h = h*17 + isHR.hashCode();
1263 }
1264
1265 if ( id != null )
1266 {
1267 h = h*17 + id.hashCode();
1268 }
1269
1270 for ( Value<?> value:values )
1271 {
1272 h = h*17 + value.hashCode();
1273 }
1274
1275 return h;
1276 }
1277
1278
1279 /**
1280 * @see Object#equals(Object)
1281 */
1282 public boolean equals( Object obj )
1283 {
1284 if ( obj == this )
1285 {
1286 return true;
1287 }
1288
1289 if ( ! (obj instanceof EntryAttribute ) )
1290 {
1291 return false;
1292 }
1293
1294 EntryAttribute other = (EntryAttribute)obj;
1295
1296 if ( id == null )
1297 {
1298 if ( other.getId() != null )
1299 {
1300 return false;
1301 }
1302 }
1303 else
1304 {
1305 if ( other.getId() == null )
1306 {
1307 return false;
1308 }
1309 else
1310 {
1311 if ( !id.equals( other.getId() ) )
1312 {
1313 return false;
1314 }
1315 }
1316 }
1317
1318 if ( isHR() != other.isHR() )
1319 {
1320 return false;
1321 }
1322
1323 if ( values.size() != other.size() )
1324 {
1325 return false;
1326 }
1327
1328 for ( Value<?> val:values )
1329 {
1330 if ( ! other.contains( val ) )
1331 {
1332 return false;
1333 }
1334 }
1335
1336 return true;
1337 }
1338
1339
1340 /**
1341 * @see Cloneable#clone()
1342 */
1343 public EntryAttribute clone()
1344 {
1345 try
1346 {
1347 DefaultClientAttribute attribute = (DefaultClientAttribute)super.clone();
1348
1349 attribute.values = new LinkedHashSet<Value<?>>( values.size() );
1350
1351 for ( Value<?> value:values )
1352 {
1353 attribute.values.add( value.clone() );
1354 }
1355
1356 return attribute;
1357 }
1358 catch ( CloneNotSupportedException cnse )
1359 {
1360 return null;
1361 }
1362 }
1363
1364
1365 /**
1366 * @see Object#toString()
1367 */
1368 public String toString()
1369 {
1370 StringBuilder sb = new StringBuilder();
1371
1372 if ( ( values != null ) && ( values.size() != 0 ) )
1373 {
1374 for ( Value<?> value:values )
1375 {
1376 sb.append( " " ).append( upId ).append( ": " );
1377
1378 if ( value.isNull() )
1379 {
1380 sb.append( "''" );
1381 }
1382 else
1383 {
1384 sb.append( value );
1385 }
1386
1387 sb.append( '\n' );
1388 }
1389 }
1390 else
1391 {
1392 sb.append( " " ).append( upId ).append( ": (null)\n" );
1393 }
1394
1395 return sb.toString();
1396 }
1397
1398
1399 /**
1400 * @see Externalizable#writeExternal(ObjectOutput)
1401 * <p>
1402 *
1403 * This is the place where we serialize attributes, and all theirs
1404 * elements.
1405 *
1406 * The inner structure is :
1407 *
1408 */
1409 public void writeExternal( ObjectOutput out ) throws IOException
1410 {
1411 // Write the UPId (the id will be deduced from the upID)
1412 out.writeUTF( upId );
1413
1414 // Write the HR flag, if not null
1415 if ( isHR != null )
1416 {
1417 out.writeBoolean( true );
1418 out.writeBoolean( isHR );
1419 }
1420 else
1421 {
1422 out.writeBoolean( false );
1423 }
1424
1425 // Write the number of values
1426 out.writeInt( size() );
1427
1428 if ( size() > 0 )
1429 {
1430 // Write each value
1431 for ( Value<?> value:values )
1432 {
1433 // Write the value
1434 out.writeObject( value );
1435 }
1436 }
1437
1438 out.flush();
1439 }
1440
1441
1442 /**
1443 * @see Externalizable#readExternal(ObjectInput)
1444 */
1445 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1446 {
1447 // Read the ID and the UPId
1448 upId = in.readUTF();
1449
1450 // Compute the id
1451 setUpId( upId );
1452
1453 // Read the HR flag, if not null
1454 if ( in.readBoolean() )
1455 {
1456 isHR = in.readBoolean();
1457 }
1458
1459 // Read the number of values
1460 int nbValues = in.readInt();
1461
1462 if ( nbValues > 0 )
1463 {
1464 for ( int i = 0; i < nbValues; i++ )
1465 {
1466 values.add( (Value<?>)in.readObject() );
1467 }
1468 }
1469 }
1470 }