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

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

CombinedConfiguration

99% | 81/82

100% | 13/13

1.8461538461538463;1,846

CombinedConfiguration$ConfigData

100% | 28/28

100% | 4/4

1.8461538461538463;1,846

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;

18

19

     import java.util.ArrayList;

20

     import java.util.Collection;

21

     import java.util.HashMap;

22

     import java.util.Iterator;

23

     import java.util.List;

24

     import java.util.Map;

25

     import java.util.Set;

26

27

     import org.apache.commons.configuration.event.ConfigurationEvent;

28

     import org.apache.commons.configuration.event.ConfigurationListener;

29

     import org.apache.commons.configuration.tree.ConfigurationNode;

30

     import org.apache.commons.configuration.tree.DefaultConfigurationKey;

31

     import org.apache.commons.configuration.tree.DefaultConfigurationNode;

32

     import org.apache.commons.configuration.tree.DefaultExpressionEngine;

33

     import org.apache.commons.configuration.tree.NodeCombiner;

34

     import org.apache.commons.configuration.tree.UnionCombiner;

35

     import org.apache.commons.configuration.tree.ViewNode;

36

37

     /**

38

      * <p>

39

      * A hierarchical composite configuration class.

40

      * </p>

41

      * <p>

42

      * This class maintains a list of configuration objects, which can be added

43

      * using the divers <code>addConfiguration()</code> methods. After that the

44

      * configurations can be accessed either by name (if one was provided when the

45

      * configuration was added) or by index. For the whole set of managed

46

      * configurations a logical node structure is constructed. For this purpose a

47

      * <code>{@link org.apache.commons.configuration.tree.NodeCombiner NodeCombiner}</code>

48

      * object can be set. This makes it possible to specify different algorithms for

49

      * the combination process.

50

      * </p>

51

      * <p>

52

      * The big advantage of this class is that it creates a truely hierarchical

53

      * structure of all the properties stored in the contained configurations - even

54

      * if some of them are no hierarchical configurations per se. So all enhanced

55

      * features provided by a hierarchical configuration (e.g. choosing an

56

      * expression engine) are applicable.

57

      * </p>

58

      * <p>

59

      * The class works by registering itself as an event listener add all added

60

      * configurations. So it gets notified whenever one of these configurations is

61

      * changed and can invalidate its internal node structure. The next time a

62

      * property is accessed the node structure will be re-constructed using the

63

      * current state of the managed configurations. Node that, depending on the used

64

      * <code>NodeCombiner</code>, this may be a complex operation.

65

      * </p>

66

      * <p>

67

      * It is not strictly forbidden to manipulate a

68

      * <code>CombinedConfiguration</code> directly, but the results may be

69

      * unpredictable. For instance some node combiners use special view nodes for

70

      * linking parts of the original configurations' data together. If new

71

      * properties are added to such a special node, they do not belong to any of the

72

      * managed configurations and thus hang in the air. It is also possible that

73

      * direct updates on a <code>CombinedConfiguration</code> are incompatible

74

      * with the used node combiner (e.g. if the

75

      * <code>{@link org.apache.commons.configuration.tree.OverrideCombiner OverrideCombiner}</code>

76

      * is used and properties are removed the resulting node structure may be

77

      * incorrect because some properties that were hidden by the removed properties

78

      * are not visible). So it is recommended to perform updates only on the managed

79

      * configurations.

80

      * </p>

81

      * <p>

82

      * Whenever the node structure of a <code>CombinedConfiguration</code> becomes

83

      * invalid (either because one of the contained configurations was modified or

84

      * because the <code>invalidate()</code> method was directly called) an event

85

      * is generated. So this can be detected by interested event listeners. This

86

      * also makes it possible to add a combined configuration into another one.

87

      * </p>

88

      * <p>

89

      * Implementation note: Adding and removing configurations to and from a

90

      * combined configuration is not thread-safe. If a combined configuration is

91

      * manipulated by multiple threads, the developer has to take care about

92

      * properly synchronization.

93

      * </p>

94

      *

95

      * @author <a

96

      * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons

97

      * Configuration team</a>

98

      * @since 1.3

99

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

100

      */

101

11

     public class CombinedConfiguration extends HierarchicalConfiguration implements

102

             ConfigurationListener, Cloneable

103

     {

104

         /**

105

          * Constant for the invalidate event that is fired when the internal node

106

          * structure becomes invalid.

107

          */

108

         public static final int EVENT_COMBINED_INVALIDATE = 40;

109

110

         /**

111

          * The serial version ID.

112

          */

113

         private static final long serialVersionUID = 8338574525528692307L;

114

115

         /** Constant for the expression engine for parsing the at path. */

116

6

         private static final DefaultExpressionEngine AT_ENGINE = new DefaultExpressionEngine();

117

118

         /** Constant for the default node combiner. */

119

3

         private static final NodeCombiner DEFAULT_COMBINER = new UnionCombiner();

120

121

         /** Stores the combiner. */

122

         private NodeCombiner nodeCombiner;

123

124

         /** Stores the combined root node. */

125

         private ConfigurationNode combinedRoot;

126

127

         /** Stores a list with the contained configurations. */

128

         private List configurations;

129

130

         /** Stores a map with the named configurations. */

131

         private Map namedConfigurations;

132

133

         /**

134

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

135

          * initializes the combiner to be used.

136

          *

137

          * @param comb the node combiner (can be <b>null</b>, then a union combiner

138

          * is used as default)

139

          */

140

         public CombinedConfiguration(NodeCombiner comb)

141

45

         {

142

45

             setNodeCombiner((comb != null) ? comb : DEFAULT_COMBINER);

143

45

             clear();

144

45

         }

145

146

         /**

147

          * Creates a new instance of <code>CombinedConfiguration</code> that uses

148

          * a union combiner.

149

          *

150

          * @see org.apache.commons.configuration.tree.UnionCombiner

151

          */

152

         public CombinedConfiguration()

153

         {

154

39

             this(null);

155

39

         }

156

157

         /**

158

          * Returns the node combiner that is used for creating the combined node

159

          * structure.

160

          *

161

          * @return the node combiner

162

          */

163

         public NodeCombiner getNodeCombiner()

164

         {

165

361

             return nodeCombiner;

166

         }

167

168

         /**

169

          * Sets the node combiner. This object will be used when the combined node

170

          * structure is to be constructed. It must not be <b>null</b>, otherwise an

171

          * <code>IllegalArgumentException</code> exception is thrown. Changing the

172

          * node combiner causes an invalidation of this combined configuration, so

173

          * that the new combiner immediately takes effect.

174

          *

175

          * @param nodeCombiner the node combiner

176

          */

177

         public void setNodeCombiner(NodeCombiner nodeCombiner)

178

         {

179

64

             if (nodeCombiner == null)

180

             {

181

1

                 throw new IllegalArgumentException(

182

                         "Node combiner must not be null!");

183

             }

184

63

             this.nodeCombiner = nodeCombiner;

185

63

             invalidate();

186

63

         }

187

188

         /**

189

          * Adds a new configuration to this combined configuration. It is possible

190

          * (but not mandatory) to give the new configuration a name. This name must

191

          * be unique, otherwise a <code>ConfigurationRuntimeException</code> will

192

          * be thrown. With the optional <code>at</code> argument you can specify

193

          * where in the resulting node structure the content of the added

194

          * configuration should appear. This is a string that uses dots as property

195

          * delimiters (independent on the current expression engine). For instance

196

          * if you pass in the string <code>&quot;database.tables&quot;</code>,

197

          * all properties of the added configuration will occur in this branch.

198

          *

199

          * @param config the configuration to add (must not be <b>null</b>)

200

          * @param name the name of this configuration (can be <b>null</b>)

201

          * @param at the position of this configuration in the combined tree (can be

202

          * <b>null</b>)

203

          */

204

         public void addConfiguration(AbstractConfiguration config, String name,

205

                 String at)

206

         {

207

80

             if (config == null)

208

             {

209

1

                 throw new IllegalArgumentException(

210

                         "Added configuration must not be null!");

211

             }

212

79

             if (name != null && namedConfigurations.containsKey(name))

213

             {

214

1

                 throw new ConfigurationRuntimeException(

215

                         "A configuration with the name '"

216

                                 + name

217

                                 + "' already exists in this combined configuration!");

218

             }

219

220

78

             ConfigData cd = new ConfigData(config, name, at);

221

78

             configurations.add(cd);

222

78

             if (name != null)

223

             {

224

32

                 namedConfigurations.put(name, config);

225

             }

226

227

78

             config.addConfigurationListener(this);

228

78

             invalidate();

229

78

         }

230

231

         /**

232

          * Adds a new configuration to this combined configuration with an optional

233

          * name. The new configuration's properties will be added under the root of

234

          * the combined node structure.

235

          *

236

          * @param config the configuration to add (must not be <b>null</b>)

237

          * @param name the name of this configuration (can be <b>null</b>)

238

          */

239

         public void addConfiguration(AbstractConfiguration config, String name)

240

         {

241

13

             addConfiguration(config, name, null);

242

13

         }

243

244

         /**

245

          * Adds a new configuration to this combined configuration. The new

246

          * configuration is not given a name. Its properties will be added under the

247

          * root of the combined node structure.

248

          *

249

          * @param config the configuration to add (must not be <b>null</b>)

250

          */

251

         public void addConfiguration(AbstractConfiguration config)

252

         {

253

8

             addConfiguration(config, null, null);

254

7

         }

255

256

         /**

257

          * Returns the number of configurations that are contained in this combined

258

          * configuration.

259

          *

260

          * @return the number of contained configurations

261

          */

262

         public int getNumberOfConfigurations()

263

         {

264

65

             return configurations.size();

265

         }

266

267

         /**

268

          * Returns the configuration at the specified index. The contained

269

          * configurations are numbered in the order they were added to this combined

270

          * configuration. The index of the first configuration is 0.

271

          *

272

          * @param index the index

273

          * @return the configuration at this index

274

          */

275

         public Configuration getConfiguration(int index)

276

         {

277

37

             ConfigData cd = (ConfigData) configurations.get(index);

278

37

             return cd.getConfiguration();

279

         }

280

281

         /**

282

          * Returns the configuration with the given name. This can be <b>null</b>

283

          * if no such configuration exists.

284

          *

285

          * @param name the name of the configuration

286

          * @return the configuration with this name

287

          */

288

         public Configuration getConfiguration(String name)

289

         {

290

13

             return (Configuration) namedConfigurations.get(name);

291

         }

292

293

         /**

294

          * Removes the specified configuration from this combined configuration.

295

          *

296

          * @param config the configuration to be removed

297

          * @return a flag whether this configuration was found and could be removed

298

          */

299

         public boolean removeConfiguration(Configuration config)

300

         {

301

5

             for (int index = 0; index < getNumberOfConfigurations(); index++)

302

             {

303

4

                 if (((ConfigData) configurations.get(index)).getConfiguration() == config)

304

                 {

305

4

                     removeConfigurationAt(index);

306

4

                     return true;

307

                 }

308

             }

309

310

1

             return false;

311

         }

312

313

         /**

314

          * Removes the configuration at the specified index.

315

          *

316

          * @param index the index

317

          * @return the removed configuration

318

          */

319

         public Configuration removeConfigurationAt(int index)

320

         {

321

6

             ConfigData cd = (ConfigData) configurations.remove(index);

322

6

             if (cd.getName() != null)

323

             {

324

4

                 namedConfigurations.remove(cd.getName());

325

             }

326

6

             cd.getConfiguration().removeConfigurationListener(this);

327

6

             invalidate();

328

6

             return cd.getConfiguration();

329

         }

330

331

         /**

332

          * Removes the configuration with the specified name.

333

          *

334

          * @param name the name of the configuration to be removed

335

          * @return the removed configuration (<b>null</b> if this configuration

336

          * was not found)

337

          */

338

         public Configuration removeConfiguration(String name)

339

         {

340

3

             Configuration conf = getConfiguration(name);

341

3

             if (conf != null)

342

             {

343

2

                 removeConfiguration(conf);

344

             }

345

3

             return conf;

346

         }

347

348

         /**

349

          * Returns a set with the names of all configurations contained in this

350

          * combined configuration. Of course here are only these configurations

351

          * listed, for which a name was specified when they were added.

352

          *

353

          * @return a set with the names of the contained configurations (never

354

          * <b>null</b>)

355

          */

356

         public Set getConfigurationNames()

357

         {

358

14

             return namedConfigurations.keySet();

359

         }

360

361

         /**

362

          * Invalidates this combined configuration. This means that the next time a

363

          * property is accessed the combined node structure must be re-constructed.

364

          * Invalidation of a combined configuration also means that an event of type

365

          * <code>EVENT_COMBINED_INVALIDATE</code> is fired. Note that while other

366

          * events most times appear twice (once before and once after an update),

367

          * this event is only fired once (after update).

368

          */

369

         public void invalidate()

370

         {

371

211

             synchronized (getNodeCombiner()) // use combiner as lock

372

             {

373

211

                 combinedRoot = null;

374

211

             }

375

211

             fireEvent(EVENT_COMBINED_INVALIDATE, null, null, false);

376

211

         }

377

378

         /**

379

          * Event listener call back for configuration update events. This method is

380

          * called whenever one of the contained configurations was modified. It

381

          * invalidates this combined configuration.

382

          *

383

          * @param event the update event

384

          */

385

         public void configurationChanged(ConfigurationEvent event)

386

         {

387

16

             invalidate();

388

16

         }

389

390

         /**

391

          * Returns the configuration root node of this combined configuration. This

392

          * method will construct a combined node structure using the current node

393

          * combiner if necessary.

394

          *

395

          * @return the combined root node

396

          */

397

         public ConfigurationNode getRootNode()

398

         {

399

97

             synchronized (getNodeCombiner())

400

             {

401

97

                 if (combinedRoot == null)

402

                 {

403

36

                     combinedRoot = constructCombinedNode();

404

                 }

405

97

                 return combinedRoot;

406

0

             }

407

         }

408

409

         /**

410

          * Clears this configuration. All contained configurations will be removed.

411

          */

412

         public void clear()

413

         {

414

48

             fireEvent(EVENT_CLEAR, null, null, true);

415

48

             configurations = new ArrayList();

416

48

             namedConfigurations = new HashMap();

417

48

             fireEvent(EVENT_CLEAR, null, null, false);

418

48

             invalidate();

419

48

         }

420

421

         /**

422

          * Returns a copy of this object. This implementation performs a deep clone,

423

          * i.e. all contained configurations will be cloned, too. For this to work,

424

          * all contained configurations must be cloneable. Registered event

425

          * listeners won't be cloned. The clone will use the same node combiner than

426

          * the original.

427

          *

428

          * @return the copied object

429

          */

430

         public Object clone()

431

         {

432

2

             CombinedConfiguration copy = (CombinedConfiguration) super.clone();

433

2

             copy.clear();

434

8

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

435

             {

436

4

                 ConfigData cd = (ConfigData) it.next();

437

4

                 copy.addConfiguration((AbstractConfiguration) ConfigurationUtils

438

                         .cloneConfiguration(cd.getConfiguration()), cd.getName(),

439

                         cd.getAt());

440

             }

441

442

2

             copy.setRootNode(new DefaultConfigurationNode());

443

2

             return copy;

444

         }

445

446

         /**

447

          * Creates the root node of this combined configuration.

448

          *

449

          * @return the combined root node

450

          */

451

         private ConfigurationNode constructCombinedNode()

452

         {

453

36

             if (getNumberOfConfigurations() < 1)

454

             {

455

7

                 return new ViewNode();

456

             }

457

458

             else

459

             {

460

29

                 Iterator it = configurations.iterator();

461

29

                 ConfigurationNode node = ((ConfigData) it.next())

462

                         .getTransformedRoot();

463

99

                 while (it.hasNext())

464

                 {

465

41

                     node = getNodeCombiner().combine(node,

466

                             ((ConfigData) it.next()).getTransformedRoot());

467

                 }

468

29

                 return node;

469

             }

470

         }

471

472

         /**

473

          * An internal helper class for storing information about contained

474

          * configurations.

475

          */

476

         static class ConfigData

477

         {

478

             /** Stores a reference to the configuration. */

479

             private AbstractConfiguration configuration;

480

481

             /** Stores the name under which the configuration is stored. */

482

             private String name;

483

484

             /** Stores the at information as path of nodes. */

485

             private Collection atPath;

486

487

             /** Stores the at string.*/

488

             private String at;

489

490

             /**

491

              * Creates a new instance of <code>ConfigData</code> and initializes

492

              * it.

493

              *

494

              * @param config the configuration

495

              * @param n the name

496

              * @param at the at position

497

              */

498

             public ConfigData(AbstractConfiguration config, String n, String at)

499

78

             {

500

78

                 configuration = config;

501

78

                 name = n;

502

78

                 atPath = parseAt(at);

503

78

                 this.at = at;

504

78

             }

505

506

             /**

507

              * Returns the stored configuration.

508

              *

509

              * @return the configuration

510

              */

511

             public AbstractConfiguration getConfiguration()

512

             {

513

127

                 return configuration;

514

             }

515

516

             /**

517

              * Returns the configuration's name.

518

              *

519

              * @return the name

520

              */

521

             public String getName()

522

             {

523

14

                 return name;

524

             }

525

526

             /**

527

              * Returns the at position of this configuration.

528

              *

529

              * @return the at position

530

              */

531

             public String getAt()

532

             {

533

4

                 return at;

534

             }

535

536

             /**

537

              * Returns the transformed root node of the stored configuration. The

538

              * term &quot;transformed&quot; means that an eventually defined at path

539

              * has been applied.

540

              *

541

              * @return the transformed root node

542

              */

543

             public ConfigurationNode getTransformedRoot()

544

             {

545

70

                 ViewNode result = new ViewNode();

546

70

                 ViewNode atParent = result;

547

548

70

                 if (atPath != null)

549

                 {

550

                     // Build the complete path

551

32

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

552

                     {

553

12

                         ViewNode node = new ViewNode();

554

12

                         node.setName((String) it.next());

555

12

                         atParent.addChild(node);

556

12

                         atParent = node;

557

                     }

558

                 }

559

560

                 // Copy data of the root node to the new path

561

70

                 HierarchicalConfiguration hc = ConfigurationUtils

562

                         .convertToHierarchical(getConfiguration());

563

70

                 atParent.appendChildren(hc.getRootNode());

564

70

                 atParent.appendAttributes(hc.getRootNode());

565

566

70

                 return result;

567

             }

568

569

             /**

570

              * Splits the at path into its components.

571

              *

572

              * @param at the at string

573

              * @return a collection with the names of the single components

574

              */

575

             private Collection parseAt(String at)

576

             {

577

78

                 if (at == null)

578

                 {

579

67

                     return null;

580

                 }

581

582

11

                 Collection result = new ArrayList();

583

11

                 DefaultConfigurationKey.KeyIterator it = new DefaultConfigurationKey(

584

                         AT_ENGINE, at).iterator();

585

35

                 while (it.hasNext())

586

                 {

587

13

                     result.add(it.nextKey());

588

                 }

589

11

                 return result;

590

             }

591

         }

592

     }