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

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

XMLConfiguration

98% | 138/141

100% | 28/28

2.4;2,4

XMLConfiguration$1

100% | 2/2

N/A | N/A

2.4;2,4

XMLConfiguration$2

100% | 3/3

N/A | N/A

2.4;2,4

XMLConfiguration$XMLBuilderVisitor

100% | 37/37

100% | 11/11

2.4;2,4

XMLConfiguration$XMLFileConfigurationDelegate

100% | 3/3

N/A | N/A

2.4;2,4

XMLConfiguration$XMLNode

96% | 45/47

100% | 14/14

2.4;2,4

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.File;

21

     import java.io.InputStream;

22

     import java.io.Reader;

23

     import java.io.Writer;

24

     import java.net.URL;

25

     import java.util.ArrayList;

26

     import java.util.Collection;

27

     import java.util.Iterator;

28

     import java.util.List;

29

30

     import javax.xml.parsers.DocumentBuilder;

31

     import javax.xml.parsers.DocumentBuilderFactory;

32

     import javax.xml.parsers.ParserConfigurationException;

33

     import javax.xml.transform.OutputKeys;

34

     import javax.xml.transform.Result;

35

     import javax.xml.transform.Source;

36

     import javax.xml.transform.Transformer;

37

     import javax.xml.transform.TransformerException;

38

     import javax.xml.transform.TransformerFactory;

39

     import javax.xml.transform.TransformerFactoryConfigurationError;

40

     import javax.xml.transform.dom.DOMSource;

41

     import javax.xml.transform.stream.StreamResult;

42

43

     import org.apache.commons.collections.iterators.SingletonIterator;

44

     import org.w3c.dom.Attr;

45

     import org.w3c.dom.CDATASection;

46

     import org.w3c.dom.DOMException;

47

     import org.w3c.dom.Document;

48

     import org.w3c.dom.Element;

49

     import org.w3c.dom.NamedNodeMap;

50

     import org.w3c.dom.NodeList;

51

     import org.w3c.dom.Text;

52

     import org.xml.sax.InputSource;

53

     import org.xml.sax.SAXException;

54

     import org.xml.sax.SAXParseException;

55

     import org.xml.sax.helpers.DefaultHandler;

56

57

     /**

58

      * <p>A specialized hierarchical configuration class that is able to parse XML

59

      * documents.</p>

60

      *

61

      * <p>The parsed document will be stored keeping its structure. The class also

62

      * tries to preserve as much information from the loaded XML document as

63

      * possible, including comments and processing instructions. These will be

64

      * contained in documents created by the <code>save()</code> methods, too.</p>

65

      *

66

      * <p>Like other file based configuration classes this class maintains the name

67

      * and path to the loaded configuration file. These properties can be altered

68

      * using several setter methods, but they are not modified by <code>save()</code>

69

      * and <code>load()</code> methods. If XML documents contain relative paths to

70

      * other documents (e.g. to a DTD), these references are resolved based on the

71

      * path set for this configuration.</p>

72

      *

73

      * <p>By inheriting from <code>{@link AbstractConfiguration}</code> this class

74

      * provides some extended functionaly, e.g. interpolation of property values.

75

      * Like in <code>{@link PropertiesConfiguration}</code> property values can

76

      * contain delimiter characters (the comma ',' per default) and are then splitted

77

      * into multiple values. This works for XML attributes and text content of

78

      * elements as well. The delimiter can be escaped by a backslash. As an example

79

      * consider the following XML fragment:</p>

80

      *

81

      * <p>

82

      * <pre>

83

      * &lt;config&gt;

84

      *   &lt;array&gt;10,20,30,40&lt;/array&gt;

85

      *   &lt;scalar&gt;3\,1415&lt;/scalar&gt;

86

      *   &lt;cite text="To be or not to be\, this is the question!"/&gt;

87

      * &lt;/config&gt;

88

      * </pre>

89

      * </p>

90

      * <p>Here the content of the <code>array</code> element will be splitted at

91

      * the commas, so the <code>array</code> key will be assigned 4 values. In the

92

      * <code>scalar</code> property and the <code>text</code> attribute of the

93

      * <code>cite</code> element the comma is escaped, so that no splitting is

94

      * performed.</p>

95

      *

96

      * <p><code>XMLConfiguration</code> implements the <code>{@link FileConfiguration}</code>

97

      * interface and thus provides full support for loading XML documents from

98

      * different sources like files, URLs, or streams. A full description of these

99

      * features can be found in the documentation of

100

      * <code>{@link AbstractFileConfiguration}</code>.</p>

101

      *

102

      * @since commons-configuration 1.0

103

      *

104

      * @author J&ouml;rg Schaible

105

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

106

      * @version $Revision: 439648 $, $Date: 2006-09-02 22:42:10 +0200 (Sa, 02 Sep 2006) $

107

      */

108

12921

     public class XMLConfiguration extends AbstractHierarchicalFileConfiguration

109

     {

110

         /**

111

          * The serial version UID.

112

          */

113

         private static final long serialVersionUID = 2453781111653383552L;

114

115

         /** Constant for the default root element name. */

116

         private static final String DEFAULT_ROOT_NAME = "configuration";

117

118

         /** The document from this configuration's data source. */

119

         private Document document;

120

121

         /** Stores the name of the root element. */

122

         private String rootElementName;

123

124

         /** Stores the public ID from the DOCTYPE.*/

125

         private String publicID;

126

127

         /** Stores the system ID from the DOCTYPE.*/

128

         private String systemID;

129

130

         /** Stores the document builder that should be used for loading.*/

131

         private DocumentBuilder documentBuilder;

132

133

         /** Stores a flag whether DTD validation should be performed.*/

134

         private boolean validating;

135

136

         /**

137

          * Creates a new instance of <code>XMLConfiguration</code>.

138

          */

139

         public XMLConfiguration()

140

         {

141

166

             super();

142

166

         }

143

144

         /**

145

          * Creates a new instance of <code>XMLConfiguration</code>.

146

          * The configuration is loaded from the specified file

147

          *

148

          * @param fileName the name of the file to load

149

          * @throws ConfigurationException if the file cannot be loaded

150

          */

151

         public XMLConfiguration(String fileName) throws ConfigurationException

152

         {

153

1

             super(fileName);

154

1

         }

155

156

         /**

157

          * Creates a new instance of <code>XMLConfiguration</code>.

158

          * The configuration is loaded from the specified file.

159

          *

160

          * @param file the file

161

          * @throws ConfigurationException if an error occurs while loading the file

162

          */

163

         public XMLConfiguration(File file) throws ConfigurationException

164

         {

165

81

             super(file);

166

81

         }

167

168

         /**

169

          * Creates a new instance of <code>XMLConfiguration</code>.

170

          * The configuration is loaded from the specified URL.

171

          *

172

          * @param url the URL

173

          * @throws ConfigurationException if loading causes an error

174

          */

175

         public XMLConfiguration(URL url) throws ConfigurationException

176

         {

177

1

             super(url);

178

1

         }

179

180

         /**

181

          * Returns the name of the root element. If this configuration was loaded

182

          * from a XML document, the name of this document's root element is

183

          * returned. Otherwise it is possible to set a name for the root element

184

          * that will be used when this configuration is stored.

185

          *

186

          * @return the name of the root element

187

          */

188

         public String getRootElementName()

189

         {

190

8

             if (getDocument() == null)

191

             {

192

6

                 return (rootElementName == null) ? DEFAULT_ROOT_NAME : rootElementName;

193

             }

194

             else

195

             {

196

2

                 return getDocument().getDocumentElement().getNodeName();

197

             }

198

         }

199

200

         /**

201

          * Sets the name of the root element. This name is used when this

202

          * configuration object is stored in an XML file. Note that setting the name

203

          * of the root element works only if this configuration has been newly

204

          * created. If the configuration was loaded from an XML file, the name

205

          * cannot be changed and an <code>UnsupportedOperationException</code>

206

          * exception is thrown. Whether this configuration has been loaded from an

207

          * XML document or not can be found out using the <code>getDocument()</code>

208

          * method.

209

          *

210

          * @param name the name of the root element

211

          */

212

         public void setRootElementName(String name)

213

         {

214

3

             if (getDocument() != null)

215

             {

216

1

                 throw new UnsupportedOperationException("The name of the root element "

217

                         + "cannot be changed when loaded from an XML document!");

218

             }

219

2

             rootElementName = name;

220

2

         }

221

222

         /**

223

          * Returns the <code>DocumentBuilder</code> object that is used for

224

          * loading documents. If no specific builder has been set, this method

225

          * returns <b>null</b>.

226

          *

227

          * @return the <code>DocumentBuilder</code> for loading new documents

228

          * @since 1.2

229

          */

230

         public DocumentBuilder getDocumentBuilder()

231

         {

232

229

             return documentBuilder;

233

         }

234

235

         /**

236

          * Sets the <code>DocumentBuilder</code> object to be used for loading

237

          * documents. This method makes it possible to specify the exact document

238

          * builder. So an application can create a builder, configure it for its

239

          * special needs, and then pass it to this method.

240

          *

241

          * @param documentBuilder the document builder to be used; if undefined, a

242

          * default builder will be used

243

          * @since 1.2

244

          */

245

         public void setDocumentBuilder(DocumentBuilder documentBuilder)

246

         {

247

2

             this.documentBuilder = documentBuilder;

248

2

         }

249

250

         /**

251

          * Returns the public ID of the DOCTYPE declaration from the loaded XML

252

          * document. This is <b>null</b> if no document has been loaded yet or if

253

          * the document does not contain a DOCTYPE declaration with a public ID.

254

          *

255

          * @return the public ID

256

          * @since 1.3

257

          */

258

         public String getPublicID()

259

         {

260

23

             return publicID;

261

         }

262

263

         /**

264

          * Sets the public ID of the DOCTYPE declaration. When this configuration is

265

          * saved, a DOCTYPE declaration will be constructed that contains this

266

          * public ID.

267

          *

268

          * @param publicID the public ID

269

          * @since 1.3

270

          */

271

         public void setPublicID(String publicID)

272

         {

273

7

             this.publicID = publicID;

274

7

         }

275

276

         /**

277

          * Returns the system ID of the DOCTYPE declaration from the loaded XML

278

          * document. This is <b>null</b> if no document has been loaded yet or if

279

          * the document does not contain a DOCTYPE declaration with a system ID.

280

          *

281

          * @return the system ID

282

          * @since 1.3

283

          */

284

         public String getSystemID()

285

         {

286

23

             return systemID;

287

         }

288

289

         /**

290

          * Sets the system ID of the DOCTYPE declaration. When this configuration is

291

          * saved, a DOCTYPE declaration will be constructed that contains this

292

          * system ID.

293

          *

294

          * @param systemID the system ID

295

          * @since 1.3

296

          */

297

         public void setSystemID(String systemID)

298

         {

299

7

             this.systemID = systemID;

300

7

         }

301

302

         /**

303

          * Returns the value of the validating flag.

304

          *

305

          * @return the validating flag

306

          * @since 1.2

307

          */

308

         public boolean isValidating()

309

         {

310

451

             return validating;

311

         }

312

313

         /**

314

          * Sets the value of the validating flag. This flag determines whether

315

          * DTD validation should be performed when loading XML documents. This

316

          * flag is evaluated only if no custom <code>DocumentBuilder</code> was set.

317

          *

318

          * @param validating the validating flag

319

          * @since 1.2

320

          */

321

         public void setValidating(boolean validating)

322

         {

323

1

             this.validating = validating;

324

1

         }

325

326

         /**

327

          * Returns the XML document this configuration was loaded from. The return

328

          * value is <b>null</b> if this configuration was not loaded from a XML

329

          * document.

330

          *

331

          * @return the XML document this configuration was loaded from

332

          */

333

         public Document getDocument()

334

         {

335

15

             return document;

336

         }

337

338

         /**

339

          * Removes all properties from this configuration. If this configuration

340

          * was loaded from a file, the associated DOM document is also cleared.

341

          */

342

         public void clear()

343

         {

344

9

             super.clear();

345

9

             document = null;

346

9

         }

347

348

         /**

349

          * Initializes this configuration from an XML document.

350

          *

351

          * @param document the document to be parsed

352

          * @param elemRefs a flag whether references to the XML elements should be set

353

          */

354

         public void initProperties(Document document, boolean elemRefs)

355

         {

356

224

             if (document.getDoctype() != null)

357

             {

358

6

                 setPublicID(document.getDoctype().getPublicId());

359

6

                 setSystemID(document.getDoctype().getSystemId());

360

             }

361

224

             constructHierarchy(getRoot(), document.getDocumentElement(), elemRefs);

362

224

         }

363

364

         /**

365

          * Helper method for building the internal storage hierarchy. The XML

366

          * elements are transformed into node objects.

367

          *

368

          * @param node the actual node

369

          * @param element the actual XML element

370

          * @param elemRefs a flag whether references to the XML elements should be set

371

          */

372

         private void constructHierarchy(Node node, Element element, boolean elemRefs)

373

         {

374

8697

             processAttributes(node, element, elemRefs);

375

8697

             StringBuffer buffer = new StringBuffer();

376

8697

             NodeList list = element.getChildNodes();

377

35788

             for (int i = 0; i < list.getLength(); i++)

378

             {

379

27091

                 org.w3c.dom.Node w3cNode = list.item(i);

380

27091

                 if (w3cNode instanceof Element)

381

                 {

382

8473

                     Element child = (Element) w3cNode;

383

8473

                     Node childNode = new XMLNode(child.getTagName(),

384

                             elemRefs ? child : null);

385

8473

                     constructHierarchy(childNode, child, elemRefs);

386

8473

                     node.addChild(childNode);

387

8473

                     handleDelimiters(node, childNode);

388

                 }

389

18618

                 else if (w3cNode instanceof Text)

390

                 {

391

17448

                     Text data = (Text) w3cNode;

392

17448

                     buffer.append(data.getData());

393

                 }

394

             }

395

8697

             String text = buffer.toString().trim();

396

8697

             if (text.length() > 0 || !node.hasChildren())

397

             {

398

5616

                 node.setValue(text);

399

             }

400

8697

         }

401

402

         /**

403

          * Helper method for constructing node objects for the attributes of the

404

          * given XML element.

405

          *

406

          * @param node the actual node

407

          * @param element the actual XML element

408

          * @param elemRefs a flag whether references to the XML elements should be set

409

          */

410

         private void processAttributes(Node node, Element element, boolean elemRefs)

411

         {

412

8697

             NamedNodeMap attributes = element.getAttributes();

413

10560

             for (int i = 0; i < attributes.getLength(); ++i)

414

             {

415

1863

                 org.w3c.dom.Node w3cNode = attributes.item(i);

416

1863

                 if (w3cNode instanceof Attr)

417

                 {

418

1863

                     Attr attr = (Attr) w3cNode;

419

                     Iterator it;

420

1863

                     if (isDelimiterParsingDisabled())

421

                     {

422

11

                         it = new SingletonIterator(attr.getValue());

423

                     }

424

                     else

425

                     {

426

1852

                         it = PropertyConverter.split(attr.getValue(), getListDelimiter()).iterator();

427

                     }

428

5870

                     while (it.hasNext())

429

                     {

430

2155

                         Node child = new XMLNode(attr.getName(),

431

                                 elemRefs ? element : null);

432

2155

                         child.setValue(it.next());

433

2155

                         node.addAttribute(child);

434

                     }

435

                 }

436

             }

437

8697

         }

438

439

         /**

440

          * Deals with elements whose value is a list. In this case multiple child

441

          * elements must be added.

442

          *

443

          * @param parent the parent element

444

          * @param child the child element

445

          */

446

         private void handleDelimiters(Node parent, Node child)

447

         {

448

8473

             if (child.getValue() != null)

449

             {

450

                 List values;

451

5616

                 if (isDelimiterParsingDisabled())

452

                 {

453

32

                     values = new ArrayList();

454

32

                     values.add(child.getValue().toString());

455

                 }

456

                 else

457

                 {

458

5584

                     values = PropertyConverter.split(child.getValue().toString(),

459

                         getListDelimiter());

460

                 }

461

462

5616

                 if (values.size() > 1)

463

                 {

464

                     // remove the original child

465

133

                     parent.remove(child);

466

                     // add multiple new children

467

665

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

468

                     {

469

399

                         Node c = new XMLNode(child.getName(), null);

470

399

                         c.setValue(it.next());

471

399

                         parent.addChild(c);

472

                     }

473

                 }

474

5483

                 else if (values.size() == 1)

475

                 {

476

                     // we will have to replace the value because it might

477

                     // contain escaped delimiters

478

5483

                     child.setValue(values.get(0));

479

                 }

480

             }

481

8473

         }

482

483

         /**

484

          * Creates the <code>DocumentBuilder</code> to be used for loading files.

485

          * This implementation checks whether a specific

486

          * <code>DocumentBuilder</code> has been set. If this is the case, this

487

          * one is used. Otherwise a default builder is created. Depending on the

488

          * value of the validating flag this builder will be a validating or a non

489

          * validating <code>DocumentBuilder</code>.

490

          *

491

          * @return the <code>DocumentBuilder</code> for loading configuration

492

          * files

493

          * @throws ParserConfigurationException if an error occurs

494

          * @since 1.2

495

          */

496

         protected DocumentBuilder createDocumentBuilder()

497

                 throws ParserConfigurationException

498

         {

499

227

             if (getDocumentBuilder() != null)

500

             {

501

2

                 return getDocumentBuilder();

502

             }

503

             else

504

             {

505

225

                 DocumentBuilderFactory factory = DocumentBuilderFactory

506

                         .newInstance();

507

225

                 factory.setValidating(isValidating());

508

225

                 DocumentBuilder result = factory.newDocumentBuilder();

509

510

225

                 if (isValidating())

511

                 {

512

                     // register an error handler which detects validation errors

513

1

                     result.setErrorHandler(new DefaultHandler()

514

                     {

515

1

                         public void error(SAXParseException ex) throws SAXException

516

                         {

517

1

                             throw ex;

518

                         }

519

                     });

520

                 }

521

225

                 return result;

522

             }

523

         }

524

525

         /**

526

          * Creates a DOM document from the internal tree of configuration nodes.

527

          *

528

          * @return the new document

529

          * @throws ConfigurationException if an error occurs

530

          */

531

         protected Document createDocument() throws ConfigurationException

532

         {

533

             try

534

             {

535

19

                 if (document == null)

536

                 {

537

4

                     DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();

538

4

                     Document newDocument = builder.newDocument();

539

4

                     Element rootElem = newDocument.createElement(getRootElementName());

540

4

                     newDocument.appendChild(rootElem);

541

4

                     document = newDocument;

542

                 }

543

544

19

                 XMLBuilderVisitor builder = new XMLBuilderVisitor(document, getListDelimiter());

545

19

                 builder.processDocument(getRoot());

546

19

                 return document;

547

             } /* try */

548

             catch (DOMException domEx)

549

             {

550

0

                 throw new ConfigurationException(domEx);

551

             }

552

             catch (ParserConfigurationException pex)

553

             {

554

0

                 throw new ConfigurationException(pex);

555

             }

556

         }

557

558

         /**

559

          * Creates a new node object. This implementation returns an instance of the

560

          * <code>XMLNode</code> class.

561

          *

562

          * @param name the node's name

563

          * @return the new node

564

          */

565

         protected Node createNode(String name)

566

         {

567

99

             return new XMLNode(name, null);

568

         }

569

570

         /**

571

          * Loads the configuration from the given input stream.

572

          *

573

          * @param in the input stream

574

          * @throws ConfigurationException if an error occurs

575

          */

576

         public void load(InputStream in) throws ConfigurationException

577

         {

578

224

             load(new InputSource(in));

579

222

         }

580

581

         /**

582

          * Load the configuration from the given reader.

583

          * Note that the <code>clear()</code> method is not called, so

584

          * the properties contained in the loaded file will be added to the

585

          * actual set of properties.

586

          *

587

          * @param in An InputStream.

588

          *

589

          * @throws ConfigurationException if an error occurs

590

          */

591

         public void load(Reader in) throws ConfigurationException

592

         {

593

3

             load(new InputSource(in));

594

2

         }

595

596

         /**

597

          * Loads a configuration file from the specified input source.

598

          * @param source the input source

599

          * @throws ConfigurationException if an error occurs

600

          */

601

         private void load(InputSource source) throws ConfigurationException

602

         {

603

             try

604

             {

605

227

                 URL sourceURL = getDelegate().getURL();

606

227

                 if (sourceURL != null)

607

                 {

608

224

                     source.setSystemId(sourceURL.toString());

609

                 }

610

611

227

                 DocumentBuilder builder = createDocumentBuilder();

612

227

                 Document newDocument = builder.parse(source);

613

224

                 Document oldDocument = document;

614

224

                 document = null;

615

224

                 initProperties(newDocument, oldDocument == null);

616

224

                 document = (oldDocument == null) ? newDocument : oldDocument;

617

224

             }

618

             catch (Exception e)

619

             {

620

3

                 throw new ConfigurationException(e.getMessage(), e);

621

             }

622

224

         }

623

624

         /**

625

          * Saves the configuration to the specified writer.

626

          *

627

          * @param writer the writer used to save the configuration

628

          * @throws ConfigurationException if an error occurs

629

          */

630

         public void save(Writer writer) throws ConfigurationException

631

         {

632

             try

633

             {

634

20

                 Transformer transformer = createTransformer();

635

19

                 Source source = new DOMSource(createDocument());

636

19

                 Result result = new StreamResult(writer);

637

19

                 transformer.transform(source, result);

638

19

             }

639

             catch (TransformerException e)

640

             {

641

0

                 throw new ConfigurationException(e.getMessage(), e);

642

             }

643

             catch (TransformerFactoryConfigurationError err)

644

             {

645

1

                 throw new ConfigurationException(err.getMessage(), err);

646

             }

647

19

         }

648

649

         /**

650

          * Creates and initializes the transformer used for save operations. This

651

          * base implementation initializes all of the default settings like

652

          * indention mode and the DOCTYPE. Derived classes may overload this method

653

          * if they have specific needs.

654

          *

655

          * @return the transformer to use for a save operation

656

          * @throws TransformerException if an error occurs

657

          * @since 1.3

658

          */

659

         protected Transformer createTransformer() throws TransformerException

660

         {

661

20

             Transformer transformer = TransformerFactory.newInstance()

662

                     .newTransformer();

663

664

19

             transformer.setOutputProperty(OutputKeys.INDENT, "yes");

665

19

             if (getEncoding() != null)

666

             {

667

3

                 transformer.setOutputProperty(OutputKeys.ENCODING, getEncoding());

668

             }

669

19

             if (getPublicID() != null)

670

             {

671

2

                 transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC,

672

                         getPublicID());

673

             }

674

19

             if (getSystemID() != null)

675

             {

676

2

                 transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,

677

                         getSystemID());

678

             }

679

680

19

             return transformer;

681

         }

682

683

         /**

684

          * Creates a copy of this object. The new configuration object will contain

685

          * the same properties as the original, but it will lose any connection to a

686

          * source document (if one exists). This is to avoid race conditions if both

687

          * the original and the copy are modified and then saved.

688

          *

689

          * @return the copy

690

          */

691

         public Object clone()

692

         {

693

1

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

694

695

             // clear document related properties

696

1

             copy.document = null;

697

1

             copy.setDelegate(createDelegate());

698

             // clear all references in the nodes, too

699

1

             copy.getRoot().visit(new NodeVisitor()

700

             {

701

1

                 public void visitBeforeChildren(Node node, ConfigurationKey key)

702

                 {

703

62

                     node.setReference(null);

704

62

                 }

705

             }, null);

706

707

1

             return copy;

708

         }

709

710

         /**

711

          * Creates the file configuration delegate for this object. This implementation

712

          * will return an instance of a class derived from <code>FileConfigurationDelegate</code>

713

          * that deals with some specialities of <code>XMLConfiguration</code>.

714

          * @return the delegate for this object

715

          */

716

         protected FileConfigurationDelegate createDelegate()

717

         {

718

250

             return new XMLFileConfigurationDelegate();

719

         }

720

721

         /**

722

          * A specialized <code>Node</code> class that is connected with an XML

723

          * element. Changes on a node are also performed on the associated element.

724

          */

725

         class XMLNode extends Node

726

         {

727

             /**

728

              * The serial version UID.

729

              */

730

             private static final long serialVersionUID = -4133988932174596562L;

731

732

             /**

733

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

734

              * with a name and the corresponding XML element.

735

              *

736

              * @param name the node's name

737

              * @param elem the XML element

738

              */

739

             public XMLNode(String name, Element elem)

740

11126

             {

741

11126

                 super(name);

742

11126

                 setReference(elem);

743

11126

             }

744

745

             /**

746

              * Sets the value of this node. If this node is associated with an XML

747

              * element, this element will be updated, too.

748

              *

749

              * @param value the node's new value

750

              */

751

             public void setValue(Object value)

752

             {

753

25265

                 super.setValue(value);

754

755

25265

                 if (getReference() != null && document != null)

756

                 {

757

307

                     if (isAttribute())

758

                     {

759

90

                         updateAttribute();

760

                     }

761

                     else

762

                     {

763

217

                         updateElement(value);

764

                     }

765

                 }

766

25265

             }

767

768

             /**

769

              * Updates the associated XML elements when a node is removed.

770

              */

771

             protected void removeReference()

772

             {

773

496

                 if (getReference() != null)

774

                 {

775

418

                     Element element = (Element) getReference();

776

418

                     if (isAttribute())

777

                     {

778

0

                         updateAttribute();

779

                     }

780

                     else

781

                     {

782

418

                         org.w3c.dom.Node parentElem = element.getParentNode();

783

418

                         if (parentElem != null)

784

                         {

785

418

                             parentElem.removeChild(element);

786

                         }

787

                     }

788

                 }

789

496

             }

790

791

             /**

792

              * Updates the node's value if it represents an element node.

793

              *

794

              * @param value the new value

795

              */

796

             private void updateElement(Object value)

797

             {

798

217

                 Text txtNode = findTextNodeForUpdate();

799

217

                 if (value == null)

800

                 {

801

                     // remove text

802

211

                     if (txtNode != null)

803

                     {

804

181

                         ((Element) getReference()).removeChild(txtNode);

805

                     }

806

                 }

807

                 else

808

                 {

809

6

                     if (txtNode == null)

810

                     {

811

1

                         txtNode = document

812

                                 .createTextNode(PropertyConverter.escapeDelimiters(

813

                                         value.toString(), getListDelimiter()));

814

1

                         if (((Element) getReference()).getFirstChild() != null)

815

                         {

816

0

                             ((Element) getReference()).insertBefore(txtNode,

817

                                     ((Element) getReference()).getFirstChild());

818

                         }

819

                         else

820

                         {

821

1

                             ((Element) getReference()).appendChild(txtNode);

822

                         }

823

                     }

824

                     else

825

                     {

826

5

                         txtNode.setNodeValue(PropertyConverter.escapeDelimiters(

827

                                 value.toString(), getListDelimiter()));

828

                     }

829

                 }

830

217

             }

831

832

             /**

833

              * Updates the node's value if it represents an attribute.

834

              *

835

              */

836

             private void updateAttribute()

837

             {

838

90

                 XMLBuilderVisitor.updateAttribute(getParent(), getName(), getListDelimiter());

839

90

             }

840

841

             /**

842

              * Returns the only text node of this element for update. This method is

843

              * called when the element's text changes. Then all text nodes except

844

              * for the first are removed. A reference to the first is returned or

845

              * <b>null </b> if there is no text node at all.

846

              *

847

              * @return the first and only text node

848

              */

849

             private Text findTextNodeForUpdate()

850

             {

851

217

                 Text result = null;

852

217

                 Element elem = (Element) getReference();

853

                 // Find all Text nodes

854

217

                 NodeList children = elem.getChildNodes();

855

217

                 Collection textNodes = new ArrayList();

856

489

                 for (int i = 0; i < children.getLength(); i++)

857

                 {

858

272

                     org.w3c.dom.Node nd = children.item(i);

859

272

                     if (nd instanceof Text)

860

                     {

861

237

                         if (result == null)

862

                         {

863

199

                             result = (Text) nd;

864

                         }

865

                         else

866

                         {

867

38

                             textNodes.add(nd);

868

                         }

869

                     }

870

                 }

871

872

                 // We don't want CDATAs

873

217

                 if (result instanceof CDATASection)

874

                 {

875

13

                     textNodes.add(result);

876

13

                     result = null;

877

                 }

878

879

                 // Remove all but the first Text node

880

485

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

881

                 {

882

51

                     elem.removeChild((org.w3c.dom.Node) it.next());

883

                 }

884

217

                 return result;

885

             }

886

         }

887

888

         /**

889

          * A concrete <code>BuilderVisitor</code> that can construct XML

890

          * documents.

891

          */

892

         static class XMLBuilderVisitor extends BuilderVisitor

893

         {

894

             /** Stores the document to be constructed. */

895

             private Document document;

896

897

             /** Stores the list delimiter.*/

898

19

             private char listDelimiter = AbstractConfiguration.

899

                     getDefaultListDelimiter();

900

901

             /**

902

              * Creates a new instance of <code>XMLBuilderVisitor</code>

903

              *

904

              * @param doc the document to be created

905

              * @param listDelimiter the delimiter for attribute properties with multiple values

906

              */

907

             public XMLBuilderVisitor(Document doc, char listDelimiter)

908

19

             {

909

19

                 document = doc;

910

19

                 this.listDelimiter = listDelimiter;

911

19

             }

912

913

             /**

914

              * Processes the node hierarchy and adds new nodes to the document.

915

              *

916

              * @param rootNode the root node

917

              */

918

             public void processDocument(Node rootNode)

919

             {

920

19

                 rootNode.visit(this, null);

921

19

             }

922

923

             /**

924

              * Inserts a new node. This implementation ensures that the correct

925

              * XML element is created and inserted between the given siblings.

926

              *

927

              * @param newNode the node to insert

928

              * @param parent the parent node

929

              * @param sibling1 the first sibling

930

              * @param sibling2 the second sibling

931

              * @return the new node

932

              */

933

             protected Object insert(Node newNode, Node parent, Node sibling1, Node sibling2)

934

             {

935

94

                 if (newNode.isAttribute())

936

                 {

937

5

                     updateAttribute(parent, getElement(parent), newNode.getName(), listDelimiter);

938

5

                     return null;

939

                 }

940

941

                 else

942

                 {

943

89

                     Element elem = document.createElement(newNode.getName());

944

89

                     if (newNode.getValue() != null)

945

                     {

946

69

                         elem.appendChild(document.createTextNode(

947

                                 PropertyConverter.escapeDelimiters(newNode.getValue().toString(), listDelimiter)));

948

                     }

949

89

                     if (sibling2 == null)

950

                     {

951

59

                         getElement(parent).appendChild(elem);

952

                     }

953

30

                     else if (sibling1 != null)

954

                     {

955

20

                         getElement(parent).insertBefore(elem, getElement(sibling1).getNextSibling());

956

                     }

957

                     else

958

                     {

959

10

                         getElement(parent).insertBefore(elem, getElement(parent).getFirstChild());

960

                     }

961

89

                     return elem;

962

                 }

963

             }

964

965

             /**

966

              * Helper method for updating the value of the specified node's

967

              * attribute with the given name.

968

              *

969

              * @param node the affected node

970

              * @param elem the element that is associated with this node

971

              * @param name the name of the affected attribute

972

              * @param listDelimiter the delimiter vor attributes with multiple values

973

              */

974

             private static void updateAttribute(Node node, Element elem, String name, char listDelimiter)

975

             {

976

95

                 if (node != null && elem != null)

977

                 {

978

94

                     List attrs = node.getAttributes(name);

979

94

                     StringBuffer buf = new StringBuffer();

980

322

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

981

                     {

982

134

                         Node attr = (Node) it.next();

983

134

                         if (attr.getValue() != null)

984

                         {

985

30

                             if (buf.length() > 0)

986

                             {

987

10

                                 buf.append(listDelimiter);

988

                             }

989

30

                             buf.append(PropertyConverter.escapeDelimiters(attr

990

                                     .getValue().toString(), getDefaultListDelimiter()));

991

                         }

992

134

                         attr.setReference(elem);

993

                     }

994

995

94

                     if (buf.length() < 1)

996

                     {

997

74

                         elem.removeAttribute(name);

998

                     }

999

                     else

1000

                     {

1001

20

                         elem.setAttribute(name, buf.toString());

1002

                     }

1003

                 }

1004

95

             }

1005

1006

             /**

1007

              * Updates the value of the specified attribute of the given node.

1008

              * Because there can be multiple child nodes representing this attribute

1009

              * the new value is determined by iterating over all those child nodes.

1010

              *

1011

              * @param node the affected node

1012

              * @param name the name of the attribute

1013

              * @param listDelimiter the delimiter vor attributes with multiple values

1014

              */

1015

             static void updateAttribute(Node node, String name, char listDelimiter)

1016

             {

1017

90

                 if (node != null)

1018

                 {

1019

90

                     updateAttribute(node, (Element) node.getReference(), name, listDelimiter);

1020

                 }

1021

90

             }

1022

1023

             /**

1024

              * Helper method for accessing the element of the specified node.

1025

              *

1026

              * @param node the node

1027

              * @return the element of this node

1028

              */

1029

             private Element getElement(Node node)

1030

             {

1031

                 // special treatement for root node of the hierarchy

1032

124

                 return (node.getName() != null) ? (Element) node.getReference() : document.getDocumentElement();

1033

             }

1034

         }

1035

1036

         /**

1037

          * A special implementation of the <code>FileConfiguration</code> interface that is

1038

          * used internally to implement the <code>FileConfiguration</code> methods

1039

          * for <code>XMLConfiguration</code>, too.

1040

          */

1041

500

         private class XMLFileConfigurationDelegate extends FileConfigurationDelegate

1042

         {

1043

             public void load(InputStream in) throws ConfigurationException

1044

             {

1045

223

                 XMLConfiguration.this.load(in);

1046

221

             }

1047

         }

1048

     }