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 033 * possible to concatenate multiple keys - e.g. if a sub key is to be 034 * constructed from another sub key. {@code NodeSelector} provides the 035 * {@code select()} method which evaluates the wrapped keys on a specified root 036 * node and returns the resulting unique target node. The class expects that the 037 * key(s) stored in an instance select exactly one target node. If this is not 038 * the case, result is <b>null</b> indicating that the selection criteria are 039 * not sufficient. 040 * </p> 041 * <p> 042 * Implementation node: Instances of this class are immutable. They can be 043 * shared between arbitrary components. 044 * </p> 045 * 046 * @since 2.0 047 */ 048public class NodeSelector 049{ 050 /** Stores the wrapped keys. */ 051 private final List<String> nodeKeys; 052 053 /** 054 * Creates a new instance of {@code NodeSelector} and initializes it with 055 * the key to the target node. 056 * 057 * @param key the key 058 */ 059 public NodeSelector(final String key) 060 { 061 this(Collections.singletonList(key)); 062 } 063 064 /** 065 * Creates a new instance of {@code NodeSelector} and initializes it with 066 * the list of keys to be used as selection criteria. 067 * 068 * @param keys the keys for selecting nodes 069 */ 070 private NodeSelector(final List<String> keys) 071 { 072 nodeKeys = keys; 073 } 074 075 /** 076 * Applies this {@code NodeSelector} on the specified root node. This method 077 * applies the selection criteria stored in this object and tries to 078 * determine a single target node. If this is successful, the target node is 079 * returned. Otherwise, result is <b>null</b>. 080 * 081 * @param root the root node on which to apply this selector 082 * @param resolver the {@code NodeKeyResolver} 083 * @param handler the {@code NodeHandler} 084 * @return the selected target node or <b>null</b> 085 */ 086 public ImmutableNode select(final ImmutableNode root, 087 final NodeKeyResolver<ImmutableNode> resolver, 088 final NodeHandler<ImmutableNode> handler) 089 { 090 List<ImmutableNode> nodes = new LinkedList<>(); 091 final Iterator<String> itKeys = nodeKeys.iterator(); 092 getFilteredResults(root, resolver, handler, itKeys.next(), nodes); 093 094 while (itKeys.hasNext()) 095 { 096 final String currentKey = itKeys.next(); 097 final List<ImmutableNode> currentResults = 098 new LinkedList<>(); 099 for (final ImmutableNode currentRoot : nodes) 100 { 101 getFilteredResults(currentRoot, resolver, handler, currentKey, 102 currentResults); 103 } 104 nodes = currentResults; 105 } 106 107 return nodes.size() == 1 ? nodes.get(0) : null; 108 } 109 110 /** 111 * Creates a sub {@code NodeSelector} object which uses the key(s) of this 112 * selector plus the specified key as selection criteria. This is useful 113 * when another selection is to be performed on the results of a first 114 * selector. 115 * 116 * @param subKey the additional key for the sub selector 117 * @return the sub {@code NodeSelector} instance 118 */ 119 public NodeSelector subSelector(final String subKey) 120 { 121 final List<String> keys = new ArrayList<>(nodeKeys.size() + 1); 122 keys.addAll(nodeKeys); 123 keys.add(subKey); 124 return new NodeSelector(keys); 125 } 126 127 /** 128 * Compares this object with another one. Two instances of 129 * {@code NodeSelector} are considered equal if they have the same keys as 130 * selection criteria. 131 * 132 * @param obj the object to be compared 133 * @return a flag whether these objects are equal 134 */ 135 @Override 136 public boolean equals(final Object obj) 137 { 138 if (this == obj) 139 { 140 return true; 141 } 142 if (!(obj instanceof NodeSelector)) 143 { 144 return false; 145 } 146 147 final NodeSelector c = (NodeSelector) obj; 148 return nodeKeys.equals(c.nodeKeys); 149 } 150 151 /** 152 * Returns a hash code for this object. 153 * 154 * @return a hash code 155 */ 156 @Override 157 public int hashCode() 158 { 159 return nodeKeys.hashCode(); 160 } 161 162 /** 163 * Returns a string representation for this object. This string contains the 164 * keys to be used as selection criteria. 165 * 166 * @return a string for this object 167 */ 168 @Override 169 public String toString() 170 { 171 return new ToStringBuilder(this).append("keys", nodeKeys).toString(); 172 } 173 174 /** 175 * Executes a query for a given key and filters the results for nodes only. 176 * 177 * @param root the root node for the query 178 * @param resolver the {@code NodeKeyResolver} 179 * @param handler the {@code NodeHandler} 180 * @param key the key 181 * @param nodes here the results are stored 182 */ 183 private void getFilteredResults(final ImmutableNode root, 184 final NodeKeyResolver<ImmutableNode> resolver, 185 final NodeHandler<ImmutableNode> handler, final String key, 186 final List<ImmutableNode> nodes) 187 { 188 final List<QueryResult<ImmutableNode>> results = 189 resolver.resolveKey(root, key, handler); 190 for (final QueryResult<ImmutableNode> result : results) 191 { 192 if (!result.isAttributeResult()) 193 { 194 nodes.add(result.getNode()); 195 } 196 } 197 } 198}