##### Coverage Report - org.apache.commons.configuration.tree.DefaultExpressionEngine

Classes in this File | Line Coverage | Branch Coverage | Complexity

DefaultExpressionEngine

100% | 71/71

100% | 16/16

2.111111111111111;2,111

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.configuration.tree;

18

19

     import java.util.Collection;

20

     import java.util.Iterator;

21

     import java.util.LinkedList;

22

     import java.util.List;

23

24

     import org.apache.commons.lang.StringUtils;

25

26

     /**

27

      * <p>

28

      * A default implementation of the <code>ExpressionEngine</code> interface

29

      * providing the &quot;native&quote; expression language for hierarchical

30

      * configurations.

31

      * </p>

32

      * <p>

33

      * This class implements a rather simple expression language for navigating

34

      * through a hierarchy of configuration nodes. It supports the following

35

      * operations:

36

      * </p>

37

      * <p>

38

      * <ul>

39

      * <li>Navigating from a node to one of its children using the child node

40

      * delimiter, which is by the default a dot (&quot;.&quot;).</li>

41

      * <li>Navigating from a node to one of its attributes using the attribute node

42

      * delimiter, which by default follows the XPATH like syntax

43

      * <code>[@&lt;attributeName&gt;]</code>.</li>

44

      * <li>If there are multiple child or attribute nodes with the same name, a

45

      * specific node can be selected using a numerical index. By default indices are

46

      * written in paranthesis.</li>

47

      * </ul>

48

      * </p>

49

      * <p>

50

      * As an example consider the following XML document:

51

      * </p>

52

      *

53

      * <pre>

54

      *  &lt;database&gt;

55

      *    &lt;tables&gt;

56

      *      &lt;table type=&quot;system&quot;&gt;

57

      *        &lt;name&gt;users&lt;/name&gt;

58

      *        &lt;fields&gt;

59

      *          &lt;field&gt;

60

      *            &lt;name&gt;lid&lt;/name&gt;

61

      *            &lt;type&gt;long&lt;/name&gt;

62

      *          &lt;/field&gt;

63

      *          &lt;field&gt;

64

      *            &lt;name&gt;usrName&lt;/name&gt;

65

      *            &lt;type&gt;java.lang.String&lt;/type&gt;

66

      *          &lt;/field&gt;

67

      *         ...

68

      *        &lt;/fields&gt;

69

      *      &lt;/table&gt;

70

      *      &lt;table&gt;

71

      *        &lt;name&gt;documents&lt;/name&gt;

72

      *        &lt;fields&gt;

73

      *          &lt;field&gt;

74

      *            &lt;name&gt;docid&lt;/name&gt;

75

      *            &lt;type&gt;long&lt;/type&gt;

76

      *          &lt;/field&gt;

77

      *          ...

78

      *        &lt;/fields&gt;

79

      *      &lt;/table&gt;

80

      *      ...

81

      *    &lt;/tables&gt;

82

      *  &lt;/database&gt;

83

      * </pre>

84

      *

85

      * </p>

86

      * <p>

87

      * If this document is parsed and stored in a hierarchical configuration object,

88

      * for instance the key <code>tables.table(0).name</code> can be used to find

89

      * out the name of the first table. In opposite <code>tables.table.name</code>

90

      * would return a collection with the names of all available tables. Similarily

91

      * the key <code>tables.table(1).fields.field.name</code> returns a collection

92

      * with the names of all fields of the second table. If another index is added

93

      * after the <code>field</code> element, a single field can be accessed:

94

      * <code>tables.table(1).fields.field(0).name</code>. The key

95

      * <code>tables.table(0)[@type]</code> would select the type attribute of the

96

      * first table.

97

      * </p>

98

      * <p>

99

      * 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

158

     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

79

         private String propertyDelimiter = DEFAULT_PROPERTY_DELIMITER;

131

132

         /** Stores the escaped property delimiter. */

133

79

         private String escapedDelimiter = DEFAULT_ESCAPED_DELIMITER;

134

135

         /** Stores the attribute start marker. */

136

79

         private String attributeStart = DEFAULT_ATTRIBUTE_START;

137

138

         /** Stores the attribute end marker. */

139

79

         private String attributeEnd = DEFAULT_ATTRIBUTE_END;

140

141

         /** Stores the index start marker. */

142

79

         private String indexStart = DEFAULT_INDEX_START;

143

144

         /** stores the index end marker. */

145

79

         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

10237

             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

8

             this.attributeEnd = attributeEnd;

166

8

         }

167

168

         /**

169

          * Returns the attribute start marker.

170

          *

171

          * @return the attribute start marker

172

          */

173

         public String getAttributeStart()

174

         {

175

25483

             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

9

             this.attributeStart = attributeStart;

187

9

         }

188

189

         /**

190

          * Returns the escaped property delimiter string.

191

          *

192

          * @return the escaped property delimiter

193

          */

194

         public String getEscapedDelimiter()

195

         {

196

56852

             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

          * &quot;.&quot; is used as property delimiter, you can set the escaped

203

          * delimiter to &quot;\.&quot; 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

6

             this.escapedDelimiter = escapedDelimiter;

211

6

         }

212

213

         /**

214

          * Returns the index end marker.

215

          *

216

          * @return the index end marker

217

          */

218

         public String getIndexEnd()

219

         {

220

485

             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

8

             this.indexEnd = indexEnd;

231

8

         }

232

233

         /**

234

          * Returns the index start marker.

235

          *

236

          * @return the index start marker

237

          */

238

         public String getIndexStart()

239

         {

240

11843

             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

8

             this.indexStart = indexStart;

252

8

         }

253

254

         /**

255

          * Returns the property delimiter.

256

          *

257

          * @return the property delimiter

258

          */

259

         public String getPropertyDelimiter()

260

         {

261

69236

             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

8

             this.propertyDelimiter = propertyDelimiter;

273

8

         }

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

2926

             List nodes = new LinkedList();

286

2926

             findNodesForKey(new DefaultConfigurationKey(this, key).iterator(),

287

                     root, nodes);

288

2926

             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

932

             if (parentKey == null)

305

             {

306

                 // this is the root node

307

38

                 return StringUtils.EMPTY;

308

             }

309

310

             else

311

             {

312

894

                 DefaultConfigurationKey key = new DefaultConfigurationKey(this,

313

                         parentKey);

314

894

                 if (node.isAttribute())

315

                 {

316

146

                     key.appendAttribute(node.getName());

317

                 }

318

                 else

319

                 {

320

748

                     key.append(node.getName(), true);

321

                 }

322

894

                 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(&quot;tables.table.fields.field.name&quot;, &quot;newField&quot;);

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(&quot;tables.table(1).fields.field(-1).name&quot;, &quot;newField&quot;);

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(&quot;tables.table(-1).fields.field.name&quot;, &quot;newField2&quot;);

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

1698

             DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(

416

                     this, key).iterator();

417

1698

             if (!it.hasNext())

418

             {

419

3

                 throw new IllegalArgumentException(

420

                         "Key for add operation must be defined!");

421

             }

422

423

1695

             NodeAddData result = new NodeAddData();

424

1695

             result.setParent(findLastPathNode(it, root));

425

426

4176

             while (it.hasNext())

427

             {

428

789

                 if (!it.isPropertyKey())

429

                 {

430

1

                     throw new IllegalArgumentException(

431

                             "Invalid key for add operation: " + key

432

                                     + " (Attribute key in the middle.)");

433

                 }

434

788

                 result.addPathNode(it.currentKey());

435

788

                 it.next();

436

             }

437

438

1693

             result.setNewNodeName(it.currentKey());

439

1693

             result.setAttribute(!it.isPropertyKey());

440

1693

             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

9967

             if (!keyPart.hasNext())

456

             {

457

2780

                 nodes.add(node);

458

             }

459

460

             else

461

             {

462

7187

                 String key = keyPart.nextKey(false);

463

7187

                 if (keyPart.isPropertyKey())

464

                 {

465

6370

                     processSubNodes(keyPart, node.getChildren(key), nodes);

466

                 }

467

7187

                 if (keyPart.isAttribute())

468

                 {

469

821

                     processSubNodes(keyPart, node.getAttributes(key), nodes);

470

                 }

471

             }

472

9967

         }

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

3827

             String keyPart = keyIt.nextKey(false);

487

488

3827

             if (keyIt.hasNext())

489

             {

490

2768

                 if (!keyIt.isPropertyKey())

491

                 {

492

                     // Attribute keys can only appear as last elements of the path

493

1

                     throw new IllegalArgumentException(

494

                             "Invalid path for add operation: "

495

                                     + "Attribute key in the middle!");

496

                 }

497

2767

                 int idx = keyIt.hasIndex() ? keyIt.getIndex() : node

498

                         .getChildrenCount(keyPart) - 1;

499

2767

                 if (idx < 0 || idx >= node.getChildrenCount(keyPart))

500

                 {

501

635

                     return node;

502

                 }

503

                 else

504

                 {

505

2132

                     return findLastPathNode(keyIt, (ConfigurationNode) node

506

                             .getChildren(keyPart).get(idx));

507

                 }

508

             }

509

510

             else

511

             {

512

1059

                 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

7191

             if (keyPart.hasIndex())

529

             {

530

271

                 if (keyPart.getIndex() >= 0 && keyPart.getIndex() < subNodes.size())

531

                 {

532

264

                     findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart

533

                             .clone(), (ConfigurationNode) subNodes.get(keyPart

534

                             .getIndex()), nodes);

535

                 }

536

             }

537

             else

538

             {

539

20617

                 for (Iterator it = subNodes.iterator(); it.hasNext();)

540

                 {

541

6777

                     findNodesForKey((DefaultConfigurationKey.KeyIterator) keyPart

542

                             .clone(), (ConfigurationNode) it.next(), nodes);

543

                 }

544

             }

545

7191

         }

546

     }