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.Collection; 020import java.util.LinkedList; 021import java.util.List; 022 023import org.apache.commons.lang3.StringUtils; 024 025/** 026 * <p> 027 * A default implementation of the {@code ExpressionEngine} interface 028 * providing the "native" expression language for hierarchical 029 * configurations. 030 * </p> 031 * <p> 032 * This class implements a rather simple expression language for navigating 033 * through a hierarchy of configuration nodes. It supports the following 034 * operations: 035 * </p> 036 * <ul> 037 * <li>Navigating from a node to one of its children using the child node 038 * delimiter, which is by the default a dot (".").</li> 039 * <li>Navigating from a node to one of its attributes using the attribute node 040 * delimiter, which by default follows the XPATH like syntax 041 * <code>[@<attributeName>]</code>.</li> 042 * <li>If there are multiple child or attribute nodes with the same name, a 043 * specific node can be selected using a numerical index. By default indices are 044 * written in parenthesis.</li> 045 * </ul> 046 * <p> 047 * As an example consider the following XML document: 048 * </p> 049 * 050 * <pre> 051 * <database> 052 * <tables> 053 * <table type="system"> 054 * <name>users</name> 055 * <fields> 056 * <field> 057 * <name>lid</name> 058 * <type>long</name> 059 * </field> 060 * <field> 061 * <name>usrName</name> 062 * <type>java.lang.String</type> 063 * </field> 064 * ... 065 * </fields> 066 * </table> 067 * <table> 068 * <name>documents</name> 069 * <fields> 070 * <field> 071 * <name>docid</name> 072 * <type>long</type> 073 * </field> 074 * ... 075 * </fields> 076 * </table> 077 * ... 078 * </tables> 079 * </database> 080 * </pre> 081 * 082 * <p> 083 * If this document is parsed and stored in a hierarchical configuration object, 084 * for instance the key {@code tables.table(0).name} can be used to find 085 * out the name of the first table. In opposite {@code tables.table.name} 086 * would return a collection with the names of all available tables. Similarly 087 * the key {@code tables.table(1).fields.field.name} returns a collection 088 * with the names of all fields of the second table. If another index is added 089 * after the {@code field} element, a single field can be accessed: 090 * {@code tables.table(1).fields.field(0).name}. The key 091 * {@code tables.table(0)[@type]} would select the type attribute of the 092 * first table. 093 * </p> 094 * <p> 095 * This example works with the default values for delimiters and index markers. 096 * It is also possible to set custom values for these properties so that you can 097 * adapt a {@code DefaultExpressionEngine} to your personal needs. 098 * </p> 099 * <p> 100 * The concrete symbols used by an instance are determined by a 101 * {@link DefaultExpressionEngineSymbols} object passed to the constructor. 102 * By providing a custom symbols object the syntax for querying properties in 103 * a hierarchical configuration can be altered. 104 * </p> 105 * <p> 106 * Instances of this class are thread-safe and can be shared between multiple 107 * hierarchical configuration objects. 108 * </p> 109 * 110 * @since 1.3 111 * @author <a 112 * href="http://commons.apache.org/configuration/team-list.html">Commons 113 * Configuration team</a> 114 * @version $Id: DefaultExpressionEngine.java 1842194 2018-09-27 22:24:23Z ggregory $ 115 */ 116public class DefaultExpressionEngine implements ExpressionEngine 117{ 118 /** 119 * A default instance of this class that is used as expression engine for 120 * hierarchical configurations per default. 121 */ 122 public static final DefaultExpressionEngine INSTANCE = 123 new DefaultExpressionEngine( 124 DefaultExpressionEngineSymbols.DEFAULT_SYMBOLS); 125 126 /** The symbols used by this instance. */ 127 private final DefaultExpressionEngineSymbols symbols; 128 129 /** The matcher for node names. */ 130 private final NodeMatcher<String> nameMatcher; 131 132 /** 133 * Creates a new instance of {@code DefaultExpressionEngine} and initializes 134 * its symbols. 135 * 136 * @param syms the object with the symbols (must not be <b>null</b>) 137 * @throws IllegalArgumentException if the symbols are <b>null</b> 138 */ 139 public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms) 140 { 141 this(syms, null); 142 } 143 144 /** 145 * Creates a new instance of {@code DefaultExpressionEngine} and initializes 146 * its symbols and the matcher for comparing node names. The passed in 147 * matcher is always used when the names of nodes have to be matched against 148 * parts of configuration keys. 149 * 150 * @param syms the object with the symbols (must not be <b>null</b>) 151 * @param nodeNameMatcher the matcher for node names; can be <b>null</b>, 152 * then a default matcher is used 153 * @throws IllegalArgumentException if the symbols are <b>null</b> 154 */ 155 public DefaultExpressionEngine(final DefaultExpressionEngineSymbols syms, 156 final NodeMatcher<String> nodeNameMatcher) 157 { 158 if (syms == null) 159 { 160 throw new IllegalArgumentException("Symbols must not be null!"); 161 } 162 163 symbols = syms; 164 nameMatcher = 165 (nodeNameMatcher != null) ? nodeNameMatcher 166 : NodeNameMatchers.EQUALS; 167 } 168 169 /** 170 * Returns the {@code DefaultExpressionEngineSymbols} object associated with 171 * this instance. 172 * 173 * @return the {@code DefaultExpressionEngineSymbols} used by this engine 174 * @since 2.0 175 */ 176 public DefaultExpressionEngineSymbols getSymbols() 177 { 178 return symbols; 179 } 180 181 /** 182 * {@inheritDoc} This method supports the syntax as described in the class 183 * comment. 184 */ 185 @Override 186 public <T> List<QueryResult<T>> query(final T root, final String key, 187 final NodeHandler<T> handler) 188 { 189 final List<QueryResult<T>> results = new LinkedList<>(); 190 findNodesForKey(new DefaultConfigurationKey(this, key).iterator(), 191 root, results, handler); 192 return results; 193 } 194 195 /** 196 * {@inheritDoc} This implementation takes the 197 * given parent key, adds a property delimiter, and then adds the node's 198 * name. 199 * The name of the root node is a blank string. Note that no indices are 200 * returned. 201 */ 202 @Override 203 public <T> String nodeKey(final T node, final String parentKey, final NodeHandler<T> handler) 204 { 205 if (parentKey == null) 206 { 207 // this is the root node 208 return StringUtils.EMPTY; 209 } 210 final DefaultConfigurationKey key = new DefaultConfigurationKey(this, 211 parentKey); 212 key.append(handler.nodeName(node), true); 213 return key.toString(); 214 } 215 216 @Override 217 public String attributeKey(final String parentKey, final String attributeName) 218 { 219 final DefaultConfigurationKey key = 220 new DefaultConfigurationKey(this, parentKey); 221 key.appendAttribute(attributeName); 222 return key.toString(); 223 } 224 225 /** 226 * {@inheritDoc} This implementation works similar to {@code nodeKey()}; 227 * however, each key returned by this method has an index (except for the 228 * root node). The parent key is prepended to the name of the current node 229 * in any case and without further checks. If it is <b>null</b>, only the 230 * name of the current node with its index is returned. 231 */ 232 @Override 233 public <T> String canonicalKey(final T node, final String parentKey, 234 final NodeHandler<T> handler) 235 { 236 final String nodeName = handler.nodeName(node); 237 final T parent = handler.getParent(node); 238 final DefaultConfigurationKey key = 239 new DefaultConfigurationKey(this, parentKey); 240 key.append(StringUtils.defaultString(nodeName)); 241 242 if (parent != null) 243 { 244 // this is not the root key 245 key.appendIndex(determineIndex(node, parent, nodeName, handler)); 246 } 247 return key.toString(); 248 } 249 250 /** 251 * <p> 252 * Prepares Adding the property with the specified key. 253 * </p> 254 * <p> 255 * To be able to deal with the structure supported by hierarchical 256 * configuration implementations the passed in key is of importance, 257 * especially the indices it might contain. The following example should 258 * clarify this: Suppose the current node structure looks like the 259 * following: 260 * </p> 261 * <pre> 262 * tables 263 * +-- table 264 * +-- name = user 265 * +-- fields 266 * +-- field 267 * +-- name = uid 268 * +-- field 269 * +-- name = firstName 270 * ... 271 * +-- table 272 * +-- name = documents 273 * +-- fields 274 * ... 275 * </pre> 276 * <p> 277 * In this example a database structure is defined, e.g. all fields of the 278 * first table could be accessed using the key 279 * {@code tables.table(0).fields.field.name}. If now properties are 280 * to be added, it must be exactly specified at which position in the 281 * hierarchy the new property is to be inserted. So to add a new field name 282 * to a table it is not enough to say just 283 * </p> 284 * <pre> 285 * config.addProperty("tables.table.fields.field.name", "newField"); 286 * </pre> 287 * <p> 288 * The statement given above contains some ambiguity. For instance it is not 289 * clear, to which table the new field should be added. If this method finds 290 * such an ambiguity, it is resolved by following the last valid path. Here 291 * this would be the last table. The same is true for the {@code field}; 292 * because there are multiple fields and no explicit index is provided, a 293 * new {@code name} property would be added to the last field - which 294 * is probably not what was desired. 295 * </p> 296 * <p> 297 * To make things clear explicit indices should be provided whenever 298 * possible. In the example above the exact table could be specified by 299 * providing an index for the {@code table} element as in 300 * {@code tables.table(1).fields}. By specifying an index it can 301 * also be expressed that at a given position in the configuration tree a 302 * new branch should be added. In the example above we did not want to add 303 * an additional {@code name} element to the last field of the table, 304 * but we want a complete new {@code field} element. This can be 305 * achieved by specifying an invalid index (like -1) after the element where 306 * a new branch should be created. Given this our example would run: 307 * </p> 308 * <pre> 309 * config.addProperty("tables.table(1).fields.field(-1).name", "newField"); 310 * </pre> 311 * <p> 312 * With this notation it is possible to add new branches everywhere. We 313 * could for instance create a new {@code table} element by 314 * specifying 315 * </p> 316 * <pre> 317 * config.addProperty("tables.table(-1).fields.field.name", "newField2"); 318 * </pre> 319 * <p> 320 * (Note that because after the {@code table} element a new branch is 321 * created indices in following elements are not relevant; the branch is new 322 * so there cannot be any ambiguities.) 323 * </p> 324 * 325 * @param <T> the type of the nodes to be dealt with 326 * @param root the root node of the nodes hierarchy 327 * @param key the key of the new property 328 * @param handler the node handler 329 * @return a data object with information needed for the add operation 330 */ 331 @Override 332 public <T> NodeAddData<T> prepareAdd(final T root, final String key, final NodeHandler<T> handler) 333 { 334 final DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey( 335 this, key).iterator(); 336 if (!it.hasNext()) 337 { 338 throw new IllegalArgumentException( 339 "Key for add operation must be defined!"); 340 } 341 342 final T parent = findLastPathNode(it, root, handler); 343 final List<String> pathNodes = new LinkedList<>(); 344 345 while (it.hasNext()) 346 { 347 if (!it.isPropertyKey()) 348 { 349 throw new IllegalArgumentException( 350 "Invalid key for add operation: " + key 351 + " (Attribute key in the middle.)"); 352 } 353 pathNodes.add(it.currentKey()); 354 it.next(); 355 } 356 357 return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(), 358 pathNodes); 359 } 360 361 /** 362 * Recursive helper method for evaluating a key. This method processes all 363 * facets of a configuration key, traverses the tree of properties and 364 * fetches the results of all matching properties. 365 * 366 * @param <T> the type of nodes to be dealt with 367 * @param keyPart the configuration key iterator 368 * @param node the current node 369 * @param results here the found results are stored 370 * @param handler the node handler 371 */ 372 protected <T> void findNodesForKey( 373 final DefaultConfigurationKey.KeyIterator keyPart, final T node, 374 final Collection<QueryResult<T>> results, final NodeHandler<T> handler) 375 { 376 if (!keyPart.hasNext()) 377 { 378 results.add(QueryResult.createNodeResult(node)); 379 } 380 381 else 382 { 383 final String key = keyPart.nextKey(false); 384 if (keyPart.isPropertyKey()) 385 { 386 processSubNodes(keyPart, findChildNodesByName(handler, node, key), 387 results, handler); 388 } 389 if (keyPart.isAttribute() && !keyPart.hasNext()) 390 { 391 if (handler.getAttributeValue(node, key) != null) 392 { 393 results.add(QueryResult.createAttributeResult(node, key)); 394 } 395 } 396 } 397 } 398 399 /** 400 * Finds the last existing node for an add operation. This method traverses 401 * the node tree along the specified key. The last existing node on this 402 * path is returned. 403 * 404 * @param <T> the type of the nodes to be dealt with 405 * @param keyIt the key iterator 406 * @param node the current node 407 * @param handler the node handler 408 * @return the last existing node on the given path 409 */ 410 protected <T> T findLastPathNode(final DefaultConfigurationKey.KeyIterator keyIt, 411 final T node, final NodeHandler<T> handler) 412 { 413 final String keyPart = keyIt.nextKey(false); 414 415 if (keyIt.hasNext()) 416 { 417 if (!keyIt.isPropertyKey()) 418 { 419 // Attribute keys can only appear as last elements of the path 420 throw new IllegalArgumentException( 421 "Invalid path for add operation: " 422 + "Attribute key in the middle!"); 423 } 424 final int idx = 425 keyIt.hasIndex() ? keyIt.getIndex() : handler 426 .getMatchingChildrenCount(node, nameMatcher, 427 keyPart) - 1; 428 if (idx < 0 429 || idx >= handler.getMatchingChildrenCount(node, 430 nameMatcher, keyPart)) 431 { 432 return node; 433 } 434 return findLastPathNode(keyIt, 435 findChildNodesByName(handler, node, keyPart).get(idx), 436 handler); 437 } 438 return node; 439 } 440 441 /** 442 * Called by {@code findNodesForKey()} to process the sub nodes of 443 * the current node depending on the type of the current key part (children, 444 * attributes, or both). 445 * 446 * @param <T> the type of the nodes to be dealt with 447 * @param keyPart the key part 448 * @param subNodes a list with the sub nodes to process 449 * @param nodes the target collection 450 * @param handler the node handler 451 */ 452 private <T> void processSubNodes(final DefaultConfigurationKey.KeyIterator keyPart, 453 final List<T> subNodes, final Collection<QueryResult<T>> nodes, final NodeHandler<T> handler) 454 { 455 if (keyPart.hasIndex()) 456 { 457 if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size()) 458 { 459 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart 460 .clone(), subNodes.get(keyPart.getIndex()), nodes, handler); 461 } 462 } 463 else 464 { 465 for (final T node : subNodes) 466 { 467 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart 468 .clone(), node, nodes, handler); 469 } 470 } 471 } 472 473 /** 474 * Determines the index of the given node based on its parent node. 475 * 476 * @param node the current node 477 * @param parent the parent node 478 * @param nodeName the name of the current node 479 * @param handler the node handler 480 * @param <T> the type of the nodes to be dealt with 481 * @return the index of this node 482 */ 483 private <T> int determineIndex(final T node, final T parent, final String nodeName, 484 final NodeHandler<T> handler) 485 { 486 return findChildNodesByName(handler, parent, nodeName).indexOf(node); 487 } 488 489 /** 490 * Returns a list with all child nodes of the given parent node which match 491 * the specified node name. The match is done using the current node name 492 * matcher. 493 * 494 * @param handler the {@code NodeHandler} 495 * @param parent the parent node 496 * @param nodeName the name of the current node 497 * @param <T> the type of the nodes to be dealt with 498 * @return a list with all matching child nodes 499 */ 500 private <T> List<T> findChildNodesByName(final NodeHandler<T> handler, final T parent, 501 final String nodeName) 502 { 503 return handler.getMatchingChildren(parent, nameMatcher, nodeName); 504 } 505}