2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
43
package org.netbeans.modules.properties;
48
import javax.swing.text.BadLocationException;
50
import org.openide.nodes.Node;
51
import org.openide.ErrorManager;
52
import org.openide.text.PositionBounds;
56
* Base class for representations of elements in properties files.
58
* @author Petr Jiricka
59
* @author Petr Kuzel - moved to nonescaped strings level
60
* //!!! why is it serializable?
62
public abstract class Element implements Serializable {
64
/** Property change support */
65
private transient PropertyChangeSupport support = new PropertyChangeSupport(this);
67
/** Position of the begin and the end of the element. Could
68
* be null indicating the element is not part of properties structure yet. */
69
protected PositionBounds bounds;
72
/** Create a new element. */
73
protected Element(PositionBounds bounds) {
78
/** Getter for bounds property. */
79
public PositionBounds getBounds() {
84
* Updates the element fields. This method is called after reparsing.
85
* @param elem the element to merge with
87
void update(Element elem) {
88
this.bounds = elem.bounds;
91
/** Fires property change event.
92
* @param name property name
96
protected final void firePropertyChange(String name, Object o, Object n) {
97
support.firePropertyChange (name, o, n);
100
/** Adds property listener */
101
public void addPropertyChangeListener (PropertyChangeListener l) {
102
support.addPropertyChangeListener (l);
105
/** Removes property listener */
106
public void removePropertyChangeListener (PropertyChangeListener l) {
107
support.removePropertyChangeListener (l);
110
/** Prints this element (and all its subelements) by calling <code>bounds.setText(...)</code>
111
* If <code>bounds</code> is null does nothing.
113
public final void print() {
114
if (bounds == null) {
118
bounds.setText(getDocumentString());
119
} catch (BadLocationException e) {
120
ErrorManager.getDefault().notify(e);
121
} catch (IOException e) {
122
ErrorManager.getDefault().notify(e);
127
* Get a string representation of the element for printing into Document.
128
* It currently means that it's properly escaped.
129
* @return the string in its Document form
131
public abstract String getDocumentString();
134
* Get debug string of the element.
137
public String toString() {
138
if (bounds == null) {
139
return "(no bounds)";
141
return new StringBuffer(16)
143
.append(bounds.getBegin().getOffset())
144
.append(", ") //NOI18N
145
.append(bounds.getEnd().getOffset())
151
/** General class for basic elements, which contain value directly. */
152
public static abstract class Basic extends Element {
154
private static final String hexaDigitChars
155
= "0123456789abcdefABCDEF"; //NOI18N
157
protected static void appendIsoControlChar(final StringBuilder buf,
161
buf.append('\\').append('b');
164
buf.append('\\').append('t');
167
buf.append('\\').append('n');
170
buf.append('\\').append('f');
173
buf.append('\\').append('r');
176
buf.append('\\').append('u');
177
for (int shift = 12; shift >= 0; shift -= 4) {
178
buf.append(hexaDigitChars.charAt(
179
((c >> shift) & 0xf)));
184
/** Parsed value of the element */
185
protected String value;
187
/** Create a new basic element. */
188
protected Basic(PositionBounds bounds, String value) {
194
* Updates the element fields. This method is called after reparsing.
195
* @param elem elemnet to merge with
197
void update(Element elem) {
199
this.value = ((Basic)elem).value;
202
/** Get a string representation of the element.
203
* @return the string + bounds
205
public String toString() {
206
return value + " " + super.toString(); // NOI18N
210
* Get a value of the element.
211
* @return the Java string (no escaping)
213
public String getValue() {
218
* Sets the value. Does not check if the value has changed.
219
* The value is immediately propadated in text Document possibly
220
* triggering DocumentEvents.
221
* @param value Java string (no escaping)
223
public void setValue(String value) {
228
} // End of nested class Basic.
231
/** Class representing key element in properties file. */
232
public static class KeyElem extends Basic {
234
/** Generated serial version UID. */
235
static final long serialVersionUID =6828294289485744331L;
238
/** Create a new key element. */
239
protected KeyElem(PositionBounds bounds, String value) {
240
super(bounds, value);
244
/** Get a string representation of the key for printing. Treats the '=' sign as a part of the key
247
public String getDocumentString() {
248
return escapeSpecialChars(value) + "="; //NOI18N
253
* @author Marian Petras
255
private static final String escapeSpecialChars(final String text) {
256
StringBuilder buf = new StringBuilder(text.length() + 16);
258
final int length = text.length();
259
for (int i = 0; i < length; i++) {
260
char c = text.charAt(i);
262
Basic.appendIsoControlChar(buf, c);
281
return buf.toString();
284
} // End of nested class KeyElem.
287
/** Class representing value element in properties files. */
288
public static class ValueElem extends Basic {
290
/** Generated serial version UID. */
291
static final long serialVersionUID =4662649023463958853L;
293
/** Create a new value element. */
294
protected ValueElem(PositionBounds bounds, String value) {
295
super(bounds, value);
298
/** Get a string representation of the value for printing. Appends end of the line after the value.
301
public String getDocumentString() {
302
// escape outerspaces and continious line marks
303
return escapeSpecialChars(value) + "\n"; //NOI18N
308
* @author Marian Petras
310
private static final String escapeSpecialChars(final String text) {
311
StringBuilder buf = new StringBuilder(text.length() + 16);
313
final int length = text.length();
314
for (int i = 0; i < length; i++) {
315
char c = text.charAt(i);
317
Basic.appendIsoControlChar(buf, c);
325
return buf.toString();
328
} // End of nested class ValueElem.
331
* Class representing comment element in properties files. <code>null</code> values of the
332
* string are legal and indicate that the comment is empty. It should contain
333
* pure comment string without comment markers.
335
public static class CommentElem extends Basic {
337
/** Genererated serial version UID. */
338
static final long serialVersionUID =2418308580934815756L;
342
* Create a new comment element.
343
* @param value Comment without its markers (leading '#' or '!'). Markers
344
* are automatically prepended while writing it down to Document.
346
protected CommentElem(PositionBounds bounds, String value) {
347
super(bounds, value);
351
/** Get a string representation of the comment for printing. Makes sure every non-empty line starts with a # and
352
* that the last line is terminated with an end of line marker.
355
public String getDocumentString() {
356
if (value == null || value.length() == 0)
359
// insert #s at the beginning of the lines which contain non-blank characters
360
// holds the last position where we might have to insert a # if this line contains non-blanks
361
StringBuffer sb = new StringBuffer(value);
362
// append the \n if missing
363
if (sb.charAt(sb.length() - 1) != '\n') {
367
boolean hasCommentChar = false;
368
for (int i=0; i<sb.length(); i++) {
369
char aChar = sb.charAt(i);
372
String line = sb.substring(lineStart, i);
373
String convertedLine = ValueElem.escapeSpecialChars(line);
374
sb.replace(lineStart, i, convertedLine);
377
i += convertedLine.length() - line.length();
379
// the next line starts after \n:
382
hasCommentChar = false;
383
} else if (!hasCommentChar
384
&& UtilConvert.whiteSpaceChars.indexOf(aChar) == -1) {
386
if ((aChar == '#') || (aChar == '!')) {
390
sb.insert(lineStart, '#');
394
hasCommentChar = true;
397
return sb.toString();
400
} // End of nested CommentElem.
404
* Class representing element in properties file. Each element contains comment (preceding the property),
405
* key and value subelement.
407
public static class ItemElem extends Element implements Node.Cookie {
412
/** Value element. */
413
private ValueElem value;
415
/** Comment element. */
416
private CommentElem comment;
418
/** Parent of this element - active element has a non-null parent. */
419
private PropertiesStructure parent;
421
/** Name of the Key property */
422
public static final String PROP_ITEM_KEY = "key"; // NOI18N
423
/** Name of the Value property */
424
public static final String PROP_ITEM_VALUE = "value"; // NOI18N
425
/** Name of the Comment property */
426
public static final String PROP_ITEM_COMMENT = "comment"; // NOI18N
428
/** Generated serial version UID. */
429
static final long serialVersionUID =1078147817847520586L;
432
/** Create a new basic element. <code>key</code> and <code>value</code> may be null. */
433
protected ItemElem(PositionBounds bounds, KeyElem key, ValueElem value, CommentElem comment) {
437
this.comment = comment;
441
/** Sets the parent of this element. */
442
void setParent(PropertiesStructure ps) {
447
* @exception IllegalStateException if the parent is <code>null</code>. */
448
private PropertiesStructure getParent() {
450
throw new IllegalStateException("Resource Bundle: Parent is missing"); // NOI18N
456
/** Get a value string of the element.
459
public String toString() {
460
return comment.toString() + "\n" + // NOI18N
461
((key == null) ? "" : key.toString()) + "\n" + // NOI18N
462
((value == null) ? "" : value.toString()) + "\n"; // NOI18N
465
/** Returns the key element for this item. */
466
public KeyElem getKeyElem() {
470
/** Returns the value element for this item. */
471
public ValueElem getValueElem() {
475
/** Returns the comment element for this item. */
476
public CommentElem getCommentElem() {
480
void update(Element elem) {
482
if (this.key == null)
483
this.key = ((ItemElem)elem).key;
485
this.key.update(((ItemElem)elem).key);
487
if (this.value == null)
488
this.value = ((ItemElem)elem).value;
490
this.value.update(((ItemElem)elem).value);
492
this.comment.update(((ItemElem)elem).comment);
495
public String getDocumentString() {
496
return comment.getDocumentString() +
497
((key == null) ? "" : key.getDocumentString()) + // NOI18N
498
((value == null) ? "" : value.getDocumentString()); // NOI18N
501
/** Get a key by which to identify this record
502
* @return nonescaped key
504
public String getKey() {
505
return (key == null) ? null : key.getValue();
508
/** Set the key for this item
509
* @param newKey nonescaped key
511
public void setKey(String newKey) {
512
String oldKey = key.getValue();
513
if (!oldKey.equals(newKey)) {
514
key.setValue(newKey);
515
getParent().itemKeyChanged(oldKey, this);
516
this.firePropertyChange(PROP_ITEM_KEY, oldKey, newKey);
520
/** Get the value of this item */
521
public String getValue() {
522
return (value == null) ? null : value.getValue();
525
/** Set the value of this item
526
* @param newValue the new value
528
public void setValue(String newValue) {
529
String oldValue = value.getValue();
530
if (!oldValue.equals(newValue)) {
532
if(oldValue.equals("")) // NOI18N
533
// Reprint key for the case it's alone yet and doesn't have seprator after (= : or whitespace).
536
value.setValue(newValue);
537
getParent().itemChanged(this);
538
this.firePropertyChange(PROP_ITEM_VALUE, oldValue, newValue);
542
/** Get the comment for this item */
543
public String getComment() {
544
return (comment == null) ? null : comment.getValue();
547
/** Set the comment for this item
548
* @param newComment the new comment (escaped value)
549
* //??? why is required escaped value? I'd expect escapng to be applied during
550
* writing value down to stream no earlier
552
public void setComment(String newComment) {
553
String oldComment = comment.getValue();
554
if ((oldComment == null && newComment != null) || (oldComment != null && !oldComment.equals(newComment))) {
555
comment.setValue(newComment);
556
getParent().itemChanged(this);
557
this.firePropertyChange(PROP_ITEM_COMMENT, oldComment, newComment);
561
/** Checks for equality of two ItemElem-s */
562
public boolean equals(Object item) {
563
if (item == null || !(item instanceof ItemElem))
565
ItemElem ie = (ItemElem)item;
566
if ( ((key==null && ie.getKeyElem()==null) || (key!=null && ie.getKeyElem()!=null && getKey().equals(ie.getKey())) ) &&
567
((value==null && ie.getValueElem()==null) || (value!=null && ie.getValueElem()!=null && getValue().equals(ie.getValue())) ) &&
568
((comment==null && ie.getCommentElem()==null) || (comment!=null && ie.getCommentElem()!=null && getComment().equals(ie.getComment())) ) )
572
} // End of nested class ItemElem.