NodeSelector.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.configuration2.tree;

  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.Iterator;
  21. import java.util.LinkedList;
  22. import java.util.List;

  23. import org.apache.commons.lang3.builder.ToStringBuilder;

  24. /**
  25.  * <p>
  26.  * A class for selecting a specific node based on a key or a set of keys.
  27.  * </p>
  28.  * <p>
  29.  * An instance of this class is initialized with the key of a node. It is also possible to concatenate multiple keys -
  30.  * for example if a sub key is to be constructed from another sub key. {@code NodeSelector} provides the {@code select()}
  31.  * method which evaluates the wrapped keys on a specified root node and returns the resulting unique target node. The
  32.  * class expects that the key(s) stored in an instance select exactly one target node. If this is not the case, result
  33.  * is <strong>null</strong> indicating that the selection criteria are not sufficient.
  34.  * </p>
  35.  * <p>
  36.  * Implementation node: Instances of this class are immutable. They can be shared between arbitrary components.
  37.  * </p>
  38.  *
  39.  * @since 2.0
  40.  */
  41. public class NodeSelector {
  42.     /** Stores the wrapped keys. */
  43.     private final List<String> nodeKeys;

  44.     /**
  45.      * Creates a new instance of {@code NodeSelector} and initializes it with the list of keys to be used as selection
  46.      * criteria.
  47.      *
  48.      * @param keys the keys for selecting nodes
  49.      */
  50.     private NodeSelector(final List<String> keys) {
  51.         nodeKeys = keys;
  52.     }

  53.     /**
  54.      * Creates a new instance of {@code NodeSelector} and initializes it with the key to the target node.
  55.      *
  56.      * @param key the key
  57.      */
  58.     public NodeSelector(final String key) {
  59.         this(Collections.singletonList(key));
  60.     }

  61.     /**
  62.      * Compares this object with another one. Two instances of {@code NodeSelector} are considered equal if they have the
  63.      * same keys as selection criteria.
  64.      *
  65.      * @param obj the object to be compared
  66.      * @return a flag whether these objects are equal
  67.      */
  68.     @Override
  69.     public boolean equals(final Object obj) {
  70.         if (this == obj) {
  71.             return true;
  72.         }
  73.         if (!(obj instanceof NodeSelector)) {
  74.             return false;
  75.         }

  76.         final NodeSelector c = (NodeSelector) obj;
  77.         return nodeKeys.equals(c.nodeKeys);
  78.     }

  79.     /**
  80.      * Executes a query for a given key and filters the results for nodes only.
  81.      *
  82.      * @param root the root node for the query
  83.      * @param resolver the {@code NodeKeyResolver}
  84.      * @param handler the {@code NodeHandler}
  85.      * @param key the key
  86.      * @param nodes here the results are stored
  87.      */
  88.     private void getFilteredResults(final ImmutableNode root, final NodeKeyResolver<ImmutableNode> resolver, final NodeHandler<ImmutableNode> handler,
  89.         final String key, final List<ImmutableNode> nodes) {
  90.         final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(root, key, handler);
  91.         results.forEach(result -> {
  92.             if (!result.isAttributeResult()) {
  93.                 nodes.add(result.getNode());
  94.             }
  95.         });
  96.     }

  97.     /**
  98.      * Returns a hash code for this object.
  99.      *
  100.      * @return a hash code
  101.      */
  102.     @Override
  103.     public int hashCode() {
  104.         return nodeKeys.hashCode();
  105.     }

  106.     /**
  107.      * Applies this {@code NodeSelector} on the specified root node. This method applies the selection criteria stored in
  108.      * this object and tries to determine a single target node. If this is successful, the target node is returned.
  109.      * Otherwise, result is <strong>null</strong>.
  110.      *
  111.      * @param root the root node on which to apply this selector
  112.      * @param resolver the {@code NodeKeyResolver}
  113.      * @param handler the {@code NodeHandler}
  114.      * @return the selected target node or <strong>null</strong>
  115.      */
  116.     public ImmutableNode select(final ImmutableNode root, final NodeKeyResolver<ImmutableNode> resolver, final NodeHandler<ImmutableNode> handler) {
  117.         List<ImmutableNode> nodes = new LinkedList<>();
  118.         final Iterator<String> itKeys = nodeKeys.iterator();
  119.         getFilteredResults(root, resolver, handler, itKeys.next(), nodes);

  120.         while (itKeys.hasNext()) {
  121.             final String currentKey = itKeys.next();
  122.             final List<ImmutableNode> currentResults = new LinkedList<>();
  123.             nodes.forEach(currentRoot -> getFilteredResults(currentRoot, resolver, handler, currentKey, currentResults));
  124.             nodes = currentResults;
  125.         }

  126.         return nodes.size() == 1 ? nodes.get(0) : null;
  127.     }

  128.     /**
  129.      * Creates a sub {@code NodeSelector} object which uses the key(s) of this selector plus the specified key as selection
  130.      * criteria. This is useful when another selection is to be performed on the results of a first selector.
  131.      *
  132.      * @param subKey the additional key for the sub selector
  133.      * @return the sub {@code NodeSelector} instance
  134.      */
  135.     public NodeSelector subSelector(final String subKey) {
  136.         final List<String> keys = new ArrayList<>(nodeKeys.size() + 1);
  137.         keys.addAll(nodeKeys);
  138.         keys.add(subKey);
  139.         return new NodeSelector(keys);
  140.     }

  141.     /**
  142.      * Returns a string representation for this object. This string contains the keys to be used as selection criteria.
  143.      *
  144.      * @return a string for this object
  145.      */
  146.     @Override
  147.     public String toString() {
  148.         return new ToStringBuilder(this).append("keys", nodeKeys).toString();
  149.     }
  150. }