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