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