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

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

PropertiesConfiguration

94% | 119/127

97% | 32/33

2.5641025641025643;2,564

PropertiesConfiguration$PropertiesReader

97% | 66/68

100% | 13/13

2.5641025641025643;2,564

PropertiesConfiguration$PropertiesWriter

93% | 41/44

100% | 8/8

2.5641025641025643;2,564

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

22

     import java.io.IOException;

23

     import java.io.LineNumberReader;

24

     import java.io.Reader;

25

     import java.io.Writer;

26

     import java.net.URL;

27

     import java.util.ArrayList;

28

     import java.util.Iterator;

29

     import java.util.List;

30

31

     import org.apache.commons.lang.ArrayUtils;

32

     import org.apache.commons.lang.StringEscapeUtils;

33

     import org.apache.commons.lang.StringUtils;

34

35

     /**

36

      * This is the "classic" Properties loader which loads the values from

37

      * a single or multiple files (which can be chained with "include =".

38

      * All given path references are either absolute or relative to the

39

      * file name supplied in the constructor.

40

      * <p>

41

      * In this class, empty PropertyConfigurations can be built, properties

42

      * added and later saved. include statements are (obviously) not supported

43

      * if you don't construct a PropertyConfiguration from a file.

44

      *

45

      * <p>The properties file syntax is explained here, basically it follows

46

      * the syntax of the stream parsed by {@link java.util.Properties#load} and

47

      * adds several useful extensions:

48

      *

49

      * <ul>

50

      *  <li>

51

      *   Each property has the syntax <code>key &lt;separator> value</code>. The

52

      *   separators accepted are <code>'='</code>, <code>':'</code> and any white

53

      *   space character. Examples:

54

      * <pre>

55

      *  key1 = value1

56

      *  key2 : value2

57

      *  key3   value3</pre>

58

      *  </li>

59

      *  <li>

60

      *   The <i>key</i> may use any character, separators must be escaped:

61

      * <pre>

62

      *  key\:foo = bar</pre>

63

      *  </li>

64

      *  <li>

65

      *   <i>value</i> may be separated on different lines if a backslash

66

      *   is placed at the end of the line that continues below.

67

      *  </li>

68

      *  <li>

69

      *   <i>value</i> can contain <em>value delimiters</em> and will then be interpreted

70

      *   as a list of tokens. Default value delimiter is the comma ','. So the

71

      *   following property definition

72

      * <pre>

73

      *  key = This property, has multiple, values

74

      * </pre>

75

      *   will result in a property with three values. You can change the value

76

      *   delimiter using the <code>{@link AbstractConfiguration#setListDelimiter(char)}</code>

77

      *   method. Setting the delimiter to 0 will disable value splitting completely.

78

      *  </li>

79

      *  <li>

80

      *   Commas in each token are escaped placing a backslash right before

81

      *   the comma.

82

      *  </li>

83

      *  <li>

84

      *   If a <i>key</i> is used more than once, the values are appended

85

      *   like if they were on the same line separated with commas.

86

      *  </li>

87

      *  <li>

88

      *   Blank lines and lines starting with character '#' or '!' are skipped.

89

      *  </li>

90

      *  <li>

91

      *   If a property is named "include" (or whatever is defined by

92

      *   setInclude() and getInclude() and the value of that property is

93

      *   the full path to a file on disk, that file will be included into

94

      *   the configuration. You can also pull in files relative to the parent

95

      *   configuration file. So if you have something like the following:

96

      *

97

      *   include = additional.properties

98

      *

99

      *   Then "additional.properties" is expected to be in the same

100

      *   directory as the parent configuration file.

101

      *

102

      *   The properties in the included file are added to the parent configuration,

103

      *   they do not replace existing properties with the same key.

104

      *

105

      *  </li>

106

      * </ul>

107

      *

108

      * <p>Here is an example of a valid extended properties file:

109

      *

110

      * <p><pre>

111

      *      # lines starting with # are comments

112

      *

113

      *      # This is the simplest property

114

      *      key = value

115

      *

116

      *      # A long property may be separated on multiple lines

117

      *      longvalue = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \

118

      *                  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

119

      *

120

      *      # This is a property with many tokens

121

      *      tokens_on_a_line = first token, second token

122

      *

123

      *      # This sequence generates exactly the same result

124

      *      tokens_on_multiple_lines = first token

125

      *      tokens_on_multiple_lines = second token

126

      *

127

      *      # commas may be escaped in tokens

128

      *      commas.escaped = Hi\, what'up?

129

      *

130

      *      # properties can reference other properties

131

      *      base.prop = /base

132

      *      first.prop = ${base.prop}/first

133

      *      second.prop = ${first.prop}/second

134

      * </pre>

135

      *

136

      * <p>A <code>PropertiesConfiguration</code> object is associated with an

137

      * instance of the <code>{@link PropertiesConfigurationLayout}</code> class,

138

      * which is responsible for storing the layout of the parsed properties file

139

      * (i.e. empty lines, comments, and such things). The <code>getLayout()</code>

140

      * method can be used to obtain this layout object. With <code>setLayout()</code>

141

      * a new layout object can be set. This should be done before a properties file

142

      * was loaded.

143

      *

144

      * @see java.util.Properties#load

145

      *

146

      * @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>

147

      * @author <a href="mailto:jon@latchkey.com">Jon S. Stevens</a>

148

      * @author <a href="mailto:daveb@miceda-data">Dave Bryson</a>

149

      * @author <a href="mailto:geirm@optonline.net">Geir Magnusson Jr.</a>

150

      * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>

151

      * @author <a href="mailto:kjohnson@transparent.com">Kent Johnson</a>

152

      * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>

153

      * @author <a href="mailto:ipriha@surfeu.fi">Ilkka Priha</a>

154

      * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>

155

      * @author <a href="mailto:mpoeschl@marmot.at">Martin Poeschl</a>

156

      * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>

157

      * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>

158

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

159

      * @author <a href="mailto:ebourg@apache.org">Emmanuel Bourg</a>

160

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

161

      */

162

681265

     public class PropertiesConfiguration extends AbstractFileConfiguration

163

     {

164

         /** Constant for the supported comment characters.*/

165

         static final String COMMENT_CHARS = "#!";

166

167

         /**

168

          * This is the name of the property that can point to other

169

          * properties file for including other properties files.

170

          */

171

40

         private static String include = "include";

172

173

         /** The list of possible key/value separators */

174

20

         private static final char[] SEPARATORS = new char[] {'=', ':'};

175

176

         /** The white space characters used as key/value separators. */

177

20

         private static final char[] WHITE_SPACE = new char[]{' ', '\t', '\f'};

178

179

         /**

180

          * The default encoding (ISO-8859-1 as specified by

181

          * http://java.sun.com/j2se/1.5.0/docs/api/java/util/Properties.html)

182

          */

183

         private static final String DEFAULT_ENCODING = "ISO-8859-1";

184

185

         /** Constant for the platform specific line separator.*/

186

20

         private static final String LINE_SEPARATOR = System.getProperty("line.separator");

187

188

         /** Constant for the radix of hex numbers.*/

189

         private static final int HEX_RADIX = 16;

190

191

         /** Constant for the length of a unicode literal.*/

192

         private static final int UNICODE_LEN = 4;

193

194

         /** Stores the layout object.*/

195

         private PropertiesConfigurationLayout layout;

196

197

         /** Allow file inclusion or not */

198

         private boolean includesAllowed;

199

200

         // initialization block to set the encoding before loading the file in the constructors

201

         {

202

378

             setEncoding(DEFAULT_ENCODING);

203

         }

204

205

         /**

206

          * Creates an empty PropertyConfiguration object which can be

207

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

208

          * then saving().

209

          */

210

         public PropertiesConfiguration()

211

151

         {

212

151

             layout = createLayout();

213

151

             setIncludesAllowed(false);

214

151

         }

215

216

         /**

217

          * Creates and loads the extended properties from the specified file.

218

          * The specified file can contain "include = " properties which then

219

          * are loaded and merged into the properties.

220

          *

221

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

222

          * @throws ConfigurationException Error while loading the properties file

223

          */

224

         public PropertiesConfiguration(String fileName) throws ConfigurationException

225

         {

226

220

             super(fileName);

227

218

         }

228

229

         /**

230

          * Creates and loads the extended properties from the specified file.

231

          * The specified file can contain "include = " properties which then

232

          * are loaded and merged into the properties.

233

          *

234

          * @param file The properties file to load.

235

          * @throws ConfigurationException Error while loading the properties file

236

          */

237

         public PropertiesConfiguration(File file) throws ConfigurationException

238

         {

239

9

             super(file);

240

8

         }

241

242

         /**

243

          * Creates and loads the extended properties from the specified URL.

244

          * The specified file can contain "include = " properties which then

245

          * are loaded and merged into the properties.

246

          *

247

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

248

          * @throws ConfigurationException Error while loading the properties file

249

          */

250

         public PropertiesConfiguration(URL url) throws ConfigurationException

251

         {

252

1

             super(url);

253

1

         }

254

255

         /**

256

          * Gets the property value for including other properties files.

257

          * By default it is "include".

258

          *

259

          * @return A String.

260

          */

261

         public static String getInclude()

262

         {

263

39464

             return PropertiesConfiguration.include;

264

         }

265

266

         /**

267

          * Sets the property value for including other properties files.

268

          * By default it is "include".

269

          *

270

          * @param inc A String.

271

          */

272

         public static void setInclude(String inc)

273

         {

274

2

             PropertiesConfiguration.include = inc;

275

2

         }

276

277

         /**

278

          * Controls whether additional files can be loaded by the include = <xxx>

279

          * statement or not. Base rule is, that objects created by the empty

280

          * C'tor can not have included files.

281

          *

282

          * @param includesAllowed includesAllowed True if Includes are allowed.

283

          */

284

         protected void setIncludesAllowed(boolean includesAllowed)

285

         {

286

482

             this.includesAllowed = includesAllowed;

287

482

         }

288

289

         /**

290

          * Reports the status of file inclusion.

291

          *

292

          * @return True if include files are loaded.

293

          */

294

         public boolean getIncludesAllowed()

295

         {

296

353

             return this.includesAllowed;

297

         }

298

299

         /**

300

          * Return the comment header.

301

          *

302

          * @return the comment header

303

          * @since 1.1

304

          */

305

         public String getHeader()

306

         {

307

4

             return getLayout().getHeaderComment();

308

         }

309

310

         /**

311

          * Set the comment header.

312

          *

313

          * @param header the header to use

314

          * @since 1.1

315

          */

316

         public void setHeader(String header)

317

         {

318

13

             getLayout().setHeaderComment(header);

319

13

         }

320

321

         /**

322

          * Returns the associated layout object.

323

          *

324

          * @return the associated layout object

325

          * @since 1.3

326

          */

327

         public synchronized PropertiesConfigurationLayout getLayout()

328

         {

329

868

             if (layout == null)

330

             {

331

227

                 layout = createLayout();

332

             }

333

868

             return layout;

334

         }

335

336

         /**

337

          * Sets the associated layout object.

338

          *

339

          * @param layout the new layout object; can be <b>null</b>, then a new

340

          * layout object will be created

341

          * @since 1.3

342

          */

343

         public synchronized void setLayout(PropertiesConfigurationLayout layout)

344

         {

345

             // only one layout must exist

346

42

             if (this.layout != null)

347

             {

348

42

                 removeConfigurationListener(this.layout);

349

             }

350

351

42

             if (layout == null)

352

             {

353

1

                 this.layout = createLayout();

354

             }

355

             else

356

             {

357

41

                 this.layout = layout;

358

             }

359

42

         }

360

361

         /**

362

          * Creates the associated layout object. This method is invoked when the

363

          * layout object is accessed and has not been created yet. Derived classes

364

          * can override this method to hook in a different layout implementation.

365

          *

366

          * @return the layout object to use

367

          * @since 1.3

368

          */

369

         protected PropertiesConfigurationLayout createLayout()

370

         {

371

379

             return new PropertiesConfigurationLayout(this);

372

         }

373

374

         /**

375

          * Load the properties from the given reader.

376

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

377

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

378

          * actual set of properties.

379

          *

380

          * @param in An InputStream.

381

          *

382

          * @throws ConfigurationException if an error occurs

383

          */

384

         public synchronized void load(Reader in) throws ConfigurationException

385

         {

386

816

             boolean oldAutoSave = isAutoSave();

387

816

             setAutoSave(false);

388

389

             try

390

             {

391

816

                 getLayout().load(in);

392

815

             }

393

             finally

394

             {

395

1

                 setAutoSave(oldAutoSave);

396

             }

397

815

         }

398

399

         /**

400

          * Save the configuration to the specified stream.

401

          *

402

          * @param writer the output stream used to save the configuration

403

          * @throws ConfigurationException if an error occurs

404

          */

405

         public void save(Writer writer) throws ConfigurationException

406

         {

407

25

             enterNoReload();

408

             try

409

             {

410

25

                 getLayout().save(writer);

411

25

             }

412

             finally

413

             {

414

0

                 exitNoReload();

415

             }

416

25

         }

417

418

         /**

419

          * Extend the setBasePath method to turn includes

420

          * on and off based on the existence of a base path.

421

          *

422

          * @param basePath The new basePath to set.

423

          */

424

         public void setBasePath(String basePath)

425

         {

426

330

             super.setBasePath(basePath);

427

330

             setIncludesAllowed(StringUtils.isNotEmpty(basePath));

428

330

         }

429

430

         /**

431

          * Creates a copy of this object.

432

          *

433

          * @return the copy

434

          */

435

         public Object clone()

436

         {

437

4

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

438

4

             if (layout != null)

439

             {

440

4

                 copy.setLayout(new PropertiesConfigurationLayout(copy, layout));

441

             }

442

4

             return copy;

443

         }

444

445

         /**

446

          * This method is invoked by the associated

447

          * <code>{@link PropertiesConfigurationLayout}</code> object for each

448

          * property definition detected in the parsed properties file. Its task is

449

          * to check whether this is a special property definition (e.g. the

450

          * <code>include</code> property). If not, the property must be added to

451

          * this configuration. The return value indicates whether the property

452

          * should be treated as a normal property. If it is <b>false</b>, the

453

          * layout object will ignore this property.

454

          *

455

          * @param key the property key

456

          * @param value the property value

457

          * @return a flag whether this is a normal property

458

          * @throws ConfigurationException if an error occurs

459

          * @since 1.3

460

          */

461

         boolean propertyLoaded(String key, String value)

462

                 throws ConfigurationException

463

         {

464

             boolean result;

465

466

19727

             if (StringUtils.isNotEmpty(getInclude())

467

                     && key.equalsIgnoreCase(getInclude()))

468

             {

469

353

                 if (getIncludesAllowed())

470

                 {

471

                     String[] files;

472

352

                     if (!isDelimiterParsingDisabled())

473

                     {

474

351

                         files = StringUtils.split(value, getListDelimiter());

475

                     }

476

                     else

477

                     {

478

1

                         files = new String[]{value};

479

                     }

480

704

                     for (int i = 0; i < files.length; i++)

481

                     {

482

353

                         loadIncludeFile(files[i].trim());

483

                     }

484

                 }

485

352

                 result = false;

486

             }

487

488

             else

489

             {

490

19374

                 addProperty(key, value);

491

19374

                 result = true;

492

             }

493

494

19726

             return result;

495

         }

496

497

         /**

498

          * Tests whether a line is a comment, i.e. whether it starts with a comment

499

          * character.

500

          *

501

          * @param line the line

502

          * @return a flag if this is a comment line

503

          * @since 1.3

504

          */

505

         static boolean isCommentLine(String line)

506

         {

507

39114

             String s = line.trim();

508

             // blanc lines are also treated as comment lines

509

39114

             return s.length() < 1 || COMMENT_CHARS.indexOf(s.charAt(0)) >= 0;

510

         }

511

512

         /**

513

          * This class is used to read properties lines. These lines do

514

          * not terminate with new-line chars but rather when there is no

515

          * backslash sign a the end of the line.  This is used to

516

          * concatenate multiple lines for readability.

517

          */

518

         public static class PropertiesReader extends LineNumberReader

519

         {

520

             /** Stores the comment lines for the currently processed property.*/

521

             private List commentLines;

522

523

             /** Stores the name of the last read property.*/

524

             private String propertyName;

525

526

             /** Stores the value of the last read property.*/

527

             private String propertyValue;

528

529

             /** Stores the list delimiter character.*/

530

             private char delimiter;

531

532

             /**

533

              * Constructor.

534

              *

535

              * @param reader A Reader.

536

              */

537

             public PropertiesReader(Reader reader)

538

             {

539

0

                 this(reader, AbstractConfiguration.getDefaultListDelimiter());

540

0

             }

541

542

             /**

543

              * Creates a new instance of <code>PropertiesReader</code> and sets

544

              * the underlaying reader and the list delimiter.

545

              *

546

              * @param reader the reader

547

              * @param listDelimiter the list delimiter character

548

              * @since 1.3

549

              */

550

             public PropertiesReader(Reader reader, char listDelimiter)

551

             {

552

833

                 super(reader);

553

833

                 commentLines = new ArrayList();

554

833

                 delimiter = listDelimiter;

555

833

             }

556

557

             /**

558

              * Reads a property line. Returns null if Stream is

559

              * at EOF. Concatenates lines ending with "\".

560

              * Skips lines beginning with "#" or "!" and empty lines.

561

              * The return value is a property definition (<code>&lt;name&gt;</code>

562

              * = <code>&lt;value&gt;</code>)

563

              *

564

              * @return A string containing a property value or null

565

              *

566

              * @throws IOException in case of an I/O error

567

              */

568

             public String readProperty() throws IOException

569

             {

570

20560

                 commentLines.clear();

571

20560

                 StringBuffer buffer = new StringBuffer();

572

573

21601

                 while (true)

574

                 {

575

39797

                     String line = readLine();

576

39797

                     if (line == null)

577

                     {

578

                         // EOF

579

832

                         return null;

580

                     }

581

582

38965

                     if (isCommentLine(line))

583

                     {

584

18196

                         commentLines.add(line);

585

18196

                         continue;

586

                     }

587

588

20769

                     line = line.trim();

589

590

20769

                     if (checkCombineLines(line))

591

                     {

592

1041

                         line = line.substring(0, line.length() - 1);

593

1041

                         buffer.append(line);

594

                     }

595

                     else

596

                     {

597

19728

                         buffer.append(line);

598

19728

                         break;

599

                     }

600

                 }

601

19728

                 return buffer.toString();

602

             }

603

604

             /**

605

              * Parses the next property from the input stream and stores the found

606

              * name and value in internal fields. These fields can be obtained using

607

              * the provided getter methods. The return value indicates whether EOF

608

              * was reached (<b>false</b>) or whether further properties are

609

              * available (<b>true</b>).

610

              *

611

              * @return a flag if further properties are available

612

              * @throws IOException if an error occurs

613

              * @since 1.3

614

              */

615

             public boolean nextProperty() throws IOException

616

             {

617

20560

                 String line = readProperty();

618

619

20560

                 if (line == null)

620

                 {

621

832

                     return false; // EOF

622

                 }

623

624

                 // parse the line

625

19728

                 String[] property = parseProperty(line);

626

19728

                 propertyName = StringEscapeUtils.unescapeJava(property[0]);

627

19728

                 propertyValue = unescapeJava(property[1], delimiter);

628

19728

                 return true;

629

             }

630

631

             /**

632

              * Returns the comment lines that have been read for the last property.

633

              *

634

              * @return the comment lines for the last property returned by

635

              * <code>readProperty()</code>

636

              * @since 1.3

637

              */

638

             public List getCommentLines()

639

             {

640

93843

                 return commentLines;

641

             }

642

643

             /**

644

              * Returns the name of the last read property. This method can be called

645

              * after <code>{@link #nextProperty()}</code> was invoked and its

646

              * return value was <b>true</b>.

647

              *

648

              * @return the name of the last read property

649

              * @since 1.3

650

              */

651

             public String getPropertyName()

652

             {

653

58480

                 return propertyName;

654

             }

655

656

             /**

657

              * Returns the value of the last read property. This method can be

658

              * called after <code>{@link #nextProperty()}</code> was invoked and

659

              * its return value was <b>true</b>.

660

              *

661

              * @return the value of the last read property

662

              * @since 1.3

663

              */

664

             public String getPropertyValue()

665

             {

666

19728

                 return propertyValue;

667

             }

668

669

             /**

670

              * Checks if the passed in line should be combined with the following.

671

              * This is true, if the line ends with an odd number of backslashes.

672

              *

673

              * @param line the line

674

              * @return a flag if the lines should be combined

675

              */

676

             private static boolean checkCombineLines(String line)

677

             {

678

20769

                 int bsCount = 0;

679

24592

                 for (int idx = line.length() - 1; idx >= 0 && line.charAt(idx) == '\\'; idx--)

680

                 {

681

3823

                     bsCount++;

682

                 }

683

684

20769

                 return bsCount % 2 == 1;

685

             }

686

687

             /**

688

              * Parse a property line and return the key and the value in an array.

689

              *

690

              * @param line the line to parse

691

              * @return an array with the property's key and value

692

              * @since 1.2

693

              */

694

             private static String[] parseProperty(String line)

695

             {

696

                 // sorry for this spaghetti code, please replace it as soon as

697

                 // possible with a regexp when the Java 1.3 requirement is dropped

698

699

19728

                 String[] result = new String[2];

700

19728

                 StringBuffer key = new StringBuffer();

701

19728

                 StringBuffer value = new StringBuffer();

702

703

                 // state of the automaton:

704

                 // 0: key parsing

705

                 // 1: antislash found while parsing the key

706

                 // 2: separator crossing

707

                 // 3: value parsing

708

19728

                 int state = 0;

709

710

579691

                 for (int pos = 0; pos < line.length(); pos++)

711

                 {

712

559963

                     char c = line.charAt(pos);

713

714

559963

                     switch (state)

715

                     {

716

                         case 0:

717

318261

                             if (c == '\\')

718

                             {

719

1743

                                 state = 1;

720

                             }

721

316518

                             else if (ArrayUtils.contains(WHITE_SPACE, c))

722

                             {

723

                                 // switch to the separator crossing state

724

19552

                                 state = 2;

725

                             }

726

296966

                             else if (ArrayUtils.contains(SEPARATORS, c))

727

                             {

728

                                 // switch to the value parsing state

729

174

                                 state = 3;

730

                             }

731

                             else

732

                             {

733

296792

                                 key.append(c);

734

                             }

735

736

296792

                             break;

737

738

                         case 1:

739

1743

                             if (ArrayUtils.contains(SEPARATORS, c) || ArrayUtils.contains(WHITE_SPACE, c))

740

                             {

741

                                 // this is an escaped separator or white space

742

1740

                                 key.append(c);

743

                             }

744

                             else

745

                             {

746

                                 // another escaped character, the '\' is preserved

747

3

                                 key.append('\\');

748

3

                                 key.append(c);

749

                             }

750

751

                             // return to the key parsing state

752

1743

                             state = 0;

753

754

1743

                             break;

755

756

                         case 2:

757

36226

                             if (ArrayUtils.contains(WHITE_SPACE, c))

758

                             {

759

                                 // do nothing, eat all white spaces

760

16674

                                 state = 2;

761

                             }

762

19552

                             else if (ArrayUtils.contains(SEPARATORS, c))

763

                             {

764

                                 // switch to the value parsing state

765

18505

                                 state = 3;

766

                             }

767

                             else

768

                             {

769

                                 // any other character indicates we encoutered the beginning of the value

770

1047

                                 value.append(c);

771

772

                                 // switch to the value parsing state

773

1047

                                 state = 3;

774

                             }

775

776

1047

                             break;

777

778

                         case 3:

779

203733

                             value.append(c);

780

                             break;

781

                     }

782

                 }

783

784

19728

                 result[0] = key.toString().trim();

785

19728

                 result[1] = value.toString().trim();

786

787

19728

                 return result;

788

             }

789

         } // class PropertiesReader

790

791

         /**

792

          * This class is used to write properties lines.

793

          */

794

         public static class PropertiesWriter extends FilterWriter

795

         {

796

             /** The delimiter for multi-valued properties.*/

797

             private char delimiter;

798

799

             /**

800

              * Constructor.

801

              *

802

              * @param writer a Writer object providing the underlying stream

803

              * @param delimiter the delimiter character for multi-valued properties

804

              */

805

             public PropertiesWriter(Writer writer, char delimiter)

806

             {

807

30

                 super(writer);

808

30

                 this.delimiter = delimiter;

809

30

             }

810

811

             /**

812

              * Write a property.

813

              *

814

              * @param key the key of the property

815

              * @param value the value of the property

816

              *

817

              * @throws IOException if an I/O error occurs

818

              */

819

             public void writeProperty(String key, Object value) throws IOException

820

             {

821

132

                 writeProperty(key, value, false);

822

132

             }

823

824

             /**

825

              * Write a property.

826

              *

827

              * @param key The key of the property

828

              * @param values The array of values of the property

829

              *

830

              * @throws IOException if an I/O error occurs

831

              */

832

             public void writeProperty(String key, List values) throws IOException

833

             {

834

188

                 for (int i = 0; i < values.size(); i++)

835

                 {

836

132

                     writeProperty(key, values.get(i));

837

                 }

838

56

             }

839

840

             /**

841

              * Writes the given property and its value. If the value happens to be a

842

              * list, the <code>forceSingleLine</code> flag is evaluated. If it is

843

              * set, all values are written on a single line using the list delimiter

844

              * as separator.

845

              *

846

              * @param key the property key

847

              * @param value the property value

848

              * @param forceSingleLine the &quot;force single line&quot; flag

849

              * @throws IOException if an error occurs

850

              * @since 1.3

851

              */

852

             public void writeProperty(String key, Object value,

853

                     boolean forceSingleLine) throws IOException

854

             {

855

                 String v;

856

857

380

                 if (value instanceof List)

858

                 {

859

63

                     List values = (List) value;

860

63

                     if (forceSingleLine)

861

                     {

862

7

                         v = makeSingleLineValue(values);

863

                     }

864

                     else

865

                     {

866

56

                         writeProperty(key, values);

867

56

                         return;

868

                     }

869

                 }

870

                 else

871

                 {

872

317

                     v = escapeValue(value);

873

                 }

874

875

324

                 write(escapeKey(key));

876

324

                 write(" = ");

877

324

                 write(v);

878

879

324

                 writeln(null);

880

324

             }

881

882

             /**

883

              * Write a comment.

884

              *

885

              * @param comment the comment to write

886

              * @throws IOException if an I/O error occurs

887

              */

888

             public void writeComment(String comment) throws IOException

889

             {

890

0

                 writeln("# " + comment);

891

0

             }

892

893

             /**

894

              * Escape the separators in the key.

895

              *

896

              * @param key the key

897

              * @return the escaped key

898

              * @since 1.2

899

              */

900

             private String escapeKey(String key)

901

             {

902

324

                 StringBuffer newkey = new StringBuffer();

903

904

4705

                 for (int i = 0; i < key.length(); i++)

905

                 {

906

4381

                     char c = key.charAt(i);

907

908

4381

                     if (ArrayUtils.contains(SEPARATORS, c) || ArrayUtils.contains(WHITE_SPACE, c))

909

                     {

910

                         // escape the separator

911

25

                         newkey.append('\\');

912

25

                         newkey.append(c);

913

                     }

914

                     else

915

                     {

916

4356

                         newkey.append(c);

917

                     }

918

                 }

919

920

324

                 return newkey.toString();

921

             }

922

923

             /**

924

              * Escapes the given property value. Delimiter characters in the value

925

              * will be escaped.

926

              *

927

              * @param value the property value

928

              * @return the escaped property value

929

              * @since 1.3

930

              */

931

             private String escapeValue(Object value)

932

             {

933

340

                 String v = StringEscapeUtils.escapeJava(String.valueOf(value));

934

340

                 return StringUtils.replace(v, String.valueOf(delimiter), "\\"

935

                         + delimiter);

936

             }

937

938

             /**

939

              * Transforms a list of values into a single line value.

940

              *

941

              * @param values the list with the values

942

              * @return a string with the single line value (can be <b>null</b>)

943

              * @since 1.3

944

              */

945

             private String makeSingleLineValue(List values)

946

             {

947

7

                 if (!values.isEmpty())

948

                 {

949

7

                     Iterator it = values.iterator();

950

7

                     StringBuffer buf = new StringBuffer(escapeValue(it.next()));

951

30

                     while (it.hasNext())

952

                     {

953

16

                         buf.append(delimiter);

954

16

                         buf.append(escapeValue(it.next()));

955

                     }

956

7

                     return buf.toString();

957

                 }

958

                 else

959

                 {

960

0

                     return null;

961

                 }

962

             }

963

964

             /**

965

              * Helper method for writing a line with the platform specific line

966

              * ending.

967

              *

968

              * @param s the content of the line (may be <b>null</b>)

969

              * @throws IOException if an error occurs

970

              * @since 1.3

971

              */

972

             public void writeln(String s) throws IOException

973

             {

974

461

                 if (s != null)

975

                 {

976

37

                     write(s);

977

                 }

978

461

                 write(LINE_SEPARATOR);

979

461

             }

980

981

         } // class PropertiesWriter

982

983

         /**

984

          * <p>Unescapes any Java literals found in the <code>String</code> to a

985

          * <code>Writer</code>.</p> This is a slightly modified version of the

986

          * StringEscapeUtils.unescapeJava() function in commons-lang that doesn't

987

          * drop escaped separators (i.e '\,').

988

          *

989

          * @param str  the <code>String</code> to unescape, may be null

990

          * @param delimiter the delimiter for multi-valued properties

991

          * @return the processed string

992

          * @throws IllegalArgumentException if the Writer is <code>null</code>

993

          */

994

         protected static String unescapeJava(String str, char delimiter)

995

         {

996

19729

             if (str == null)

997

             {

998

0

                 return null;

999

             }

1000

19729

             int sz = str.length();

1001

19729

             StringBuffer out = new StringBuffer(sz);

1002

19729

             StringBuffer unicode = new StringBuffer(UNICODE_LEN);

1003

19729

             boolean hadSlash = false;

1004

19729

             boolean inUnicode = false;

1005

206362

             for (int i = 0; i < sz; i++)

1006

             {

1007

186633

                 char ch = str.charAt(i);

1008

186633

                 if (inUnicode)

1009

                 {

1010

                     // if in unicode, then we're reading unicode

1011

                     // values in somehow

1012

1388

                     unicode.append(ch);

1013

1388

                     if (unicode.length() == UNICODE_LEN)

1014

                     {

1015

                         // unicode now contains the four hex digits

1016

                         // which represents our unicode character

1017

                         try

1018

                         {

1019

347

                             int value = Integer.parseInt(unicode.toString(), HEX_RADIX);

1020

347

                             out.append((char) value);

1021

347

                             unicode.setLength(0);

1022

347

                             inUnicode = false;

1023

347

                             hadSlash = false;

1024

347

                         }

1025

                         catch (NumberFormatException nfe)

1026

                         {

1027

0

                             throw new ConfigurationRuntimeException("Unable to parse unicode value: " + unicode, nfe);

1028

347

                         }

1029

                     }

1030

                     continue;

1031

                 }

1032

1033

185245

                 if (hadSlash)

1034

                 {

1035

                     // handle an escaped value

1036

4872

                     hadSlash = false;

1037

1038

4872

                     if (ch == '\\')

1039

                     {

1040

3132

                         out.append('\\');

1041

                     }

1042

1740

                     else if (ch == '\'')

1043

                     {

1044

0

                         out.append('\'');

1045

                     }

1046

1740

                     else if (ch == '\"')

1047

                     {

1048

348

                         out.append('"');

1049

                     }

1050

1392

                     else if (ch == 'r')

1051

                     {

1052

0

                         out.append('\r');

1053

                     }

1054

1392

                     else if (ch == 'f')

1055

                     {

1056

0

                         out.append('\f');

1057

                     }

1058

1392

                     else if (ch == 't')

1059

                     {

1060

348

                         out.append('\t');

1061

                     }

1062

1044

                     else if (ch == 'n')

1063

                     {

1064

348

                         out.append('\n');

1065

                     }

1066

696

                     else if (ch == 'b')

1067

                     {

1068

0

                         out.append('\b');

1069

                     }

1070

696

                     else if (ch == delimiter)

1071

                     {

1072

347

                         out.append('\\');

1073

347

                         out.append(delimiter);

1074

                     }

1075

349

                     else if (ch == 'u')

1076

                     {

1077

                         // uh-oh, we're in unicode country....

1078

347

                         inUnicode = true;

1079

                     }

1080

                     else

1081

                     {

1082

2

                         out.append(ch);

1083

                     }

1084

1085

2

                     continue;

1086

                 }

1087

180373

                 else if (ch == '\\')

1088

                 {

1089

4872

                     hadSlash = true;

1090

4872

                     continue;

1091

                 }

1092

175501

                 out.append(ch);

1093

             }

1094

1095

19729

             if (hadSlash)

1096

             {

1097

                 // then we're in the weird case of a \ at the end of the

1098

                 // string, let's output it anyway.

1099

0

                 out.append('\\');

1100

             }

1101

1102

19729

             return out.toString();

1103

         }

1104

1105

         /**

1106

          * Helper method for loading an included properties file. This method is

1107

          * called by <code>load()</code> when an <code>include</code> property

1108

          * is encountered. It tries to resolve relative file names based on the

1109

          * current base path. If this fails, a resolution based on the location of

1110

          * this properties file is tried.

1111

          *

1112

          * @param fileName the name of the file to load

1113

          * @throws ConfigurationException if loading fails

1114

          */

1115

         private void loadIncludeFile(String fileName) throws ConfigurationException

1116

         {

1117

353

             URL url = ConfigurationUtils.locate(getBasePath(), fileName);

1118

353

             if (url == null)

1119

             {

1120

2

                 URL baseURL = getURL();

1121

2

                 if (baseURL != null)

1122

                 {

1123

2

                     url = ConfigurationUtils.locate(baseURL.toString(), fileName);

1124

                 }

1125

             }

1126

1127

353

             if (url == null)

1128

             {

1129

1

                 throw new ConfigurationException("Cannot resolve include file "

1130

                         + fileName);

1131

             }

1132

352

             load(url);

1133

352

         }

1134

     }