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 1790899 2017-04-10 21:56:46Z 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(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(DefaultExpressionEngineSymbols syms, 156 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(T root, String key, 187 NodeHandler<T> handler) 188 { 189 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(T node, String parentKey, NodeHandler<T> handler) 204 { 205 if (parentKey == null) 206 { 207 // this is the root node 208 return StringUtils.EMPTY; 209 } 210 211 else 212 { 213 DefaultConfigurationKey key = new DefaultConfigurationKey(this, 214 parentKey); 215 key.append(handler.nodeName(node), true); 216 return key.toString(); 217 } 218 } 219 220 @Override 221 public String attributeKey(String parentKey, String attributeName) 222 { 223 DefaultConfigurationKey key = 224 new DefaultConfigurationKey(this, parentKey); 225 key.appendAttribute(attributeName); 226 return key.toString(); 227 } 228 229 /** 230 * {@inheritDoc} This implementation works similar to {@code nodeKey()}; 231 * however, each key returned by this method has an index (except for the 232 * root node). The parent key is prepended to the name of the current node 233 * in any case and without further checks. If it is <b>null</b>, only the 234 * name of the current node with its index is returned. 235 */ 236 @Override 237 public <T> String canonicalKey(T node, String parentKey, 238 NodeHandler<T> handler) 239 { 240 String nodeName = handler.nodeName(node); 241 T parent = handler.getParent(node); 242 DefaultConfigurationKey key = 243 new DefaultConfigurationKey(this, parentKey); 244 key.append(StringUtils.defaultString(nodeName)); 245 246 if (parent != null) 247 { 248 // this is not the root key 249 key.appendIndex(determineIndex(node, parent, nodeName, handler)); 250 } 251 return key.toString(); 252 } 253 254 /** 255 * <p> 256 * Prepares Adding the property with the specified key. 257 * </p> 258 * <p> 259 * To be able to deal with the structure supported by hierarchical 260 * configuration implementations the passed in key is of importance, 261 * especially the indices it might contain. The following example should 262 * clarify this: Suppose the current node structure looks like the 263 * following: 264 * </p> 265 * <pre> 266 * tables 267 * +-- table 268 * +-- name = user 269 * +-- fields 270 * +-- field 271 * +-- name = uid 272 * +-- field 273 * +-- name = firstName 274 * ... 275 * +-- table 276 * +-- name = documents 277 * +-- fields 278 * ... 279 * </pre> 280 * <p> 281 * In this example a database structure is defined, e.g. all fields of the 282 * first table could be accessed using the key 283 * {@code tables.table(0).fields.field.name}. If now properties are 284 * to be added, it must be exactly specified at which position in the 285 * hierarchy the new property is to be inserted. So to add a new field name 286 * to a table it is not enough to say just 287 * </p> 288 * <pre> 289 * config.addProperty("tables.table.fields.field.name", "newField"); 290 * </pre> 291 * <p> 292 * The statement given above contains some ambiguity. For instance it is not 293 * clear, to which table the new field should be added. If this method finds 294 * such an ambiguity, it is resolved by following the last valid path. Here 295 * this would be the last table. The same is true for the {@code field}; 296 * because there are multiple fields and no explicit index is provided, a 297 * new {@code name} property would be added to the last field - which 298 * is probably not what was desired. 299 * </p> 300 * <p> 301 * To make things clear explicit indices should be provided whenever 302 * possible. In the example above the exact table could be specified by 303 * providing an index for the {@code table} element as in 304 * {@code tables.table(1).fields}. By specifying an index it can 305 * also be expressed that at a given position in the configuration tree a 306 * new branch should be added. In the example above we did not want to add 307 * an additional {@code name} element to the last field of the table, 308 * but we want a complete new {@code field} element. This can be 309 * achieved by specifying an invalid index (like -1) after the element where 310 * a new branch should be created. Given this our example would run: 311 * </p> 312 * <pre> 313 * config.addProperty("tables.table(1).fields.field(-1).name", "newField"); 314 * </pre> 315 * <p> 316 * With this notation it is possible to add new branches everywhere. We 317 * could for instance create a new {@code table} element by 318 * specifying 319 * </p> 320 * <pre> 321 * config.addProperty("tables.table(-1).fields.field.name", "newField2"); 322 * </pre> 323 * <p> 324 * (Note that because after the {@code table} element a new branch is 325 * created indices in following elements are not relevant; the branch is new 326 * so there cannot be any ambiguities.) 327 * </p> 328 * 329 * @param <T> the type of the nodes to be dealt with 330 * @param root the root node of the nodes hierarchy 331 * @param key the key of the new property 332 * @param handler the node handler 333 * @return a data object with information needed for the add operation 334 */ 335 @Override 336 public <T> NodeAddData<T> prepareAdd(T root, String key, NodeHandler<T> handler) 337 { 338 DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey( 339 this, key).iterator(); 340 if (!it.hasNext()) 341 { 342 throw new IllegalArgumentException( 343 "Key for add operation must be defined!"); 344 } 345 346 T parent = findLastPathNode(it, root, handler); 347 List<String> pathNodes = new LinkedList<>(); 348 349 while (it.hasNext()) 350 { 351 if (!it.isPropertyKey()) 352 { 353 throw new IllegalArgumentException( 354 "Invalid key for add operation: " + key 355 + " (Attribute key in the middle.)"); 356 } 357 pathNodes.add(it.currentKey()); 358 it.next(); 359 } 360 361 return new NodeAddData<>(parent, it.currentKey(), !it.isPropertyKey(), 362 pathNodes); 363 } 364 365 /** 366 * Recursive helper method for evaluating a key. This method processes all 367 * facets of a configuration key, traverses the tree of properties and 368 * fetches the results of all matching properties. 369 * 370 * @param <T> the type of nodes to be dealt with 371 * @param keyPart the configuration key iterator 372 * @param node the current node 373 * @param results here the found results are stored 374 * @param handler the node handler 375 */ 376 protected <T> void findNodesForKey( 377 DefaultConfigurationKey.KeyIterator keyPart, T node, 378 Collection<QueryResult<T>> results, NodeHandler<T> handler) 379 { 380 if (!keyPart.hasNext()) 381 { 382 results.add(QueryResult.createNodeResult(node)); 383 } 384 385 else 386 { 387 String key = keyPart.nextKey(false); 388 if (keyPart.isPropertyKey()) 389 { 390 processSubNodes(keyPart, findChildNodesByName(handler, node, key), 391 results, handler); 392 } 393 if (keyPart.isAttribute() && !keyPart.hasNext()) 394 { 395 if (handler.getAttributeValue(node, key) != null) 396 { 397 results.add(QueryResult.createAttributeResult(node, key)); 398 } 399 } 400 } 401 } 402 403 /** 404 * Finds the last existing node for an add operation. This method traverses 405 * the node tree along the specified key. The last existing node on this 406 * path is returned. 407 * 408 * @param <T> the type of the nodes to be dealt with 409 * @param keyIt the key iterator 410 * @param node the current node 411 * @param handler the node handler 412 * @return the last existing node on the given path 413 */ 414 protected <T> T findLastPathNode(DefaultConfigurationKey.KeyIterator keyIt, 415 T node, NodeHandler<T> handler) 416 { 417 String keyPart = keyIt.nextKey(false); 418 419 if (keyIt.hasNext()) 420 { 421 if (!keyIt.isPropertyKey()) 422 { 423 // Attribute keys can only appear as last elements of the path 424 throw new IllegalArgumentException( 425 "Invalid path for add operation: " 426 + "Attribute key in the middle!"); 427 } 428 int idx = 429 keyIt.hasIndex() ? keyIt.getIndex() : handler 430 .getMatchingChildrenCount(node, nameMatcher, 431 keyPart) - 1; 432 if (idx < 0 433 || idx >= handler.getMatchingChildrenCount(node, 434 nameMatcher, keyPart)) 435 { 436 return node; 437 } 438 else 439 { 440 return findLastPathNode(keyIt, 441 findChildNodesByName(handler, node, keyPart).get(idx), 442 handler); 443 } 444 } 445 446 else 447 { 448 return node; 449 } 450 } 451 452 /** 453 * Called by {@code findNodesForKey()} to process the sub nodes of 454 * the current node depending on the type of the current key part (children, 455 * attributes, or both). 456 * 457 * @param <T> the type of the nodes to be dealt with 458 * @param keyPart the key part 459 * @param subNodes a list with the sub nodes to process 460 * @param nodes the target collection 461 * @param handler the node handler 462 */ 463 private <T> void processSubNodes(DefaultConfigurationKey.KeyIterator keyPart, 464 List<T> subNodes, Collection<QueryResult<T>> nodes, NodeHandler<T> handler) 465 { 466 if (keyPart.hasIndex()) 467 { 468 if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size()) 469 { 470 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart 471 .clone(), subNodes.get(keyPart.getIndex()), nodes, handler); 472 } 473 } 474 else 475 { 476 for (T node : subNodes) 477 { 478 findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart 479 .clone(), node, nodes, handler); 480 } 481 } 482 } 483 484 /** 485 * Determines the index of the given node based on its parent node. 486 * 487 * @param node the current node 488 * @param parent the parent node 489 * @param nodeName the name of the current node 490 * @param handler the node handler 491 * @param <T> the type of the nodes to be dealt with 492 * @return the index of this node 493 */ 494 private <T> int determineIndex(T node, T parent, String nodeName, 495 NodeHandler<T> handler) 496 { 497 return findChildNodesByName(handler, parent, nodeName).indexOf(node); 498 } 499 500 /** 501 * Returns a list with all child nodes of the given parent node which match 502 * the specified node name. The match is done using the current node name 503 * matcher. 504 * 505 * @param handler the {@code NodeHandler} 506 * @param parent the parent node 507 * @param nodeName the name of the current node 508 * @param <T> the type of the nodes to be dealt with 509 * @return a list with all matching child nodes 510 */ 511 private <T> List<T> findChildNodesByName(NodeHandler<T> handler, T parent, 512 String nodeName) 513 { 514 return handler.getMatchingChildren(parent, nameMatcher, nodeName); 515 } 516}