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