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 19 import java.util.Collection; 20 import java.util.LinkedList; 21 import java.util.List; 22 23 import org.apache.commons.lang3.StringUtils; 24 25 /** 26 * <p> 27 * A default implementation of the {@code ExpressionEngine} interface providing the "native" expression 28 * language for hierarchical configurations. 29 * </p> 30 * <p> 31 * This class implements a rather simple expression language for navigating through a hierarchy of configuration nodes. 32 * It supports the following operations: 33 * </p> 34 * <ul> 35 * <li>Navigating from a node to one of its children using the child node delimiter, which is by the default a dot 36 * (".").</li> 37 * <li>Navigating from a node to one of its attributes using the attribute node delimiter, which by default follows the 38 * XPATH like syntax {@code [@<attributeName>]}.</li> 39 * <li>If there are multiple child or attribute nodes with the same name, a specific node can be selected using a 40 * numerical index. By default indices are written in parenthesis.</li> 41 * </ul> 42 * <p> 43 * As an example consider the following XML document: 44 * </p> 45 * 46 * <pre> 47 * <database> 48 * <tables> 49 * <table type="system"> 50 * <name>users</name> 51 * <fields> 52 * <field> 53 * <name>lid</name> 54 * <type>long</name> 55 * </field> 56 * <field> 57 * <name>usrName</name> 58 * <type>java.lang.String</type> 59 * </field> 60 * ... 61 * </fields> 62 * </table> 63 * <table> 64 * <name>documents</name> 65 * <fields> 66 * <field> 67 * <name>docid</name> 68 * <type>long</type> 69 * </field> 70 * ... 71 * </fields> 72 * </table> 73 * ... 74 * </tables> 75 * </database> 76 * </pre> 77 * 78 * <p> 79 * If this document is parsed and stored in a hierarchical configuration object, for instance the key 80 * {@code tables.table(0).name} can be used to find out the name of the first table. In opposite 81 * {@code tables.table.name} would return a collection with the names of all available tables. Similarly the key 82 * {@code tables.table(1).fields.field.name} returns a collection with the names of all fields of the second table. If 83 * another index is added after the {@code field} element, a single field can be accessed: 84 * {@code tables.table(1).fields.field(0).name}. The key {@code tables.table(0)[@type]} would select the type attribute 85 * of the first table. 86 * </p> 87 * <p> 88 * This example works with the default values for delimiters and index markers. It is also possible to set custom values 89 * for these properties so that you can adapt a {@code DefaultExpressionEngine} to your personal needs. 90 * </p> 91 * <p> 92 * The concrete symbols used by an instance are determined by a {@link DefaultExpressionEngineSymbols} object passed to 93 * the constructor. By providing a custom symbols object the syntax for querying properties in a hierarchical 94 * configuration can be altered. 95 * </p> 96 * <p> 97 * Instances of this class are thread-safe and can be shared between multiple hierarchical configuration objects. 98 * </p> 99 * 100 * @since 1.3 101 */ 102 public class DefaultExpressionEngine implements ExpressionEngine { 103 /** 104 * A default instance of this class that is used as expression engine for hierarchical configurations per default. 105 */ 106 public static final DefaultExpressionEngine INSTANCE = new DefaultExpressionEngine(DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS); 107 108 /** The symbols used by this instance. */ 109 private final DefaultExpressionEngineSymbols symbols; 110 111 /** The matcher for node names. */ 112 private final NodeMatcher<String> nameMatcher; 113 114 /** 115 * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols. 116 * 117 * @param syms the object with the symbols (must not be <b>null</b>) 118 * @throws IllegalArgumentException if the symbols are <b>null</b> 119 */ 120 public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms) { 121 this(syms, null); 122 } 123 124 /** 125 * Creates a new instance of {@code DefaultExpressionEngine} and initializes its symbols and the matcher for comparing 126 * node names. The passed in matcher is always used when the names of nodes have to be matched against parts of 127 * configuration keys. 128 * 129 * @param syms the object with the symbols (must not be <b>null</b>) 130 * @param nodeNameMatcher the matcher for node names; can be <b>null</b>, then a default matcher is used 131 * @throws IllegalArgumentException if the symbols are <b>null</b> 132 */ 133 public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms, final NodeMatcher<String> nodeNameMatcher) { 134 if (syms == null) { 135 throw new IllegalArgumentException("Symbols must not be null!"); 136 } 137 138 symbols = syms; 139 nameMatcher = nodeNameMatcher != null ? nodeNameMatcher : NodeNameMatchers.EQUALS; 140 } 141 142 /** 143 * Gets the {@code DefaultExpressionEngineSymbols} object associated with this instance. 144 * 145 * @return the {@code DefaultExpressionEngineSymbols} used by this engine 146 * @since 2.0 147 */ 148 public DefaultExpressionEngineSymbols getSymbols() { 149 return symbols; 150 } 151 152 /** 153 * {@inheritDoc} This method supports the syntax as described in the class comment. 154 */ 155 @Override 156 public <T> List<QueryResult<T>> query(final T root, final String key, final NodeHandler<T> handler) { 157 final List<QueryResult<T>> results = new LinkedList<>(); 158 findNodesForKey(new DefaultConfigurationKey(this, key).iterator(), root, results, handler); 159 return results; 160 } 161 162 /** 163 * {@inheritDoc} This implementation takes the given parent key, adds a property delimiter, and then adds the node's 164 * name. The name of the root node is a blank string. Note that no indices are returned. 165 */ 166 @Override 167 public <T> String nodeKey(final T node, final String parentKey, final NodeHandler<T> handler) { 168 if (parentKey == null) { 169 // this is the root node 170 return StringUtils.EMPTY; 171 } 172 final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey); 173 key.append(handler.nodeName(node), true); 174 return key.toString(); 175 } 176 177 @Override 178 public String attributeKey(final String parentKey, final String attributeName) { 179 final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey); 180 key.appendAttribute(attributeName); 181 return key.toString(); 182 } 183 184 /** 185 * {@inheritDoc} This implementation works similar to {@code nodeKey()}; however, each key returned by this method has 186 * an index (except for the root node). The parent key is prepended to the name of the current node in any case and 187 * without further checks. If it is <b>null</b>, only the name of the current node with its index is returned. 188 */ 189 @Override 190 public <T> String canonicalKey(final T node, final String parentKey, final NodeHandler<T> handler) { 191 final String nodeName = handler.nodeName(node); 192 final T parent = handler.getParent(node); 193 final DefaultConfigurationKey key = new DefaultConfigurationKey(this, parentKey); 194 key.append(StringUtils.defaultString(nodeName)); 195 196 if (parent != null) { 197 // this is not the root key 198 key.appendIndex(determineIndex(node, parent, nodeName, handler)); 199 } 200 return key.toString(); 201 } 202 203 /** 204 * <p> 205 * Prepares Adding the property with the specified key. 206 * </p> 207 * <p> 208 * To be able to deal with the structure supported by hierarchical configuration implementations the passed in key is of 209 * importance, especially the indices it might contain. The following example should clarify this: Suppose the current 210 * node structure looks like the following: 211 * </p> 212 * 213 * <pre> 214 * tables 215 * +-- table 216 * +-- name = user 217 * +-- fields 218 * +-- field 219 * +-- name = uid 220 * +-- field 221 * +-- name = firstName 222 * ... 223 * +-- table 224 * +-- name = documents 225 * +-- fields 226 * ... 227 * </pre> 228 * <p> 229 * In this example a database structure is defined, e.g. all fields of the first table could be accessed using the key 230 * {@code tables.table(0).fields.field.name}. If now properties are to be added, it must be exactly specified at which 231 * position in the hierarchy the new property is to be inserted. So to add a new field name to a table it is not enough 232 * to say just 233 * </p> 234 * 235 * <pre> 236 * config.addProperty("tables.table.fields.field.name", "newField"); 237 * </pre> 238 * <p> 239 * The statement given above contains some ambiguity. For instance it is not clear, to which table the new field should 240 * be added. If this method finds such an ambiguity, it is resolved by following the last valid path. Here this would be 241 * the last table. The same is true for the {@code field}; because there are multiple fields and no explicit index is 242 * provided, a new {@code name} property would be added to the last field - which is probably not what was desired. 243 * </p> 244 * <p> 245 * To make things clear explicit indices should be provided whenever possible. In the example above the exact table 246 * could be specified by providing an index for the {@code table} element as in {@code tables.table(1).fields}. By 247 * specifying an index it can also be expressed that at a given position in the configuration tree a new branch should 248 * be added. In the example above we did not want to add an additional {@code name} element to the last field of the 249 * table, but we want a complete new {@code field} element. This can be achieved by specifying an invalid index (like 250 * -1) after the element where a new branch should be created. Given this our example would run: 251 * </p> 252 * 253 * <pre> 254 * config.addProperty("tables.table(1).fields.field(-1).name", "newField"); 255 * </pre> 256 * <p> 257 * With this notation it is possible to add new branches everywhere. We could for instance create a new {@code table} 258 * element by specifying 259 * </p> 260 * 261 * <pre> 262 * config.addProperty("tables.table(-1).fields.field.name", "newField2"); 263 * </pre> 264 * <p> 265 * (Note that because after the {@code table} element a new branch is created indices in following elements are not 266 * relevant; the branch is new so there cannot be any ambiguities.) 267 * </p> 268 * 269 * @param <T> the type of the nodes to be dealt with 270 * @param root the root node of the nodes hierarchy 271 * @param key the key of the new property 272 * @param handler the node handler 273 * @return a data object with information needed for the add operation 274 */ 275 @Override 276 public <T> NodeAddData<T> prepareAdd(final T root, final String key, final NodeHandler<T> handler) { 277 final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(this, key).iterator(); 278 if (!it.hasNext()) { 279 throw new IllegalArgumentException("Key for add operation must be defined!"); 280 } 281 282 final T parent = findLastPathNode(it, root, handler); 283 final List<String> pathNodes = new LinkedList<>(); 284 285 while (it.hasNext()) { 286 if (!it.isPropertyKey()) { 287 throw new IllegalArgumentException("Invalid key for add operation: " + key + " (Attribute key in the middle.)"); 288 } 289 pathNodes.add(it.currentKey()); 290 it.next(); 291 } 292 293 return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(), pathNodes); 294 } 295 296 /** 297 * Recursive helper method for evaluating a key. This method processes all facets of a configuration key, traverses the 298 * tree of properties and fetches the results of all matching properties. 299 * 300 * @param <T> the type of nodes to be dealt with 301 * @param keyPart the configuration key iterator 302 * @param node the current node 303 * @param results here the found results are stored 304 * @param handler the node handler 305 */ 306 protected <T> void findNodesForKey(final DefaultConfigurationKey.KeyIterator keyPart, final T node, final Collection<QueryResult<T>> results, 307 final NodeHandler<T> handler) { 308 if (!keyPart.hasNext()) { 309 results.add(QueryResult.createNodeResult(node)); 310 } else { 311 final String key = keyPart.nextKey(false); 312 if (keyPart.isPropertyKey()) { 313 processSubNodes(keyPart, findChildNodesByName(handler, node, key), results, handler); 314 } 315 if (keyPart.isAttribute() && !keyPart.hasNext() && handler.getAttributeValue(node, key) != null) { 316 results.add(QueryResult.createAttributeResult(node, key)); 317 } 318 } 319 } 320 321 /** 322 * Finds the last existing node for an add operation. This method traverses the node tree along the specified key. The 323 * last existing node on this path is returned. 324 * 325 * @param <T> the type of the nodes to be dealt with 326 * @param keyIt the key iterator 327 * @param node the current node 328 * @param handler the node handler 329 * @return the last existing node on the given path 330 */ 331 protected <T> T findLastPathNode(final DefaultConfigurationKey.KeyIterator keyIt, final T node, final NodeHandler<T> handler) { 332 final String keyPart = keyIt.nextKey(false); 333 334 if (keyIt.hasNext()) { 335 if (!keyIt.isPropertyKey()) { 336 // Attribute keys can only appear as last elements of the path 337 throw new IllegalArgumentException("Invalid path for add operation: " + "Attribute key in the middle!"); 338 } 339 final int idx = keyIt.hasIndex() ? keyIt.getIndex() : handler.getMatchingChildrenCount(node, nameMatcher, keyPart) - 1; 340 if (idx < 0 || idx >= handler.getMatchingChildrenCount(node, nameMatcher, keyPart)) { 341 return node; 342 } 343 return findLastPathNode(keyIt, findChildNodesByName(handler, node, keyPart).get(idx), handler); 344 } 345 return node; 346 } 347 348 /** 349 * Called by {@code findNodesForKey()} to process the sub nodes of the current node depending on the type of the current 350 * key part (children, attributes, or both). 351 * 352 * @param <T> the type of the nodes to be dealt with 353 * @param keyPart the key part 354 * @param subNodes a list with the sub nodes to process 355 * @param nodes the target collection 356 * @param handler the node handler 357 */ 358 private <T> void processSubNodes(final DefaultConfigurationKey.KeyIterator keyPart, final List<T> subNodes, final Collection<QueryResult<T>> nodes, 359 final NodeHandler<T> handler) { 360 if (keyPart.hasIndex()) { 361 if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size()) { 362 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), subNodes.get(keyPart.getIndex()), nodes, handler); 363 } 364 } else { 365 subNodes.forEach(node -> findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart.clone(), node, nodes, handler)); 366 } 367 } 368 369 /** 370 * Determines the index of the given node based on its parent node. 371 * 372 * @param node the current node 373 * @param parent the parent node 374 * @param nodeName the name of the current node 375 * @param handler the node handler 376 * @param <T> the type of the nodes to be dealt with 377 * @return the index of this node 378 */ 379 private <T> int determineIndex(final T node, final T parent, final String nodeName, final NodeHandler<T> handler) { 380 return findChildNodesByName(handler, parent, nodeName).indexOf(node); 381 } 382 383 /** 384 * Returns a list with all child nodes of the given parent node which match the specified node name. The match is done 385 * using the current node name matcher. 386 * 387 * @param handler the {@code NodeHandler} 388 * @param parent the parent node 389 * @param nodeName the name of the current node 390 * @param <T> the type of the nodes to be dealt with 391 * @return a list with all matching child nodes 392 */ 393 private <T> List<T> findChildNodesByName(final NodeHandler<T> handler, final T parent, final String nodeName) { 394 return handler.getMatchingChildren(parent, nameMatcher, nodeName); 395 } 396 }