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 */
020 package org.apache.directory.shared.ldap.name;
021
022
023 import java.io.Externalizable;
024 import java.io.IOException;
025 import java.io.ObjectInput;
026 import java.io.ObjectOutput;
027 import java.util.Collection;
028 import java.util.Iterator;
029 import java.util.List;
030 import java.util.Map;
031 import java.util.Set;
032 import java.util.TreeSet;
033
034 import javax.naming.InvalidNameException;
035
036 import org.apache.commons.collections.MultiMap;
037 import org.apache.commons.collections.map.MultiValueMap;
038 import org.apache.directory.shared.ldap.entry.Value;
039 import org.apache.directory.shared.ldap.entry.client.ClientStringValue;
040 import org.apache.directory.shared.ldap.util.StringTools;
041 import org.slf4j.Logger;
042 import org.slf4j.LoggerFactory;
043
044
045 /**
046 * This class store the name-component part or the following BNF grammar (as of
047 * RFC2253, par. 3, and RFC1779, fig. 1) : <br> - <name-component> ::=
048 * <attributeType> <spaces> '=' <spaces>
049 * <attributeValue> <attributeTypeAndValues> <br> -
050 * <attributeTypeAndValues> ::= <spaces> '+' <spaces>
051 * <attributeType> <spaces> '=' <spaces>
052 * <attributeValue> <attributeTypeAndValues> | e <br> -
053 * <attributeType> ::= [a-zA-Z] <keychars> | <oidPrefix> [0-9]
054 * <digits> <oids> | [0-9] <digits> <oids> <br> -
055 * <keychars> ::= [a-zA-Z] <keychars> | [0-9] <keychars> | '-'
056 * <keychars> | e <br> - <oidPrefix> ::= 'OID.' | 'oid.' | e <br> -
057 * <oids> ::= '.' [0-9] <digits> <oids> | e <br> -
058 * <attributeValue> ::= <pairs-or-strings> | '#' <hexstring>
059 * |'"' <quotechar-or-pairs> '"' <br> - <pairs-or-strings> ::= '\'
060 * <pairchar> <pairs-or-strings> | <stringchar>
061 * <pairs-or-strings> | e <br> - <quotechar-or-pairs> ::=
062 * <quotechar> <quotechar-or-pairs> | '\' <pairchar>
063 * <quotechar-or-pairs> | e <br> - <pairchar> ::= ',' | '=' | '+' |
064 * '<' | '>' | '#' | ';' | '\' | '"' | [0-9a-fA-F] [0-9a-fA-F] <br> -
065 * <hexstring> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> <br> -
066 * <hexpairs> ::= [0-9a-fA-F] [0-9a-fA-F] <hexpairs> | e <br> -
067 * <digits> ::= [0-9] <digits> | e <br> - <stringchar> ::=
068 * [0x00-0xFF] - [,=+<>#;\"\n\r] <br> - <quotechar> ::= [0x00-0xFF] -
069 * [\"] <br> - <separator> ::= ',' | ';' <br> - <spaces> ::= ' '
070 * <spaces> | e <br>
071 * <br>
072 * A RDN is a part of a DN. It can be composed of many types, as in the RDN
073 * following RDN :<br>
074 * ou=value + cn=other value<br>
075 * <br>
076 * or <br>
077 * ou=value + ou=another value<br>
078 * <br>
079 * In this case, we have to store an 'ou' and a 'cn' in the RDN.<br>
080 * <br>
081 * The types are case insensitive. <br>
082 * Spaces before and after types and values are not stored.<br>
083 * Spaces before and after '+' are not stored.<br>
084 * <br>
085 * Thus, we can consider that the following RDNs are equals :<br>
086 * <br>
087 * 'ou=test 1'<br> ' ou=test 1'<br>
088 * 'ou =test 1'<br>
089 * 'ou= test 1'<br>
090 * 'ou=test 1 '<br> ' ou = test 1 '<br>
091 * <br>
092 * So are the following :<br>
093 * <br>
094 * 'ou=test 1+cn=test 2'<br>
095 * 'ou = test 1 + cn = test 2'<br> ' ou =test 1+ cn =test 2 ' <br>
096 * 'cn = test 2 +ou = test 1'<br>
097 * <br>
098 * but the following are not equal :<br>
099 * 'ou=test 1' <br>
100 * 'ou=test 1'<br>
101 * because we have more than one spaces inside the value.<br>
102 * <br>
103 * The Rdn is composed of one or more AttributeTypeAndValue (atav) Those atavs
104 * are ordered in the alphabetical natural order : a < b < c ... < z As the type
105 * are not case sensitive, we can say that a = A
106 *
107 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
108 * @version $Rev: 887710 $, $Date: 2009-12-06 17:50:22 +0100 (Dim, 06 déc 2009) $
109 */
110 public class Rdn implements Cloneable, Comparable, Externalizable, Iterable<AttributeTypeAndValue>
111 {
112 /** The LoggerFactory used by this class */
113 protected static final Logger LOG = LoggerFactory.getLogger( Rdn.class );
114
115 /**
116 * Declares the Serial Version Uid.
117 *
118 * @see <a
119 * href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always
120 * Declare Serial Version Uid</a>
121 */
122 private static final long serialVersionUID = 1L;
123
124 /** The User Provided RDN */
125 private String upName = null;
126
127 /** The normalized RDN */
128 private String normName = null;
129
130 /** The starting position of this RDN in the given string from which
131 * we have extracted the upName */
132 private int start;
133
134 /** The length of this RDN upName */
135 private int length;
136
137 /**
138 * Stores all couple type = value. We may have more than one type, if the
139 * '+' character appears in the AttributeTypeAndValue. This is a TreeSet,
140 * because we want the ATAVs to be sorted. An atav may contain more than one
141 * value. In this case, the values are String stored in a List.
142 */
143 private Set<AttributeTypeAndValue> atavs = null;
144
145 /**
146 * We also keep a set of types, in order to use manipulations. A type is
147 * connected with the atav it represents.
148 *
149 * Note : there is no Generic available classes in commons-collection...
150 */
151 @SuppressWarnings(
152 { "unchecked" })
153 private MultiMap atavTypes = new MultiValueMap();
154
155 /**
156 * We keep the type for a single valued RDN, to avoid the creation of an HashMap
157 */
158 private String atavType = null;
159
160 /**
161 * A simple AttributeTypeAndValue is used to store the Rdn for the simple
162 * case where we only have a single type=value. This will be 99.99% the
163 * case. This avoids the creation of a HashMap.
164 */
165 protected AttributeTypeAndValue atav = null;
166
167 /**
168 * The number of atavs. We store this number here to avoid complex
169 * manipulation of atav and atavs
170 */
171 private int nbAtavs = 0;
172
173 /** CompareTo() results */
174 public static final int UNDEFINED = Integer.MAX_VALUE;
175
176 /** Constant used in comparisons */
177 public static final int SUPERIOR = 1;
178
179 /** Constant used in comparisons */
180 public static final int INFERIOR = -1;
181
182 /** Constant used in comparisons */
183 public static final int EQUAL = 0;
184
185
186 /**
187 * A empty constructor.
188 */
189 public Rdn()
190 {
191 // Don't waste space... This is not so often we have multiple
192 // name-components in a RDN... So we won't initialize the Map and the
193 // treeSet.
194 upName = "";
195 normName = "";
196 }
197
198
199 /**
200 * A constructor that parse a String representing a RDN.
201 *
202 * @param rdn The String containing the RDN to parse
203 * @throws InvalidNameException If the RDN is invalid
204 */
205 public Rdn( String rdn ) throws InvalidNameException
206 {
207 start = 0;
208
209 if ( StringTools.isNotEmpty( rdn ) )
210 {
211 // Parse the string. The Rdn will be updated.
212 RdnParser.parse( rdn, this );
213
214 // create the internal normalized form
215 // and store the user provided form
216 normalize();
217 upName = rdn;
218 length = rdn.length();
219 }
220 else
221 {
222 upName = "";
223 normName = "";
224 length = 0;
225 }
226 }
227
228
229 /**
230 * A constructor that constructs a RDN from a type and a value. Constructs
231 * an Rdn from the given attribute type and value. The string attribute
232 * values are not interpreted as RFC 2253 formatted RDN strings. That is,
233 * the values are used literally (not parsed) and assumed to be un-escaped.
234 *
235 * @param upType The user provided type of the RDN
236 * @param upValue The user provided value of the RDN
237 * @param normType The normalized provided type of the RDN
238 * @param normValue The normalized provided value of the RDN
239 * @throws InvalidNameException If the RDN is invalid
240 */
241 public Rdn( String upType, String normType, String upValue, String normValue ) throws InvalidNameException
242 {
243 addAttributeTypeAndValue( upType, normType, new ClientStringValue( upValue ), new ClientStringValue( normValue ) );
244
245 upName = upType + '=' + upValue;
246 start = 0;
247 length = upName.length();
248 // create the internal normalized form
249 normalize();
250 }
251
252
253 /**
254 * A constructor that constructs a RDN from a type, a position and a length.
255 *
256 * @param start The starting point for this RDN in the user provided DN
257 * @param length The RDN's length
258 * @param upName The user provided name
259 * @param normName the normalized name
260 */
261 /* No protection */Rdn( int start, int length, String upName, String normName )
262 {
263 this.start = 0;
264 this.length = length;
265 this.upName = upName;
266 this.normName = normName;
267 }
268
269
270 /**
271 * Constructs an Rdn from the given rdn. The contents of the rdn are simply
272 * copied into the newly created
273 *
274 * @param rdn
275 * The non-null Rdn to be copied.
276 */
277 @SuppressWarnings(
278 { "unchecked" })
279 public Rdn( Rdn rdn )
280 {
281 nbAtavs = rdn.getNbAtavs();
282 this.normName = rdn.normName;
283 this.upName = rdn.getUpName();
284 this.start = rdn.start;
285 this.length = rdn.length;
286
287 switch ( rdn.getNbAtavs() )
288 {
289 case 0:
290 return;
291
292 case 1:
293 this.atav = ( AttributeTypeAndValue ) rdn.atav.clone();
294 return;
295
296 default:
297 // We must duplicate the treeSet and the hashMap
298 atavs = new TreeSet<AttributeTypeAndValue>();
299 atavTypes = new MultiValueMap();
300
301 for ( AttributeTypeAndValue currentAtav : rdn.atavs )
302 {
303 atavs.add( ( AttributeTypeAndValue ) currentAtav.clone() );
304 atavTypes.put( currentAtav.getNormType(), currentAtav );
305 }
306
307 return;
308 }
309 }
310
311
312 /**
313 * Transform the external representation of the current RDN to an internal
314 * normalized form where : - types are trimmed and lowercased - values are
315 * trimmed and lowercased
316 */
317 // WARNING : The protection level is left unspecified intentionnaly.
318 // We need this method to be visible from the DnParser class, but not
319 // from outside this package.
320 /* Unspecified protection */void normalize()
321 {
322 switch ( nbAtavs )
323 {
324 case 0:
325 // An empty RDN
326 normName = "";
327 break;
328
329 case 1:
330 // We have a single AttributeTypeAndValue
331 // We will trim and lowercase type and value.
332 if ( !atav.getNormValue().isBinary() )
333 {
334 normName = atav.getNormalizedValue();
335 }
336 else
337 {
338 normName = atav.getNormType() + "=#" + StringTools.dumpHexPairs( atav.getNormValue().getBytes() );
339 }
340
341 break;
342
343 default:
344 // We have more than one AttributeTypeAndValue
345 StringBuffer sb = new StringBuffer();
346
347 boolean isFirst = true;
348
349 for ( AttributeTypeAndValue ata : atavs )
350 {
351 if ( isFirst )
352 {
353 isFirst = false;
354 }
355 else
356 {
357 sb.append( '+' );
358 }
359
360 sb.append( ata.normalize() );
361 }
362
363 normName = sb.toString();
364 break;
365 }
366 }
367
368
369 /**
370 * Add a AttributeTypeAndValue to the current RDN
371 *
372 * @param upType The user provided type of the added RDN.
373 * @param type The normalized provided type of the added RDN.
374 * @param upValue The user provided value of the added RDN
375 * @param value The normalized provided value of the added RDN
376 * @throws InvalidNameException
377 * If the RDN is invalid
378 */
379 // WARNING : The protection level is left unspecified intentionally.
380 // We need this method to be visible from the DnParser class, but not
381 // from outside this package.
382 @SuppressWarnings(
383 { "unchecked" })
384 /* Unspecified protection */void addAttributeTypeAndValue( String upType, String type, Value<?> upValue,
385 Value<?> value ) throws InvalidNameException
386 {
387 // First, let's normalize the type
388 String normalizedType = StringTools.lowerCaseAscii( type );
389 Value<?> normalizedValue = value;
390
391 switch ( nbAtavs )
392 {
393 case 0:
394 // This is the first AttributeTypeAndValue. Just stores it.
395 atav = new AttributeTypeAndValue( upType, type, upValue, normalizedValue );
396 nbAtavs = 1;
397 atavType = normalizedType;
398 return;
399
400 case 1:
401 // We already have an atav. We have to put it in the HashMap
402 // before adding a new one.
403 // First, create the HashMap,
404 atavs = new TreeSet<AttributeTypeAndValue>();
405
406 // and store the existing AttributeTypeAndValue into it.
407 atavs.add( atav );
408 atavTypes = new MultiValueMap();
409 atavTypes.put( atavType, atav );
410
411 atav = null;
412
413 // Now, fall down to the commmon case
414 // NO BREAK !!!
415
416 default:
417 // add a new AttributeTypeAndValue
418 AttributeTypeAndValue newAtav = new AttributeTypeAndValue( upType, type, upValue, normalizedValue );
419 atavs.add( newAtav );
420 atavTypes.put( normalizedType, newAtav );
421
422 nbAtavs++;
423 break;
424
425 }
426 }
427
428
429 /**
430 * Add a AttributeTypeAndValue to the current RDN
431 *
432 * @param value The added AttributeTypeAndValue
433 */
434 // WARNING : The protection level is left unspecified intentionnaly.
435 // We need this method to be visible from the DnParser class, but not
436 // from outside this package.
437 @SuppressWarnings(
438 { "unchecked" })
439 /* Unspecified protection */void addAttributeTypeAndValue( AttributeTypeAndValue value )
440 {
441 String normalizedType = value.getNormType();
442
443 switch ( nbAtavs )
444 {
445 case 0:
446 // This is the first AttributeTypeAndValue. Just stores it.
447 this.atav = value;
448 nbAtavs = 1;
449 atavType = normalizedType;
450 return;
451
452 case 1:
453 // We already have an atav. We have to put it in the HashMap
454 // before adding a new one.
455 // First, create the HashMap,
456 atavs = new TreeSet<AttributeTypeAndValue>();
457
458 // and store the existing AttributeTypeAndValue into it.
459 atavs.add( this.atav );
460 atavTypes = new MultiValueMap();
461 atavTypes.put( atavType, this.atav );
462
463 this.atav = null;
464
465 // Now, fall down to the commmon case
466 // NO BREAK !!!
467
468 default:
469 // add a new AttributeTypeAndValue
470 atavs.add( value );
471 atavTypes.put( normalizedType, value );
472
473 nbAtavs++;
474 break;
475
476 }
477 }
478
479
480 /**
481 * Clear the RDN, removing all the AttributeTypeAndValues.
482 */
483 public void clear()
484 {
485 atav = null;
486 atavs = null;
487 atavType = null;
488 atavTypes.clear();
489 nbAtavs = 0;
490 normName = "";
491 upName = "";
492 start = -1;
493 length = 0;
494 }
495
496
497 /**
498 * Get the Value of the AttributeTypeAndValue which type is given as an
499 * argument.
500 *
501 * @param type
502 * The type of the NameArgument
503 * @return The Value to be returned, or null if none found.
504 * @throws InvalidNameException
505 */
506 public Object getValue( String type ) throws InvalidNameException
507 {
508 // First, let's normalize the type
509 String normalizedType = StringTools.lowerCaseAscii( StringTools.trim( type ) );
510
511 switch ( nbAtavs )
512 {
513 case 0:
514 return "";
515
516 case 1:
517 if ( StringTools.equals( atav.getNormType(), normalizedType ) )
518 {
519 return atav.getNormValue().get();
520 }
521
522 return "";
523
524 default:
525 if ( atavTypes.containsKey( normalizedType ) )
526 {
527 Collection<AttributeTypeAndValue> atavList = ( Collection<AttributeTypeAndValue> ) atavTypes.get( normalizedType );
528 StringBuffer sb = new StringBuffer();
529 boolean isFirst = true;
530
531 for ( AttributeTypeAndValue elem : atavList )
532 {
533 if ( isFirst )
534 {
535 isFirst = false;
536 }
537 else
538 {
539 sb.append( ',' );
540 }
541
542 sb.append( elem.getNormValue() );
543 }
544
545 return sb.toString();
546 }
547
548 return "";
549 }
550 }
551
552
553 /**
554 * Get the start position
555 *
556 * @return The start position in the DN
557 */
558 public int getStart()
559 {
560 return start;
561 }
562
563
564 /**
565 * Get the Rdn length
566 *
567 * @return The Rdn length
568 */
569 public int getLength()
570 {
571 return length;
572 }
573
574
575 /**
576 * Get the AttributeTypeAndValue which type is given as an argument. If we
577 * have more than one value associated with the type, we will return only
578 * the first one.
579 *
580 * @param type
581 * The type of the NameArgument to be returned
582 * @return The AttributeTypeAndValue, of null if none is found.
583 */
584 public AttributeTypeAndValue getAttributeTypeAndValue( String type )
585 {
586 // First, let's normalize the type
587 String normalizedType = StringTools.lowerCaseAscii( StringTools.trim( type ) );
588
589 switch ( nbAtavs )
590 {
591 case 0:
592 return null;
593
594 case 1:
595 if ( atav.getNormType().equals( normalizedType ) )
596 {
597 return atav;
598 }
599
600 return null;
601
602 default:
603 if ( atavTypes.containsKey( normalizedType ) )
604 {
605 Collection<AttributeTypeAndValue> atavList = ( Collection<AttributeTypeAndValue> ) atavTypes.get( normalizedType );
606 return atavList.iterator().next();
607 }
608
609 return null;
610 }
611 }
612
613
614 /**
615 * Retrieves the components of this RDN as an iterator of AttributeTypeAndValue.
616 * The effect on the iterator of updates to this RDN is undefined. If the
617 * RDN has zero components, an empty (non-null) iterator is returned.
618 *
619 * @return an iterator of the components of this RDN, each an AttributeTypeAndValue
620 */
621 public Iterator<AttributeTypeAndValue> iterator()
622 {
623 if ( nbAtavs == 1 || nbAtavs == 0 )
624 {
625 return new Iterator<AttributeTypeAndValue>()
626 {
627 private boolean hasMoreElement = nbAtavs == 1;
628
629
630 public boolean hasNext()
631 {
632 return hasMoreElement;
633 }
634
635
636 public AttributeTypeAndValue next()
637 {
638 AttributeTypeAndValue obj = atav;
639 hasMoreElement = false;
640 return obj;
641 }
642
643
644 public void remove()
645 {
646 // nothing to do
647 }
648 };
649 }
650 else
651 {
652 return atavs.iterator();
653 }
654 }
655
656
657 /**
658 * Clone the Rdn
659 *
660 * @return A clone of the current RDN
661 */
662 public Object clone()
663 {
664 try
665 {
666 Rdn rdn = ( Rdn ) super.clone();
667
668 // The AttributeTypeAndValue is immutable. We won't clone it
669
670 switch ( rdn.getNbAtavs() )
671 {
672 case 0:
673 break;
674
675 case 1:
676 rdn.atav = ( AttributeTypeAndValue ) this.atav.clone();
677 rdn.atavTypes = atavTypes;
678 break;
679
680 default:
681 // We must duplicate the treeSet and the hashMap
682 rdn.atavTypes = new MultiValueMap();
683 rdn.atavs = new TreeSet<AttributeTypeAndValue>();
684
685 for ( AttributeTypeAndValue currentAtav : this.atavs )
686 {
687 rdn.atavs.add( ( AttributeTypeAndValue ) currentAtav.clone() );
688 rdn.atavTypes.put( currentAtav.getNormType(), currentAtav );
689 }
690
691 break;
692 }
693
694 return rdn;
695 }
696 catch ( CloneNotSupportedException cnse )
697 {
698 throw new Error( "Assertion failure" );
699 }
700 }
701
702
703 /**
704 * Compares two RDNs. They are equals if :
705 * <li>their have the same number of NC (AttributeTypeAndValue)
706 * <li>each ATAVs are equals
707 * <li>comparison of type are done case insensitive
708 * <li>each value is equal, case sensitive
709 * <li>Order of ATAV is not important If the RDNs are not equals, a positive number is
710 * returned if the first RDN is greater, negative otherwise
711 *
712 * @param object
713 * @return 0 if both rdn are equals. -1 if the current RDN is inferior, 1 if
714 * the current Rdn is superior, UNDEFINED otherwise.
715 */
716 public int compareTo( Object object )
717 {
718 if ( object == null )
719 {
720 return SUPERIOR;
721 }
722
723 if ( object instanceof Rdn )
724 {
725 Rdn rdn = ( Rdn ) object;
726
727 if ( rdn.nbAtavs != nbAtavs )
728 {
729 // We don't have the same number of ATAVs. The Rdn which
730 // has the higher number of Atav is the one which is
731 // superior
732 return nbAtavs - rdn.nbAtavs;
733 }
734
735 switch ( nbAtavs )
736 {
737 case 0:
738 return EQUAL;
739
740 case 1:
741 return atav.compareTo( rdn.atav );
742
743 default:
744 // We have more than one value. We will
745 // go through all of them.
746
747 // the types are already normalized and sorted in the atavs TreeSet
748 // so we could compare the 1st with the 1st, then the 2nd with the 2nd, etc.
749 Iterator<AttributeTypeAndValue> localIterator = atavs.iterator();
750 Iterator<AttributeTypeAndValue> paramIterator = rdn.atavs.iterator();
751
752 while ( localIterator.hasNext() || paramIterator.hasNext() )
753 {
754 if ( !localIterator.hasNext() )
755 {
756 return SUPERIOR;
757 }
758 if ( !paramIterator.hasNext() )
759 {
760 return INFERIOR;
761 }
762
763 AttributeTypeAndValue localAtav = localIterator.next();
764 AttributeTypeAndValue paramAtav = paramIterator.next();
765 int result = localAtav.compareTo( paramAtav );
766 if ( result != EQUAL )
767 {
768 return result;
769 }
770 }
771
772 return EQUAL;
773 }
774 }
775 else
776 {
777 return UNDEFINED;
778 }
779 }
780
781
782 /**
783 * @return a String representation of the RDN
784 */
785 public String toString()
786 {
787 return normName == null ? "" : normName;
788 }
789
790
791 /**
792 * @return the user provided name
793 */
794 public String getUpName()
795 {
796 return upName;
797 }
798
799
800 /**
801 * @return The normalized name
802 */
803 public String getNormName()
804 {
805 return normName == null ? "" : normName;
806 }
807
808
809 /**
810 * Set the User Provided Name
811 * @param upName the User Provided dame
812 */
813 public void setUpName( String upName )
814 {
815 this.upName = upName;
816 }
817
818
819 /**
820 * @return Returns the nbAtavs.
821 */
822 public int getNbAtavs()
823 {
824 return nbAtavs;
825 }
826
827
828 /**
829 * Return the unique AttributeTypeAndValue, or the first one of we have more
830 * than one
831 *
832 * @return The first AttributeTypeAndValue of this RDN
833 */
834 public AttributeTypeAndValue getAtav()
835 {
836 switch ( nbAtavs )
837 {
838 case 0:
839 return null;
840
841 case 1:
842 return atav;
843
844 default:
845 return ( ( TreeSet<AttributeTypeAndValue> ) atavs ).first();
846 }
847 }
848
849
850 /**
851 * Return the user provided type, or the first one of we have more than one (the lowest)
852 *
853 * @return The first user provided type of this RDN
854 */
855 public String getUpType()
856 {
857 switch ( nbAtavs )
858 {
859 case 0:
860 return null;
861
862 case 1:
863 return atav.getUpType();
864
865 default:
866 return ( ( TreeSet<AttributeTypeAndValue> ) atavs ).first().getUpType();
867 }
868 }
869
870
871 /**
872 * Return the normalized type, or the first one of we have more than one (the lowest)
873 *
874 * @return The first normalized type of this RDN
875 */
876 public String getNormType()
877 {
878 switch ( nbAtavs )
879 {
880 case 0:
881 return null;
882
883 case 1:
884 return atav.getNormType();
885
886 default:
887 return ( ( TreeSet<AttributeTypeAndValue> ) atavs ).first().getNormType();
888 }
889 }
890
891
892 /**
893 * Return the value, or the first one of we have more than one (the lowest)
894 *
895 * @return The first value of this RDN
896 */
897 public Object getValue()
898 {
899 switch ( nbAtavs )
900 {
901 case 0:
902 return null;
903
904 case 1:
905 return atav.getNormValue().get();
906
907 default:
908 return ( ( TreeSet<AttributeTypeAndValue> ) atavs ).first().getNormValue().get();
909 }
910 }
911
912
913 /**
914 * Return the User Provided value
915 *
916 * @return The first User provided value of this RDN
917 */
918 public String getUpValue()
919 {
920 switch ( nbAtavs )
921 {
922 case 0:
923 return null;
924
925 case 1:
926 return atav.getUpValue().getString();
927
928 default:
929 return ( ( TreeSet<AttributeTypeAndValue> ) atavs ).first().getUpValue().getString();
930 }
931 }
932
933
934 /**
935 * Return the normalized value, or the first one of we have more than one (the lowest)
936 *
937 * @return The first normalized value of this RDN
938 */
939 public String getNormValue()
940 {
941 switch ( nbAtavs )
942 {
943 case 0:
944 return null;
945
946 case 1:
947 return atav.getNormValue().getString();
948
949 default:
950 return ( ( TreeSet<AttributeTypeAndValue> ) atavs ).first().getNormalizedValue();
951 }
952 }
953
954
955 /**
956 * Compares the specified Object with this Rdn for equality. Returns true if
957 * the given object is also a Rdn and the two Rdns represent the same
958 * attribute type and value mappings. The order of components in
959 * multi-valued Rdns is not significant.
960 *
961 * @param rdn
962 * Rdn to be compared for equality with this Rdn
963 * @return true if the specified object is equal to this Rdn
964 */
965 public boolean equals( Object rdn )
966 {
967 if ( this == rdn )
968 {
969 return true;
970 }
971
972 if ( !( rdn instanceof Rdn ) )
973 {
974 return false;
975 }
976
977 return compareTo( rdn ) == EQUAL;
978 }
979
980
981 /**
982 * Get the number of Attribute type and value of this Rdn
983 *
984 * @return The number of ATAVs in this Rdn
985 */
986 public int size()
987 {
988 return nbAtavs;
989 }
990
991
992 /**
993 * Unescape the given string according to RFC 2253 If in <string> form, a
994 * LDAP string representation asserted value can be obtained by replacing
995 * (left-to-right, non-recursively) each <pair> appearing in the <string> as
996 * follows: replace <ESC><ESC> with <ESC>; replace <ESC><special> with
997 * <special>; replace <ESC><hexpair> with the octet indicated by the
998 * <hexpair> If in <hexstring> form, a BER representation can be obtained
999 * from converting each <hexpair> of the <hexstring> to the octet indicated
1000 * by the <hexpair>
1001 *
1002 * @param value
1003 * The value to be unescaped
1004 * @return Returns a string value as a String, and a binary value as a byte
1005 * array.
1006 * @throws IllegalArgumentException -
1007 * When an Illegal value is provided.
1008 */
1009 public static Object unescapeValue( String value )
1010 {
1011 if ( StringTools.isEmpty( value ) )
1012 {
1013 return "";
1014 }
1015
1016 char[] chars = value.toCharArray();
1017
1018 if ( chars[0] == '#' )
1019 {
1020 if ( chars.length == 1 )
1021 {
1022 // The value is only containing a #
1023 return StringTools.EMPTY_BYTES;
1024 }
1025
1026 if ( ( chars.length % 2 ) != 1 )
1027 {
1028 throw new IllegalArgumentException( "This value is not in hex form, we have an odd number of hex chars" );
1029 }
1030
1031 // HexString form
1032 byte[] hexValue = new byte[( chars.length - 1 ) / 2];
1033 int pos = 0;
1034
1035 for ( int i = 1; i < chars.length; i += 2 )
1036 {
1037 if ( StringTools.isHex( chars, i ) && StringTools.isHex( chars, i + 1 ) )
1038 {
1039 hexValue[pos++] = StringTools.getHexValue( chars[i], chars[i + 1] );
1040 }
1041 else
1042 {
1043 throw new IllegalArgumentException( "This value is not in hex form" );
1044 }
1045 }
1046
1047 return hexValue;
1048 }
1049 else
1050 {
1051 boolean escaped = false;
1052 boolean isHex = false;
1053 byte pair = -1;
1054 int pos = 0;
1055
1056 byte[] bytes = new byte[chars.length * 6];
1057
1058 for ( int i = 0; i < chars.length; i++ )
1059 {
1060 if ( escaped )
1061 {
1062 escaped = false;
1063
1064 switch ( chars[i] )
1065 {
1066 case '\\':
1067 case '"':
1068 case '+':
1069 case ',':
1070 case ';':
1071 case '<':
1072 case '>':
1073 case '#':
1074 case '=':
1075 case ' ':
1076 bytes[pos++] = ( byte ) chars[i];
1077 break;
1078
1079 default:
1080 if ( StringTools.isHex( chars, i ) )
1081 {
1082 isHex = true;
1083 pair = ( ( byte ) ( StringTools.getHexValue( chars[i] ) << 4 ) );
1084 }
1085
1086 break;
1087 }
1088 }
1089 else
1090 {
1091 if ( isHex )
1092 {
1093 if ( StringTools.isHex( chars, i ) )
1094 {
1095 pair += StringTools.getHexValue( chars[i] );
1096 bytes[pos++] = pair;
1097 }
1098 }
1099 else
1100 {
1101 switch ( chars[i] )
1102 {
1103 case '\\':
1104 escaped = true;
1105 break;
1106
1107 // We must not have a special char
1108 // Specials are : '"', '+', ',', ';', '<', '>', ' ',
1109 // '#' and '='
1110 case '"':
1111 case '+':
1112 case ',':
1113 case ';':
1114 case '<':
1115 case '>':
1116 case '#':
1117 if ( i != 0 )
1118 {
1119 // '#' are allowed if not in first position
1120 bytes[pos++] = '#';
1121 break;
1122 }
1123 case '=':
1124 throw new IllegalArgumentException( "Unescaped special characters are not allowed" );
1125
1126 case ' ':
1127 if ( ( i == 0 ) || ( i == chars.length - 1 ) )
1128 {
1129 throw new IllegalArgumentException( "Unescaped special characters are not allowed" );
1130 }
1131 else
1132 {
1133 bytes[pos++] = ' ';
1134 break;
1135 }
1136
1137 default:
1138 if ( ( chars[i] >= 0 ) && ( chars[i] < 128 ) )
1139 {
1140 bytes[pos++] = ( byte ) chars[i];
1141 }
1142 else
1143 {
1144 byte[] result = StringTools.charToBytes( chars[i] );
1145 System.arraycopy( result, 0, bytes, pos, result.length );
1146 pos += result.length;
1147 }
1148
1149 break;
1150 }
1151 }
1152 }
1153 }
1154
1155 return StringTools.utf8ToString( bytes, pos );
1156 }
1157 }
1158
1159
1160 /**
1161 * Transform a value in a String, accordingly to RFC 2253
1162 *
1163 * @param value The attribute value to be escaped
1164 * @return The escaped string value.
1165 */
1166 public static String escapeValue( String value )
1167 {
1168 if ( StringTools.isEmpty( value ) )
1169 {
1170 return "";
1171 }
1172
1173 char[] chars = value.toCharArray();
1174 char[] newChars = new char[chars.length * 3];
1175 int pos = 0;
1176
1177 for ( int i = 0; i < chars.length; i++ )
1178 {
1179 switch ( chars[i] )
1180 {
1181 case ' ':
1182 if ( ( i > 0 ) && ( i < chars.length - 1 ) )
1183 {
1184 newChars[pos++] = chars[i];
1185 }
1186 else
1187 {
1188 newChars[pos++] = '\\';
1189 newChars[pos++] = chars[i];
1190 }
1191
1192 break;
1193
1194 case '#':
1195 if ( i != 0 )
1196 {
1197 newChars[pos++] = chars[i];
1198 }
1199 else
1200 {
1201 newChars[pos++] = '\\';
1202 newChars[pos++] = chars[i];
1203 }
1204
1205 break;
1206
1207 case '"':
1208 case '+':
1209 case ',':
1210 case ';':
1211 case '=':
1212 case '<':
1213 case '>':
1214 case '\\':
1215 newChars[pos++] = '\\';
1216 newChars[pos++] = chars[i];
1217 break;
1218
1219 case 0x7F:
1220 newChars[pos++] = '\\';
1221 newChars[pos++] = '7';
1222 newChars[pos++] = 'F';
1223 break;
1224
1225 case 0x00:
1226 case 0x01:
1227 case 0x02:
1228 case 0x03:
1229 case 0x04:
1230 case 0x05:
1231 case 0x06:
1232 case 0x07:
1233 case 0x08:
1234 case 0x09:
1235 case 0x0A:
1236 case 0x0B:
1237 case 0x0C:
1238 case 0x0D:
1239 case 0x0E:
1240 case 0x0F:
1241 newChars[pos++] = '\\';
1242 newChars[pos++] = '0';
1243 newChars[pos++] = StringTools.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
1244 break;
1245
1246 case 0x10:
1247 case 0x11:
1248 case 0x12:
1249 case 0x13:
1250 case 0x14:
1251 case 0x15:
1252 case 0x16:
1253 case 0x17:
1254 case 0x18:
1255 case 0x19:
1256 case 0x1A:
1257 case 0x1B:
1258 case 0x1C:
1259 case 0x1D:
1260 case 0x1E:
1261 case 0x1F:
1262 newChars[pos++] = '\\';
1263 newChars[pos++] = '1';
1264 newChars[pos++] = StringTools.dumpHex( ( byte ) ( chars[i] & 0x0F ) );
1265 break;
1266
1267 default:
1268 newChars[pos++] = chars[i];
1269 break;
1270
1271 }
1272 }
1273
1274 return new String( newChars, 0, pos );
1275 }
1276
1277
1278 /**
1279 * Transform a value in a String, accordingly to RFC 2253
1280 *
1281 * @param attrValue
1282 * The attribute value to be escaped
1283 * @return The escaped string value.
1284 */
1285 public static String escapeValue( byte[] attrValue )
1286 {
1287 if ( StringTools.isEmpty( attrValue ) )
1288 {
1289 return "";
1290 }
1291
1292 String value = StringTools.utf8ToString( attrValue );
1293
1294 return escapeValue( value );
1295 }
1296
1297
1298 /**
1299 * Gets the hashcode of this rdn.
1300 *
1301 * @see java.lang.Object#hashCode()
1302 * @return the instance's hash code
1303 */
1304 public int hashCode()
1305 {
1306 int result = 37;
1307
1308 switch ( nbAtavs )
1309 {
1310 case 0:
1311 // An empty RDN
1312 break;
1313
1314 case 1:
1315 // We have a single AttributeTypeAndValue
1316 result = result * 17 + atav.hashCode();
1317 break;
1318
1319 default:
1320 // We have more than one AttributeTypeAndValue
1321
1322 for ( AttributeTypeAndValue ata : atavs )
1323 {
1324 result = result * 17 + ata.hashCode();
1325 }
1326
1327 break;
1328 }
1329
1330 return result;
1331 }
1332
1333
1334 /**
1335 * @see Externalizable#readExternal(ObjectInput)<p>
1336 *
1337 * A RDN is composed of on to many ATAVs (AttributeType And Value).
1338 * We should write all those ATAVs sequencially, following the
1339 * structure :
1340 *
1341 * <li>nbAtavs</li> The number of ATAVs to write. Can't be 0.
1342 * <li>upName</li> The User provided RDN
1343 * <li>normName</li> The normalized RDN. It can be empty if the normalized
1344 * name equals the upName.
1345 * <li>atavs</li>
1346 * <p>
1347 * For each ATAV :<p>
1348 * <li>start</li> The position of this ATAV in the upName string
1349 * <li>length</li> The ATAV user provided length
1350 * <li>Call the ATAV write method</li> The ATAV itself
1351 *
1352 * @param out The stream into which the serialized RDN will be put
1353 * @throws IOException If the stream can't be written
1354 */
1355 public void writeExternal( ObjectOutput out ) throws IOException
1356 {
1357 out.writeInt( nbAtavs );
1358 out.writeUTF( upName );
1359
1360 if ( upName.equals( normName ) )
1361 {
1362 out.writeUTF( "" );
1363 }
1364 else
1365 {
1366 out.writeUTF( normName );
1367 }
1368
1369 out.writeInt( start );
1370 out.writeInt( length );
1371
1372 switch ( nbAtavs )
1373 {
1374 case 0:
1375 break;
1376
1377 case 1:
1378 out.writeObject( atav );
1379 break;
1380
1381 default:
1382 for ( AttributeTypeAndValue value : atavs )
1383 {
1384 out.writeObject( value );
1385 }
1386
1387 break;
1388 }
1389 }
1390
1391
1392 /**
1393 * @see Externalizable#readExternal(ObjectInput)
1394 *
1395 * We read back the data to create a new RDB. The structure
1396 * read is exposed in the {@link Rdn#writeExternal(ObjectOutput)}
1397 * method<p>
1398 *
1399 * @param in The input stream from which the RDN will be read
1400 * @throws IOException If we can't read from the input stream
1401 * @throws ClassNotFoundException If we can't create a new RDN
1402 */
1403 public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException
1404 {
1405 // Read the ATAV number
1406 nbAtavs = in.readInt();
1407
1408 // Read the UPName
1409 upName = in.readUTF();
1410
1411 // Read the normName
1412 normName = in.readUTF();
1413
1414 if ( StringTools.isEmpty( normName ) )
1415 {
1416 normName = upName;
1417 }
1418
1419 start = in.readInt();
1420 length = in.readInt();
1421
1422 switch ( nbAtavs )
1423 {
1424 case 0:
1425 atav = null;
1426 break;
1427
1428 case 1:
1429 atav = ( AttributeTypeAndValue ) in.readObject();
1430 atavType = atav.getNormType();
1431
1432 break;
1433
1434 default:
1435 atavs = new TreeSet<AttributeTypeAndValue>();
1436
1437 atavTypes = new MultiValueMap();
1438
1439 for ( int i = 0; i < nbAtavs; i++ )
1440 {
1441 AttributeTypeAndValue value = ( AttributeTypeAndValue ) in.readObject();
1442 atavs.add( value );
1443 atavTypes.put( value.getNormType(), value );
1444 }
1445
1446 atav = null;
1447 atavType = null;
1448
1449 break;
1450 }
1451 }
1452 }