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