| New file |
| | |
| | | /* |
| | | * CDDL HEADER START |
| | | * |
| | | * The contents of this file are subject to the terms of the |
| | | * Common Development and Distribution License, Version 1.0 only |
| | | * (the "License"). You may not use this file except in compliance |
| | | * with the License. |
| | | * |
| | | * You can obtain a copy of the license at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE |
| | | * or https://OpenDS.dev.java.net/OpenDS.LICENSE. |
| | | * See the License for the specific language governing permissions |
| | | * and limitations under the License. |
| | | * |
| | | * When distributing Covered Code, include this CDDL HEADER in each |
| | | * file and include the License file at |
| | | * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, |
| | | * add the following below this CDDL HEADER, with the fields enclosed |
| | | * by brackets "[]" replaced with your own identifying information: |
| | | * Portions Copyright [yyyy] [name of copyright owner] |
| | | * |
| | | * CDDL HEADER END |
| | | * |
| | | * |
| | | * Copyright 2006-2010 Sun Microsystems, Inc. |
| | | * Portions copyright 2011 ForgeRock AS |
| | | */ |
| | | package org.opends.server.types; |
| | | |
| | | |
| | | |
| | | import static org.opends.messages.SchemaMessages.*; |
| | | |
| | | import java.util.*; |
| | | import java.util.regex.Pattern; |
| | | |
| | | import org.opends.messages.Message; |
| | | import org.opends.server.core.DirectoryServer; |
| | | import org.opends.server.util.StaticUtils; |
| | | |
| | | |
| | | |
| | | /** |
| | | * An RFC 3672 subtree specification. |
| | | * <p> |
| | | * This implementation extends RFC 3672 by supporting search filters |
| | | * for specification filters. More specifically, the |
| | | * {@code Refinement} product has been extended as follows: |
| | | * |
| | | * <pre> |
| | | * Refinement = item / and / or / not / Filter |
| | | * |
| | | * Filter = dquote *SafeUTF8Character dquote |
| | | * </pre> |
| | | * |
| | | * @see <a href="http://tools.ietf.org/html/rfc3672">RFC 3672 - |
| | | * Subentries inthe Lightweight Directory Access Protocol (LDAP) |
| | | * </a> |
| | | */ |
| | | @org.opends.server.types.PublicAPI( |
| | | stability = org.opends.server.types.StabilityLevel.VOLATILE, |
| | | mayInstantiate = false, |
| | | mayExtend = true, |
| | | mayInvoke = false) |
| | | public final class SubtreeSpecification |
| | | { |
| | | |
| | | /** |
| | | * RFC 3672 subtree specification AND refinement. This type of |
| | | * refinement filters entries based on all of the underlying |
| | | * refinements being <code>true</code>. |
| | | */ |
| | | public static final class AndRefinement extends Refinement |
| | | { |
| | | // The set of refinements which must all be true. |
| | | private final Collection<Refinement> refinementSet; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create a new AND refinement. |
| | | * |
| | | * @param refinementSet |
| | | * The set of refinements which must all be |
| | | * <code>true</code>. |
| | | */ |
| | | public AndRefinement(final Collection<Refinement> refinementSet) |
| | | { |
| | | |
| | | this.refinementSet = refinementSet; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean equals(final Object obj) |
| | | { |
| | | |
| | | if (this == obj) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | if (obj instanceof AndRefinement) |
| | | { |
| | | final AndRefinement other = (AndRefinement) obj; |
| | | |
| | | return refinementSet.equals(other.refinementSet); |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int hashCode() |
| | | { |
| | | |
| | | return refinementSet.hashCode(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean matches(final Entry entry) |
| | | { |
| | | for (final Refinement refinement : refinementSet) |
| | | { |
| | | if (refinement.matches(entry) == false) |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // All sub-refinements matched. |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public StringBuilder toString(final StringBuilder builder) |
| | | { |
| | | switch (refinementSet.size()) |
| | | { |
| | | case 0: |
| | | // Do nothing. |
| | | break; |
| | | case 1: |
| | | refinementSet.iterator().next().toString(builder); |
| | | break; |
| | | default: |
| | | builder.append("and:{"); |
| | | final Iterator<Refinement> iterator = refinementSet |
| | | .iterator(); |
| | | iterator.next().toString(builder); |
| | | while (iterator.hasNext()) |
| | | { |
| | | builder.append(", "); |
| | | iterator.next().toString(builder); |
| | | } |
| | | builder.append("}"); |
| | | break; |
| | | } |
| | | |
| | | return builder; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * A refinement which uses a search filter. |
| | | */ |
| | | public static final class FilterRefinement extends Refinement |
| | | { |
| | | // The search filter. |
| | | private final SearchFilter filter; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create a new filter refinement. |
| | | * |
| | | * @param filter |
| | | * The search filter. |
| | | */ |
| | | public FilterRefinement(final SearchFilter filter) |
| | | { |
| | | |
| | | this.filter = filter; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean equals(final Object obj) |
| | | { |
| | | |
| | | if (this == obj) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | if (obj instanceof FilterRefinement) |
| | | { |
| | | final FilterRefinement other = (FilterRefinement) obj; |
| | | return filter.equals(other.filter); |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int hashCode() |
| | | { |
| | | |
| | | return filter.hashCode(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean matches(final Entry entry) |
| | | { |
| | | try |
| | | { |
| | | return filter.matchesEntry(entry); |
| | | } |
| | | catch (final DirectoryException e) |
| | | { |
| | | // TODO: need to decide what to do with the exception here. |
| | | // It's probably safe to ignore, but we could log it perhaps. |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public StringBuilder toString(final StringBuilder builder) |
| | | { |
| | | StaticUtils.toRFC3641StringValue(builder, filter.toString()); |
| | | return builder; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * RFC 3672 subtree specification Item refinement. This type of |
| | | * refinement filters entries based on the presence of a specified |
| | | * object class. |
| | | */ |
| | | public static final class ItemRefinement extends Refinement |
| | | { |
| | | // The item's object class. |
| | | private final String objectClass; |
| | | |
| | | // The item's normalized object class. |
| | | private final String normalizedObjectClass; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create a new item refinement. |
| | | * |
| | | * @param objectClass |
| | | * The item's object class. |
| | | */ |
| | | public ItemRefinement(final String objectClass) |
| | | { |
| | | |
| | | this.objectClass = objectClass; |
| | | this.normalizedObjectClass = StaticUtils |
| | | .toLowerCase(objectClass.trim()); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean equals(final Object obj) |
| | | { |
| | | |
| | | if (this == obj) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | if (obj instanceof ItemRefinement) |
| | | { |
| | | final ItemRefinement other = (ItemRefinement) obj; |
| | | |
| | | return normalizedObjectClass |
| | | .equals(other.normalizedObjectClass); |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int hashCode() |
| | | { |
| | | |
| | | return normalizedObjectClass.hashCode(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean matches(final Entry entry) |
| | | { |
| | | final ObjectClass oc = DirectoryServer |
| | | .getObjectClass(normalizedObjectClass); |
| | | |
| | | if (oc == null) |
| | | { |
| | | return false; |
| | | } |
| | | else |
| | | { |
| | | return entry.hasObjectClass(oc); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public StringBuilder toString(final StringBuilder builder) |
| | | { |
| | | builder.append("item:"); |
| | | builder.append(objectClass); |
| | | return builder; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * RFC 3672 subtree specification NOT refinement. This type of |
| | | * refinement filters entries based on the underlying refinement |
| | | * being <code>false</code> |
| | | * . |
| | | */ |
| | | public static final class NotRefinement extends Refinement |
| | | { |
| | | // The inverted refinement. |
| | | private final Refinement refinement; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create a new NOT refinement. |
| | | * |
| | | * @param refinement |
| | | * The refinement which must be <code>false</code>. |
| | | */ |
| | | public NotRefinement(final Refinement refinement) |
| | | { |
| | | |
| | | this.refinement = refinement; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean equals(final Object obj) |
| | | { |
| | | |
| | | if (this == obj) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | if (obj instanceof NotRefinement) |
| | | { |
| | | final NotRefinement other = (NotRefinement) obj; |
| | | |
| | | return refinement.equals(other.refinement); |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int hashCode() |
| | | { |
| | | |
| | | return refinement.hashCode(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean matches(final Entry entry) |
| | | { |
| | | return !refinement.matches(entry); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public StringBuilder toString(final StringBuilder builder) |
| | | { |
| | | builder.append("not:"); |
| | | return refinement.toString(builder); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * RFC 3672 subtree specification OR refinement. This type of |
| | | * refinement filters entries based on at least one of the |
| | | * underlying refinements being <code>true</code>. |
| | | */ |
| | | public static final class OrRefinement extends Refinement |
| | | { |
| | | // The set of refinements of which at least one must be true. |
| | | private final Collection<Refinement> refinementSet; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create a new OR refinement. |
| | | * |
| | | * @param refinementSet |
| | | * The set of refinements of which at least one must be |
| | | * <code>true</code>. |
| | | */ |
| | | public OrRefinement(final Collection<Refinement> refinementSet) |
| | | { |
| | | |
| | | this.refinementSet = refinementSet; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean equals(final Object obj) |
| | | { |
| | | |
| | | if (this == obj) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | if (obj instanceof AndRefinement) |
| | | { |
| | | final AndRefinement other = (AndRefinement) obj; |
| | | |
| | | return refinementSet.equals(other.refinementSet); |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public int hashCode() |
| | | { |
| | | |
| | | return refinementSet.hashCode(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public boolean matches(final Entry entry) |
| | | { |
| | | for (final Refinement refinement : refinementSet) |
| | | { |
| | | if (refinement.matches(entry) == true) |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | |
| | | // No sub-refinements matched. |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public StringBuilder toString(final StringBuilder builder) |
| | | { |
| | | switch (refinementSet.size()) |
| | | { |
| | | case 0: |
| | | // Do nothing. |
| | | break; |
| | | case 1: |
| | | refinementSet.iterator().next().toString(builder); |
| | | break; |
| | | default: |
| | | builder.append("or:{"); |
| | | final Iterator<Refinement> iterator = refinementSet |
| | | .iterator(); |
| | | iterator.next().toString(builder); |
| | | while (iterator.hasNext()) |
| | | { |
| | | builder.append(", "); |
| | | iterator.next().toString(builder); |
| | | } |
| | | builder.append("}"); |
| | | break; |
| | | } |
| | | |
| | | return builder; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Abstract interface for RFC3672 specification filter refinements. |
| | | */ |
| | | public static abstract class Refinement |
| | | { |
| | | /** |
| | | * Create a new RFC3672 specification filter refinement. |
| | | */ |
| | | protected Refinement() |
| | | { |
| | | // No implementation required. |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public abstract boolean equals(Object obj); |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public abstract int hashCode(); |
| | | |
| | | |
| | | |
| | | /** |
| | | * Check if the refinement matches the given entry. |
| | | * |
| | | * @param entry |
| | | * The filterable entry. |
| | | * @return Returns <code>true</code> if the entry matches the |
| | | * refinement, or <code>false</code> otherwise. |
| | | */ |
| | | public abstract boolean matches(Entry entry); |
| | | |
| | | |
| | | |
| | | /** |
| | | * {@inheritDoc} |
| | | */ |
| | | @Override |
| | | public final String toString() |
| | | { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | |
| | | return toString(builder).toString(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Append the string representation of the refinement to the |
| | | * provided strin builder. |
| | | * |
| | | * @param builder |
| | | * The string builder. |
| | | * @return The string builder. |
| | | */ |
| | | public abstract StringBuilder toString(StringBuilder builder); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Internal utility class which can be used by sub-classes to help |
| | | * parse string representations. |
| | | */ |
| | | protected static final class Parser |
| | | { |
| | | // Text scanner used to parse the string value. |
| | | private final Scanner scanner; |
| | | |
| | | // Pattern used to detect left braces. |
| | | private static Pattern LBRACE = Pattern.compile("\\{.*"); |
| | | |
| | | // Pattern used to parse left braces. |
| | | private static Pattern LBRACE_TOKEN = Pattern.compile("\\{"); |
| | | |
| | | // Pattern used to detect right braces. |
| | | private static Pattern RBRACE = Pattern.compile("\\}.*"); |
| | | |
| | | // Pattern used to parse right braces. |
| | | private static Pattern RBRACE_TOKEN = Pattern.compile("\\}"); |
| | | |
| | | // Pattern used to detect comma separators. |
| | | private static Pattern SEP = Pattern.compile(",.*"); |
| | | |
| | | // Pattern used to parse comma separators. |
| | | private static Pattern SEP_TOKEN = Pattern.compile(","); |
| | | |
| | | // Pattern used to detect colon separators. |
| | | private static Pattern COLON = Pattern.compile(":.*"); |
| | | |
| | | // Pattern used to parse colon separators. |
| | | private static Pattern COLON_TOKEN = Pattern.compile(":"); |
| | | |
| | | // Pattern used to detect integer values. |
| | | private static Pattern INT = Pattern.compile("\\d.*"); |
| | | |
| | | // Pattern used to parse integer values. |
| | | private static Pattern INT_TOKEN = Pattern.compile("\\d+"); |
| | | |
| | | // Pattern used to detect name values. |
| | | private static Pattern NAME = Pattern.compile("[\\w_;-].*"); |
| | | |
| | | // Pattern used to parse name values. |
| | | private static Pattern NAME_TOKEN = Pattern.compile("[\\w_;-]+"); |
| | | |
| | | // Pattern used to detect RFC3641 string values. |
| | | private static Pattern STRING_VALUE = Pattern.compile("\".*"); |
| | | |
| | | // Pattern used to parse RFC3641 string values. |
| | | private static Pattern STRING_VALUE_TOKEN = Pattern |
| | | .compile("\"([^\"]|(\"\"))*\""); |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create a new parser for a subtree specification string value. |
| | | * |
| | | * @param value |
| | | * The subtree specification string value. |
| | | */ |
| | | public Parser(final String value) |
| | | { |
| | | this.scanner = new Scanner(value); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Determine if there are remaining tokens. |
| | | * |
| | | * @return <code>true</code> if and only if there are remaining |
| | | * tokens. |
| | | */ |
| | | public boolean hasNext() |
| | | { |
| | | return scanner.hasNext(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Determine if the next token is a right-brace character. |
| | | * |
| | | * @return <code>true</code> if and only if the next token is a |
| | | * valid right brace character. |
| | | */ |
| | | public boolean hasNextRightBrace() |
| | | { |
| | | return scanner.hasNext(RBRACE); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Scans the next token of the input as a non-negative |
| | | * <code>int</code> value. |
| | | * |
| | | * @return The name value scanned from the input. |
| | | * @throws InputMismatchException |
| | | * If the next token is not a valid non-negative integer |
| | | * string. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public int nextInt() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | final String s = nextValue(INT, INT_TOKEN); |
| | | return Integer.parseInt(s); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Scans the next token of the input as a key value. |
| | | * |
| | | * @return The lower-case key value scanned from the input. |
| | | * @throws InputMismatchException |
| | | * If the next token is not a valid key string. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public String nextKey() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | return StaticUtils.toLowerCase(scanner.next()); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Scans the next token of the input as a name value. |
| | | * <p> |
| | | * A name is any string containing only alpha-numeric characters |
| | | * or hyphens, semi-colons, or underscores. |
| | | * |
| | | * @return The name value scanned from the input. |
| | | * @throws InputMismatchException |
| | | * If the next token is not a valid name string. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public String nextName() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | return nextValue(NAME, NAME_TOKEN); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Scans the next tokens of the input as a set of specific |
| | | * exclusions encoded according to the SpecificExclusion |
| | | * production in RFC 3672. |
| | | * |
| | | * @param chopBefore |
| | | * The set of chop before local names. |
| | | * @param chopAfter |
| | | * The set of chop after local names. |
| | | * @throws InputMismatchException |
| | | * If the common component did not have a valid syntax. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | * @throws DirectoryException |
| | | * If an error occurred when attempting to parse a |
| | | * DN value. |
| | | */ |
| | | public void nextSpecificExclusions(final Set<DN> chopBefore, |
| | | final Set<DN> chopAfter) throws InputMismatchException, |
| | | NoSuchElementException, DirectoryException |
| | | { |
| | | |
| | | // Skip leading open-brace. |
| | | skipLeftBrace(); |
| | | |
| | | // Parse each chop DN in the sequence. |
| | | boolean isFirstValue = true; |
| | | while (true) |
| | | { |
| | | // Make sure that there is a closing brace. |
| | | if (hasNextRightBrace()) |
| | | { |
| | | skipRightBrace(); |
| | | break; |
| | | } |
| | | |
| | | // Make sure that there is a comma separator if this is not |
| | | // the first element. |
| | | if (!isFirstValue) |
| | | { |
| | | skipSeparator(); |
| | | } |
| | | else |
| | | { |
| | | isFirstValue = false; |
| | | } |
| | | |
| | | // Parse each chop specification which is of the form |
| | | // <type>:<value>. |
| | | final String type = StaticUtils.toLowerCase(nextName()); |
| | | skipColon(); |
| | | if (type.equals("chopbefore")) |
| | | { |
| | | chopBefore.add(DN.decode(nextStringValue())); |
| | | } |
| | | else if (type.equals("chopafter")) |
| | | { |
| | | chopAfter.add(DN.decode(nextStringValue())); |
| | | } |
| | | else |
| | | { |
| | | throw new java.util.InputMismatchException(); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Scans the next token of the input as a string quoted according |
| | | * to the StringValue production in RFC 3641. |
| | | * <p> |
| | | * The return string has its outer double quotes removed and any |
| | | * escape inner double quotes unescaped. |
| | | * |
| | | * @return The string value scanned from the input. |
| | | * @throws InputMismatchException |
| | | * If the next token is not a valid string. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public String nextStringValue() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | final String s = nextValue(STRING_VALUE, STRING_VALUE_TOKEN); |
| | | return s.substring(1, s.length() - 1).replace("\"\"", "\""); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Skip a colon separator. |
| | | * |
| | | * @throws InputMismatchException |
| | | * If the next token is not a colon separator character. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public void skipColon() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | nextValue(COLON, COLON_TOKEN); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Skip a left-brace character. |
| | | * |
| | | * @throws InputMismatchException |
| | | * If the next token is not a left-brace character. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public void skipLeftBrace() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | nextValue(LBRACE, LBRACE_TOKEN); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Skip a right-brace character. |
| | | * |
| | | * @throws InputMismatchException |
| | | * If the next token is not a right-brace character. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public void skipRightBrace() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | nextValue(RBRACE, RBRACE_TOKEN); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Skip a comma separator. |
| | | * |
| | | * @throws InputMismatchException |
| | | * If the next token is not a comma separator character. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | public void skipSeparator() throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | nextValue(SEP, SEP_TOKEN); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Parse the next token from the string using the specified |
| | | * patterns. |
| | | * |
| | | * @param head |
| | | * The pattern used to determine if the next token is a |
| | | * possible match. |
| | | * @param content |
| | | * The pattern used to parse the token content. |
| | | * @return The next token matching the <code>content</code> |
| | | * pattern. |
| | | * @throws InputMismatchException |
| | | * If the next token does not match the |
| | | * <code>content</code> pattern. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | private String nextValue(final Pattern head, |
| | | final Pattern content) |
| | | throws InputMismatchException, NoSuchElementException |
| | | { |
| | | if (!scanner.hasNext()) |
| | | { |
| | | throw new java.util.NoSuchElementException(); |
| | | } |
| | | |
| | | if (!scanner.hasNext(head)) |
| | | { |
| | | throw new java.util.InputMismatchException(); |
| | | } |
| | | |
| | | final String s = scanner.findInLine(content); |
| | | if (s == null) |
| | | { |
| | | throw new java.util.InputMismatchException(); |
| | | } |
| | | |
| | | return s; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Parses the string argument as an RFC3672 subtree specification. |
| | | * |
| | | * @param rootDN |
| | | * The DN of the subtree specification's base entry. |
| | | * @param s |
| | | * The string to be parsed. |
| | | * @return The RFC3672 subtree specification represented by the |
| | | * string argument. |
| | | * @throws DirectoryException |
| | | * If the string does not contain a parsable relative |
| | | * subtree specification. |
| | | */ |
| | | public static SubtreeSpecification valueOf(final DN rootDN, |
| | | final String s) throws DirectoryException |
| | | { |
| | | |
| | | // Default values. |
| | | DN relativeBaseDN = null; |
| | | |
| | | int minimum = -1; |
| | | int maximum = -1; |
| | | |
| | | final HashSet<DN> chopBefore = new HashSet<DN>(); |
| | | final HashSet<DN> chopAfter = new HashSet<DN>(); |
| | | |
| | | Refinement refinement = null; |
| | | |
| | | // Value must have an opening left brace. |
| | | final Parser parser = new Parser(s); |
| | | boolean isValid = true; |
| | | |
| | | try |
| | | { |
| | | parser.skipLeftBrace(); |
| | | |
| | | // Parse each element of the value sequence. |
| | | boolean isFirst = true; |
| | | |
| | | while (true) |
| | | { |
| | | if (parser.hasNextRightBrace()) |
| | | { |
| | | // Make sure that there is a closing brace and no trailing |
| | | // text. |
| | | parser.skipRightBrace(); |
| | | |
| | | if (parser.hasNext()) |
| | | { |
| | | throw new java.util.InputMismatchException(); |
| | | } |
| | | break; |
| | | } |
| | | |
| | | // Make sure that there is a comma separator if this is not |
| | | // the first element. |
| | | if (!isFirst) |
| | | { |
| | | parser.skipSeparator(); |
| | | } |
| | | else |
| | | { |
| | | isFirst = false; |
| | | } |
| | | |
| | | final String key = parser.nextKey(); |
| | | if (key.equals("base")) |
| | | { |
| | | if (relativeBaseDN != null) |
| | | { |
| | | // Relative base DN specified more than once. |
| | | throw new InputMismatchException(); |
| | | } |
| | | relativeBaseDN = DN.decode(parser.nextStringValue()); |
| | | } |
| | | else if (key.equals("minimum")) |
| | | { |
| | | if (minimum != -1) |
| | | { |
| | | // Minimum specified more than once. |
| | | throw new InputMismatchException(); |
| | | } |
| | | minimum = parser.nextInt(); |
| | | } |
| | | else if (key.equals("maximum")) |
| | | { |
| | | if (maximum != -1) |
| | | { |
| | | // Maximum specified more than once. |
| | | throw new InputMismatchException(); |
| | | } |
| | | maximum = parser.nextInt(); |
| | | } |
| | | else if (key.equals("specificationfilter")) |
| | | { |
| | | if (refinement != null) |
| | | { |
| | | // Refinements specified more than once. |
| | | throw new InputMismatchException(); |
| | | } |
| | | |
| | | // First try normal search filter before RFC3672 |
| | | // refinements. |
| | | try |
| | | { |
| | | final SearchFilter filter = SearchFilter |
| | | .createFilterFromString(parser.nextStringValue()); |
| | | refinement = new FilterRefinement(filter); |
| | | } |
| | | catch (final InputMismatchException e) |
| | | { |
| | | refinement = parseRefinement(parser); |
| | | } |
| | | } |
| | | else if (key.equals("specificexclusions")) |
| | | { |
| | | if (!chopBefore.isEmpty() || !chopAfter.isEmpty()) |
| | | { |
| | | // Specific exclusions specified more than once. |
| | | throw new InputMismatchException(); |
| | | } |
| | | |
| | | parser.nextSpecificExclusions(chopBefore, chopAfter); |
| | | } |
| | | else |
| | | { |
| | | throw new InputMismatchException(); |
| | | } |
| | | } |
| | | |
| | | // Make default minimum value is 0. |
| | | if (minimum < 0) |
| | | { |
| | | minimum = 0; |
| | | } |
| | | |
| | | // Check that the maximum, if specified, is gte the minimum. |
| | | if (maximum >= 0 && maximum < minimum) |
| | | { |
| | | isValid = false; |
| | | } |
| | | } |
| | | catch (final InputMismatchException e) |
| | | { |
| | | isValid = false; |
| | | } |
| | | catch (final NoSuchElementException e) |
| | | { |
| | | isValid = false; |
| | | } |
| | | |
| | | if (isValid) |
| | | { |
| | | return new SubtreeSpecification(rootDN, relativeBaseDN, |
| | | minimum, maximum, chopBefore, chopAfter, refinement); |
| | | } |
| | | else |
| | | { |
| | | final Message message = |
| | | ERR_ATTR_SYNTAX_RFC3672_SUBTREE_SPECIFICATION_INVALID.get(s); |
| | | throw new DirectoryException( |
| | | ResultCode.INVALID_ATTRIBUTE_SYNTAX, message); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Parse a single refinement. |
| | | * |
| | | * @param parser |
| | | * The active subtree specification parser. |
| | | * @return The parsed refinement. |
| | | * @throws InputMismatchException |
| | | * If the common component did not have a valid syntax. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | private static Refinement parseRefinement(final Parser parser) |
| | | throws InputMismatchException, NoSuchElementException |
| | | { |
| | | // Get the type of refinement. |
| | | final String type = StaticUtils.toLowerCase(parser.nextName()); |
| | | |
| | | // Skip the colon separator. |
| | | parser.skipColon(); |
| | | |
| | | if (type.equals("item")) |
| | | { |
| | | return new ItemRefinement(parser.nextName()); |
| | | } |
| | | else if (type.equals("not")) |
| | | { |
| | | final Refinement refinement = parseRefinement(parser); |
| | | return new NotRefinement(refinement); |
| | | } |
| | | else if (type.equals("and")) |
| | | { |
| | | final ArrayList<Refinement> refinements = |
| | | parseRefinementSet(parser); |
| | | return new AndRefinement(refinements); |
| | | } |
| | | else if (type.equals("or")) |
| | | { |
| | | final ArrayList<Refinement> refinements = |
| | | parseRefinementSet(parser); |
| | | return new OrRefinement(refinements); |
| | | } |
| | | else |
| | | { |
| | | // Unknown refinement type. |
| | | throw new InputMismatchException(); |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Parse a list of refinements. |
| | | * |
| | | * @param parser |
| | | * The active subtree specification parser. |
| | | * @return The parsed refinement list. |
| | | * @throws InputMismatchException |
| | | * If the common component did not have a valid syntax. |
| | | * @throws NoSuchElementException |
| | | * If input is exhausted. |
| | | */ |
| | | private static ArrayList<Refinement> parseRefinementSet( |
| | | final Parser parser) throws InputMismatchException, |
| | | NoSuchElementException |
| | | { |
| | | final ArrayList<Refinement> refinements = |
| | | new ArrayList<Refinement>(); |
| | | |
| | | // Skip leading open-brace. |
| | | parser.skipLeftBrace(); |
| | | |
| | | // Parse each chop DN in the sequence. |
| | | boolean isFirstValue = true; |
| | | while (true) |
| | | { |
| | | // Make sure that there is a closing brace. |
| | | if (parser.hasNextRightBrace()) |
| | | { |
| | | parser.skipRightBrace(); |
| | | break; |
| | | } |
| | | |
| | | // Make sure that there is a comma separator if this is not |
| | | // the first element. |
| | | if (!isFirstValue) |
| | | { |
| | | parser.skipSeparator(); |
| | | } |
| | | else |
| | | { |
| | | isFirstValue = false; |
| | | } |
| | | |
| | | // Parse each sub-refinement. |
| | | final Refinement refinement = parseRefinement(parser); |
| | | refinements.add(refinement); |
| | | } |
| | | |
| | | return refinements; |
| | | } |
| | | |
| | | |
| | | |
| | | // The absolute base of the subtree. |
| | | private final DN baseDN; |
| | | |
| | | // Optional minimum depth (<=0 means unlimited). |
| | | private final int minimumDepth; |
| | | |
| | | // Optional maximum depth (<0 means unlimited). |
| | | private final int maximumDepth; |
| | | |
| | | // Optional set of chop before absolute DNs (mapping to their |
| | | // local-names). |
| | | private final Map<DN, DN> chopBefore; |
| | | |
| | | // Optional set of chop after absolute DNs (mapping to their |
| | | // local-names). |
| | | private final Map<DN, DN> chopAfter; |
| | | |
| | | // The root DN. |
| | | private final DN rootDN; |
| | | |
| | | // The optional relative base DN. |
| | | private final DN relativeBaseDN; |
| | | |
| | | // The optional specification filter refinements. |
| | | private final Refinement refinements; |
| | | |
| | | |
| | | |
| | | /** |
| | | * Create a new RFC3672 subtree specification. |
| | | * |
| | | * @param rootDN |
| | | * The root DN of the subtree. |
| | | * @param relativeBaseDN |
| | | * The relative base DN (or <code>null</code> if not |
| | | * specified). |
| | | * @param minimumDepth |
| | | * The minimum depth (<=0 means unlimited). |
| | | * @param maximumDepth |
| | | * The maximum depth (<0 means unlimited). |
| | | * @param chopBefore |
| | | * The set of chop before local names (relative to the |
| | | * relative base DN), or <code>null</code> if there are |
| | | * none. |
| | | * @param chopAfter |
| | | * The set of chop after local names (relative to the |
| | | * relative base DN), or <code>null</code> if there are |
| | | * none. |
| | | * @param refinements |
| | | * The optional specification filter refinements, or |
| | | * <code>null</code> if there are none. |
| | | */ |
| | | public SubtreeSpecification(final DN rootDN, |
| | | final DN relativeBaseDN, final int minimumDepth, |
| | | final int maximumDepth, final Iterable<DN> chopBefore, |
| | | final Iterable<DN> chopAfter, final Refinement refinements) |
| | | { |
| | | this.baseDN = relativeBaseDN == null ? rootDN : rootDN |
| | | .concat(relativeBaseDN); |
| | | this.minimumDepth = minimumDepth; |
| | | this.maximumDepth = maximumDepth; |
| | | |
| | | if (chopBefore != null && chopBefore.iterator().hasNext()) |
| | | { |
| | | // Calculate the absolute DNs. |
| | | final TreeMap<DN, DN> map = new TreeMap<DN, DN>(); |
| | | for (final DN localName : chopBefore) |
| | | { |
| | | map.put(baseDN.concat(localName), localName); |
| | | } |
| | | this.chopBefore = Collections.unmodifiableMap(map); |
| | | } |
| | | else |
| | | { |
| | | // No chop before specifications. |
| | | this.chopBefore = Collections.emptyMap(); |
| | | } |
| | | |
| | | if (chopAfter != null && chopAfter.iterator().hasNext()) |
| | | { |
| | | // Calculate the absolute DNs. |
| | | final TreeMap<DN, DN> map = new TreeMap<DN, DN>(); |
| | | for (final DN localName : chopAfter) |
| | | { |
| | | map.put(baseDN.concat(localName), localName); |
| | | } |
| | | this.chopAfter = Collections.unmodifiableMap(map); |
| | | } |
| | | else |
| | | { |
| | | // No chop after specifications. |
| | | this.chopAfter = Collections.emptyMap(); |
| | | } |
| | | |
| | | this.rootDN = rootDN; |
| | | this.relativeBaseDN = relativeBaseDN; |
| | | this.refinements = refinements; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Indicates whether the provided object is logically equal to this |
| | | * subtree specification object. |
| | | * |
| | | * @param obj |
| | | * The object for which to make the determination. |
| | | * @return {@code true} if the provided object is logically equal |
| | | * to this subtree specification object, or {@code false} |
| | | * if not. |
| | | */ |
| | | @Override |
| | | public boolean equals(final Object obj) |
| | | { |
| | | |
| | | if (this == obj) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | if (obj instanceof SubtreeSpecification) |
| | | { |
| | | final SubtreeSpecification other = (SubtreeSpecification) obj; |
| | | |
| | | if (!commonComponentsEquals(other)) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | if (!getBaseDN().equals(other.getBaseDN())) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | if (refinements != null) |
| | | { |
| | | return refinements.equals(other.refinements); |
| | | } |
| | | else |
| | | { |
| | | return refinements == other.refinements; |
| | | } |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the absolute base DN of the subtree specification. |
| | | * |
| | | * @return Returns the absolute base DN of the subtree |
| | | * specification. |
| | | */ |
| | | public DN getBaseDN() |
| | | { |
| | | return baseDN; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the set of chop after relative DNs. |
| | | * |
| | | * @return Returns the set of chop after relative DNs. |
| | | */ |
| | | public Iterable<DN> getChopAfter() |
| | | { |
| | | |
| | | return chopAfter.values(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the set of chop before relative DNs. |
| | | * |
| | | * @return Returns the set of chop before relative DNs. |
| | | */ |
| | | public Iterable<DN> getChopBefore() |
| | | { |
| | | |
| | | return chopBefore.values(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the maximum depth of the subtree specification. |
| | | * |
| | | * @return Returns the maximum depth (<0 indicates unlimited depth). |
| | | */ |
| | | public int getMaximumDepth() |
| | | { |
| | | return maximumDepth; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the minimum depth of the subtree specification. |
| | | * |
| | | * @return Returns the minimum depth (<=0 indicates unlimited |
| | | * depth). |
| | | */ |
| | | public int getMinimumDepth() |
| | | { |
| | | return minimumDepth; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the specification filter refinements. |
| | | * |
| | | * @return Returns the specification filter refinements, or |
| | | * <code>null</code> if none were specified. |
| | | */ |
| | | public Refinement getRefinements() |
| | | { |
| | | return refinements; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the relative base DN. |
| | | * |
| | | * @return Returns the relative base DN or <code>null</code> if |
| | | * none was specified. |
| | | */ |
| | | public DN getRelativeBaseDN() |
| | | { |
| | | return relativeBaseDN; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get the root DN. |
| | | * |
| | | * @return Returns the root DN. |
| | | */ |
| | | public DN getRootDN() |
| | | { |
| | | return rootDN; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves the hash code for this subtree specification object. |
| | | * |
| | | * @return The hash code for this subtree specification object. |
| | | */ |
| | | @Override |
| | | public int hashCode() |
| | | { |
| | | |
| | | int hash = commonComponentsHashCode(); |
| | | |
| | | hash = hash * 31 + getBaseDN().hashCode(); |
| | | |
| | | if (refinements != null) |
| | | { |
| | | hash = hash * 31 + refinements.hashCode(); |
| | | } |
| | | |
| | | return hash; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Determine if the specified DN is within the scope of the subtree |
| | | * specification. |
| | | * |
| | | * @param dn |
| | | * The distinguished name. |
| | | * @return Returns <code>true</code> if the DN is within the scope |
| | | * of the subtree specification, or <code>false</code> |
| | | * otherwise. |
| | | */ |
| | | public boolean isDNWithinScope(final DN dn) |
| | | { |
| | | |
| | | if (!dn.isDescendantOf(baseDN)) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | // Check minimum and maximum depths. |
| | | final int baseRDNCount = baseDN.getNumComponents(); |
| | | |
| | | if (minimumDepth > 0) |
| | | { |
| | | final int entryRDNCount = dn.getNumComponents(); |
| | | |
| | | if (entryRDNCount - baseRDNCount < minimumDepth) |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | if (maximumDepth >= 0) |
| | | { |
| | | final int entryRDNCount = dn.getNumComponents(); |
| | | |
| | | if (entryRDNCount - baseRDNCount > maximumDepth) |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // Check exclusions. |
| | | for (final DN chopBeforeDN : chopBefore.keySet()) |
| | | { |
| | | if (dn.isDescendantOf(chopBeforeDN)) |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | for (final DN chopAfterDN : chopAfter.keySet()) |
| | | { |
| | | if (!dn.equals(chopAfterDN) && dn.isDescendantOf(chopAfterDN)) |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | // Everything seemed to match. |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Determine if an entry is within the scope of the subtree |
| | | * specification. |
| | | * |
| | | * @param entry |
| | | * The entry. |
| | | * @return {@code true} if the entry is within the scope of the |
| | | * subtree specification, or {@code false} if not. |
| | | */ |
| | | public boolean isWithinScope(final Entry entry) |
| | | { |
| | | |
| | | if (isDNWithinScope(entry.getDN())) |
| | | { |
| | | if (refinements != null) |
| | | { |
| | | return refinements.matches(entry); |
| | | } |
| | | else |
| | | { |
| | | return true; |
| | | } |
| | | } |
| | | else |
| | | { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Retrieves a string representation of this subtree specification |
| | | * object. |
| | | * |
| | | * @return A string representation of this subtree specification |
| | | * object. |
| | | */ |
| | | @Override |
| | | public String toString() |
| | | { |
| | | final StringBuilder builder = new StringBuilder(); |
| | | return toString(builder).toString(); |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Append the string representation of the subtree specification to |
| | | * the provided string builder. |
| | | * |
| | | * @param builder |
| | | * The string builder. |
| | | * @return The string builder. |
| | | */ |
| | | public StringBuilder toString(final StringBuilder builder) |
| | | { |
| | | |
| | | boolean isFirstElement = true; |
| | | |
| | | // Output the optional base DN. |
| | | builder.append("{"); |
| | | if (relativeBaseDN != null && !relativeBaseDN.isNullDN()) |
| | | { |
| | | builder.append(" base "); |
| | | StaticUtils.toRFC3641StringValue(builder, |
| | | relativeBaseDN.toString()); |
| | | isFirstElement = false; |
| | | } |
| | | |
| | | // Output the optional specific exclusions. |
| | | final Iterable<DN> chopBefore = getChopBefore(); |
| | | final Iterable<DN> chopAfter = getChopAfter(); |
| | | |
| | | if ((chopBefore.iterator().hasNext()) |
| | | || (chopAfter.iterator().hasNext())) |
| | | { |
| | | |
| | | if (!isFirstElement) |
| | | { |
| | | builder.append(","); |
| | | } |
| | | else |
| | | { |
| | | isFirstElement = false; |
| | | } |
| | | builder.append(" specificExclusions { "); |
| | | |
| | | boolean isFirst = true; |
| | | |
| | | for (final DN dn : chopBefore) |
| | | { |
| | | if (!isFirst) |
| | | { |
| | | builder.append(", chopBefore:"); |
| | | } |
| | | else |
| | | { |
| | | builder.append("chopBefore:"); |
| | | isFirst = false; |
| | | } |
| | | StaticUtils.toRFC3641StringValue(builder, dn.toString()); |
| | | } |
| | | |
| | | for (final DN dn : chopAfter) |
| | | { |
| | | if (!isFirst) |
| | | { |
| | | builder.append(", chopAfter:"); |
| | | } |
| | | else |
| | | { |
| | | builder.append("chopAfter:"); |
| | | isFirst = false; |
| | | } |
| | | StaticUtils.toRFC3641StringValue(builder, dn.toString()); |
| | | } |
| | | |
| | | builder.append(" }"); |
| | | } |
| | | |
| | | // Output the optional minimum depth. |
| | | if (getMinimumDepth() > 0) |
| | | { |
| | | if (!isFirstElement) |
| | | { |
| | | builder.append(","); |
| | | } |
| | | else |
| | | { |
| | | isFirstElement = false; |
| | | } |
| | | builder.append(" minimum "); |
| | | builder.append(getMinimumDepth()); |
| | | } |
| | | |
| | | // Output the optional maximum depth. |
| | | if (getMaximumDepth() >= 0) |
| | | { |
| | | if (!isFirstElement) |
| | | { |
| | | builder.append(","); |
| | | } |
| | | else |
| | | { |
| | | isFirstElement = false; |
| | | } |
| | | builder.append(" maximum "); |
| | | builder.append(getMaximumDepth()); |
| | | } |
| | | |
| | | // Output the optional refinements. |
| | | if (refinements != null) |
| | | { |
| | | if (!isFirstElement) |
| | | { |
| | | builder.append(","); |
| | | } |
| | | else |
| | | { |
| | | isFirstElement = false; |
| | | } |
| | | builder.append(" specificationFilter "); |
| | | refinements.toString(builder); |
| | | } |
| | | |
| | | builder.append(" }"); |
| | | |
| | | return builder; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Determine if the common components of this subtree specification |
| | | * are equal to the common components of another subtree |
| | | * specification. |
| | | * |
| | | * @param other |
| | | * The other subtree specification. |
| | | * @return Returns <code>true</code> if they are equal. |
| | | */ |
| | | private boolean commonComponentsEquals( |
| | | final SubtreeSpecification other) |
| | | { |
| | | |
| | | if (this == other) |
| | | { |
| | | return true; |
| | | } |
| | | |
| | | if (minimumDepth != other.minimumDepth) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | if (maximumDepth != other.maximumDepth) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | if (!chopBefore.values().equals(other.chopBefore.values())) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | if (!chopAfter.values().equals(other.chopAfter.values())) |
| | | { |
| | | return false; |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | |
| | | |
| | | /** |
| | | * Get a hash code of the subtree specification's common components. |
| | | * |
| | | * @return The computed hash code. |
| | | */ |
| | | private int commonComponentsHashCode() |
| | | { |
| | | int hash = minimumDepth * 31 + maximumDepth; |
| | | hash = hash * 31 + chopBefore.values().hashCode(); |
| | | hash = hash * 31 + chopAfter.values().hashCode(); |
| | | return hash; |
| | | } |
| | | } |