##### Coverage Report - org.apache.commons.configuration.ConfigurationKey

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

ConfigurationKey

98% | 65/66

100% | 15/15

1.8333333333333333;1,833

ConfigurationKey$KeyIterator

92% | 55/60

100% | 13/13

1.8333333333333333;1,833

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

18

     package org.apache.commons.configuration;

19

20

     import java.io.Serializable;

21

     import java.util.Iterator;

22

     import java.util.NoSuchElementException;

23

24

     /**

25

      * <p>A simple class that supports creation of and iteration on complex

26

      * configuration keys.</p>

27

      *

28

      * <p>For key creation the class works similar to a StringBuffer: There are

29

      * several <code>appendXXXX()</code> methods with which single parts

30

      * of a key can be constructed. All these methods return a reference to the

31

      * actual object so they can be written in a chain. When using this methods

32

      * the exact syntax for keys need not be known.</p>

33

      *

34

      * <p>This class also defines a specialized iterator for configuration keys.

35

      * With such an iterator a key can be tokenized into its single parts. For

36

      * each part it can be checked whether it has an associated index.</p>

37

      *

38

      * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>

39

      * @version $Id: ConfigurationKey.java 439648 2006-09-02 20:42:10Z oheger $

40

      */

41

9120

     public class ConfigurationKey implements Serializable

42

     {

43

         /** Constant for a property delimiter.*/

44

         public static final char PROPERTY_DELIMITER = '.';

45

46

         /** Constant for an escaped delimiter. */

47

3

         public static final String ESCAPED_DELIMITER =

48

             String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);

49

50

         /** Constant for an attribute start marker.*/

51

         private static final String ATTRIBUTE_START = "[@";

52

53

         /** Constant for an attribute end marker.*/

54

         private static final String ATTRIBUTE_END = "]";

55

56

         /** Constant for an index start marker.*/

57

         private static final char INDEX_START = '(';

58

59

         /** Constant for an index end marker.*/

60

         private static final char INDEX_END = ')';

61

62

         /** Constant for the initial StringBuffer size.*/

63

         private static final int INITIAL_SIZE = 32;

64

65

         /**

66

          * The serial version ID.

67

          */

68

         private static final long serialVersionUID = -4299732083605277656L;

69

70

         /** Holds a buffer with the so far created key.*/

71

         private StringBuffer keyBuffer;

72

73

         /**

74

          * Creates a new, empty instance of <code>ConfigurationKey</code>.

75

          */

76

         public ConfigurationKey()

77

183

         {

78

183

             keyBuffer = new StringBuffer(INITIAL_SIZE);

79

183

         }

80

81

         /**

82

          * Creates a new instance of <code>ConfigurationKey</code> and

83

          * initializes it with the given key.

84

          *

85

          * @param key the key as a string

86

          */

87

         public ConfigurationKey(String key)

88

54

         {

89

54

             keyBuffer = new StringBuffer(key);

90

54

             removeTrailingDelimiter();

91

54

         }

92

93

         /**

94

          * Appends the name of a property to this key. If necessary, a

95

          * property delimiter will be added.

96

          *

97

          * @param property the name of the property to be added

98

          * @return a reference to this object

99

          */

100

         public ConfigurationKey append(String property)

101

         {

102

257

             if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))

103

             {

104

108

                 keyBuffer.append(PROPERTY_DELIMITER);

105

             }

106

107

257

             keyBuffer.append(property);

108

257

             removeTrailingDelimiter();

109

257

             return this;

110

         }

111

112

         /**

113

          * Appends an index to this configuration key.

114

          *

115

          * @param index the index to be appended

116

          * @return a reference to this object

117

          */

118

         public ConfigurationKey appendIndex(int index)

119

         {

120

21

             keyBuffer.append(INDEX_START).append(index);

121

21

             keyBuffer.append(INDEX_END);

122

21

             return this;

123

         }

124

125

         /**

126

          * Appends an attribute to this configuration key.

127

          *

128

          * @param attr the name of the attribute to be appended

129

          * @return a reference to this object

130

          */

131

         public ConfigurationKey appendAttribute(String attr)

132

         {

133

5

             keyBuffer.append(constructAttributeKey(attr));

134

5

             return this;

135

         }

136

137

         /**

138

          * Checks if this key is an attribute key.

139

          *

140

          * @return a flag if this key is an attribute key

141

          */

142

         public boolean isAttributeKey()

143

         {

144

0

             return isAttributeKey(keyBuffer.toString());

145

         }

146

147

         /**

148

          * Checks if the passed in key is an attribute key. Such attribute keys

149

          * start and end with certain marker strings. In some cases they must be

150

          * treated slightly different.

151

          *

152

          * @param key the key (part) to be checked

153

          * @return a flag if this key is an attribute key

154

          */

155

         public static boolean isAttributeKey(String key)

156

         {

157

681

             return key != null

158

             && key.startsWith(ATTRIBUTE_START)

159

             && key.endsWith(ATTRIBUTE_END);

160

         }

161

162

         /**

163

          * Decorates the given key so that it represents an attribute. Adds

164

          * special start and end markers.

165

          *

166

          * @param key the key to be decorated

167

          * @return the decorated attribute key

168

          */

169

         public static String constructAttributeKey(String key)

170

         {

171

8

             StringBuffer buf = new StringBuffer();

172

8

             buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);

173

8

             return buf.toString();

174

         }

175

176

         /**

177

          * Extracts the name of the attribute from the given attribute key.

178

          * This method removes the attribute markers - if any - from the

179

          * specified key.

180

          *

181

          * @param key the attribute key

182

          * @return the name of the corresponding attribute

183

          */

184

         public static String attributeName(String key)

185

         {

186

2

             return isAttributeKey(key) ? removeAttributeMarkers(key) : key;

187

         }

188

189

         /**

190

          * Helper method for removing attribute markers from a key.

191

          *

192

          * @param key the key

193

          * @return the key with removed attribute markers

194

          */

195

         static String removeAttributeMarkers(String key)

196

         {

197

7

             return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());

198

         }

199

200

         /**

201

          * Helper method that checks if the actual buffer ends with a property

202

          * delimiter.

203

          *

204

          * @return a flag if there is a trailing delimiter

205

          */

206

         private boolean hasDelimiter()

207

         {

208

424

             int count = 0;

209

424

             for (int idx = keyBuffer.length() - 1; idx >= 0

210

8

                     && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)

211

             {

212

8

                 count++;

213

             }

214

424

             return count % 2 == 1;

215

         }

216

217

         /**

218

          * Removes a trailing delimiter if there is any.

219

          */

220

         private void removeTrailingDelimiter()

221

         {

222

626

             while (hasDelimiter())

223

             {

224

4

                 keyBuffer.deleteCharAt(keyBuffer.length() - 1);

225

             }

226

311

         }

227

228

         /**

229

          * Returns a string representation of this object. This is the

230

          * configuration key as a plain string.

231

          *

232

          * @return a string for this object

233

          */

234

         public String toString()

235

         {

236

121

             return keyBuffer.toString();

237

         }

238

239

         /**

240

          * Returns an iterator for iterating over the single components of

241

          * this configuration key.

242

          *

243

          * @return an iterator for this key

244

          */

245

         public KeyIterator iterator()

246

         {

247

277

             return new KeyIterator();

248

         }

249

250

         /**

251

          * Returns the actual length of this configuration key.

252

          *

253

          * @return the length of this key

254

          */

255

         public int length()

256

         {

257

203

             return keyBuffer.length();

258

         }

259

260

         /**

261

          * Sets the new length of this configuration key. With this method it is

262

          * possible to truncate the key, e.g. to return to a state prior calling

263

          * some <code>append()</code> methods. The semantic is the same as

264

          * the <code>setLength()</code> method of <code>StringBuffer</code>.

265

          *

266

          * @param len the new length of the key

267

          */

268

         public void setLength(int len)

269

         {

270

1

             keyBuffer.setLength(len);

271

1

         }

272

273

         /**

274

          * Checks if two <code>ConfigurationKey</code> objects are equal. The

275

          * method can be called with strings or other objects, too.

276

          *

277

          * @param c the object to compare

278

          * @return a flag if both objects are equal

279

          */

280

         public boolean equals(Object c)

281

         {

282

11

             if (c == null)

283

             {

284

1

                 return false;

285

             }

286

287

10

             return keyBuffer.toString().equals(c.toString());

288

         }

289

290

         /**

291

          * Returns the hash code for this object.

292

          *

293

          * @return the hash code

294

          */

295

         public int hashCode()

296

         {

297

2

             return String.valueOf(keyBuffer).hashCode();

298

         }

299

300

         /**

301

          * Returns a configuration key object that is initialized with the part

302

          * of the key that is common to this key and the passed in key.

303

          *

304

          * @param other the other key

305

          * @return a key object with the common key part

306

          */

307

         public ConfigurationKey commonKey(ConfigurationKey other)

308

         {

309

104

             if (other == null)

310

             {

311

1

                 throw new IllegalArgumentException("Other key must no be null!");

312

             }

313

314

103

             ConfigurationKey result = new ConfigurationKey();

315

103

             KeyIterator it1 = iterator();

316

103

             KeyIterator it2 = other.iterator();

317

318

350

             while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))

319

             {

320

144

                 if (it1.isAttribute())

321

                 {

322

2

                     result.appendAttribute(it1.currentKey());

323

                 }

324

                 else

325

                 {

326

142

                     result.append(it1.currentKey());

327

142

                     if (it1.hasIndex)

328

                     {

329

7

                         result.appendIndex(it1.getIndex());

330

                     }

331

                 }

332

             }

333

334

103

             return result;

335

         }

336

337

         /**

338

          * Returns the &quot;difference key&quot; to a given key. This value

339

          * is the part of the passed in key that differs from this key. There is

340

          * the following relation:

341

          * <code>other = key.commonKey(other) + key.differenceKey(other)</code>

342

          * for an arbitrary configuration key <code>key</code>.

343

          *

344

          * @param other the key for which the difference is to be calculated

345

          * @return the difference key

346

          */

347

         public ConfigurationKey differenceKey(ConfigurationKey other)

348

         {

349

67

             ConfigurationKey common = commonKey(other);

350

67

             ConfigurationKey result = new ConfigurationKey();

351

352

67

             if (common.length() < other.length())

353

             {

354

63

                 String k = other.toString().substring(common.length());

355

                 // skip trailing delimiters

356

63

                 int i = 0;

357

170

                 while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)

358

                 {

359

44

                     i++;

360

                 }

361

362

63

                 if (i < k.length())

363

                 {

364

63

                     result.append(k.substring(i));

365

                 }

366

             }

367

368

67

             return result;

369

         }

370

371

         /**

372

          * Helper method for comparing two key parts.

373

          *

374

          * @param it1 the iterator with the first part

375

          * @param it2 the iterator with the second part

376

          * @return a flag if both parts are equal

377

          */

378

         private static boolean partsEqual(KeyIterator it1, KeyIterator it2)

379

         {

380

232

             return it1.nextKey().equals(it2.nextKey())

381

             && it1.getIndex() == it2.getIndex()

382

             && it1.isAttribute() == it2.isAttribute();

383

         }

384

385

         /**

386

          * A specialized iterator class for tokenizing a configuration key.

387

          * This class implements the normal iterator interface. In addition it

388

          * provides some specific methods for configuration keys.

389

          */

390

419

         public class KeyIterator implements Iterator, Cloneable

391

         {

392

             /** Stores the current key name.*/

393

             private String current;

394

395

             /** Stores the start index of the actual token.*/

396

             private int startIndex;

397

398

             /** Stores the end index of the actual token.*/

399

             private int endIndex;

400

401

             /** Stores the index of the actual property if there is one.*/

402

             private int indexValue;

403

404

             /** Stores a flag if the actual property has an index.*/

405

             private boolean hasIndex;

406

407

             /** Stores a flag if the actual property is an attribute.*/

408

             private boolean attribute;

409

410

             /**

411

              * Helper method for determining the next indices.

412

              *

413

              * @return the next key part

414

              */

415

             private String findNextIndices()

416

             {

417

567

                 startIndex = endIndex;

418

                 // skip empty names

419

567

                 while (startIndex < keyBuffer.length()

420

883

                         && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)

421

                 {

422

316

                     startIndex++;

423

                 }

424

425

                 // Key ends with a delimiter?

426

567

                 if (startIndex >= keyBuffer.length())

427

                 {

428

0

                     endIndex = keyBuffer.length();

429

0

                     startIndex = endIndex - 1;

430

0

                     return keyBuffer.substring(startIndex, endIndex);

431

                 }

432

                 else

433

                 {

434

567

                     return nextKeyPart();

435

                 }

436

             }

437

438

             /**

439

              * Helper method for extracting the next key part. Takes escaping of

440

              * delimiter characters into account.

441

              *

442

              * @return the next key part

443

              */

444

             private String nextKeyPart()

445

             {

446

567

                 StringBuffer key = new StringBuffer(INITIAL_SIZE);

447

567

                 int idx = startIndex;

448

567

                 int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,

449

                         startIndex);

450

567

                 if (endIdx < 0 || endIdx == startIndex)

451

                 {

452

532

                     endIdx = keyBuffer.length();

453

                 }

454

567

                 boolean found = false;

455

456

5254

                 while (!found && idx < endIdx)

457

                 {

458

4120

                     char c = keyBuffer.charAt(idx);

459

4120

                     if (c == PROPERTY_DELIMITER)

460

                     {

461

                         // a duplicated delimiter means escaping

462

385

                         if (idx == endIdx - 1

463

                                 || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)

464

                         {

465

382

                             found = true;

466

                         }

467

                         else

468

                         {

469

3

                             idx++;

470

                         }

471

                     }

472

4120

                     if (!found)

473

                     {

474

3738

                         key.append(c);

475

3738

                         idx++;

476

                     }

477

                 }

478

479

567

                 endIndex = idx;

480

567

                 return key.toString();

481

             }

482

483

             /**

484

              * Returns the next key part of this configuration key. This is a short

485

              * form of <code>nextKey(false)</code>.

486

              *

487

              * @return the next key part

488

              */

489

             public String nextKey()

490

             {

491

566

                 return nextKey(false);

492

             }

493

494

             /**

495

              * Returns the next key part of this configuration key. The boolean

496

              * parameter indicates wheter a decorated key should be returned. This

497

              * affects only attribute keys: if the parameter is <b>false</b>, the

498

              * attribute markers are stripped from the key; if it is <b>true</b>,

499

              * they remain.

500

              *

501

              * @param decorated a flag if the decorated key is to be returned

502

              * @return the next key part

503

              */

504

             public String nextKey(boolean decorated)

505

             {

506

568

                 if (!hasNext())

507

                 {

508

1

                     throw new NoSuchElementException("No more key parts!");

509

                 }

510

511

567

                 hasIndex = false;

512

567

                 indexValue = -1;

513

567

                 String key = findNextIndices();

514

515

567

                 current = key;

516

567

                 hasIndex = checkIndex(key);

517

567

                 attribute = checkAttribute(current);

518

519

567

                 return currentKey(decorated);

520

             }

521

522

             /**

523

              * Helper method for checking if the passed key is an attribute.

524

              * If this is the case, the internal fields will be set.

525

              *

526

              * @param key the key to be checked

527

              * @return a flag if the key is an attribute

528

              */

529

             private boolean checkAttribute(String key)

530

             {

531

567

                 if (isAttributeKey(key))

532

                 {

533

6

                     current = removeAttributeMarkers(key);

534

6

                     return true;

535

                 }

536

                 else

537

                 {

538

561

                     return false;

539

                 }

540

             }

541

542

             /**

543

              * Helper method for checking if the passed key contains an index.

544

              * If this is the case, internal fields will be set.

545

              *

546

              * @param key the key to be checked

547

              * @return a flag if an index is defined

548

              */

549

             private boolean checkIndex(String key)

550

             {

551

567

                 boolean result = false;

552

553

567

                 int idx = key.lastIndexOf(INDEX_START);

554

567

                 if (idx > 0)

555

                 {

556

23

                     int endidx = key.indexOf(INDEX_END, idx);

557

558

23

                     if (endidx > idx + 1)

559

                     {

560

22

                         indexValue = Integer.parseInt(key.substring(idx + 1, endidx));

561

22

                         current = key.substring(0, idx);

562

22

                         result = true;

563

                     }

564

                 }

565

566

567

                 return result;

567

             }

568

569

             /**

570

              * Checks if there is a next element.

571

              *

572

              * @return a flag if there is a next element

573

              */

574

             public boolean hasNext()

575

             {

576

1183

                 return endIndex < keyBuffer.length();

577

             }

578

579

             /**

580

              * Returns the next object in the iteration.

581

              *

582

              * @return the next object

583

              */

584

             public Object next()

585

             {

586

5

                 return nextKey();

587

             }

588

589

             /**

590

              * Removes the current object in the iteration. This method is not

591

              * supported by this iterator type, so an exception is thrown.

592

              */

593

             public void remove()

594

             {

595

1

                 throw new UnsupportedOperationException("Remove not supported!");

596

             }

597

598

             /**

599

              * Returns the current key of the iteration (without skipping to the

600

              * next element). This is the same key the previous <code>next()</code>

601

              * call had returned. (Short form of <code>currentKey(false)</code>.

602

              *

603

              * @return the current key

604

              */

605

             public String currentKey()

606

             {

607

144

                 return currentKey(false);

608

             }

609

610

             /**

611

              * Returns the current key of the iteration (without skipping to the

612

              * next element). The boolean parameter indicates wheter a decorated

613

              * key should be returned. This affects only attribute keys: if the

614

              * parameter is <b>false</b>, the attribute markers are stripped from

615

              * the key; if it is <b>true</b>, they remain.

616

              *

617

              * @param decorated a flag if the decorated key is to be returned

618

              * @return the current key

619

              */

620

             public String currentKey(boolean decorated)

621

             {

622

775

                 return (decorated && isAttribute()) ? constructAttributeKey(current) : current;

623

             }

624

625

             /**

626

              * Returns a flag if the current key is an attribute. This method can

627

              * be called after <code>next()</code>.

628

              *

629

              * @return a flag if the current key is an attribute

630

              */

631

             public boolean isAttribute()

632

             {

633

499

                 return attribute;

634

             }

635

636

             /**

637

              * Returns the index value of the current key. If the current key does

638

              * not have an index, return value is -1. This method can be called

639

              * after <code>next()</code>.

640

              *

641

              * @return the index value of the current key

642

              */

643

             public int getIndex()

644

             {

645

303

                 return indexValue;

646

             }

647

648

             /**

649

              * Returns a flag if the current key has an associated index.

650

              * This method can be called after <code>next()</code>.

651

              *

652

              * @return a flag if the current key has an index

653

              */

654

             public boolean hasIndex()

655

             {

656

5

                 return hasIndex;

657

             }

658

659

             /**

660

              * Creates a clone of this object.

661

              *

662

              * @return a clone of this object

663

              */

664

             public Object clone()

665

             {

666

                 try

667

                 {

668

0

                     return super.clone();

669

                 }

670

                 catch (CloneNotSupportedException cex)

671

                 {

672

                     // should not happen

673

0

                     return null;

674

                 }

675

             }

676

         }

677

     }