001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.server.types;
028
029import java.util.*;
030import java.util.regex.Pattern;
031
032import org.forgerock.i18n.LocalizableMessage;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.opends.server.core.DirectoryServer;
035import org.opends.server.util.StaticUtils;
036
037import static org.opends.messages.SchemaMessages.*;
038
039/**
040 * An RFC 3672 subtree specification.
041 * <p>
042 * This implementation extends RFC 3672 by supporting search filters
043 * for specification filters. More specifically, the
044 * {@code Refinement} product has been extended as follows:
045 *
046 * <pre>
047 *  Refinement = item / and / or / not / Filter
048 *
049 *  Filter     = dquote *SafeUTF8Character dquote
050 * </pre>
051 *
052 * @see <a href="http://tools.ietf.org/html/rfc3672">RFC 3672 -
053 *      Subentries in the Lightweight Directory Access Protocol (LDAP)
054 *      </a>
055 */
056@org.opends.server.types.PublicAPI(
057    stability = org.opends.server.types.StabilityLevel.VOLATILE,
058    mayInstantiate = false,
059    mayExtend = true,
060    mayInvoke = false)
061public final class SubtreeSpecification
062{
063
064  /**
065   * RFC 3672 subtree specification AND refinement. This type of
066   * refinement filters entries based on all of the underlying
067   * refinements being <code>true</code>.
068   */
069  public static final class AndRefinement extends Refinement
070  {
071    /** The set of refinements which must all be true. */
072    private final Collection<Refinement> refinementSet;
073
074
075
076    /**
077     * Create a new AND refinement.
078     *
079     * @param refinementSet
080     *          The set of refinements which must all be
081     *          <code>true</code>.
082     */
083    public AndRefinement(final Collection<Refinement> refinementSet)
084    {
085      this.refinementSet = refinementSet;
086    }
087
088
089
090    /** {@inheritDoc} */
091    @Override
092    public boolean equals(final Object obj)
093    {
094      if (this == obj)
095      {
096        return true;
097      }
098
099      if (obj instanceof AndRefinement)
100      {
101        final AndRefinement other = (AndRefinement) obj;
102
103        return refinementSet.equals(other.refinementSet);
104      }
105
106      return false;
107    }
108
109
110
111    /** {@inheritDoc} */
112    @Override
113    public int hashCode()
114    {
115      return refinementSet.hashCode();
116    }
117
118
119
120    /** {@inheritDoc} */
121    @Override
122    public boolean matches(final Entry entry)
123    {
124      for (final Refinement refinement : refinementSet)
125      {
126        if (!refinement.matches(entry))
127        {
128          return false;
129        }
130      }
131
132      // All sub-refinements matched.
133      return true;
134    }
135
136
137
138    /** {@inheritDoc} */
139    @Override
140    public StringBuilder toString(final StringBuilder builder)
141    {
142      switch (refinementSet.size())
143      {
144      case 0:
145        // Do nothing.
146        break;
147      case 1:
148        refinementSet.iterator().next().toString(builder);
149        break;
150      default:
151        builder.append("and:{");
152        final Iterator<Refinement> iterator = refinementSet
153            .iterator();
154        iterator.next().toString(builder);
155        while (iterator.hasNext())
156        {
157          builder.append(", ");
158          iterator.next().toString(builder);
159        }
160        builder.append("}");
161        break;
162      }
163
164      return builder;
165    }
166  }
167
168
169
170  /**
171   * A refinement which uses a search filter.
172   */
173  public static final class FilterRefinement extends Refinement
174  {
175    /** The search filter. */
176    private final SearchFilter filter;
177
178
179
180    /**
181     * Create a new filter refinement.
182     *
183     * @param filter
184     *          The search filter.
185     */
186    public FilterRefinement(final SearchFilter filter)
187    {
188      this.filter = filter;
189    }
190
191
192
193    /** {@inheritDoc} */
194    @Override
195    public boolean equals(final Object obj)
196    {
197      if (this == obj)
198      {
199        return true;
200      }
201
202      if (obj instanceof FilterRefinement)
203      {
204        final FilterRefinement other = (FilterRefinement) obj;
205        return filter.equals(other.filter);
206      }
207
208      return false;
209    }
210
211
212
213    /** {@inheritDoc} */
214    @Override
215    public int hashCode()
216    {
217      return filter.hashCode();
218    }
219
220
221
222    /** {@inheritDoc} */
223    @Override
224    public boolean matches(final Entry entry)
225    {
226      try
227      {
228        return filter.matchesEntry(entry);
229      }
230      catch (final DirectoryException e)
231      {
232        // TODO: need to decide what to do with the exception here.
233        // It's probably safe to ignore, but we could log it perhaps.
234        return false;
235      }
236    }
237
238
239
240    /** {@inheritDoc} */
241    @Override
242    public StringBuilder toString(final StringBuilder builder)
243    {
244      StaticUtils.toRFC3641StringValue(builder, filter.toString());
245      return builder;
246    }
247  }
248
249
250
251  /**
252   * RFC 3672 subtree specification Item refinement. This type of
253   * refinement filters entries based on the presence of a specified
254   * object class.
255   */
256  public static final class ItemRefinement extends Refinement
257  {
258    /** The item's object class. */
259    private final String objectClass;
260
261    /** The item's normalized object class. */
262    private final String normalizedObjectClass;
263
264
265
266    /**
267     * Create a new item refinement.
268     *
269     * @param objectClass
270     *          The item's object class.
271     */
272    public ItemRefinement(final String objectClass)
273    {
274      this.objectClass = objectClass;
275      this.normalizedObjectClass = StaticUtils
276          .toLowerCase(objectClass.trim());
277    }
278
279
280
281    /** {@inheritDoc} */
282    @Override
283    public boolean equals(final Object obj)
284    {
285      if (this == obj)
286      {
287        return true;
288      }
289
290      if (obj instanceof ItemRefinement)
291      {
292        final ItemRefinement other = (ItemRefinement) obj;
293
294        return normalizedObjectClass
295            .equals(other.normalizedObjectClass);
296      }
297
298      return false;
299    }
300
301
302
303    /** {@inheritDoc} */
304    @Override
305    public int hashCode()
306    {
307      return normalizedObjectClass.hashCode();
308    }
309
310
311
312    /** {@inheritDoc} */
313    @Override
314    public boolean matches(final Entry entry)
315    {
316      final ObjectClass oc = DirectoryServer.getObjectClass(normalizedObjectClass);
317      return oc != null && entry.hasObjectClass(oc);
318    }
319
320
321
322    /** {@inheritDoc} */
323    @Override
324    public StringBuilder toString(final StringBuilder builder)
325    {
326      builder.append("item:");
327      builder.append(objectClass);
328      return builder;
329    }
330  }
331
332
333
334  /**
335   * RFC 3672 subtree specification NOT refinement. This type of
336   * refinement filters entries based on the underlying refinement
337   * being <code>false</code>
338   * .
339   */
340  public static final class NotRefinement extends Refinement
341  {
342    /** The inverted refinement. */
343    private final Refinement refinement;
344
345
346
347    /**
348     * Create a new NOT refinement.
349     *
350     * @param refinement
351     *          The refinement which must be <code>false</code>.
352     */
353    public NotRefinement(final Refinement refinement)
354    {
355      this.refinement = refinement;
356    }
357
358
359
360    /** {@inheritDoc} */
361    @Override
362    public boolean equals(final Object obj)
363    {
364      if (this == obj)
365      {
366        return true;
367      }
368
369      if (obj instanceof NotRefinement)
370      {
371        final NotRefinement other = (NotRefinement) obj;
372
373        return refinement.equals(other.refinement);
374      }
375
376      return false;
377    }
378
379
380
381    /** {@inheritDoc} */
382    @Override
383    public int hashCode()
384    {
385      return refinement.hashCode();
386    }
387
388
389
390    /** {@inheritDoc} */
391    @Override
392    public boolean matches(final Entry entry)
393    {
394      return !refinement.matches(entry);
395    }
396
397
398
399    /** {@inheritDoc} */
400    @Override
401    public StringBuilder toString(final StringBuilder builder)
402    {
403      builder.append("not:");
404      return refinement.toString(builder);
405    }
406  }
407
408
409
410  /**
411   * RFC 3672 subtree specification OR refinement. This type of
412   * refinement filters entries based on at least one of the
413   * underlying refinements being <code>true</code>.
414   */
415  public static final class OrRefinement extends Refinement
416  {
417    /** The set of refinements of which at least one must be true. */
418    private final Collection<Refinement> refinementSet;
419
420
421
422    /**
423     * Create a new OR refinement.
424     *
425     * @param refinementSet
426     *          The set of refinements of which at least one must be
427     *          <code>true</code>.
428     */
429    public OrRefinement(final Collection<Refinement> refinementSet)
430    {
431      this.refinementSet = refinementSet;
432    }
433
434
435
436    /** {@inheritDoc} */
437    @Override
438    public boolean equals(final Object obj)
439    {
440      if (this == obj)
441      {
442        return true;
443      }
444
445      if (obj instanceof AndRefinement)
446      {
447        final AndRefinement other = (AndRefinement) obj;
448
449        return refinementSet.equals(other.refinementSet);
450      }
451
452      return false;
453    }
454
455
456
457    /** {@inheritDoc} */
458    @Override
459    public int hashCode()
460    {
461      return refinementSet.hashCode();
462    }
463
464
465
466    /** {@inheritDoc} */
467    @Override
468    public boolean matches(final Entry entry)
469    {
470      for (final Refinement refinement : refinementSet)
471      {
472        if (refinement.matches(entry))
473        {
474          return true;
475        }
476      }
477
478      // No sub-refinements matched.
479      return false;
480    }
481
482
483
484    /** {@inheritDoc} */
485    @Override
486    public StringBuilder toString(final StringBuilder builder)
487    {
488      switch (refinementSet.size())
489      {
490      case 0:
491        // Do nothing.
492        break;
493      case 1:
494        refinementSet.iterator().next().toString(builder);
495        break;
496      default:
497        builder.append("or:{");
498        final Iterator<Refinement> iterator = refinementSet
499            .iterator();
500        iterator.next().toString(builder);
501        while (iterator.hasNext())
502        {
503          builder.append(", ");
504          iterator.next().toString(builder);
505        }
506        builder.append("}");
507        break;
508      }
509
510      return builder;
511    }
512  }
513
514
515
516  /**
517   * Abstract interface for RFC3672 specification filter refinements.
518   */
519  public static abstract class Refinement
520  {
521    /**
522     * Create a new RFC3672 specification filter refinement.
523     */
524    protected Refinement()
525    {
526      // No implementation required.
527    }
528
529
530
531    /** {@inheritDoc} */
532    @Override
533    public abstract boolean equals(Object obj);
534
535
536
537    /** {@inheritDoc} */
538    @Override
539    public abstract int hashCode();
540
541
542
543    /**
544     * Check if the refinement matches the given entry.
545     *
546     * @param entry
547     *          The filterable entry.
548     * @return Returns <code>true</code> if the entry matches the
549     *         refinement, or <code>false</code> otherwise.
550     */
551    public abstract boolean matches(Entry entry);
552
553
554
555    /** {@inheritDoc} */
556    @Override
557    public final String toString()
558    {
559      final StringBuilder builder = new StringBuilder();
560
561      return toString(builder).toString();
562    }
563
564
565
566    /**
567     * Append the string representation of the refinement to the
568     * provided strin builder.
569     *
570     * @param builder
571     *          The string builder.
572     * @return The string builder.
573     */
574    public abstract StringBuilder toString(StringBuilder builder);
575  }
576
577
578
579  /**
580   * Internal utility class which can be used by sub-classes to help
581   * parse string representations.
582   */
583  protected static final class Parser
584  {
585    /** Text scanner used to parse the string value. */
586    private final Scanner scanner;
587
588    /** Pattern used to detect left braces. */
589    private static Pattern LBRACE = Pattern.compile("\\{.*");
590    /** Pattern used to parse left braces. */
591    private static Pattern LBRACE_TOKEN = Pattern.compile("\\{");
592    /** Pattern used to detect right braces. */
593    private static Pattern RBRACE = Pattern.compile("\\}.*");
594    /** Pattern used to parse right braces. */
595    private static Pattern RBRACE_TOKEN = Pattern.compile("\\}");
596    /** Pattern used to detect comma separators. */
597    private static Pattern SEP = Pattern.compile(",.*");
598    /** Pattern used to parse comma separators. */
599    private static Pattern SEP_TOKEN = Pattern.compile(",");
600    /** Pattern used to detect colon separators. */
601    private static Pattern COLON = Pattern.compile(":.*");
602    /** Pattern used to parse colon separators. */
603    private static Pattern COLON_TOKEN = Pattern.compile(":");
604    /** Pattern used to detect integer values. */
605    private static Pattern INT = Pattern.compile("\\d.*");
606    /** Pattern used to parse integer values. */
607    private static Pattern INT_TOKEN = Pattern.compile("\\d+");
608    /** Pattern used to detect name values. */
609    private static Pattern NAME = Pattern.compile("[\\w_;-].*");
610    /** Pattern used to parse name values. */
611    private static Pattern NAME_TOKEN = Pattern.compile("[\\w_;-]+");
612    /** Pattern used to detect RFC3641 string values. */
613    private static Pattern STRING_VALUE = Pattern.compile("\".*");
614    /** Pattern used to parse RFC3641 string values. */
615    private static Pattern STRING_VALUE_TOKEN = Pattern
616        .compile("\"([^\"]|(\"\"))*\"");
617
618
619
620    /**
621     * Create a new parser for a subtree specification string value.
622     *
623     * @param value
624     *          The subtree specification string value.
625     */
626    public Parser(final String value)
627    {
628      this.scanner = new Scanner(value);
629    }
630
631
632
633    /**
634     * Determine if there are remaining tokens.
635     *
636     * @return <code>true</code> if and only if there are remaining
637     *         tokens.
638     */
639    public boolean hasNext()
640    {
641      return scanner.hasNext();
642    }
643
644
645
646    /**
647     * Determine if the next token is a right-brace character.
648     *
649     * @return <code>true</code> if and only if the next token is a
650     *         valid right brace character.
651     */
652    public boolean hasNextRightBrace()
653    {
654      return scanner.hasNext(RBRACE);
655    }
656
657
658
659    /**
660     * Scans the next token of the input as a non-negative
661     * <code>int</code> value.
662     *
663     * @return The name value scanned from the input.
664     * @throws InputMismatchException
665     *           If the next token is not a valid non-negative integer
666     *           string.
667     * @throws NoSuchElementException
668     *           If input is exhausted.
669     */
670    public int nextInt() throws InputMismatchException,
671        NoSuchElementException
672    {
673      final String s = nextValue(INT, INT_TOKEN);
674      return Integer.parseInt(s);
675    }
676
677
678
679    /**
680     * Scans the next token of the input as a key value.
681     *
682     * @return The lower-case key value scanned from the input.
683     * @throws InputMismatchException
684     *           If the next token is not a valid key string.
685     * @throws NoSuchElementException
686     *           If input is exhausted.
687     */
688    public String nextKey() throws InputMismatchException,
689        NoSuchElementException
690    {
691      return StaticUtils.toLowerCase(scanner.next());
692    }
693
694
695
696    /**
697     * Scans the next token of the input as a name value.
698     * <p>
699     * A name is any string containing only alpha-numeric characters
700     * or hyphens, semi-colons, or underscores.
701     *
702     * @return The name value scanned from the input.
703     * @throws InputMismatchException
704     *           If the next token is not a valid name string.
705     * @throws NoSuchElementException
706     *           If input is exhausted.
707     */
708    public String nextName() throws InputMismatchException,
709        NoSuchElementException
710    {
711      return nextValue(NAME, NAME_TOKEN);
712    }
713
714
715
716    /**
717     * Scans the next tokens of the input as a set of specific
718     * exclusions encoded according to the SpecificExclusion
719     * production in RFC 3672.
720     *
721     * @param chopBefore
722     *          The set of chop before local names.
723     * @param chopAfter
724     *          The set of chop after local names.
725     * @throws InputMismatchException
726     *           If the common component did not have a valid syntax.
727     * @throws NoSuchElementException
728     *           If input is exhausted.
729     * @throws DirectoryException
730     *           If an error occurred when attempting to parse a
731     *           DN value.
732     */
733    public void nextSpecificExclusions(final Set<DN> chopBefore,
734        final Set<DN> chopAfter) throws InputMismatchException,
735        NoSuchElementException, DirectoryException
736    {
737
738      // Skip leading open-brace.
739      skipLeftBrace();
740
741      // Parse each chop DN in the sequence.
742      boolean isFirstValue = true;
743      while (true)
744      {
745        // Make sure that there is a closing brace.
746        if (hasNextRightBrace())
747        {
748          skipRightBrace();
749          break;
750        }
751
752        // Make sure that there is a comma separator if this is not
753        // the first element.
754        if (!isFirstValue)
755        {
756          skipSeparator();
757        }
758        else
759        {
760          isFirstValue = false;
761        }
762
763        // Parse each chop specification which is of the form
764        // <type>:<value>.
765        final String type = StaticUtils.toLowerCase(nextName());
766        skipColon();
767        if (type.equals("chopbefore"))
768        {
769          chopBefore.add(DN.valueOf(nextStringValue()));
770        }
771        else if (type.equals("chopafter"))
772        {
773          chopAfter.add(DN.valueOf(nextStringValue()));
774        }
775        else
776        {
777          throw new java.util.InputMismatchException();
778        }
779      }
780    }
781
782
783
784    /**
785     * Scans the next token of the input as a string quoted according
786     * to the StringValue production in RFC 3641.
787     * <p>
788     * The return string has its outer double quotes removed and any
789     * escape inner double quotes unescaped.
790     *
791     * @return The string value scanned from the input.
792     * @throws InputMismatchException
793     *           If the next token is not a valid string.
794     * @throws NoSuchElementException
795     *           If input is exhausted.
796     */
797    public String nextStringValue() throws InputMismatchException,
798        NoSuchElementException
799    {
800      final String s = nextValue(STRING_VALUE, STRING_VALUE_TOKEN);
801      return s.substring(1, s.length() - 1).replace("\"\"", "\"");
802    }
803
804
805
806    /**
807     * Skip a colon separator.
808     *
809     * @throws InputMismatchException
810     *           If the next token is not a colon separator character.
811     * @throws NoSuchElementException
812     *           If input is exhausted.
813     */
814    public void skipColon() throws InputMismatchException,
815        NoSuchElementException
816    {
817      nextValue(COLON, COLON_TOKEN);
818    }
819
820
821
822    /**
823     * Skip a left-brace character.
824     *
825     * @throws InputMismatchException
826     *           If the next token is not a left-brace character.
827     * @throws NoSuchElementException
828     *           If input is exhausted.
829     */
830    public void skipLeftBrace() throws InputMismatchException,
831        NoSuchElementException
832    {
833      nextValue(LBRACE, LBRACE_TOKEN);
834    }
835
836
837
838    /**
839     * Skip a right-brace character.
840     *
841     * @throws InputMismatchException
842     *           If the next token is not a right-brace character.
843     * @throws NoSuchElementException
844     *           If input is exhausted.
845     */
846    public void skipRightBrace() throws InputMismatchException,
847        NoSuchElementException
848    {
849      nextValue(RBRACE, RBRACE_TOKEN);
850    }
851
852
853
854    /**
855     * Skip a comma separator.
856     *
857     * @throws InputMismatchException
858     *           If the next token is not a comma separator character.
859     * @throws NoSuchElementException
860     *           If input is exhausted.
861     */
862    public void skipSeparator() throws InputMismatchException,
863        NoSuchElementException
864    {
865      nextValue(SEP, SEP_TOKEN);
866    }
867
868
869
870    /**
871     * Parse the next token from the string using the specified
872     * patterns.
873     *
874     * @param head
875     *          The pattern used to determine if the next token is a
876     *          possible match.
877     * @param content
878     *          The pattern used to parse the token content.
879     * @return The next token matching the <code>content</code>
880     *         pattern.
881     * @throws InputMismatchException
882     *           If the next token does not match the
883     *           <code>content</code> pattern.
884     * @throws NoSuchElementException
885     *           If input is exhausted.
886     */
887    private String nextValue(final Pattern head,
888        final Pattern content)
889        throws InputMismatchException, NoSuchElementException
890    {
891      if (!scanner.hasNext())
892      {
893        throw new java.util.NoSuchElementException();
894      }
895
896      if (!scanner.hasNext(head))
897      {
898        throw new java.util.InputMismatchException();
899      }
900
901      final String s = scanner.findInLine(content);
902      if (s == null)
903      {
904        throw new java.util.InputMismatchException();
905      }
906
907      return s;
908    }
909  }
910
911
912
913  /**
914   * Parses the string argument as an RFC3672 subtree specification.
915   *
916   * @param rootDN
917   *          The DN of the subtree specification's base entry.
918   * @param s
919   *          The string to be parsed.
920   * @return The RFC3672 subtree specification represented by the
921   *         string argument.
922   * @throws DirectoryException
923   *           If the string does not contain a parsable relative
924   *           subtree specification.
925   */
926  public static SubtreeSpecification valueOf(final DN rootDN,
927      final String s) throws DirectoryException
928  {
929
930    // Default values.
931    DN relativeBaseDN = null;
932
933    int minimum = -1;
934    int maximum = -1;
935
936    final HashSet<DN> chopBefore = new HashSet<>();
937    final HashSet<DN> chopAfter = new HashSet<>();
938
939    Refinement refinement = null;
940
941    // Value must have an opening left brace.
942    final Parser parser = new Parser(s);
943    boolean isValid = true;
944
945    try
946    {
947      parser.skipLeftBrace();
948
949      // Parse each element of the value sequence.
950      boolean isFirst = true;
951
952      while (true)
953      {
954        if (parser.hasNextRightBrace())
955        {
956          // Make sure that there is a closing brace and no trailing text.
957          parser.skipRightBrace();
958
959          if (parser.hasNext())
960          {
961            throw new java.util.InputMismatchException();
962          }
963          break;
964        }
965
966        // Make sure that there is a comma separator if this is not
967        // the first element.
968        if (!isFirst)
969        {
970          parser.skipSeparator();
971        }
972        else
973        {
974          isFirst = false;
975        }
976
977        final String key = parser.nextKey();
978        if (key.equals("base"))
979        {
980          if (relativeBaseDN != null)
981          {
982            // Relative base DN specified more than once.
983            throw new InputMismatchException();
984          }
985          relativeBaseDN = DN.valueOf(parser.nextStringValue());
986        }
987        else if (key.equals("minimum"))
988        {
989          if (minimum != -1)
990          {
991            // Minimum specified more than once.
992            throw new InputMismatchException();
993          }
994          minimum = parser.nextInt();
995        }
996        else if (key.equals("maximum"))
997        {
998          if (maximum != -1)
999          {
1000            // Maximum specified more than once.
1001            throw new InputMismatchException();
1002          }
1003          maximum = parser.nextInt();
1004        }
1005        else if (key.equals("specificationfilter"))
1006        {
1007          if (refinement != null)
1008          {
1009            // Refinements specified more than once.
1010            throw new InputMismatchException();
1011          }
1012
1013          // First try normal search filter before RFC3672
1014          // refinements.
1015          try
1016          {
1017            final SearchFilter filter = SearchFilter
1018                .createFilterFromString(parser.nextStringValue());
1019            refinement = new FilterRefinement(filter);
1020          }
1021          catch (final InputMismatchException e)
1022          {
1023            refinement = parseRefinement(parser);
1024          }
1025        }
1026        else if (key.equals("specificexclusions"))
1027        {
1028          if (!chopBefore.isEmpty() || !chopAfter.isEmpty())
1029          {
1030            // Specific exclusions specified more than once.
1031            throw new InputMismatchException();
1032          }
1033
1034          parser.nextSpecificExclusions(chopBefore, chopAfter);
1035        }
1036        else
1037        {
1038          throw new InputMismatchException();
1039        }
1040      }
1041
1042      // Make default minimum value is 0.
1043      if (minimum < 0)
1044      {
1045        minimum = 0;
1046      }
1047
1048      // Check that the maximum, if specified, is gte the minimum.
1049      if (maximum >= 0 && maximum < minimum)
1050      {
1051        isValid = false;
1052      }
1053    }
1054    catch (final NoSuchElementException e)
1055    {
1056      isValid = false;
1057    }
1058
1059    if (isValid)
1060    {
1061      return new SubtreeSpecification(rootDN, relativeBaseDN,
1062          minimum, maximum, chopBefore, chopAfter, refinement);
1063    }
1064    else
1065    {
1066      final LocalizableMessage message =
1067        ERR_ATTR_SYNTAX_RFC3672_SUBTREE_SPECIFICATION_INVALID.get(s);
1068      throw new DirectoryException(
1069          ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1070    }
1071  }
1072
1073
1074
1075  /**
1076   * Parse a single refinement.
1077   *
1078   * @param parser
1079   *          The active subtree specification parser.
1080   * @return The parsed refinement.
1081   * @throws InputMismatchException
1082   *           If the common component did not have a valid syntax.
1083   * @throws NoSuchElementException
1084   *           If input is exhausted.
1085   */
1086  private static Refinement parseRefinement(final Parser parser)
1087      throws InputMismatchException, NoSuchElementException
1088  {
1089    // Get the type of refinement.
1090    final String type = StaticUtils.toLowerCase(parser.nextName());
1091
1092    // Skip the colon separator.
1093    parser.skipColon();
1094
1095    if (type.equals("item"))
1096    {
1097      return new ItemRefinement(parser.nextName());
1098    }
1099    else if (type.equals("not"))
1100    {
1101      final Refinement refinement = parseRefinement(parser);
1102      return new NotRefinement(refinement);
1103    }
1104    else if (type.equals("and"))
1105    {
1106      final ArrayList<Refinement> refinements =
1107        parseRefinementSet(parser);
1108      return new AndRefinement(refinements);
1109    }
1110    else if (type.equals("or"))
1111    {
1112      final ArrayList<Refinement> refinements =
1113        parseRefinementSet(parser);
1114      return new OrRefinement(refinements);
1115    }
1116    else
1117    {
1118      // Unknown refinement type.
1119      throw new InputMismatchException();
1120    }
1121  }
1122
1123
1124
1125  /**
1126   * Parse a list of refinements.
1127   *
1128   * @param parser
1129   *          The active subtree specification parser.
1130   * @return The parsed refinement list.
1131   * @throws InputMismatchException
1132   *           If the common component did not have a valid syntax.
1133   * @throws NoSuchElementException
1134   *           If input is exhausted.
1135   */
1136  private static ArrayList<Refinement> parseRefinementSet(
1137      final Parser parser) throws InputMismatchException,
1138      NoSuchElementException
1139  {
1140    final ArrayList<Refinement> refinements = new ArrayList<>();
1141
1142    // Skip leading open-brace.
1143    parser.skipLeftBrace();
1144
1145    // Parse each chop DN in the sequence.
1146    boolean isFirstValue = true;
1147    while (true)
1148    {
1149      // Make sure that there is a closing brace.
1150      if (parser.hasNextRightBrace())
1151      {
1152        parser.skipRightBrace();
1153        break;
1154      }
1155
1156      // Make sure that there is a comma separator if this is not
1157      // the first element.
1158      if (!isFirstValue)
1159      {
1160        parser.skipSeparator();
1161      }
1162      else
1163      {
1164        isFirstValue = false;
1165      }
1166
1167      // Parse each sub-refinement.
1168      final Refinement refinement = parseRefinement(parser);
1169      refinements.add(refinement);
1170    }
1171
1172    return refinements;
1173  }
1174
1175
1176
1177  /** The absolute base of the subtree. */
1178  private final DN baseDN;
1179
1180  /** Optional minimum depth (<=0 means unlimited). */
1181  private final int minimumDepth;
1182  /** Optional maximum depth (<0 means unlimited). */
1183  private final int maximumDepth;
1184
1185  /** Optional set of chop before absolute DNs (mapping to their local-names). */
1186  private final Map<DN, DN> chopBefore;
1187
1188  /** Optional set of chop after absolute DNs (mapping to their local-names). */
1189  private final Map<DN, DN> chopAfter;
1190
1191  /** The root DN. */
1192  private final DN rootDN;
1193
1194  /** The optional relative base DN. */
1195  private final DN relativeBaseDN;
1196
1197  /** The optional specification filter refinements. */
1198  private final Refinement refinements;
1199
1200
1201
1202  /**
1203   * Create a new RFC3672 subtree specification.
1204   *
1205   * @param rootDN
1206   *          The root DN of the subtree.
1207   * @param relativeBaseDN
1208   *          The relative base DN (or {@code null} if not
1209   *          specified).
1210   * @param minimumDepth
1211   *          The minimum depth (less than or equal to 0 means unlimited).
1212   * @param maximumDepth
1213   *          The maximum depth (less than 0 means unlimited).
1214   * @param chopBefore
1215   *          The set of chop before local names (relative to the
1216   *          relative base DN), or {@code null} if there are
1217   *          none.
1218   * @param chopAfter
1219   *          The set of chop after local names (relative to the
1220   *          relative base DN), or {@code null} if there are
1221   *          none.
1222   * @param refinements
1223   *          The optional specification filter refinements, or
1224   *          {@code null} if there are none.
1225   */
1226  public SubtreeSpecification(final DN rootDN,
1227      final DN relativeBaseDN, final int minimumDepth,
1228      final int maximumDepth, final Iterable<DN> chopBefore,
1229      final Iterable<DN> chopAfter, final Refinement refinements)
1230  {
1231    this.baseDN = relativeBaseDN == null ? rootDN : rootDN
1232        .child(relativeBaseDN);
1233    this.minimumDepth = minimumDepth;
1234    this.maximumDepth = maximumDepth;
1235
1236    if (chopBefore != null && chopBefore.iterator().hasNext())
1237    {
1238      // Calculate the absolute DNs.
1239      final TreeMap<DN, DN> map = new TreeMap<>();
1240      for (final DN localName : chopBefore)
1241      {
1242        map.put(baseDN.child(localName), localName);
1243      }
1244      this.chopBefore = Collections.unmodifiableMap(map);
1245    }
1246    else
1247    {
1248      // No chop before specifications.
1249      this.chopBefore = Collections.emptyMap();
1250    }
1251
1252    if (chopAfter != null && chopAfter.iterator().hasNext())
1253    {
1254      // Calculate the absolute DNs.
1255      final TreeMap<DN, DN> map = new TreeMap<>();
1256      for (final DN localName : chopAfter)
1257      {
1258        map.put(baseDN.child(localName), localName);
1259      }
1260      this.chopAfter = Collections.unmodifiableMap(map);
1261    }
1262    else
1263    {
1264      // No chop after specifications.
1265      this.chopAfter = Collections.emptyMap();
1266    }
1267
1268    this.rootDN = rootDN;
1269    this.relativeBaseDN = relativeBaseDN;
1270    this.refinements = refinements;
1271  }
1272
1273
1274
1275  /**
1276   * Indicates whether the provided object is logically equal to this
1277   * subtree specification object.
1278   *
1279   * @param obj
1280   *          The object for which to make the determination.
1281   * @return {@code true} if the provided object is logically equal
1282   *         to this subtree specification object, or {@code false}
1283   *         if not.
1284   */
1285  @Override
1286  public boolean equals(final Object obj)
1287  {
1288
1289    if (this == obj)
1290    {
1291      return true;
1292    }
1293
1294    if (obj instanceof SubtreeSpecification)
1295    {
1296      final SubtreeSpecification other = (SubtreeSpecification) obj;
1297
1298      if (!commonComponentsEquals(other))
1299      {
1300        return false;
1301      }
1302
1303      if (!getBaseDN().equals(other.getBaseDN()))
1304      {
1305        return false;
1306      }
1307
1308      if (refinements != null)
1309      {
1310        return refinements.equals(other.refinements);
1311      }
1312      else
1313      {
1314        return refinements == other.refinements;
1315      }
1316    }
1317
1318    return false;
1319  }
1320
1321
1322
1323  /**
1324   * Get the absolute base DN of the subtree specification.
1325   *
1326   * @return Returns the absolute base DN of the subtree
1327   *         specification.
1328   */
1329  public DN getBaseDN()
1330  {
1331    return baseDN;
1332  }
1333
1334
1335
1336  /**
1337   * Get the set of chop after relative DNs.
1338   *
1339   * @return Returns the set of chop after relative DNs.
1340   */
1341  public Iterable<DN> getChopAfter()
1342  {
1343
1344    return chopAfter.values();
1345  }
1346
1347
1348
1349  /**
1350   * Get the set of chop before relative DNs.
1351   *
1352   * @return Returns the set of chop before relative DNs.
1353   */
1354  public Iterable<DN> getChopBefore()
1355  {
1356
1357    return chopBefore.values();
1358  }
1359
1360
1361
1362  /**
1363   * Get the maximum depth of the subtree specification.
1364   *
1365   * @return Returns the maximum depth (less than 0 indicates unlimited depth).
1366   */
1367  public int getMaximumDepth()
1368  {
1369    return maximumDepth;
1370  }
1371
1372
1373
1374  /**
1375   * Get the minimum depth of the subtree specification.
1376   *
1377   * @return Returns the minimum depth (<=0 indicates unlimited
1378   *         depth).
1379   */
1380  public int getMinimumDepth()
1381  {
1382    return minimumDepth;
1383  }
1384
1385
1386
1387  /**
1388   * Get the specification filter refinements.
1389   *
1390   * @return Returns the specification filter refinements, or
1391   *         <code>null</code> if none were specified.
1392   */
1393  public Refinement getRefinements()
1394  {
1395    return refinements;
1396  }
1397
1398
1399
1400  /**
1401   * Get the relative base DN.
1402   *
1403   * @return Returns the relative base DN or <code>null</code> if
1404   *         none was specified.
1405   */
1406  public DN getRelativeBaseDN()
1407  {
1408    return relativeBaseDN;
1409  }
1410
1411
1412
1413  /**
1414   * Get the root DN.
1415   *
1416   * @return Returns the root DN.
1417   */
1418  public DN getRootDN()
1419  {
1420    return rootDN;
1421  }
1422
1423
1424
1425  /**
1426   * Retrieves the hash code for this subtree specification object.
1427   *
1428   * @return The hash code for this subtree specification object.
1429   */
1430  @Override
1431  public int hashCode()
1432  {
1433
1434    int hash = commonComponentsHashCode();
1435
1436    hash = hash * 31 + getBaseDN().hashCode();
1437
1438    if (refinements != null)
1439    {
1440      hash = hash * 31 + refinements.hashCode();
1441    }
1442
1443    return hash;
1444  }
1445
1446
1447
1448  /**
1449   * Determine if the specified DN is within the scope of the subtree
1450   * specification.
1451   *
1452   * @param dn
1453   *          The distinguished name.
1454   * @return Returns <code>true</code> if the DN is within the scope
1455   *         of the subtree specification, or <code>false</code>
1456   *         otherwise.
1457   */
1458  public boolean isDNWithinScope(final DN dn)
1459  {
1460
1461    if (!dn.isDescendantOf(baseDN))
1462    {
1463      return false;
1464    }
1465
1466    // Check minimum and maximum depths.
1467    final int baseRDNCount = baseDN.size();
1468
1469    if (minimumDepth > 0)
1470    {
1471      final int entryRDNCount = dn.size();
1472
1473      if (entryRDNCount - baseRDNCount < minimumDepth)
1474      {
1475        return false;
1476      }
1477    }
1478
1479    if (maximumDepth >= 0)
1480    {
1481      final int entryRDNCount = dn.size();
1482
1483      if (entryRDNCount - baseRDNCount > maximumDepth)
1484      {
1485        return false;
1486      }
1487    }
1488
1489    // Check exclusions.
1490    for (final DN chopBeforeDN : chopBefore.keySet())
1491    {
1492      if (dn.isDescendantOf(chopBeforeDN))
1493      {
1494        return false;
1495      }
1496    }
1497
1498    for (final DN chopAfterDN : chopAfter.keySet())
1499    {
1500      if (!dn.equals(chopAfterDN) && dn.isDescendantOf(chopAfterDN))
1501      {
1502        return false;
1503      }
1504    }
1505
1506    // Everything seemed to match.
1507    return true;
1508  }
1509
1510
1511
1512  /**
1513   * Determine if an entry is within the scope of the subtree
1514   * specification.
1515   *
1516   * @param entry
1517   *          The entry.
1518   * @return {@code true} if the entry is within the scope of the
1519   *         subtree specification, or {@code false} if not.
1520   */
1521  public boolean isWithinScope(final Entry entry)
1522  {
1523    return isDNWithinScope(entry.getName())
1524        && (refinements == null || refinements.matches(entry));
1525  }
1526
1527
1528
1529  /**
1530   * Retrieves a string representation of this subtree specification
1531   * object.
1532   *
1533   * @return A string representation of this subtree specification
1534   *         object.
1535   */
1536  @Override
1537  public String toString()
1538  {
1539    final StringBuilder builder = new StringBuilder();
1540    return toString(builder).toString();
1541  }
1542
1543
1544
1545  /**
1546   * Append the string representation of the subtree specification to
1547   * the provided string builder.
1548   *
1549   * @param builder
1550   *          The string builder.
1551   * @return The string builder.
1552   */
1553  public StringBuilder toString(final StringBuilder builder)
1554  {
1555
1556    boolean isFirstElement = true;
1557
1558    // Output the optional base DN.
1559    builder.append("{");
1560    if (relativeBaseDN != null && !relativeBaseDN.isRootDN())
1561    {
1562      builder.append(" base ");
1563      StaticUtils.toRFC3641StringValue(builder,
1564          relativeBaseDN.toString());
1565      isFirstElement = false;
1566    }
1567
1568    // Output the optional specific exclusions.
1569    final Iterable<DN> chopBefore = getChopBefore();
1570    final Iterable<DN> chopAfter = getChopAfter();
1571
1572    if (chopBefore.iterator().hasNext()
1573        || chopAfter.iterator().hasNext())
1574    {
1575      if (!isFirstElement)
1576      {
1577        builder.append(",");
1578      }
1579      else
1580      {
1581        isFirstElement = false;
1582      }
1583      builder.append(" specificExclusions { ");
1584
1585      boolean isFirst = true;
1586
1587      for (final DN dn : chopBefore)
1588      {
1589        if (!isFirst)
1590        {
1591          builder.append(", chopBefore:");
1592        }
1593        else
1594        {
1595          builder.append("chopBefore:");
1596          isFirst = false;
1597        }
1598        StaticUtils.toRFC3641StringValue(builder, dn.toString());
1599      }
1600
1601      for (final DN dn : chopAfter)
1602      {
1603        if (!isFirst)
1604        {
1605          builder.append(", chopAfter:");
1606        }
1607        else
1608        {
1609          builder.append("chopAfter:");
1610          isFirst = false;
1611        }
1612        StaticUtils.toRFC3641StringValue(builder, dn.toString());
1613      }
1614
1615      builder.append(" }");
1616    }
1617
1618    // Output the optional minimum depth.
1619    if (getMinimumDepth() > 0)
1620    {
1621      if (!isFirstElement)
1622      {
1623        builder.append(",");
1624      }
1625      else
1626      {
1627        isFirstElement = false;
1628      }
1629      builder.append(" minimum ");
1630      builder.append(getMinimumDepth());
1631    }
1632
1633    // Output the optional maximum depth.
1634    if (getMaximumDepth() >= 0)
1635    {
1636      if (!isFirstElement)
1637      {
1638        builder.append(",");
1639      }
1640      else
1641      {
1642        isFirstElement = false;
1643      }
1644      builder.append(" maximum ");
1645      builder.append(getMaximumDepth());
1646    }
1647
1648    // Output the optional refinements.
1649    if (refinements != null)
1650    {
1651      if (!isFirstElement)
1652      {
1653        builder.append(",");
1654      }
1655      else
1656      {
1657        isFirstElement = false;
1658      }
1659      builder.append(" specificationFilter ");
1660      refinements.toString(builder);
1661    }
1662
1663    builder.append(" }");
1664
1665    return builder;
1666  }
1667
1668
1669
1670  /**
1671   * Determine if the common components of this subtree specification
1672   * are equal to the common components of another subtree
1673   * specification.
1674   *
1675   * @param other
1676   *          The other subtree specification.
1677   * @return Returns <code>true</code> if they are equal.
1678   */
1679  private boolean commonComponentsEquals(
1680      final SubtreeSpecification other)
1681  {
1682    if (this == other)
1683    {
1684      return true;
1685    }
1686    return minimumDepth == other.minimumDepth
1687        && maximumDepth == other.maximumDepth
1688        && chopBefore.values().equals(other.chopBefore.values())
1689        && chopAfter.values().equals(other.chopAfter.values());
1690  }
1691
1692
1693
1694  /**
1695   * Get a hash code of the subtree specification's common components.
1696   *
1697   * @return The computed hash code.
1698   */
1699  private int commonComponentsHashCode()
1700  {
1701    int hash = minimumDepth * 31 + maximumDepth;
1702    hash = hash * 31 + chopBefore.values().hashCode();
1703    hash = hash * 31 + chopAfter.values().hashCode();
1704    return hash;
1705  }
1706}