diff -r df371fd16386 -r 3da26790ccb7 test/tools/javac/diags/MessageFile.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/diags/MessageFile.java Wed Jan 26 13:45:25 2011 -0800 @@ -0,0 +1,463 @@ +/* + * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.*; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Class to facilitate manipulating compiler.properties. + */ +class MessageFile { + static final Pattern emptyOrCommentPattern = Pattern.compile("( *#.*)?"); + static final Pattern infoPattern = Pattern.compile("# ([0-9]+: [-A-Za-z ]+, )*[0-9]+: [-A-Za-z ]+"); + + /** + * A line of text within the message file. + * The lines form a doubly linked list for simple navigation. + */ + class Line { + String text; + Line prev; + Line next; + + Line(String text) { + this.text = text; + } + + boolean isEmptyOrComment() { + return emptyOrCommentPattern.matcher(text).matches(); + } + + boolean isInfo() { + return infoPattern.matcher(text).matches(); + } + + boolean hasContinuation() { + return (next != null) && text.endsWith("\\"); + } + + Line insertAfter(String text) { + Line l = new Line(text); + insertAfter(l); + return l; + } + + void insertAfter(Line l) { + assert prev == null && next == null; + l.prev = this; + l.next = next; + if (next == null) + lastLine = l; + else + next.prev = l; + next = l; + } + + Line insertBefore(String text) { + Line l = new Line(text); + insertBefore(l); + return l; + } + + void insertBefore(Line l) { + assert prev == null && next == null; + l.prev = prev; + l.next = this; + if (prev == null) + firstLine = l; + else + prev.next = l; + prev = l; + } + + void remove() { + if (prev == null) + firstLine = next; + else + prev.next = next; + if (next == null) + lastLine = prev; + else + next.prev = prev; + prev = null; + next = null; + } + } + + /** + * A message within the message file. + * A message is a series of lines containing a "name=value" property, + * optionally preceded by a comment describing the use of placeholders + * such as {0}, {1}, etc within the property value. + */ + static final class Message { + final Line firstLine; + private Info info; + + Message(Line l) { + firstLine = l; + } + + boolean needInfo() { + Line l = firstLine; + while (true) { + if (l.text.matches(".*\\{[0-9]+\\}.*")) + return true; + if (!l.hasContinuation()) + return false; + l = l.next; + } + } + + Set getPlaceholders() { + Pattern p = Pattern.compile("\\{([0-9]+)\\}"); + Set results = new TreeSet(); + Line l = firstLine; + while (true) { + Matcher m = p.matcher(l.text); + while (m.find()) + results.add(Integer.parseInt(m.group(1))); + if (!l.hasContinuation()) + return results; + l = l.next; + } + } + + /** + * Get the Info object for this message. It may be empty if there + * if no comment preceding the property specification. + */ + Info getInfo() { + if (info == null) { + Line l = firstLine.prev; + if (l != null && l.isInfo()) + info = new Info(l.text); + else + info = new Info(); + } + return info; + } + + /** + * Set the Info for this message. + * If there was an info comment preceding the property specification, + * it will be updated; otherwise, one will be inserted. + */ + void setInfo(Info info) { + this.info = info; + Line l = firstLine.prev; + if (l != null && l.isInfo()) + l.text = info.toComment(); + else + firstLine.insertBefore(info.toComment()); + } + + /** + * Get all the lines pertaining to this message. + */ + List getLines(boolean includeAllPrecedingComments) { + List lines = new ArrayList(); + Line l = firstLine; + if (includeAllPrecedingComments) { + // scan back to find end of prev message + while (l.prev != null && l.prev.isEmptyOrComment()) + l = l.prev; + // skip leading blank lines + while (l.text.isEmpty()) + l = l.next; + } else { + if (l.prev != null && l.prev.isInfo()) + l = l.prev; + } + + // include any preceding lines + for ( ; l != firstLine; l = l.next) + lines.add(l); + + // include message lines + for (l = firstLine; l != null && l.hasContinuation(); l = l.next) + lines.add(l); + lines.add(l); + + // include trailing blank line if present + l = l.next; + if (l != null && l.text.isEmpty()) + lines.add(l); + + return lines; + } + } + + /** + * An object to represent the comment that may precede the property + * specification in a Message. + * The comment is modelled as a list of fields, where the fields correspond + * to the placeholder values (e.g. {0}, {1}, etc) within the message value. + */ + static final class Info { + /** + * An ordered set of descriptions for a placeholder value in a + * message. + */ + static class Field { + boolean unused; + Set values; + boolean listOfAny = false; + boolean setOfAny = false; + Field(String s) { + s = s.substring(s.indexOf(": ") + 2); + values = new LinkedHashSet(Arrays.asList(s.split(" or "))); + for (String v: values) { + if (v.startsWith("list of")) + listOfAny = true; + if (v.startsWith("set of")) + setOfAny = true; + } + } + + /** + * Return true if this field logically contains all the values of + * another field. + */ + boolean contains(Field other) { + if (unused != other.unused) + return false; + + for (String v: other.values) { + if (values.contains(v)) + continue; + if (v.equals("null") || v.equals("string")) + continue; + if (v.equals("list") && listOfAny) + continue; + if (v.equals("set") && setOfAny) + continue; + return false; + } + return true; + } + + /** + * Merge the values of another field into this field. + */ + void merge(Field other) { + unused |= other.unused; + values.addAll(other.values); + + // cleanup unnecessary entries + + if (values.contains("null") && values.size() > 1) { + // "null" is superceded by anything else + values.remove("null"); + } + + if (values.contains("string") && values.size() > 1) { + // "string" is superceded by anything else + values.remove("string"); + } + + if (values.contains("list")) { + // list is superceded by "list of ..." + for (String s: values) { + if (s.startsWith("list of ")) { + values.remove("list"); + break; + } + } + } + + if (values.contains("set")) { + // set is superceded by "set of ..." + for (String s: values) { + if (s.startsWith("set of ")) { + values.remove("set"); + break; + } + } + } + + if (other.values.contains("unused")) { + values.clear(); + values.add("unused"); + } + } + + void markUnused() { + values = new LinkedHashSet(); + values.add("unused"); + listOfAny = false; + setOfAny = false; + } + + @Override + public String toString() { + return values.toString(); + } + } + + /** The fields of the Info object. */ + List fields = new ArrayList(); + + Info() { } + + Info(String text) throws IllegalArgumentException { + if (!text.startsWith("# ")) + throw new IllegalArgumentException(); + String[] segs = text.substring(2).split(", "); + fields = new ArrayList(); + for (String seg: segs) { + fields.add(new Field(seg)); + } + } + + Info(Set infos) throws IllegalArgumentException { + for (String s: infos) + merge(new Info(s)); + } + + boolean isEmpty() { + return fields.isEmpty(); + } + + boolean contains(Info other) { + if (other.isEmpty()) + return true; + + if (fields.size() != other.fields.size()) + return false; + + Iterator oIter = other.fields.iterator(); + for (Field values: fields) { + if (!values.contains(oIter.next())) + return false; + } + + return true; + } + + void merge(Info other) { + if (fields.isEmpty()) { + fields.addAll(other.fields); + return; + } + + if (other.fields.size() != fields.size()) + throw new IllegalArgumentException(); + + Iterator oIter = other.fields.iterator(); + for (Field d: fields) { + d.merge(oIter.next()); + } + } + + void markUnused(Set used) { + for (int i = 0; i < fields.size(); i++) { + if (!used.contains(i)) + fields.get(i).markUnused(); + } + } + + @Override + public String toString() { + return fields.toString(); + } + + String toComment() { + StringBuilder sb = new StringBuilder(); + sb.append("# "); + String sep = ""; + int i = 0; + for (Field f: fields) { + sb.append(sep); + sb.append(i++); + sb.append(": "); + sep = ""; + for (String s: f.values) { + sb.append(sep); + sb.append(s); + sep = " or "; + } + sep = ", "; + } + return sb.toString(); + } + } + + Line firstLine; + Line lastLine; + Map messages = new TreeMap(); + + MessageFile(File file) throws IOException { + Reader in = new FileReader(file); + try { + read(in); + } finally { + in.close(); + } + } + + MessageFile(Reader in) throws IOException { + read(in); + } + + final void read(Reader in) throws IOException { + BufferedReader br = (in instanceof BufferedReader) + ? (BufferedReader) in + : new BufferedReader(in); + String line; + while ((line = br.readLine()) != null) { + Line l; + if (firstLine == null) + l = firstLine = lastLine = new Line(line); + else + l = lastLine.insertAfter(line); + if (line.startsWith("compiler.")) { + int eq = line.indexOf("="); + if (eq > 0) + messages.put(line.substring(0, eq), new Message(l)); + } + } + } + + void write(File file) throws IOException { + Writer out = new FileWriter(file); + try { + write(out); + } finally { + out.close(); + } + } + + void write(Writer out) throws IOException { + BufferedWriter bw = (out instanceof BufferedWriter) + ? (BufferedWriter) out + : new BufferedWriter(out); + for (Line l = firstLine; l != null; l = l.next) { + bw.write(l.text); + bw.write("\n"); // always use Unix line endings + } + bw.flush(); + } +}