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