##### Coverage Report - org.apache.commons.configuration.plist.XMLPropertyListConfiguration

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

XMLPropertyListConfiguration

82% | 93/113

91% | 20/22

2.0434782608695654;2,043

XMLPropertyListConfiguration$1

100% | 2/2

N/A | N/A

2.0434782608695654;2,043

XMLPropertyListConfiguration$2

100% | 2/2

N/A | N/A

2.0434782608695654;2,043

XMLPropertyListConfiguration$3

100% | 5/5

N/A | N/A

2.0434782608695654;2,043

XMLPropertyListConfiguration$ArrayNode

100% | 5/5

N/A | N/A

2.0434782608695654;2,043

XMLPropertyListConfiguration$PListNode

71% | 20/28

50% | 1/2

2.0434782608695654;2,043

XMLPropertyListConfiguration$SetNextAndPopRule

100% | 6/6

N/A | N/A

2.0434782608695654;2,043

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

19

20

     import java.io.File;

21

     import java.io.PrintWriter;

22

     import java.io.Reader;

23

     import java.io.Writer;

24

     import java.math.BigDecimal;

25

     import java.net.URL;

26

     import java.text.DateFormat;

27

     import java.text.ParseException;

28

     import java.text.SimpleDateFormat;

29

     import java.util.ArrayList;

30

     import java.util.Calendar;

31

     import java.util.Date;

32

     import java.util.Iterator;

33

     import java.util.List;

34

     import java.util.Map;

35

36

     import org.apache.commons.codec.binary.Base64;

37

     import org.apache.commons.configuration.AbstractHierarchicalFileConfiguration;

38

     import org.apache.commons.configuration.Configuration;

39

     import org.apache.commons.configuration.ConfigurationException;

40

     import org.apache.commons.configuration.HierarchicalConfiguration;

41

     import org.apache.commons.configuration.MapConfiguration;

42

     import org.apache.commons.digester.AbstractObjectCreationFactory;

43

     import org.apache.commons.digester.Digester;

44

     import org.apache.commons.digester.ObjectCreateRule;

45

     import org.apache.commons.digester.SetNextRule;

46

     import org.apache.commons.lang.StringEscapeUtils;

47

     import org.apache.commons.lang.StringUtils;

48

     import org.xml.sax.Attributes;

49

     import org.xml.sax.EntityResolver;

50

     import org.xml.sax.InputSource;

51

52

     /**

53

      * Mac OS X configuration file (http://www.apple.com/DTDs/PropertyList-1.0.dtd).

54

      *

55

      * <p>Example:</p>

56

      * <pre>

57

      * &lt;?xml version="1.0"?>

58

      * &lt;!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd">

59

      * &lt;plist version="1.0">

60

      *     &lt;dict>

61

      *         &lt;key>string&lt;/key>

62

      *         &lt;string>value1&lt;/string>

63

      *

64

      *         &lt;key>integer&lt;/key>

65

      *         &lt;integer>12345&lt;/integer>

66

      *

67

      *         &lt;key>real&lt;/key>

68

      *         &lt;real>-123.45E-1&lt;/real>

69

      *

70

      *         &lt;key>boolean&lt;/key>

71

      *         &lt;true/>

72

      *

73

      *         &lt;key>date&lt;/key>

74

      *         &lt;date>2005-01-01T12:00:00-0700&lt;/date>

75

      *

76

      *         &lt;key>data&lt;/key>

77

      *         &lt;data>RHJhY28gRG9ybWllbnMgTnVucXVhbSBUaXRpbGxhbmR1cw==&lt;/data>

78

      *

79

      *         &lt;key>array&lt;/key>

80

      *         &lt;array>

81

      *             &lt;string>value1&lt;/string>

82

      *             &lt;string>value2&lt;/string>

83

      *             &lt;string>value3&lt;/string>

84

      *         &lt;/array>

85

      *

86

      *         &lt;key>dictionnary&lt;/key>

87

      *         &lt;dict>

88

      *             &lt;key>key1&lt;/key>

89

      *             &lt;string>value1&lt;/string>

90

      *             &lt;key>key2&lt;/key>

91

      *             &lt;string>value2&lt;/string>

92

      *             &lt;key>key3&lt;/key>

93

      *             &lt;string>value3&lt;/string>

94

      *         &lt;/dict>

95

      *

96

      *         &lt;key>nested&lt;/key>

97

      *         &lt;dict>

98

      *             &lt;key>node1&lt;/key>

99

      *             &lt;dict>

100

      *                 &lt;key>node2&lt;/key>

101

      *                 &lt;dict>

102

      *                     &lt;key>node3&lt;/key>

103

      *                     &lt;string>value&lt;/string>

104

      *                 &lt;/dict>

105

      *             &lt;/dict>

106

      *         &lt;/dict>

107

      *

108

      *     &lt;/dict>

109

      * &lt;/plist>

110

      * </pre>

111

      *

112

      * @since 1.2

113

      *

114

      * @author Emmanuel Bourg

115

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

116

      */

117

     public class XMLPropertyListConfiguration extends AbstractHierarchicalFileConfiguration

118

     {

119

         /**

120

          * The serial version UID.

121

          */

122

         private static final long serialVersionUID = -3162063751042475985L;

123

124

         /** Size of the indentation for the generated file. */

125

         private static final int INDENT_SIZE = 4;

126

127

         /**

128

          * Creates an empty XMLPropertyListConfiguration object which can be

129

          * used to synthesize a new plist file by adding values and

130

          * then saving().

131

          */

132

         public XMLPropertyListConfiguration()

133

35

         {

134

35

         }

135

136

         /**

137

          * Creates and loads the property list from the specified file.

138

          *

139

          * @param fileName The name of the plist file to load.

140

          * @throws org.apache.commons.configuration.ConfigurationException Error while loading the plist file

141

          */

142

         public XMLPropertyListConfiguration(String fileName) throws ConfigurationException

143

         {

144

0

             super(fileName);

145

0

         }

146

147

         /**

148

          * Creates and loads the property list from the specified file.

149

          *

150

          * @param file The plist file to load.

151

          * @throws ConfigurationException Error while loading the plist file

152

          */

153

         public XMLPropertyListConfiguration(File file) throws ConfigurationException

154

         {

155

1

             super(file);

156

1

         }

157

158

         /**

159

          * Creates and loads the property list from the specified URL.

160

          *

161

          * @param url The location of the plist file to load.

162

          * @throws ConfigurationException Error while loading the plist file

163

          */

164

         public XMLPropertyListConfiguration(URL url) throws ConfigurationException

165

         {

166

0

             super(url);

167

0

         }

168

169

         public void load(Reader in) throws ConfigurationException

170

         {

171

             // set up the digester

172

12

             Digester digester = new Digester();

173

174

             // set up the DTD validation

175

14

             digester.setEntityResolver(new EntityResolver()

176

             {

177

12

                 public InputSource resolveEntity(String publicId, String systemId)

178

                 {

179

12

                     return new InputSource(getClass().getClassLoader().getResourceAsStream("PropertyList-1.0.dtd"));

180

                 }

181

             });

182

12

             digester.setValidating(true);

183

184

             // dictionary rules

185

12

             digester.addRule("*/key", new ObjectCreateRule(PListNode.class)

186

             {

187

12

                 public void end() throws Exception

188

                 {

189

                     // leave the node on the stack to set the value

190

240

                 }

191

             });

192

193

12

             digester.addCallMethod("*/key", "setName", 0);

194

195

12

             digester.addRule("*/dict/string", new SetNextAndPopRule("addChild"));

196

12

             digester.addRule("*/dict/data", new SetNextAndPopRule("addChild"));

197

12

             digester.addRule("*/dict/integer", new SetNextAndPopRule("addChild"));

198

12

             digester.addRule("*/dict/real", new SetNextAndPopRule("addChild"));

199

12

             digester.addRule("*/dict/true", new SetNextAndPopRule("addChild"));

200

12

             digester.addRule("*/dict/false", new SetNextAndPopRule("addChild"));

201

12

             digester.addRule("*/dict/date", new SetNextAndPopRule("addChild"));

202

12

             digester.addRule("*/dict/dict", new SetNextAndPopRule("addChild"));

203

204

12

             digester.addCallMethod("*/dict/string", "addValue", 0);

205

12

             digester.addCallMethod("*/dict/data", "addDataValue", 0);

206

12

             digester.addCallMethod("*/dict/integer", "addIntegerValue", 0);

207

12

             digester.addCallMethod("*/dict/real", "addRealValue", 0);

208

12

             digester.addCallMethod("*/dict/true", "addTrueValue");

209

12

             digester.addCallMethod("*/dict/false", "addFalseValue");

210

12

             digester.addCallMethod("*/dict/date", "addDateValue", 0);

211

212

             // rules for arrays

213

12

             digester.addRule("*/dict/array", new SetNextAndPopRule("addChild"));

214

12

             digester.addRule("*/dict/array", new ObjectCreateRule(ArrayNode.class));

215

12

             digester.addSetNext("*/dict/array", "addList");

216

217

12

             digester.addRule("*/array/array", new ObjectCreateRule(ArrayNode.class));

218

12

             digester.addSetNext("*/array/array", "addList");

219

220

12

             digester.addCallMethod("*/array/string", "addValue", 0);

221

12

             digester.addCallMethod("*/array/data", "addDataValue", 0);

222

12

             digester.addCallMethod("*/array/integer", "addIntegerValue", 0);

223

12

             digester.addCallMethod("*/array/real", "addRealValue", 0);

224

12

             digester.addCallMethod("*/array/true", "addTrueValue");

225

12

             digester.addCallMethod("*/array/false", "addFalseValue");

226

12

             digester.addCallMethod("*/array/date", "addDateValue", 0);

227

228

             // rule for a dictionary in an array

229

12

             digester.addFactoryCreate("*/array/dict", new AbstractObjectCreationFactory()

230

             {

231

12

                 public Object createObject(Attributes attributes) throws Exception

232

                 {

233

                     // create the configuration

234

24

                     XMLPropertyListConfiguration config = new XMLPropertyListConfiguration();

235

236

                     // add it to the ArrayNode

237

24

                     ArrayNode node = (ArrayNode) getDigester().peek();

238

24

                     node.addValue(config);

239

240

                     // push the root on the stack

241

24

                     return config.getRoot();

242

                 }

243

             });

244

245

             // parse the file

246

12

             digester.push(getRoot());

247

             try

248

             {

249

12

                 digester.parse(in);

250

12

             }

251

             catch (Exception e)

252

             {

253

0

                 throw new ConfigurationException("Unable to parse the configuration file", e);

254

             }

255

12

         }

256

257

         /**

258

          * Digester rule that sets the object on the stack to the n-1 object

259

          * and remove both of them from the stack. This rule is used to remove

260

          * the configuration node from the stack once its value has been parsed.

261

          */

262

         private class SetNextAndPopRule extends SetNextRule

263

         {

264

             public SetNextAndPopRule(String methodName)

265

108

             {

266

108

                 super(methodName);

267

108

             }

268

269

             public void end(String namespace, String name) throws Exception

270

             {

271

240

                 super.end(namespace, name);

272

240

                 digester.pop();

273

240

             }

274

         }

275

276

         public void save(Writer out) throws ConfigurationException

277

         {

278

1

             PrintWriter writer = new PrintWriter(out);

279

280

1

             if (getEncoding() != null)

281

             {

282

0

                 writer.println("<?xml version=\"1.0\" encoding=\"" + getEncoding() + "\"?>");

283

             }

284

             else

285

             {

286

1

                 writer.println("<?xml version=\"1.0\"?>");

287

             }

288

289

1

             writer.println("<!DOCTYPE plist SYSTEM \"file://localhost/System/Library/DTDs/PropertyList.dtd\">");

290

1

             writer.println("<plist version=\"1.0\">");

291

292

1

             printNode(writer, 1, getRoot());

293

294

1

             writer.println("</plist>");

295

1

             writer.flush();

296

1

         }

297

298

         /**

299

          * Append a node to the writer, indented according to a specific level.

300

          */

301

         private void printNode(PrintWriter out, int indentLevel, Node node)

302

         {

303

23

             String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);

304

305

23

             if (node.getName() != null)

306

             {

307

20

                 out.println(padding + "<key>" + StringEscapeUtils.escapeXml(node.getName()) + "</key>");

308

             }

309

310

23

             List children = node.getChildren();

311

23

             if (!children.isEmpty())

312

             {

313

7

                 out.println(padding + "<dict>");

314

315

7

                 Iterator it = children.iterator();

316

34

                 while (it.hasNext())

317

                 {

318

20

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

319

20

                     printNode(out, indentLevel + 1, child);

320

321

20

                     if (it.hasNext())

322

                     {

323

13

                         out.println();

324

                     }

325

                 }

326

327

7

                 out.println(padding + "</dict>");

328

             }

329

             else

330

             {

331

16

                 Object value = node.getValue();

332

16

                 printValue(out, indentLevel, value);

333

             }

334

23

         }

335

336

         /**

337

          * Append a value to the writer, indented according to a specific level.

338

          */

339

         private void printValue(PrintWriter out, int indentLevel, Object value)

340

         {

341

27

             String padding = StringUtils.repeat(" ", indentLevel * INDENT_SIZE);

342

343

27

             if (value instanceof Date)

344

             {

345

1

                 out.println(padding + "<date>" + PListNode.format.format((Date) value) + "</date>");

346

             }

347

26

             else if (value instanceof Calendar)

348

             {

349

0

                 printValue(out, indentLevel, ((Calendar) value).getTime());

350

             }

351

26

             else if (value instanceof Number)

352

             {

353

2

                 if (value instanceof Double || value instanceof Float || value instanceof BigDecimal)

354

                 {

355

1

                     out.println(padding + "<real>" + value.toString() + "</real>");

356

                 }

357

                 else

358

                 {

359

1

                     out.println(padding + "<integer>" + value.toString() + "</integer>");

360

                 }

361

             }

362

24

             else if (value instanceof Boolean)

363

             {

364

2

                 if (((Boolean) value).booleanValue())

365

                 {

366

1

                     out.println(padding + "<true/>");

367

                 }

368

                 else

369

                 {

370

1

                     out.println(padding + "<false/>");

371

                 }

372

             }

373

22

             else if (value instanceof List)

374

             {

375

5

                 out.println(padding + "<array>");

376

5

                 Iterator it = ((List) value).iterator();

377

21

                 while (it.hasNext())

378

                 {

379

11

                     printValue(out, indentLevel + 1, it.next());

380

                 }

381

5

                 out.println(padding + "</array>");

382

             }

383

17

             else if (value instanceof HierarchicalConfiguration)

384

             {

385

2

                 printNode(out, indentLevel, ((HierarchicalConfiguration) value).getRoot());

386

             }

387

15

             else if (value instanceof Configuration)

388

             {

389

                 // display a flat Configuration as a dictionary

390

0

                 out.println(padding + "<dict>");

391

392

0

                 Configuration config = (Configuration) value;

393

0

                 Iterator it = config.getKeys();

394

0

                 while (it.hasNext())

395

                 {

396

                     // create a node for each property

397

0

                     String key = (String) it.next();

398

0

                     Node node = new Node(key);

399

0

                     node.setValue(config.getProperty(key));

400

401

                     // print the node

402

0

                     printNode(out, indentLevel + 1, node);

403

404

0

                     if (it.hasNext())

405

                     {

406

0

                         out.println();

407

                     }

408

                 }

409

0

                 out.println(padding + "</dict>");

410

             }

411

15

             else if (value instanceof Map)

412

             {

413

                 // display a Map as a dictionary

414

0

                 Map map = (Map) value;

415

0

                 printValue(out, indentLevel, new MapConfiguration(map));

416

             }

417

15

             else if (value instanceof byte[])

418

             {

419

1

                 String base64 = new String(Base64.encodeBase64((byte[]) value));

420

1

                 out.println(padding + "<data>" + StringEscapeUtils.escapeXml(base64) + "</data>");

421

             }

422

             else

423

             {

424

14

                 out.println(padding + "<string>" + StringEscapeUtils.escapeXml(String.valueOf(value)) + "</string>");

425

             }

426

27

         }

427

428

429

         /**

430

          * Node extension with addXXX methods to parse the typed data passed by Digester.

431

          * <b>Do not use this class !</b> It is used internally by XMLPropertyConfiguration

432

          * to parse the configuration file, it may be removed at any moment in the future.

433

          */

434

301

         public static class PListNode extends Node

435

         {

436

             /**

437

              * The serial version UID.

438

              */

439

             private static final long serialVersionUID = -7614060264754798317L;

440

441

             /** The standard format of dates in plist files. */

442

1

             private static DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");

443

444

             /**

445

              * Update the value of the node. If the existing value is null, it's

446

              * replaced with the new value. If the existing value is a list, the

447

              * specified value is appended to the list. If the existing value is

448

              * not null, a list with the two values is built.

449

              *

450

              * @param value the value to be added

451

              */

452

             public void addValue(Object value)

453

             {

454

192

                 if (getValue() == null)

455

                 {

456

192

                     setValue(value);

457

                 }

458

0

                 else if (getValue() instanceof List)

459

                 {

460

0

                     List list = (List) getValue();

461

0

                     list.add(value);

462

                 }

463

                 else

464

                 {

465

0

                     List list = new ArrayList();

466

0

                     list.add(getValue());

467

0

                     list.add(value);

468

0

                     setValue(list);

469

                 }

470

192

             }

471

472

             /**

473

              * Parse the specified string as a date and add it to the values of the node.

474

              *

475

              * @param value the value to be added

476

              */

477

             public void addDateValue(String value)

478

             {

479

                 try

480

                 {

481

12

                     addValue(format.parse(value));

482

12

                 }

483

                 catch (ParseException e)

484

                 {

485

0

                     e.printStackTrace();

486

                 }

487

12

             }

488

489

             /**

490

              * Parse the specified string as a byte array in base 64 format

491

              * and add it to the values of the node.

492

              *

493

              * @param value the value to be added

494

              */

495

             public void addDataValue(String value)

496

             {

497

12

                 addValue(Base64.decodeBase64(value.getBytes()));

498

12

             }

499

500

             /**

501

              * Parse the specified string as an Interger and add it to the values of the node.

502

              *

503

              * @param value the value to be added

504

              */

505

             public void addIntegerValue(String value)

506

             {

507

12

                 addValue(new Integer(value));

508

12

             }

509

510

             /**

511

              * Parse the specified string as a Double and add it to the values of the node.

512

              *

513

              * @param value the value to be added

514

              */

515

             public void addRealValue(String value)

516

             {

517

12

                 addValue(new Double(value));

518

12

             }

519

520

             /**

521

              * Add a boolean value 'true' to the values of the node.

522

              */

523

             public void addTrueValue()

524

             {

525

12

                 addValue(Boolean.TRUE);

526

12

             }

527

528

             /**

529

              * Add a boolean value 'false' to the values of the node.

530

              */

531

             public void addFalseValue()

532

             {

533

12

                 addValue(Boolean.FALSE);

534

12

             }

535

536

             /**

537

              * Add a sublist to the values of the node.

538

              *

539

              * @param node the node whose value will be added to the current node value

540

              */

541

             public void addList(ArrayNode node)

542

             {

543

60

                 addValue(node.getValue());

544

60

             }

545

         }

546

547

         /**

548

          * Container for array elements. <b>Do not use this class !</b>

549

          * It is used internally by XMLPropertyConfiguration to parse the

550

          * configuration file, it may be removed at any moment in the future.

551

          */

552

120

         public static class ArrayNode extends PListNode

553

         {

554

             /**

555

              * The serial version UID.

556

              */

557

             private static final long serialVersionUID = 5586544306664205835L;

558

559

             /** The list of values in the array. */

560

60

             private List list = new ArrayList();

561

562

             /**

563

              * Add an object to the array.

564

              *

565

              * @param value the value to be added

566

              */

567

             public void addValue(Object value)

568

             {

569

132

                 list.add(value);

570

132

             }

571

572

             /**

573

              * Return the list of values in the array.

574

              *

575

              * @return the {@link List} of values

576

              */

577

             public Object getValue()

578

             {

579

60

                 return list;

580

             }

581

         }

582

     }