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