KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > modules > properties > PropertiesParser


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20
21 package org.netbeans.modules.properties;
22
23
24 import java.io.BufferedReader JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.Reader JavaDoc;
27 import java.io.StringReader JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import java.util.Map JavaDoc;
30 import java.util.HashMap JavaDoc;
31 import java.util.List JavaDoc;
32 import javax.swing.text.BadLocationException JavaDoc;
33 import javax.swing.text.Document JavaDoc;
34 import javax.swing.text.Position JavaDoc;
35 import org.openide.text.PositionRef;
36 import org.openide.text.PositionBounds;
37
38
39 /**
40  * Parser of .properties files. It generates structure of comment-key-vaue property elements.
41  *
42  * @author Petr Jiricka, Petr Hamernik, Peter Zavadsky
43  * @see PropertiesStructure
44  * @see Element.ItemElem
45  */

46 class PropertiesParser {
47
48     /** PropertiesFileEntry for which source is this parser created. */
49     PropertiesFileEntry pfe;
50
51     /** Appropriate properties editor - used for creating the PositionRefs */
52     PropertiesEditorSupport editor;
53
54     /** Properties file reader. Input stream. */
55     PropertiesReader propertiesReader;
56     
57     /** Flag if parsing should be stopped. */
58     private boolean stop = false;
59
60     
61     /**
62      * Creates parser. Has to be {@link init} afterwards.
63      * @param pfe FileEntry where the properties file is stored.
64      */

65     public PropertiesParser(PropertiesFileEntry pfe) {
66         this.pfe = pfe;
67     }
68
69     
70     /** Inits parser.
71      * @exception IOException if any i/o problem occured during reading */

72     void initParser() throws IOException JavaDoc {
73         editor = pfe.getPropertiesEditor();
74         propertiesReader = createReader();
75     }
76     
77     /** Creates new input stream from the file object.
78      * Finds the properties data object, checks if the document is loaded,
79      * if not is loaded and created a stream from the document.
80      * @exception IOException if any i/o problem occured during reading
81      */

82     private PropertiesReader createReader() throws IOException JavaDoc {
83         // Get loaded document, or load it if necessary.
84
Document JavaDoc loadDoc = null;
85         
86         if(editor.isDocumentLoaded()) {
87             loadDoc = editor.getDocument();
88         }
89     
90         if(loadDoc == null) {
91             loadDoc = editor.openDocument();
92         }
93             
94         final Document JavaDoc document = loadDoc;
95         final String JavaDoc[] str = new String JavaDoc[1];
96
97         // safely take the text from the document
98
document.render(new Runnable JavaDoc() {
99             public void run() {
100                 try {
101                     str[0] = document.getText(0, document.getLength());
102                 } catch(BadLocationException JavaDoc ble) {
103                     // Should be not possible.
104
ble.printStackTrace();
105                 }
106             }
107         });
108
109         return new PropertiesReader(str[0]);
110     }
111
112     /** Parses .properties file specified by <code>pfe</code> and resets its properties
113      * structure.
114      * @return new properties structure or null if parsing failed
115      */

116     public PropertiesStructure parseFile() {
117         try {
118             PropertiesStructure propStructure = parseFileMain();
119             
120             return propStructure;
121         } catch(IOException JavaDoc e) {
122             // Parsing failed, return null.
123
return null;
124         }
125     }
126     
127     /** Stops parsing. */
128     public void stop() {
129         stop = true;
130         clean();
131     }
132     
133     /** Provides clean up after finish parsing. */
134     public void clean() {
135         if(propertiesReader != null) {
136             try {
137                 propertiesReader.close();
138                 propertiesReader = null;
139             } catch(IOException JavaDoc ioe) {
140                 org.openide.ErrorManager.getDefault().notify(org.openide.ErrorManager.INFORMATIONAL, ioe);
141             }
142         }
143     }
144
145     /** Parses .properties file and creates <code>PropertiesStruture</code>. */
146     private PropertiesStructure parseFileMain() throws IOException JavaDoc {
147
148         Map JavaDoc<String JavaDoc,Element.ItemElem> items = new HashMap JavaDoc<String JavaDoc,Element.ItemElem>(25, 1.0F);
149
150         PropertiesReader reader = null;
151         
152         while (true) {
153             if (stop) {
154                 // Parsing stopped -> return immediatelly.
155
return null;
156             }
157             
158             reader = propertiesReader;
159             if (reader == null) {
160                 // Parsing was stopped.
161
return null;
162             }
163             Element.ItemElem element = readNextElem(reader);
164             
165             if (element == null) {
166                 break;
167             } else {
168                 // add at the end of the list
169
items.put(element.getKey(), element);
170             }
171         }
172         
173         return new PropertiesStructure(createBiasBounds(0, reader.position), items);
174     }
175
176     /**
177      * Reads next element from input stream.
178      * @return next element or null if the end of the stream occurred */

179     private Element.ItemElem readNextElem(PropertiesReader in) throws IOException JavaDoc {
180         Element.CommentElem commE;
181         Element.KeyElem keyE;
182         Element.ValueElem valueE;
183
184         int begPos = in.position;
185
186         // read the comment
187
int keyPos = begPos;
188         FlaggedLine fl = in.readLineExpectComment();
189         StringBuffer JavaDoc comment = new StringBuffer JavaDoc();
190         boolean firstNull = true;
191         while (fl != null) {
192             firstNull = false;
193             if(fl.flag) {
194                 //part of the comment
195
comment.append(trimComment(fl.line));
196                 comment.append(fl.lineSep);
197                 keyPos = in.position;
198             } else
199                 // not a part of a comment
200
break;
201             fl = in.readLineExpectComment();
202         }
203
204         // exit completely if null is returned the very first time
205
if (firstNull) {
206             return null;
207         }
208
209         String JavaDoc comHelp;
210         comHelp = comment.toString();
211         if(comment.length() > 0)
212             if(comment.charAt(comment.length() - 1) == '\n')
213                 comHelp = comment.substring(0, comment.length() - 1);
214
215         commE = new Element.CommentElem(createBiasBounds(begPos, keyPos), UtilConvert.loadConvert(comHelp));
216         // fl now contains the line after the comment or null if none exists
217

218
219         if(fl == null) {
220             keyE = null;
221             valueE = null;
222         } else {
223             // read the key and the value
224
// list of
225
ArrayList JavaDoc<FlaggedLine> lines = new ArrayList JavaDoc<FlaggedLine>(2);
226             fl.startPosition = keyPos;
227             fl.stringValue = fl.line.toString();
228             lines.add(fl);
229             int nowPos;
230             while (isPartialLine(fl.line)) {
231                 // do something with the previous line
232
fl.stringValue = fl.stringValue.substring(0, fl.stringValue/*fix: was: line*/.length() - 1);
233                 // now the new line
234
nowPos = in.position;
235                 fl = in.readLineNoFrills();
236                 if(fl == null) break;
237                 // delete the leading whitespaces
238
int startIndex=0;
239                 for(startIndex=0; startIndex < fl.line.length(); startIndex++)
240                     if(UtilConvert.whiteSpaceChars.indexOf(fl.line.charAt(startIndex)) == -1)
241                         break;
242                 fl.stringValue = fl.line.substring(startIndex, fl.line.length());
243                 fl.startPosition = nowPos + startIndex;
244                 lines.add(fl);
245             }
246             // now I have an ArrayList with strings representing lines and positions of the first non-whitespace character
247

248             PositionMap positionMap = new PositionMap(lines);
249             String JavaDoc line = positionMap.getString();
250
251             // Find start of key
252
int len = line.length();
253             int keyStart;
254             for(keyStart=0; keyStart<len; keyStart++) {
255                 if(UtilConvert.whiteSpaceChars.indexOf(line.charAt(keyStart)) == -1)
256                     break;
257             }
258             
259             // Find separation between key and value
260
int separatorIndex;
261             for(separatorIndex=keyStart; separatorIndex<len; separatorIndex++) {
262                 char currentChar = line.charAt(separatorIndex);
263                 if(currentChar == '\\')
264                     separatorIndex++;
265                 else if(UtilConvert.keyValueSeparators.indexOf(currentChar) != -1)
266                     break;
267             }
268
269             // Skip over whitespace after key if any
270
int valueIndex;
271             for (valueIndex=separatorIndex; valueIndex<len; valueIndex++)
272                 if(UtilConvert.whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
273                     break;
274
275             // Skip over one non whitespace key value separators if any
276
if(valueIndex < len)
277                 if(UtilConvert.strictKeyValueSeparators.indexOf(line.charAt(valueIndex)) != -1)
278                     valueIndex++;
279
280             // Skip over white space after other separators if any
281
while (valueIndex < len) {
282                 if(UtilConvert.whiteSpaceChars.indexOf(line.charAt(valueIndex)) == -1)
283                     break;
284                 valueIndex++;
285             }
286             String JavaDoc key = line.substring(keyStart, separatorIndex);
287             String JavaDoc value = (separatorIndex < len) ? line.substring(valueIndex, len) : ""; // NOI18N
288

289             if(key == null)
290                 // PENDING - should join with the next comment
291
;
292
293             int currentPos = in.position;
294             int valuePosFile = 0;
295             
296             try {
297                 valuePosFile = positionMap.getFilePosition(valueIndex);
298             } catch (ArrayIndexOutOfBoundsException JavaDoc e) {
299                 valuePosFile = currentPos;
300             }
301             
302             keyE = new Element.KeyElem (createBiasBounds(keyPos, valuePosFile), UtilConvert.loadConvert(key));
303             valueE = new Element.ValueElem(createBiasBounds(valuePosFile, currentPos), UtilConvert.loadConvert(value));
304         }
305         
306         return new Element.ItemElem(createBiasBounds(begPos, in.position), keyE, valueE, commE);
307     }
308
309     /** Remove leading comment markers. */
310     private StringBuffer JavaDoc trimComment(StringBuffer JavaDoc line) {
311         while (line.length() > 0) {
312             char lead = line.charAt(0);
313             if (lead == '#' || lead == '!') {
314                 line.deleteCharAt(0);
315             } else {
316                 break;
317             }
318         }
319         return line;
320     }
321
322     /** Utility method. Computes the real offset from the long value representing position in the parser.
323      * @return the offset
324      */

325     private static int position(long p) {
326         return (int)(p & 0xFFFFFFFFL);
327     }
328
329     /** Creates position bounds. For obtaining the real offsets is used
330      * previous method position()
331      * @param begin the begin in the internal position form
332      * @param end the end in the internal position form
333      * @return the bounds
334      */

335     private PositionBounds createBiasBounds(long begin, long end) {
336         PositionRef posBegin = editor.createPositionRef(position(begin), Position.Bias.Forward);
337         PositionRef posEnd = editor.createPositionRef(position(end), Position.Bias.Backward);
338         return new PositionBounds(posBegin, posEnd);
339     }
340
341     /**
342      * Properties reader which allows reading from an input stream or from a string and remembers
343      * its position in the document.
344      */

345     private static class PropertiesReader extends BufferedReader JavaDoc {
346
347         /** Name constant of line separator system property. */
348         private static final String JavaDoc LINE_SEPARATOR = "line.separator"; // NOI18N
349

350         /** The character that someone peeked. */
351         private int peekChar = -1;
352         
353         /** Position after the last character read. */
354         public int position = 0;
355
356
357         /** Creates <code>PropertiesReader</code> from buffer. */
358         private PropertiesReader(String JavaDoc buffer) {
359             super(new StringReader JavaDoc(buffer));
360         }
361
362         /** Creates <code>PropertiesReader</code> from another reader. */
363         private PropertiesReader(Reader JavaDoc reader) {
364             super(reader);
365         }
366         
367         
368         /** Read one character from the stream and increases the position.
369          * @return the character or -1 if the end of the stream has been reached
370          */

371         public int read() throws IOException JavaDoc {
372             int character = peek();
373             peekChar = -1;
374             if(character != -1)
375                 position++;
376
377             return character;
378         }
379
380         /** Returns the next character without increasing the position. Subsequent calls
381          * to peek() and read() will return the same character.
382          * @return the character or -1 if the end of the stream has been reached
383          */

384         private int peek() throws IOException JavaDoc {
385             if(peekChar == -1)
386                 peekChar = super.read();
387             
388             return peekChar;
389         }
390
391         /** Reads the next line and returns the flag as true if the line is a comment line.
392          * If the input is empty returns null
393          * Flag in the result is true if the line is a comment line
394          */

395         public FlaggedLine readLineExpectComment() throws IOException JavaDoc {
396             int charRead = read();
397             if(charRead == -1)
398                 // end of the reader reached
399
return null;
400
401             boolean decided = false;
402             FlaggedLine fl = new FlaggedLine();
403             while (charRead != -1 && charRead != (int)'\n' && charRead != (int)'\r') {
404                 if(!decided)
405                     if(UtilConvert.whiteSpaceChars.indexOf((char)charRead) == -1) {
406                         // not a whitespace - decide now
407
fl.flag = (((char)charRead == '!') || ((char)charRead == '#'));
408                         decided = true;
409                     }
410                 fl.line.append((char)charRead);
411                 charRead = read();
412             }
413
414             if(!decided)
415                 // all were whitespaces
416
fl.flag = true;
417
418             // set the line separator
419
if(charRead == (int)'\r')
420                 if(peek() == (int)'\n') {
421                     charRead = read();
422                     fl.lineSep = "\r\n"; // NOI18N
423
} else
424                     fl.lineSep = "\r"; // NOI18N
425
else
426                 if(charRead == (int)'\n')
427                     fl.lineSep = "\n"; // NOI18N
428
else
429                     fl.lineSep = System.getProperty(LINE_SEPARATOR);
430
431             return fl;
432         }
433
434         /** Reads the next line.
435          * @return <code>FlaggedLine</code> or null if the input is empty */

436         public FlaggedLine readLineNoFrills() throws IOException JavaDoc {
437             int charRead = read();
438             if(charRead == -1)
439                 // end of the reader reached
440
return null;
441
442             FlaggedLine fl = new FlaggedLine();
443             while (charRead != -1 && charRead != (int)'\n' && charRead != (int)'\r') {
444                 fl.line.append((char)charRead);
445                 charRead = read();
446             }
447
448             // set the line separator
449
if(charRead == (int)'\r')
450                 if(peek() == (int)'\n') {
451                     charRead = read();
452                     fl.lineSep = "\r\n"; // NOI18N
453
} else
454                     fl.lineSep = "\r"; // NOI18N
455
else
456                 if(charRead == (int)'\n') // NOI18N
457
fl.lineSep = "\n"; // NOI18N
458
else
459                     fl.lineSep = System.getProperty(LINE_SEPARATOR);
460
461             return fl;
462         }
463
464     } // End of nested class PropertiesReader.
465

466     /**
467      * Returns true if the given line is a line that must
468      * be appended to the next line
469      */

470     private static boolean isPartialLine (StringBuffer JavaDoc line) {
471         int slashCount = 0;
472         int index = line.length() - 1;
473         while((index >= 0) && (line.charAt(index--) == '\\'))
474             slashCount++;
475         return (slashCount % 2 == 1);
476     }
477
478     /** Nested class which maps positions in a string to positions in the underlying file.
479      * @see FlaggedLine */

480     private static class PositionMap {
481
482         /** List of <code>FlaggedLine</code>'s. */
483         private List JavaDoc<FlaggedLine> list;
484         
485         
486         /** Constructor - expects a list of FlaggedLine */
487         PositionMap(List JavaDoc<FlaggedLine> lines) {
488             list = lines;
489         }
490         
491
492         /** Returns the string represented by the object */
493         public String JavaDoc getString() {
494             String JavaDoc allLines = list.get(0).stringValue;
495             for (int part=1; part<list.size(); part++) {
496                 allLines += list.get(part).stringValue;
497             }
498             return allLines;
499         }
500
501         /** Returns position in the file for a position in a string
502          * @param posString position in the string to find file position for
503          * @return position in the file
504          * @exception ArrayIndexOutOfBoundsException if the requested position is outside
505          * the area represented by this object
506          */

507         public int getFilePosition(int posString) throws ArrayIndexOutOfBoundsException JavaDoc {
508             // get the part
509
int part;
510             int lengthSoFar = 0;
511             int lastLengthSoFar = 0;
512             for (part=0; part < list.size(); part++) {
513                 lastLengthSoFar = lengthSoFar;
514                 lengthSoFar += list.get(part).stringValue.length();
515                 // brute patch - last (cr)lf should not be the part of the thing, other should
516
if (part == list.size() - 1) {
517                     if (lengthSoFar >= posString) {
518                         break;
519                     }
520                 } else {
521                     if (lengthSoFar > posString) {
522                         break;
523                     }
524                 }
525             }
526             if (posString > lengthSoFar) {
527                 throw new ArrayIndexOutOfBoundsException JavaDoc("not in scope"); // NOI18N
528
}
529             return list.get(part).startPosition + posString - lastLengthSoFar;
530         }
531     } // End of nested class PositionMap.
532

533     
534     /** Helper nested class. */
535     private static class FlaggedLine {
536
537         /** Line buffer. */
538         StringBuffer JavaDoc line;
539         
540         /** Flag. */
541         boolean flag;
542         
543         /** Line separator. */
544         String JavaDoc lineSep;
545         
546         /** Start position. */
547         int startPosition;
548         
549         /** Value. */
550         String JavaDoc stringValue;
551
552         
553         /** Constructor. */
554         FlaggedLine() {
555             line = new StringBuffer JavaDoc();
556             flag = false;
557             lineSep = "\n"; // NOI18N
558
startPosition = 0;
559         }
560     } // End of nested class FlaggedLine.
561
}
562
Popular Tags