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