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;
020
021
022 import java.io.Externalizable;
023 import java.io.IOException;
024 import java.io.ObjectInput;
025 import java.io.ObjectOutput;
026
027 import javax.naming.NamingException;
028
029 import org.apache.directory.shared.asn1.primitives.OID;
030 import org.apache.directory.shared.i18n.I18n;
031 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
032 import org.apache.directory.shared.ldap.exception.LdapException;
033 import org.apache.directory.shared.ldap.schema.AttributeType;
034 import org.apache.directory.shared.ldap.util.StringTools;
035 import org.slf4j.Logger;
036 import org.slf4j.LoggerFactory;
037
038
039 /**
040 * A server side entry attribute aware of schema.
041 *
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 * @version $Rev$, $Date$
044 */
045 public final class DefaultServerAttribute extends DefaultClientAttribute implements EntryAttribute
046 {
047 public static final long serialVersionUID = 1L;
048
049 /** logger for reporting errors that might not be handled properly upstream */
050 private static final Logger LOG = LoggerFactory.getLogger( DefaultServerAttribute.class );
051
052 //-------------------------------------------------------------------------
053 // Constructors
054 //-------------------------------------------------------------------------
055 /**
056 *
057 * Creates a new instance of DefaultServerAttribute, by copying
058 * another attribute, which can be a ClientAttribute. If the other
059 * attribute is a ServerAttribute, it will be copied.
060 *
061 * @param attributeType The attribute's type
062 * @param attribute The attribute to be copied
063 */
064 public DefaultServerAttribute( AttributeType attributeType, EntryAttribute attribute )
065 {
066 // Copy the common values. isHR is only available on a ServerAttribute
067 this.attributeType = attributeType;
068 this.id = attribute.getId();
069 this.upId = attribute.getUpId();
070
071 if ( attributeType == null )
072 {
073 isHR = attribute.isHR();
074
075 // Copy all the values
076 for ( Value<?> value:attribute )
077 {
078 add( value.clone() );
079 }
080 }
081 else
082 {
083
084 isHR = attributeType.getSyntax().isHumanReadable();
085
086 // Copy all the values
087 for ( Value<?> clientValue:attribute )
088 {
089 Value<?> serverValue = null;
090
091 // We have to convert the value first
092 if ( clientValue instanceof StringValue )
093 {
094 if ( isHR )
095 {
096 serverValue = new StringValue( attributeType, clientValue.getString() );
097 }
098 else
099 {
100 // We have to convert the value to a binary value first
101 serverValue = new BinaryValue( attributeType,
102 clientValue.getBytes() );
103 }
104 }
105 else if ( clientValue instanceof BinaryValue )
106 {
107 if ( isHR )
108 {
109 // We have to convert the value to a String value first
110 serverValue = new StringValue( attributeType,
111 clientValue.getString() );
112 }
113 else
114 {
115 serverValue = new BinaryValue( attributeType, clientValue.getBytes() );
116 }
117 }
118
119 add( serverValue );
120 }
121 }
122 }
123
124
125 // maybe have some additional convenience constructors which take
126 // an initial value as a string or a byte[]
127 /**
128 * Create a new instance of a EntryAttribute, without ID nor value.
129 *
130 * @param attributeType the attributeType for the empty attribute added into the entry
131 */
132 public DefaultServerAttribute( AttributeType attributeType )
133 {
134 if ( attributeType == null )
135 {
136 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
137 }
138
139 setAttributeType( attributeType );
140 }
141
142
143 /**
144 * Create a new instance of a EntryAttribute, without value.
145 *
146 * @param upId the ID for the added attributeType
147 * @param attributeType the added AttributeType
148 */
149 public DefaultServerAttribute( String upId, AttributeType attributeType )
150 {
151 if ( attributeType == null )
152 {
153 String message = I18n.err( I18n.ERR_04442 );
154 LOG.error( message );
155 throw new IllegalArgumentException( message );
156 }
157
158 setAttributeType( attributeType );
159 setUpId( upId, attributeType );
160 }
161
162
163 /**
164 * Doc me more!
165 *
166 * If the value does not correspond to the same attributeType, then it's
167 * wrapped value is copied into a new Value which uses the specified
168 * attributeType.
169 *
170 * @param attributeType the attribute type according to the schema
171 * @param vals an initial set of values for this attribute
172 */
173 public DefaultServerAttribute( AttributeType attributeType, Value<?>... vals )
174 {
175 this( null, attributeType, vals );
176 }
177
178
179 /**
180 * Doc me more!
181 *
182 * If the value does not correspond to the same attributeType, then it's
183 * wrapped value is copied into a new Value which uses the specified
184 * attributeType.
185 *
186 * Otherwise, the value is stored, but as a reference. It's not a copy.
187 *
188 * @param upId the ID of the added attribute
189 * @param attributeType the attribute type according to the schema
190 * @param vals an initial set of values for this attribute
191 */
192 public DefaultServerAttribute( String upId, AttributeType attributeType, Value<?>... vals )
193 {
194 if ( attributeType == null )
195 {
196 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
197 }
198
199 setAttributeType( attributeType );
200 setUpId( upId, attributeType );
201 add( vals );
202 }
203
204
205 /**
206 * Create a new instance of a EntryAttribute, without ID but with some values.
207 *
208 * @param attributeType The attributeType added on creation
209 * @param vals The added value for this attribute
210 */
211 public DefaultServerAttribute( AttributeType attributeType, String... vals )
212 {
213 this( null, attributeType, vals );
214 }
215
216
217 /**
218 * Create a new instance of a EntryAttribute.
219 *
220 * @param upId the ID for the added attribute
221 * @param attributeType The attributeType added on creation
222 * @param vals the added values for this attribute
223 */
224 public DefaultServerAttribute( String upId, AttributeType attributeType, String... vals )
225 {
226 if ( attributeType == null )
227 {
228 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
229 }
230
231 setAttributeType( attributeType );
232 add( vals );
233 setUpId( upId, attributeType );
234 }
235
236
237 /**
238 * Create a new instance of a EntryAttribute, with some byte[] values.
239 *
240 * @param attributeType The attributeType added on creation
241 * @param vals The value for the added attribute
242 */
243 public DefaultServerAttribute( AttributeType attributeType, byte[]... vals )
244 {
245 this( null, attributeType, vals );
246 }
247
248
249 /**
250 * Create a new instance of a EntryAttribute, with some byte[] values.
251 *
252 * @param upId the ID for the added attribute
253 * @param attributeType the AttributeType to be added
254 * @param vals the values for the added attribute
255 */
256 public DefaultServerAttribute( String upId, AttributeType attributeType, byte[]... vals )
257 {
258 if ( attributeType == null )
259 {
260 throw new IllegalArgumentException( I18n.err( I18n.ERR_04442 ) );
261 }
262
263 setAttributeType( attributeType );
264 add( vals );
265 setUpId( upId, attributeType );
266 }
267
268
269 //-------------------------------------------------------------------------
270 // API
271 //-------------------------------------------------------------------------
272 /**
273 * <p>
274 * Adds some values to this attribute. If the new values are already present in
275 * the attribute values, the method has no effect.
276 * </p>
277 * <p>
278 * The new values are added at the end of list of values.
279 * </p>
280 * <p>
281 * This method returns the number of values that were added.
282 * </p>
283 * <p>
284 * If the value's type is different from the attribute's type,
285 * the value is not added.
286 * </p>
287 * It's the responsibility of the caller to check if the stored
288 * values are consistent with the attribute's type.
289 * <p>
290 *
291 * @param vals some new values to be added which may be null
292 * @return the number of added values, or 0 if none has been added
293 */
294 public int add( byte[]... vals )
295 {
296 if ( !isHR )
297 {
298 int nbAdded = 0;
299
300 for ( byte[] val:vals )
301 {
302 Value<?> value = new BinaryValue( attributeType, val );
303
304 try
305 {
306 value.normalize();
307 }
308 catch( LdapException ne )
309 {
310 // The value can't be normalized : we don't add it.
311 LOG.error( I18n.err( I18n.ERR_04449, StringTools.dumpBytes( val ) ) );
312 return 0;
313 }
314
315 if ( add( value ) != 0 )
316 {
317 nbAdded++;
318 }
319 else
320 {
321 LOG.error( I18n.err( I18n.ERR_04450, StringTools.dumpBytes( val ) ) );
322 }
323 }
324
325 return nbAdded;
326 }
327 else
328 {
329 // We can't add Binary values into a String serverAttribute
330 return 0;
331 }
332 }
333
334
335 /**
336 * <p>
337 * Adds some values to this attribute. If the new values are already present in
338 * the attribute values, the method has no effect.
339 * </p>
340 * <p>
341 * The new values are added at the end of list of values.
342 * </p>
343 * <p>
344 * This method returns the number of values that were added.
345 * </p>
346 * If the value's type is different from the attribute's type,
347 * the value is not added.
348 *
349 * @param vals some new values to be added which may be null
350 * @return the number of added values, or 0 if none has been added
351 */
352 public int add( String... vals )
353 {
354 if ( isHR )
355 {
356 int nbAdded = 0;
357
358 for ( String val:vals )
359 {
360 Value<String> newValue = new StringValue( attributeType, val );
361
362 if ( add( newValue ) != 0 )
363 {
364 nbAdded++;
365 }
366 else
367 {
368 LOG.error( I18n.err( I18n.ERR_04450, val ) );
369 }
370 }
371
372 return nbAdded;
373 }
374 else
375 {
376 // We can't add String values into a Binary serverAttribute
377 return 0;
378 }
379 }
380
381
382 /**
383 * @see EntryAttribute#add(org.apache.directory.shared.ldap.entry.Value...)
384 *
385 * @return the number of added values into this attribute
386 */
387 public int add( Value<?>... vals )
388 {
389 int nbAdded = 0;
390
391 for ( Value<?> val:vals )
392 {
393 if ( attributeType.getSyntax().isHumanReadable() )
394 {
395 if ( ( val == null ) || val.isNull() )
396 {
397 Value<String> nullSV = new StringValue( attributeType, (String)null );
398
399 if ( values.add( nullSV ) )
400 {
401 nbAdded++;
402 }
403 }
404 else if ( val instanceof StringValue )
405 {
406 StringValue stringValue = (StringValue)val;
407
408 if ( stringValue.getAttributeType() == null )
409 {
410 stringValue.apply( attributeType );
411 }
412
413 if ( values.add( val ) )
414 {
415 nbAdded++;
416 }
417 }
418 else
419 {
420 String message = I18n.err( I18n.ERR_04451 );
421 LOG.error( message );
422 }
423 }
424 else
425 {
426 if ( val == null )
427 {
428 Value<byte[]> nullSV = new BinaryValue( attributeType, (byte[])null );
429
430 if ( values.add( nullSV ) )
431 {
432 nbAdded++;
433 }
434 }
435 else
436 {
437 if ( val instanceof BinaryValue )
438 {
439 BinaryValue binaryValue = (BinaryValue)val;
440
441 if ( binaryValue.getAttributeType() == null )
442 {
443 binaryValue = new BinaryValue( attributeType, val.getBytes() );
444 }
445
446 if ( values.add( binaryValue ) )
447 {
448 nbAdded++;
449 }
450 }
451 else
452 {
453 String message = I18n.err( I18n.ERR_04452 );
454 LOG.error( message );
455 }
456 }
457 }
458 }
459
460 return nbAdded;
461 }
462
463
464 /**
465 * Remove all the values from this attribute type, including a
466 * null value.
467 */
468 public void clear()
469 {
470 values.clear();
471 }
472
473
474 /**
475 * <p>
476 * Indicates whether all the specified values are attribute's values. If
477 * at least one value is not an attribute's value, this method will return
478 * <code>false</code>
479 * </p>
480 * <p>
481 * If the Attribute is HR, this method will returns <code>false</code>
482 * </p>
483 *
484 * @param vals the values
485 * @return true if this attribute contains all the values, otherwise false
486 */
487 public boolean contains( byte[]... vals )
488 {
489 if ( !isHR )
490 {
491 // Iterate through all the values, and quit if we
492 // don't find one in the values
493 for ( byte[] val:vals )
494 {
495 BinaryValue value = new BinaryValue( attributeType, val );
496
497 try
498 {
499 value.normalize();
500 }
501 catch ( LdapException ne )
502 {
503 return false;
504 }
505
506 if ( !values.contains( value ) )
507 {
508 return false;
509 }
510 }
511
512 return true;
513 }
514 else
515 {
516 return false;
517 }
518 }
519
520
521 /**
522 * <p>
523 * Indicates whether all the specified values are attribute's values. If
524 * at least one value is not an attribute's value, this method will return
525 * <code>false</code>
526 * </p>
527 * <p>
528 * If the Attribute is not HR, this method will returns <code>false</code>
529 * </p>
530 *
531 * @param vals the values
532 * @return true if this attribute contains all the values, otherwise false
533 */
534 public boolean contains( String... vals )
535 {
536 if ( isHR )
537 {
538 // Iterate through all the values, and quit if we
539 // don't find one in the values
540 for ( String val:vals )
541 {
542 StringValue value = new StringValue( attributeType, val );
543
544 if ( !values.contains( value ) )
545 {
546 return false;
547 }
548 }
549
550 return true;
551 }
552 else
553 {
554 return false;
555 }
556 }
557
558
559 /**
560 * <p>
561 * Indicates whether the specified values are some of the attribute's values.
562 * </p>
563 * <p>
564 * If the Attribute is HR, te metho will only accept String Values. Otherwise,
565 * it will only accept Binary values.
566 * </p>
567 *
568 * @param vals the values
569 * @return true if this attribute contains all the values, otherwise false
570 */
571 public boolean contains( Value<?>... vals )
572 {
573 // Iterate through all the values, and quit if we
574 // don't find one in the values. We have to separate the check
575 // depending on the isHR flag value.
576 if ( isHR )
577 {
578 for ( Value<?> val:vals )
579 {
580 if ( val instanceof StringValue )
581 {
582 StringValue stringValue = (StringValue)val;
583
584 if ( stringValue.getAttributeType() == null )
585 {
586 stringValue.apply( attributeType );
587 }
588
589 if ( !values.contains( val ) )
590 {
591 return false;
592 }
593 }
594 else
595 {
596 // Not a String value
597 return false;
598 }
599 }
600 }
601 else
602 {
603 for ( Value<?> val:vals )
604 {
605 if ( val instanceof BinaryValue )
606 {
607 if ( !values.contains( val ) )
608 {
609 return false;
610 }
611 }
612 else
613 {
614 // Not a Binary value
615 return false;
616 }
617 }
618 }
619
620 return true;
621 }
622
623
624 /**
625 * <p>
626 * Checks to see if this attribute is valid along with the values it contains.
627 * </p>
628 * <p>
629 * An attribute is valid if :
630 * <li>All of its values are valid with respect to the attributeType's syntax checker</li>
631 * <li>If the attributeType is SINGLE-VALUE, then no more than a value should be present</li>
632 *</p>
633 * @return true if the attribute and it's values are valid, false otherwise
634 * @throws NamingException if there is a failure to check syntaxes of values
635 */
636 public boolean isValid() throws LdapException
637 {
638 // First check if the attribute has more than one value
639 // if the attribute is supposed to be SINGLE_VALUE
640 if ( attributeType.isSingleValued() && ( values.size() > 1 ) )
641 {
642 return false;
643 }
644
645 // Check that we can have no value for this attributeType
646 if ( values.size() == 0 )
647 {
648 return attributeType.getSyntax().getSyntaxChecker().isValidSyntax( null );
649 }
650
651 for ( Value<?> value : values )
652 {
653 if ( ! value.isValid() )
654 {
655 return false;
656 }
657 }
658
659 return true;
660 }
661
662
663 /**
664 * @see EntryAttribute#remove(byte[]...)
665 *
666 * @return <code>true</code> if all the values shave been removed from this attribute
667 */
668 public boolean remove( byte[]... vals )
669 {
670 if ( isHR )
671 {
672 return false;
673 }
674
675 boolean removed = true;
676
677 for ( byte[] val:vals )
678 {
679 BinaryValue value = new BinaryValue( attributeType, val );
680 removed &= values.remove( value );
681 }
682
683 return removed;
684 }
685
686
687 /**
688 * @see EntryAttribute#remove(String...)
689 *
690 * @return <code>true</code> if all the values shave been removed from this attribute
691 */
692 public boolean remove( String... vals )
693 {
694 if ( !isHR )
695 {
696 return false;
697 }
698
699 boolean removed = true;
700
701 for ( String val:vals )
702 {
703 StringValue value = new StringValue( attributeType, val );
704 removed &= values.remove( value );
705 }
706
707 return removed;
708 }
709
710
711 /**
712 * @see EntryAttribute#remove(org.apache.directory.shared.ldap.entry.Value...)
713 *
714 * @return <code>true</code> if all the values shave been removed from this attribute
715 */
716 public boolean remove( Value<?>... vals )
717 {
718 boolean removed = true;
719
720 // Loop through all the values to remove. If one of
721 // them is not present, the method will return false.
722 // As the attribute may be HR or not, we have two separated treatments
723 if ( isHR )
724 {
725 for ( Value<?> val:vals )
726 {
727 if ( val instanceof StringValue )
728 {
729 StringValue stringValue = (StringValue)val;
730
731 if ( stringValue.getAttributeType() == null )
732 {
733 stringValue.apply( attributeType );
734 }
735
736 removed &= values.remove( stringValue );
737 }
738 else
739 {
740 removed = false;
741 }
742 }
743 }
744 else
745 {
746 for ( Value<?> val:vals )
747 {
748 if ( val instanceof BinaryValue )
749 {
750 BinaryValue binaryValue = (BinaryValue)val;
751
752 if ( binaryValue.getAttributeType() == null )
753 {
754 binaryValue = new BinaryValue( attributeType, (byte[])val.get() );
755 }
756
757 removed &= values.remove( binaryValue );
758 }
759 else
760 {
761 removed = false;
762 }
763 }
764 }
765
766 return removed;
767 }
768
769
770
771 /**
772 * <p>
773 * Overload the ClientAttribte isHR method : we can't change this flag
774 * for a ServerAttribute, as the HR is already set using the AttributeType.
775 * Set the attribute to Human Readable or to Binary.
776 * </p>
777 *
778 * @param isHR <code>true</code> for a Human Readable attribute,
779 * <code>false</code> for a Binary attribute.
780 */
781 public void setHR( boolean isHR )
782 {
783 // Do nothing...
784 }
785
786
787 /**
788 * <p>
789 * Overload the {@link DefaultClientAttribute#setId(String)} method.
790 * </p>
791 * <p>
792 * As the attributeType has already been set, we have to be sure that the
793 * argument is compatible with the attributeType's name.
794 * </p>
795 * <p>
796 * If the given ID is not compatible with the attributeType's possible
797 * names, the previously loaded ID will be kept.
798 * </p>
799 *
800 * @param id The attribute ID
801 */
802 public void setId( String id )
803 {
804 if ( !StringTools.isEmpty( StringTools.trim( id ) ) )
805 {
806 if ( attributeType.getName() == null )
807 {
808 // If the name is null, then we may have to store an OID
809 if ( OID.isOID( id ) && attributeType.getOid().equals( id ) )
810 {
811 // Everything is fine, store the upId.
812 // This should not happen...
813 super.setId( id );
814 }
815 }
816 else
817 {
818 // We have at least one name. Check that the normalized upId
819 // is one of those names. Otherwise, the upId may be an OID too.
820 // In this case, it must be equals to the attributeType OID.
821 String normId = StringTools.lowerCaseAscii( StringTools.trim( id ) );
822
823 for ( String atName:attributeType.getNames() )
824 {
825 if ( atName.equalsIgnoreCase( normId ) )
826 {
827 // Found ! We can store the upId and get out
828 super.setId( normId );
829 return;
830 }
831 }
832
833 // Last case, the UpId is an OID
834 if ( OID.isOID( normId ) && attributeType.getOid().equals( normId ) )
835 {
836 // We have an OID : stores it
837 super.setUpId( normId );
838 }
839 else
840 {
841 // The id is incorrect : this is not allowed
842 throw new IllegalArgumentException( I18n.err( I18n.ERR_04455, id, attributeType.getName() ) );
843 }
844 }
845 }
846 else
847 {
848 throw new IllegalArgumentException( I18n.err( I18n.ERR_04456 ) );
849 }
850 }
851
852
853 /**
854 * <p>
855 * Overload the {@link DefaultClientAttribute#setUpId(String)} method.
856 * </p>
857 * <p>
858 * As the attributeType has already been set, we have to be sure that the
859 * argument is compatible with the attributeType's name.
860 * </p>
861 * <p>
862 * If the given ID is not compatible with the attributeType's possible
863 * names, the previously loaded ID will be kept.
864 * </p>
865 *
866 * @param upId The attribute ID
867 *
868 public void setUpId( String upId )
869 {
870 if ( !StringTools.isEmpty( StringTools.trim( upId ) ) )
871 {
872 if ( attributeType.getName() == null )
873 {
874 // If the name is null, then we may have to store an OID
875 if ( OID.isOID( upId ) && attributeType.getOid().equals( upId ) )
876 {
877 // Everything is fine, store the upId.
878 // This should not happen...
879 super.setUpId( upId );
880 return;
881 }
882 }
883 else
884 {
885 // We have at least one name. Check that the normalized upId
886 // is one of those names. Otherwise, the upId may be an OID too.
887 // In this case, it must be equals to the attributeType OID.
888 String normUpId = StringTools.lowerCaseAscii( StringTools.trim( upId ) );
889
890 for ( String atId:attributeType.getNames() )
891 {
892 if ( atId.equalsIgnoreCase( normUpId ) )
893 {
894 // Found ! We can store the upId and get out
895 super.setUpId( upId );
896 return;
897 }
898 }
899
900 // Last case, the UpId is an OID
901 if ( OID.isOID( normUpId ) && attributeType.getOid().equals( normUpId ) )
902 {
903 // We have an OID : stores it
904 super.setUpId( upId );
905 return;
906 }
907
908 return;
909 }
910 }
911
912 return;
913 }
914
915
916 //-------------------------------------------------------------------------
917 // Serialization methods
918 //-------------------------------------------------------------------------
919
920 /**
921 * @see java.io.Externalizable#writeExternal(ObjectOutput)
922 *
923 * We can't use this method for a ServerAttribute, as we have to feed the value
924 * with an AttributeType object
925 */
926 public void writeExternal( ObjectOutput out ) throws IOException
927 {
928 throw new IllegalStateException( I18n.err( I18n.ERR_04454 ) );
929 }
930
931
932 /**
933 * @see Externalizable#writeExternal(ObjectOutput)
934 * <p>
935 *
936 * This is the place where we serialize attributes, and all theirs
937 * elements.
938 *
939 * The inner structure is the same as the client attribute, but we can't call
940 * it as we won't be able to serialize the serverValues
941 *
942 */
943 public void serialize( ObjectOutput out ) throws IOException
944 {
945 // Write the UPId (the id will be deduced from the upID)
946 out.writeUTF( upId );
947
948 // Write the HR flag, if not null
949 if ( isHR != null )
950 {
951 out.writeBoolean( true );
952 out.writeBoolean( isHR );
953 }
954 else
955 {
956 out.writeBoolean( false );
957 }
958
959 // Write the number of values
960 out.writeInt( size() );
961
962 if ( size() > 0 )
963 {
964 // Write each value
965 for ( Value<?> value:values )
966 {
967 // Write the value, using the correct method
968 if ( value instanceof StringValue )
969 {
970 ((StringValue)value).serialize( out );
971 }
972 else
973 {
974 ((BinaryValue)value).serialize( out );
975 }
976 }
977 }
978 }
979
980
981 /**
982 * @see java.io.Externalizable#readExternal(ObjectInput)
983 *
984 * We can't use this method for a ServerAttribute, as we have to feed the value
985 * with an AttributeType object
986 */
987 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
988 {
989 throw new IllegalStateException( I18n.err( I18n.ERR_04454 ) );
990 }
991
992
993 /**
994 * @see Externalizable#readExternal(ObjectInput)
995 */
996 public void deserialize( ObjectInput in ) throws IOException, ClassNotFoundException
997 {
998 // Read the ID and the UPId
999 upId = in.readUTF();
1000
1001 // Compute the id
1002 setUpId( upId );
1003
1004 // Read the HR flag, if not null
1005 if ( in.readBoolean() )
1006 {
1007 isHR = in.readBoolean();
1008 }
1009
1010 // Read the number of values
1011 int nbValues = in.readInt();
1012
1013 if ( nbValues > 0 )
1014 {
1015 for ( int i = 0; i < nbValues; i++ )
1016 {
1017 Value<?> value = null;
1018
1019 if ( isHR )
1020 {
1021 value = new StringValue( attributeType );
1022 ((StringValue)value).deserialize( in );
1023 }
1024 else
1025 {
1026 value = new BinaryValue( attributeType );
1027 ((BinaryValue)value).deserialize( in );
1028 }
1029
1030 try
1031 {
1032 value.normalize();
1033 }
1034 catch ( LdapException ne )
1035 {
1036 // Do nothing...
1037 }
1038
1039 values.add( value );
1040 }
1041 }
1042 }
1043
1044
1045 //-------------------------------------------------------------------------
1046 // Overloaded Object class methods
1047 //-------------------------------------------------------------------------
1048 /**
1049 * Clone an attribute. All the element are duplicated, so a modification on
1050 * the original object won't affect the cloned object, as a modification
1051 * on the cloned object has no impact on the original object
1052 *
1053 * @return a clone of the current attribute
1054 */
1055 public EntryAttribute clone()
1056 {
1057 // clone the structure by cloner the inherited class
1058 EntryAttribute clone = (EntryAttribute)super.clone();
1059
1060 // We are done !
1061 return clone;
1062 }
1063
1064
1065 /**
1066 * @see Object#equals(Object)
1067 *
1068 * @return <code>true</code> if the two objects are equal
1069 */
1070 public boolean equals( Object obj )
1071 {
1072 if ( obj == this )
1073 {
1074 return true;
1075 }
1076
1077 if ( ! (obj instanceof EntryAttribute ) )
1078 {
1079 return false;
1080 }
1081
1082 EntryAttribute other = (EntryAttribute)obj;
1083
1084 if ( !attributeType.equals( other.getAttributeType() ) )
1085 {
1086 return false;
1087 }
1088
1089 if ( values.size() != other.size() )
1090 {
1091 return false;
1092 }
1093
1094 for ( Value<?> val:values )
1095 {
1096 if ( ! other.contains( val ) )
1097 {
1098 return false;
1099 }
1100 }
1101
1102 return true;
1103 }
1104
1105
1106 /**
1107 * The hashCode is based on the id, the isHR flag and
1108 * on the internal values.
1109 *
1110 * @see Object#hashCode()
1111 *
1112 * @return the instance's hash code
1113 */
1114 public int hashCode()
1115 {
1116 int h = super.hashCode();
1117
1118 if ( attributeType != null )
1119 {
1120 h = h*17 + attributeType.hashCode();
1121 }
1122
1123 return h;
1124 }
1125
1126
1127 /**
1128 * @see Object#toString()
1129 *
1130 * @return A String representation of this instance
1131 */
1132 public String toString()
1133 {
1134 StringBuilder sb = new StringBuilder();
1135
1136 if ( ( values != null ) && ( values.size() != 0 ) )
1137 {
1138 for ( Value<?> value:values )
1139 {
1140 sb.append( " " ).append( upId ).append( ": " );
1141
1142 if ( value.isNull() )
1143 {
1144 sb.append( "''" );
1145 }
1146 else
1147 {
1148 sb.append( value );
1149 }
1150
1151 sb.append( '\n' );
1152 }
1153 }
1154 else
1155 {
1156 sb.append( " " ).append( upId ).append( ": (null)\n" );
1157 }
1158
1159 return sb.toString();
1160 }
1161 }