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