~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to properties/src/org/netbeans/modules/properties/Element.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
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]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
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.
 
29
 *
 
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.
 
40
 */
 
41
 
 
42
 
 
43
package org.netbeans.modules.properties;
 
44
 
 
45
 
 
46
import java.beans.*;
 
47
import java.io.*;
 
48
import javax.swing.text.BadLocationException;
 
49
 
 
50
import org.openide.nodes.Node;
 
51
import org.openide.ErrorManager;
 
52
import org.openide.text.PositionBounds;
 
53
 
 
54
 
 
55
/**
 
56
 * Base class for representations of elements in properties files.
 
57
 *
 
58
 * @author Petr Jiricka
 
59
 * @author Petr Kuzel - moved to nonescaped strings level
 
60
 * //!!! why is it serializable?
 
61
 */
 
62
public abstract class Element implements Serializable {
 
63
 
 
64
    /** Property change support */
 
65
    private transient PropertyChangeSupport support = new PropertyChangeSupport(this);
 
66
 
 
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;
 
70
 
 
71
    
 
72
    /** Create a new element. */
 
73
    protected Element(PositionBounds bounds) {
 
74
        this.bounds = bounds;
 
75
    }
 
76
 
 
77
    
 
78
    /** Getter for bounds property. */
 
79
    public PositionBounds getBounds() {
 
80
        return bounds;
 
81
    }
 
82
 
 
83
    /**
 
84
     * Updates the element fields. This method is called after reparsing.
 
85
     * @param elem the element to merge with
 
86
     */
 
87
    void update(Element elem) {
 
88
        this.bounds = elem.bounds;
 
89
    }
 
90
 
 
91
    /** Fires property change event.
 
92
     * @param name property name
 
93
     * @param o old value
 
94
     * @param n new value
 
95
     */
 
96
    protected final void firePropertyChange(String name, Object o, Object n) {
 
97
        support.firePropertyChange (name, o, n);
 
98
    }
 
99
 
 
100
    /** Adds property listener */
 
101
    public void addPropertyChangeListener (PropertyChangeListener l) {
 
102
        support.addPropertyChangeListener (l);
 
103
    }
 
104
 
 
105
    /** Removes property listener */
 
106
    public void removePropertyChangeListener (PropertyChangeListener l) {
 
107
        support.removePropertyChangeListener (l);
 
108
    }
 
109
 
 
110
    /** Prints this element (and all its subelements) by calling <code>bounds.setText(...)</code>
 
111
     * If <code>bounds</code> is null does nothing. 
 
112
     * @see #bounds */
 
113
    public final void print() {
 
114
        if (bounds == null) {
 
115
            return;
 
116
        }
 
117
        try {
 
118
            bounds.setText(getDocumentString());
 
119
        } catch (BadLocationException e) {
 
120
            ErrorManager.getDefault().notify(e);
 
121
        } catch (IOException e) {
 
122
            ErrorManager.getDefault().notify(e);
 
123
        }
 
124
    }
 
125
 
 
126
    /**
 
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
 
130
     */
 
131
    public abstract String getDocumentString();
 
132
 
 
133
    /**
 
134
     * Get debug string of the element.
 
135
     * @return the string
 
136
     */
 
137
    public String toString() {
 
138
        if (bounds == null) {
 
139
            return "(no bounds)";
 
140
        }
 
141
        return new StringBuffer(16)
 
142
                .append('(')
 
143
                .append(bounds.getBegin().getOffset())
 
144
                .append(", ")                                           //NOI18N
 
145
                .append(bounds.getEnd().getOffset())
 
146
                .append(')')
 
147
                .toString();
 
148
    }
 
149
 
 
150
    
 
151
    /** General class for basic elements, which contain value directly. */
 
152
    public static abstract class Basic extends Element {
 
153
 
 
154
        private static final String hexaDigitChars
 
155
                                    = "0123456789abcdefABCDEF";         //NOI18N
 
156
 
 
157
        protected static void appendIsoControlChar(final StringBuilder buf,
 
158
                                                   final char c) {
 
159
            switch (c) {
 
160
            case '\b':
 
161
                buf.append('\\').append('b');
 
162
                break;
 
163
            case '\t':
 
164
                buf.append('\\').append('t');
 
165
                break;
 
166
            case '\n':
 
167
                buf.append('\\').append('n');
 
168
                break;
 
169
            case '\f':
 
170
                buf.append('\\').append('f');
 
171
                break;
 
172
            case '\r':
 
173
                buf.append('\\').append('r');
 
174
                break;
 
175
            default:
 
176
                buf.append('\\').append('u');
 
177
                for (int shift = 12; shift >= 0; shift -= 4) {
 
178
                    buf.append(hexaDigitChars.charAt(
 
179
                                    ((c >> shift) & 0xf)));
 
180
                }
 
181
            }
 
182
        }
 
183
 
 
184
        /** Parsed value of the element */
 
185
        protected String value;
 
186
 
 
187
        /** Create a new basic element. */
 
188
        protected Basic(PositionBounds bounds, String value) {
 
189
            super(bounds);
 
190
            this.value = value;
 
191
        }
 
192
 
 
193
        /**
 
194
         * Updates the element fields. This method is called after reparsing.
 
195
         * @param elem elemnet to merge with
 
196
         */
 
197
        void update(Element elem) {
 
198
            super.update(elem);
 
199
            this.value = ((Basic)elem).value;
 
200
        }
 
201
 
 
202
        /** Get a string representation of the element.
 
203
         * @return the string + bounds
 
204
         */
 
205
        public String toString() {
 
206
            return value + "   " + super.toString(); // NOI18N
 
207
        }
 
208
 
 
209
        /**
 
210
         * Get a value of the element.
 
211
         * @return the Java string (no escaping)
 
212
         */
 
213
        public String getValue() {
 
214
            return value;
 
215
        }
 
216
 
 
217
        /**
 
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)
 
222
         */
 
223
        public void setValue(String value) {
 
224
            this.value = value;
 
225
            this.print();
 
226
        }
 
227
 
 
228
    } // End of nested class Basic.
 
229
 
 
230
 
 
231
    /** Class representing key element in properties file. */
 
232
    public static class KeyElem extends Basic {
 
233
 
 
234
        /** Generated serial version UID. */
 
235
        static final long serialVersionUID =6828294289485744331L;
 
236
        
 
237
        
 
238
        /** Create a new key element. */
 
239
        protected KeyElem(PositionBounds bounds, String value) {
 
240
            super(bounds, value);
 
241
        }
 
242
 
 
243
        
 
244
        /** Get a string representation of the key for printing. Treats the '=' sign as a part of the key
 
245
        * @return the string
 
246
        */
 
247
        public String getDocumentString() {
 
248
            return escapeSpecialChars(value) + "=";                     //NOI18N
 
249
        }
 
250
 
 
251
        /**
 
252
         * 
 
253
         * @author  Marian Petras
 
254
         */
 
255
        private static final String escapeSpecialChars(final String text) {
 
256
            StringBuilder buf = new StringBuilder(text.length() + 16);
 
257
 
 
258
            final int length = text.length();
 
259
            for (int i = 0; i < length; i++) {
 
260
                char c = text.charAt(i);
 
261
                if (c < 0x20) {
 
262
                    Basic.appendIsoControlChar(buf, c);
 
263
                } else {
 
264
                    switch (c) {
 
265
                    case '#':
 
266
                    case '!':
 
267
                        if (i == 0) {
 
268
                            buf.append('\\');
 
269
                        }
 
270
                        break;
 
271
                    case ' ':
 
272
                    case '=':
 
273
                    case ':':
 
274
                    case '\\':
 
275
                        buf.append('\\');
 
276
                        break;
 
277
                    }
 
278
                    buf.append(c);
 
279
                }
 
280
            }
 
281
            return buf.toString();
 
282
        }
 
283
 
 
284
    } // End of nested class KeyElem.
 
285
    
 
286
 
 
287
    /** Class representing value element in properties files. */
 
288
    public static class ValueElem extends Basic {
 
289
 
 
290
        /** Generated serial version UID. */
 
291
        static final long serialVersionUID =4662649023463958853L;
 
292
        
 
293
        /** Create a new value element. */
 
294
        protected ValueElem(PositionBounds bounds, String value) {
 
295
            super(bounds, value);
 
296
        }
 
297
 
 
298
        /** Get a string representation of the value for printing. Appends end of the line after the value.
 
299
        * @return the string
 
300
        */
 
301
        public String getDocumentString() {
 
302
            // escape outerspaces and continious line marks
 
303
            return escapeSpecialChars(value) + "\n";                    //NOI18N
 
304
        }
 
305
 
 
306
        /**
 
307
         * 
 
308
         * @author  Marian Petras
 
309
         */
 
310
        private static final String escapeSpecialChars(final String text) {
 
311
            StringBuilder buf = new StringBuilder(text.length() + 16);
 
312
 
 
313
            final int length = text.length();
 
314
            for (int i = 0; i < length; i++) {
 
315
                char c = text.charAt(i);
 
316
                if (c < 0x20) {
 
317
                    Basic.appendIsoControlChar(buf, c);
 
318
                } else {
 
319
                    if (c == '\\') {
 
320
                        buf.append('\\');
 
321
                    }
 
322
                    buf.append(c);
 
323
                }
 
324
            }
 
325
            return buf.toString();
 
326
        }
 
327
 
 
328
    } // End of nested class ValueElem.
 
329
 
 
330
    /**
 
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.
 
334
     */
 
335
    public static class CommentElem extends Basic {
 
336
 
 
337
        /** Genererated serial version UID. */
 
338
        static final long serialVersionUID =2418308580934815756L;
 
339
        
 
340
        
 
341
        /**
 
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.
 
345
         */
 
346
        protected CommentElem(PositionBounds bounds, String value) {
 
347
            super(bounds, value);
 
348
        }
 
349
 
 
350
        
 
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.
 
353
        * @return the string
 
354
        */
 
355
        public String getDocumentString() {
 
356
            if (value == null || value.length() == 0)
 
357
                return ""; // NOI18N
 
358
            else {
 
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') {
 
364
                    sb.append('\n');
 
365
                }
 
366
                int lineStart = 0;
 
367
                boolean hasCommentChar = false;
 
368
                for (int i=0; i<sb.length(); i++) {
 
369
                    char aChar = sb.charAt(i);
 
370
                    // new line
 
371
                    if (aChar == '\n') {
 
372
                        String line = sb.substring(lineStart, i);
 
373
                        String convertedLine = ValueElem.escapeSpecialChars(line);
 
374
                        sb.replace(lineStart, i, convertedLine);
 
375
 
 
376
                        // shift the index:
 
377
                        i += convertedLine.length() - line.length();
 
378
 
 
379
                        // the next line starts after \n:
 
380
                        lineStart = i + 1;
 
381
 
 
382
                        hasCommentChar = false;
 
383
                    } else if (!hasCommentChar
 
384
                          && UtilConvert.whiteSpaceChars.indexOf(aChar) == -1) {
 
385
                        // nonempty symbol
 
386
                        if ((aChar == '#') || (aChar == '!')) {
 
387
                            lineStart = i + 1;
 
388
                        } else {
 
389
                            // insert a #
 
390
                            sb.insert(lineStart, '#');
 
391
                            i++;
 
392
                            lineStart = i;
 
393
                        }
 
394
                        hasCommentChar = true;
 
395
                    }
 
396
                }
 
397
                return sb.toString();
 
398
            }
 
399
        }
 
400
    } // End of nested CommentElem.
 
401
 
 
402
 
 
403
    /** 
 
404
     * Class representing element in  properties file. Each element contains comment (preceding the property),
 
405
     * key and value subelement.
 
406
     */
 
407
    public static class ItemElem extends Element implements Node.Cookie {
 
408
 
 
409
        /** Key element.  */
 
410
        private KeyElem     key;
 
411
        
 
412
        /** Value element. */        
 
413
        private ValueElem   value;
 
414
        
 
415
        /** Comment element. */
 
416
        private CommentElem comment;
 
417
        
 
418
        /** Parent of this element - active element has a non-null parent. */
 
419
        private PropertiesStructure parent;
 
420
 
 
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
 
427
 
 
428
        /** Generated serial version UID. */
 
429
        static final long serialVersionUID =1078147817847520586L;
 
430
 
 
431
        
 
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) {
 
434
            super(bounds);
 
435
            this.key     = key;
 
436
            this.value   = value;
 
437
            this.comment = comment;
 
438
        }
 
439
 
 
440
        
 
441
        /** Sets the parent of this element. */
 
442
        void setParent(PropertiesStructure ps) {
 
443
            parent = ps;
 
444
        }
 
445
 
 
446
        /** Gets parent.
 
447
         * @exception IllegalStateException if the parent is <code>null</code>. */
 
448
        private PropertiesStructure getParent() {
 
449
            if(parent == null) {
 
450
                throw new IllegalStateException("Resource Bundle: Parent is missing"); // NOI18N
 
451
            }
 
452
 
 
453
            return parent;
 
454
        }
 
455
 
 
456
        /** Get a value string of the element.
 
457
         * @return the string
 
458
         */
 
459
        public String toString() {
 
460
            return comment.toString() + "\n" + // NOI18N
 
461
                ((key   == null) ? "" : key.toString()) + "\n" + // NOI18N
 
462
                ((value == null) ? "" : value.toString()) + "\n"; // NOI18N
 
463
        }
 
464
 
 
465
        /** Returns the key element for this item. */
 
466
        public KeyElem getKeyElem() {
 
467
            return key;
 
468
        }
 
469
 
 
470
        /** Returns the value element for this item. */
 
471
        public ValueElem getValueElem() {
 
472
            return value;
 
473
        }
 
474
 
 
475
        /** Returns the comment element for this item. */
 
476
        public CommentElem getCommentElem() {
 
477
            return comment;
 
478
        }
 
479
 
 
480
        void update(Element elem) {
 
481
            super.update(elem);
 
482
            if (this.key == null)
 
483
                this.key     = ((ItemElem)elem).key;
 
484
            else
 
485
                this.key.update(((ItemElem)elem).key);
 
486
 
 
487
            if (this.value == null)
 
488
                this.value   = ((ItemElem)elem).value;
 
489
            else
 
490
                this.value.update(((ItemElem)elem).value);
 
491
 
 
492
            this.comment.update(((ItemElem)elem).comment);
 
493
        }
 
494
 
 
495
        public String getDocumentString() {
 
496
            return comment.getDocumentString() +
 
497
                ((key   == null) ? "" : key.getDocumentString()) + // NOI18N
 
498
                ((value == null) ? "" : value.getDocumentString()); // NOI18N
 
499
        }
 
500
 
 
501
        /** Get a key by which to identify this record
 
502
         * @return nonescaped key
 
503
         */
 
504
        public String getKey() {
 
505
            return (key == null) ? null : key.getValue();
 
506
        }
 
507
 
 
508
        /** Set the key for this item
 
509
        *  @param newKey nonescaped key
 
510
        */                        
 
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);
 
517
            }
 
518
        }
 
519
 
 
520
        /** Get the value of this item */
 
521
        public String getValue() {
 
522
            return (value == null) ? null : value.getValue();
 
523
        }
 
524
 
 
525
        /** Set the value of this item
 
526
         *  @param newValue the new value
 
527
         */                        
 
528
        public void setValue(String newValue) {
 
529
            String oldValue = value.getValue();
 
530
            if (!oldValue.equals(newValue)) {
 
531
                
 
532
                if(oldValue.equals("")) // NOI18N
 
533
                    // Reprint key for the case it's alone yet and doesn't have seprator after (= : or whitespace).
 
534
                    key.print();
 
535
                
 
536
                value.setValue(newValue);
 
537
                getParent().itemChanged(this);
 
538
                this.firePropertyChange(PROP_ITEM_VALUE, oldValue, newValue);
 
539
            }
 
540
        }
 
541
 
 
542
        /** Get the comment for this item */
 
543
        public String getComment() {
 
544
            return (comment == null) ? null : comment.getValue();
 
545
        }
 
546
 
 
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
 
551
         */                        
 
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);
 
558
            }
 
559
        }
 
560
 
 
561
        /** Checks for equality of two ItemElem-s */
 
562
        public boolean equals(Object item) {
 
563
            if (item == null || !(item instanceof ItemElem))
 
564
                return false;
 
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())) ) )
 
569
                return true;
 
570
            return false;
 
571
        }
 
572
    } // End of nested class ItemElem.
 
573
}