test/tools/javac/diags/MessageFile.java

changeset 842
3da26790ccb7
child 1657
f4500abff1fd
equal deleted inserted replaced
841:df371fd16386 842:3da26790ccb7
1 /*
2 * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 import java.io.*;
25 import java.util.*;
26 import java.util.regex.Matcher;
27 import java.util.regex.Pattern;
28
29 /**
30 * Class to facilitate manipulating compiler.properties.
31 */
32 class MessageFile {
33 static final Pattern emptyOrCommentPattern = Pattern.compile("( *#.*)?");
34 static final Pattern infoPattern = Pattern.compile("# ([0-9]+: [-A-Za-z ]+, )*[0-9]+: [-A-Za-z ]+");
35
36 /**
37 * A line of text within the message file.
38 * The lines form a doubly linked list for simple navigation.
39 */
40 class Line {
41 String text;
42 Line prev;
43 Line next;
44
45 Line(String text) {
46 this.text = text;
47 }
48
49 boolean isEmptyOrComment() {
50 return emptyOrCommentPattern.matcher(text).matches();
51 }
52
53 boolean isInfo() {
54 return infoPattern.matcher(text).matches();
55 }
56
57 boolean hasContinuation() {
58 return (next != null) && text.endsWith("\\");
59 }
60
61 Line insertAfter(String text) {
62 Line l = new Line(text);
63 insertAfter(l);
64 return l;
65 }
66
67 void insertAfter(Line l) {
68 assert prev == null && next == null;
69 l.prev = this;
70 l.next = next;
71 if (next == null)
72 lastLine = l;
73 else
74 next.prev = l;
75 next = l;
76 }
77
78 Line insertBefore(String text) {
79 Line l = new Line(text);
80 insertBefore(l);
81 return l;
82 }
83
84 void insertBefore(Line l) {
85 assert prev == null && next == null;
86 l.prev = prev;
87 l.next = this;
88 if (prev == null)
89 firstLine = l;
90 else
91 prev.next = l;
92 prev = l;
93 }
94
95 void remove() {
96 if (prev == null)
97 firstLine = next;
98 else
99 prev.next = next;
100 if (next == null)
101 lastLine = prev;
102 else
103 next.prev = prev;
104 prev = null;
105 next = null;
106 }
107 }
108
109 /**
110 * A message within the message file.
111 * A message is a series of lines containing a "name=value" property,
112 * optionally preceded by a comment describing the use of placeholders
113 * such as {0}, {1}, etc within the property value.
114 */
115 static final class Message {
116 final Line firstLine;
117 private Info info;
118
119 Message(Line l) {
120 firstLine = l;
121 }
122
123 boolean needInfo() {
124 Line l = firstLine;
125 while (true) {
126 if (l.text.matches(".*\\{[0-9]+\\}.*"))
127 return true;
128 if (!l.hasContinuation())
129 return false;
130 l = l.next;
131 }
132 }
133
134 Set<Integer> getPlaceholders() {
135 Pattern p = Pattern.compile("\\{([0-9]+)\\}");
136 Set<Integer> results = new TreeSet<Integer>();
137 Line l = firstLine;
138 while (true) {
139 Matcher m = p.matcher(l.text);
140 while (m.find())
141 results.add(Integer.parseInt(m.group(1)));
142 if (!l.hasContinuation())
143 return results;
144 l = l.next;
145 }
146 }
147
148 /**
149 * Get the Info object for this message. It may be empty if there
150 * if no comment preceding the property specification.
151 */
152 Info getInfo() {
153 if (info == null) {
154 Line l = firstLine.prev;
155 if (l != null && l.isInfo())
156 info = new Info(l.text);
157 else
158 info = new Info();
159 }
160 return info;
161 }
162
163 /**
164 * Set the Info for this message.
165 * If there was an info comment preceding the property specification,
166 * it will be updated; otherwise, one will be inserted.
167 */
168 void setInfo(Info info) {
169 this.info = info;
170 Line l = firstLine.prev;
171 if (l != null && l.isInfo())
172 l.text = info.toComment();
173 else
174 firstLine.insertBefore(info.toComment());
175 }
176
177 /**
178 * Get all the lines pertaining to this message.
179 */
180 List<Line> getLines(boolean includeAllPrecedingComments) {
181 List<Line> lines = new ArrayList<Line>();
182 Line l = firstLine;
183 if (includeAllPrecedingComments) {
184 // scan back to find end of prev message
185 while (l.prev != null && l.prev.isEmptyOrComment())
186 l = l.prev;
187 // skip leading blank lines
188 while (l.text.isEmpty())
189 l = l.next;
190 } else {
191 if (l.prev != null && l.prev.isInfo())
192 l = l.prev;
193 }
194
195 // include any preceding lines
196 for ( ; l != firstLine; l = l.next)
197 lines.add(l);
198
199 // include message lines
200 for (l = firstLine; l != null && l.hasContinuation(); l = l.next)
201 lines.add(l);
202 lines.add(l);
203
204 // include trailing blank line if present
205 l = l.next;
206 if (l != null && l.text.isEmpty())
207 lines.add(l);
208
209 return lines;
210 }
211 }
212
213 /**
214 * An object to represent the comment that may precede the property
215 * specification in a Message.
216 * The comment is modelled as a list of fields, where the fields correspond
217 * to the placeholder values (e.g. {0}, {1}, etc) within the message value.
218 */
219 static final class Info {
220 /**
221 * An ordered set of descriptions for a placeholder value in a
222 * message.
223 */
224 static class Field {
225 boolean unused;
226 Set<String> values;
227 boolean listOfAny = false;
228 boolean setOfAny = false;
229 Field(String s) {
230 s = s.substring(s.indexOf(": ") + 2);
231 values = new LinkedHashSet<String>(Arrays.asList(s.split(" or ")));
232 for (String v: values) {
233 if (v.startsWith("list of"))
234 listOfAny = true;
235 if (v.startsWith("set of"))
236 setOfAny = true;
237 }
238 }
239
240 /**
241 * Return true if this field logically contains all the values of
242 * another field.
243 */
244 boolean contains(Field other) {
245 if (unused != other.unused)
246 return false;
247
248 for (String v: other.values) {
249 if (values.contains(v))
250 continue;
251 if (v.equals("null") || v.equals("string"))
252 continue;
253 if (v.equals("list") && listOfAny)
254 continue;
255 if (v.equals("set") && setOfAny)
256 continue;
257 return false;
258 }
259 return true;
260 }
261
262 /**
263 * Merge the values of another field into this field.
264 */
265 void merge(Field other) {
266 unused |= other.unused;
267 values.addAll(other.values);
268
269 // cleanup unnecessary entries
270
271 if (values.contains("null") && values.size() > 1) {
272 // "null" is superceded by anything else
273 values.remove("null");
274 }
275
276 if (values.contains("string") && values.size() > 1) {
277 // "string" is superceded by anything else
278 values.remove("string");
279 }
280
281 if (values.contains("list")) {
282 // list is superceded by "list of ..."
283 for (String s: values) {
284 if (s.startsWith("list of ")) {
285 values.remove("list");
286 break;
287 }
288 }
289 }
290
291 if (values.contains("set")) {
292 // set is superceded by "set of ..."
293 for (String s: values) {
294 if (s.startsWith("set of ")) {
295 values.remove("set");
296 break;
297 }
298 }
299 }
300
301 if (other.values.contains("unused")) {
302 values.clear();
303 values.add("unused");
304 }
305 }
306
307 void markUnused() {
308 values = new LinkedHashSet<String>();
309 values.add("unused");
310 listOfAny = false;
311 setOfAny = false;
312 }
313
314 @Override
315 public String toString() {
316 return values.toString();
317 }
318 }
319
320 /** The fields of the Info object. */
321 List<Field> fields = new ArrayList<Field>();
322
323 Info() { }
324
325 Info(String text) throws IllegalArgumentException {
326 if (!text.startsWith("# "))
327 throw new IllegalArgumentException();
328 String[] segs = text.substring(2).split(", ");
329 fields = new ArrayList<Field>();
330 for (String seg: segs) {
331 fields.add(new Field(seg));
332 }
333 }
334
335 Info(Set<String> infos) throws IllegalArgumentException {
336 for (String s: infos)
337 merge(new Info(s));
338 }
339
340 boolean isEmpty() {
341 return fields.isEmpty();
342 }
343
344 boolean contains(Info other) {
345 if (other.isEmpty())
346 return true;
347
348 if (fields.size() != other.fields.size())
349 return false;
350
351 Iterator<Field> oIter = other.fields.iterator();
352 for (Field values: fields) {
353 if (!values.contains(oIter.next()))
354 return false;
355 }
356
357 return true;
358 }
359
360 void merge(Info other) {
361 if (fields.isEmpty()) {
362 fields.addAll(other.fields);
363 return;
364 }
365
366 if (other.fields.size() != fields.size())
367 throw new IllegalArgumentException();
368
369 Iterator<Field> oIter = other.fields.iterator();
370 for (Field d: fields) {
371 d.merge(oIter.next());
372 }
373 }
374
375 void markUnused(Set<Integer> used) {
376 for (int i = 0; i < fields.size(); i++) {
377 if (!used.contains(i))
378 fields.get(i).markUnused();
379 }
380 }
381
382 @Override
383 public String toString() {
384 return fields.toString();
385 }
386
387 String toComment() {
388 StringBuilder sb = new StringBuilder();
389 sb.append("# ");
390 String sep = "";
391 int i = 0;
392 for (Field f: fields) {
393 sb.append(sep);
394 sb.append(i++);
395 sb.append(": ");
396 sep = "";
397 for (String s: f.values) {
398 sb.append(sep);
399 sb.append(s);
400 sep = " or ";
401 }
402 sep = ", ";
403 }
404 return sb.toString();
405 }
406 }
407
408 Line firstLine;
409 Line lastLine;
410 Map<String, Message> messages = new TreeMap<String, Message>();
411
412 MessageFile(File file) throws IOException {
413 Reader in = new FileReader(file);
414 try {
415 read(in);
416 } finally {
417 in.close();
418 }
419 }
420
421 MessageFile(Reader in) throws IOException {
422 read(in);
423 }
424
425 final void read(Reader in) throws IOException {
426 BufferedReader br = (in instanceof BufferedReader)
427 ? (BufferedReader) in
428 : new BufferedReader(in);
429 String line;
430 while ((line = br.readLine()) != null) {
431 Line l;
432 if (firstLine == null)
433 l = firstLine = lastLine = new Line(line);
434 else
435 l = lastLine.insertAfter(line);
436 if (line.startsWith("compiler.")) {
437 int eq = line.indexOf("=");
438 if (eq > 0)
439 messages.put(line.substring(0, eq), new Message(l));
440 }
441 }
442 }
443
444 void write(File file) throws IOException {
445 Writer out = new FileWriter(file);
446 try {
447 write(out);
448 } finally {
449 out.close();
450 }
451 }
452
453 void write(Writer out) throws IOException {
454 BufferedWriter bw = (out instanceof BufferedWriter)
455 ? (BufferedWriter) out
456 : new BufferedWriter(out);
457 for (Line l = firstLine; l != null; l = l.next) {
458 bw.write(l.text);
459 bw.write("\n"); // always use Unix line endings
460 }
461 bw.flush();
462 }
463 }

mercurial