001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.configuration2.tree; 018 019import java.util.ArrayList; 020import java.util.Collections; 021import java.util.Iterator; 022import java.util.LinkedList; 023import java.util.List; 024 025import org.apache.commons.lang3.builder.ToStringBuilder; 026 027/** 028 * <p> 029 * A class for selecting a specific node based on a key or a set of keys. 030 * </p> 031 * <p> 032 * An instance of this class is initialized with the key of a node. It is also possible to concatenate multiple keys - 033 * e.g. if a sub key is to be constructed from another sub key. {@code NodeSelector} provides the {@code select()} 034 * method which evaluates the wrapped keys on a specified root node and returns the resulting unique target node. The 035 * class expects that the key(s) stored in an instance select exactly one target node. If this is not the case, result 036 * is <b>null</b> indicating that the selection criteria are not sufficient. 037 * </p> 038 * <p> 039 * Implementation node: Instances of this class are immutable. They can be shared between arbitrary components. 040 * </p> 041 * 042 * @since 2.0 043 */ 044public class NodeSelector { 045 /** Stores the wrapped keys. */ 046 private final List<String> nodeKeys; 047 048 /** 049 * Creates a new instance of {@code NodeSelector} and initializes it with the key to the target node. 050 * 051 * @param key the key 052 */ 053 public NodeSelector(final String key) { 054 this(Collections.singletonList(key)); 055 } 056 057 /** 058 * Creates a new instance of {@code NodeSelector} and initializes it with the list of keys to be used as selection 059 * criteria. 060 * 061 * @param keys the keys for selecting nodes 062 */ 063 private NodeSelector(final List<String> keys) { 064 nodeKeys = keys; 065 } 066 067 /** 068 * Applies this {@code NodeSelector} on the specified root node. This method applies the selection criteria stored in 069 * this object and tries to determine a single target node. If this is successful, the target node is returned. 070 * Otherwise, result is <b>null</b>. 071 * 072 * @param root the root node on which to apply this selector 073 * @param resolver the {@code NodeKeyResolver} 074 * @param handler the {@code NodeHandler} 075 * @return the selected target node or <b>null</b> 076 */ 077 public ImmutableNode select(final ImmutableNode root, final NodeKeyResolver<ImmutableNode> resolver, final NodeHandler<ImmutableNode> handler) { 078 List<ImmutableNode> nodes = new LinkedList<>(); 079 final Iterator<String> itKeys = nodeKeys.iterator(); 080 getFilteredResults(root, resolver, handler, itKeys.next(), nodes); 081 082 while (itKeys.hasNext()) { 083 final String currentKey = itKeys.next(); 084 final List<ImmutableNode> currentResults = new LinkedList<>(); 085 for (final ImmutableNode currentRoot : nodes) { 086 getFilteredResults(currentRoot, resolver, handler, currentKey, currentResults); 087 } 088 nodes = currentResults; 089 } 090 091 return nodes.size() == 1 ? nodes.get(0) : null; 092 } 093 094 /** 095 * Creates a sub {@code NodeSelector} object which uses the key(s) of this selector plus the specified key as selection 096 * criteria. This is useful when another selection is to be performed on the results of a first selector. 097 * 098 * @param subKey the additional key for the sub selector 099 * @return the sub {@code NodeSelector} instance 100 */ 101 public NodeSelector subSelector(final String subKey) { 102 final List<String> keys = new ArrayList<>(nodeKeys.size() + 1); 103 keys.addAll(nodeKeys); 104 keys.add(subKey); 105 return new NodeSelector(keys); 106 } 107 108 /** 109 * Compares this object with another one. Two instances of {@code NodeSelector} are considered equal if they have the 110 * same keys as selection criteria. 111 * 112 * @param obj the object to be compared 113 * @return a flag whether these objects are equal 114 */ 115 @Override 116 public boolean equals(final Object obj) { 117 if (this == obj) { 118 return true; 119 } 120 if (!(obj instanceof NodeSelector)) { 121 return false; 122 } 123 124 final NodeSelector c = (NodeSelector) obj; 125 return nodeKeys.equals(c.nodeKeys); 126 } 127 128 /** 129 * Returns a hash code for this object. 130 * 131 * @return a hash code 132 */ 133 @Override 134 public int hashCode() { 135 return nodeKeys.hashCode(); 136 } 137 138 /** 139 * Returns a string representation for this object. This string contains the keys to be used as selection criteria. 140 * 141 * @return a string for this object 142 */ 143 @Override 144 public String toString() { 145 return new ToStringBuilder(this).append("keys", nodeKeys).toString(); 146 } 147 148 /** 149 * Executes a query for a given key and filters the results for nodes only. 150 * 151 * @param root the root node for the query 152 * @param resolver the {@code NodeKeyResolver} 153 * @param handler the {@code NodeHandler} 154 * @param key the key 155 * @param nodes here the results are stored 156 */ 157 private void getFilteredResults(final ImmutableNode root, final NodeKeyResolver<ImmutableNode> resolver, final NodeHandler<ImmutableNode> handler, 158 final String key, final List<ImmutableNode> nodes) { 159 final List<QueryResult<ImmutableNode>> results = resolver.resolveKey(root, key, handler); 160 for (final QueryResult<ImmutableNode> result : results) { 161 if (!result.isAttributeResult()) { 162 nodes.add(result.getNode()); 163 } 164 } 165 } 166}