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