/* * Copyright (c) 2010, 2013, 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 typePattern = Pattern.compile("[-\\\\'A-Z\\.a-z ]+( \\([A-Za-z 0-9]+\\))?"); static final Pattern infoPattern = Pattern.compile(String.format("# ([0-9]+: %s, )*[0-9]+: %s", typePattern.pattern(), typePattern.pattern())); /** * 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 l.prev == null && l.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 l.prev == null && l.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<Integer> getPlaceholders() { Pattern p = Pattern.compile("\\{([0-9]+)\\}"); Set<Integer> results = new TreeSet<Integer>(); 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<Line> getLines(boolean includeAllPrecedingComments) { List<Line> lines = new ArrayList<Line>(); 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<String> values; boolean listOfAny = false; boolean setOfAny = false; Field(String s) { s = s.substring(s.indexOf(": ") + 2); values = new LinkedHashSet<String>(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<String>(); values.add("unused"); listOfAny = false; setOfAny = false; } @Override public String toString() { return values.toString(); } } /** The fields of the Info object. */ List<Field> fields = new ArrayList<Field>(); Info() { } Info(String text) throws IllegalArgumentException { if (!text.startsWith("# ")) throw new IllegalArgumentException(); String[] segs = text.substring(2).split(", "); fields = new ArrayList<Field>(); for (String seg: segs) { fields.add(new Field(seg)); } } Info(Set<String> 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<Field> 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<Field> oIter = other.fields.iterator(); for (Field d: fields) { d.merge(oIter.next()); } } void markUnused(Set<Integer> 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<String, Message> messages = new TreeMap<String, Message>(); 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(); } }