7013272: Automatically generate info about how compiler resource keys are used

Reviewed-by: mcimadamore
This commit is contained in:
Jonathan Gibbons 2011-01-26 13:45:25 -08:00
parent a184b53cec
commit de3bde6688
8 changed files with 1859 additions and 65 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
Copyright (c) 2007, 2009, Oracle and/or its affiliates. All rights reserved. Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
This code is free software; you can redistribute it and/or modify it This code is free software; you can redistribute it and/or modify it
@ -331,7 +331,7 @@
executable="${dist.bin.dir}/javac" executable="${dist.bin.dir}/javac"
srcdir="test/tools/javac/diags" srcdir="test/tools/javac/diags"
destdir="${build.dir}/diag-examples/classes" destdir="${build.dir}/diag-examples/classes"
includes="Example.java,FileManager.java,HTMLWriter.java,RunExamples.java" includes="ArgTypeCompilerFactory.java,Example.java,FileManager.java,HTMLWriter.java,RunExamples.java"
sourcepath="" sourcepath=""
classpath="${dist.lib.dir}/javac.jar" classpath="${dist.lib.dir}/javac.jar"
includeAntRuntime="no" includeAntRuntime="no"

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2009, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -106,7 +106,7 @@ public abstract class Printer implements Type.Visitor<String, Locale>, Symbol.Vi
} }
/** /**
* * Get a localized string represenation for all the symbols in the input list. * * Get a localized string representation for all the symbols in the input list.
* *
* @param ts symbols to be displayed * @param ts symbols to be displayed
* @param locale the locale in which the string is to be rendered * @param locale the locale in which the string is to be rendered

View file

@ -0,0 +1,346 @@
/*
* 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.List;
import javax.tools.*;
import com.sun.tools.javac.api.*;
import com.sun.tools.javac.api.DiagnosticFormatter.Configuration.DiagnosticPart;
import com.sun.tools.javac.api.Formattable.LocalizedString;
import com.sun.tools.javac.code.Flags.Flag;
import com.sun.tools.javac.code.Kinds.KindName;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.file.*;
import com.sun.tools.javac.main.Main;
import com.sun.tools.javac.parser.Token;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.AbstractDiagnosticFormatter.SimpleConfiguration;
import javax.lang.model.SourceVersion;
/**
* Compiler factory for instances of Example.Compiler that use custom
* DiagnosticFormatter and Messages objects to track the types of args
* when when localizing diagnostics.
* The compiler objects only support "output" mode, not "check" mode.
*/
class ArgTypeCompilerFactory implements Example.Compiler.Factory {
// Same code as Example.Compiler.DefaultFactory, but the names resolve differently
public Example.Compiler getCompiler(List<String> opts, boolean verbose) {
String first;
String[] rest;
if (opts == null || opts.isEmpty()) {
first = null;
rest = new String[0];
} else {
first = opts.get(0);
rest = opts.subList(1, opts.size()).toArray(new String[opts.size() - 1]);
}
if (first == null || first.equals("jsr199"))
return new Jsr199Compiler(verbose, rest);
else if (first.equals("simple"))
return new SimpleCompiler(verbose);
else if (first.equals("backdoor"))
return new BackdoorCompiler(verbose);
else
throw new IllegalArgumentException(first);
}
/**
* Compile using the JSR 199 API. The diagnostics generated are
* scanned for resource keys. Not all diagnostic keys are generated
* via the JSR 199 API -- for example, rich diagnostics are not directly
* accessible, and some diagnostics generated by the file manager may
* not be generated (for example, the JSR 199 file manager does not see
* -Xlint:path).
*/
static class Jsr199Compiler extends Example.Compiler {
List<String> fmOpts;
Jsr199Compiler(boolean verbose, String... args) {
super(verbose);
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (arg.equals("-filemanager") && (i + 1 < args.length)) {
fmOpts = Arrays.asList(args[++i].split(","));
} else
throw new IllegalArgumentException(arg);
}
}
@Override
boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
assert out != null && keys == null;
if (verbose)
System.err.println("run_jsr199: " + opts + " " + files);
JavacTool tool = JavacTool.create();
StandardJavaFileManager fm = tool.getStandardFileManager(null, null, null);
if (fmOpts != null)
fm = new FileManager(fm, fmOpts);
Iterable<? extends JavaFileObject> fos = fm.getJavaFileObjectsFromFiles(files);
JavacTaskImpl t = (JavacTaskImpl) tool.getTask(out, fm, null, opts, null, fos);
Context c = t.getContext();
ArgTypeMessages.preRegister(c);
Options options = Options.instance(c);
Log.instance(c).setDiagnosticFormatter(new ArgTypeDiagnosticFormatter(options));
Boolean ok = t.call();
return ok;
}
}
/**
* Run the test using the standard simple entry point.
*/
static class SimpleCompiler extends Example.Compiler {
SimpleCompiler(boolean verbose) {
super(verbose);
}
@Override
boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
assert out != null && keys == null;
if (verbose)
System.err.println("run_simple: " + opts + " " + files);
List<String> args = new ArrayList<String>();
args.addAll(opts);
for (File f: files)
args.add(f.getPath());
Main main = new Main("javac", out);
Context c = new Context() {
@Override public void clear() {
((JavacFileManager) get(JavaFileManager.class)).close();
super.clear();
}
};
JavacFileManager.preRegister(c); // can't create it until Log has been set up
ArgTypeDiagnosticFormatter.preRegister(c);
ArgTypeMessages.preRegister(c);
int result = main.compile(args.toArray(new String[args.size()]), c);
return (result == 0);
}
}
static class BackdoorCompiler extends Example.Compiler {
BackdoorCompiler(boolean verbose) {
super(verbose);
}
@Override
boolean run(PrintWriter out, Set<String> keys, boolean raw, List<String> opts, List<File> files) {
assert out != null && keys == null;
if (verbose)
System.err.println("run_simple: " + opts + " " + files);
List<String> args = new ArrayList<String>(opts);
for (File f: files)
args.add(f.getPath());
Context c = new Context();
JavacFileManager.preRegister(c); // can't create it until Log has been set up
ArgTypeDiagnosticFormatter.preRegister(c);
ArgTypeMessages.preRegister(c);
com.sun.tools.javac.main.Main m = new com.sun.tools.javac.main.Main("javac", out);
int rc = m.compile(args.toArray(new String[args.size()]), c);
return (rc == 0);
}
}
// <editor-fold defaultstate="collapsed" desc="Custom Javac components">
/**
* Diagnostic formatter which reports formats a diag as a series of lines
* containing a key, and a possibly empty set of descriptive strings for the
* arg types.
*/
static class ArgTypeDiagnosticFormatter extends AbstractDiagnosticFormatter {
static void preRegister(final Context context) {
context.put(Log.logKey, new Context.Factory<Log>() {
public Log make() {
Log log = new Log(context) { };
Options options = Options.instance(context);
log.setDiagnosticFormatter(new ArgTypeDiagnosticFormatter(options));
return log;
}
});
}
ArgTypeDiagnosticFormatter(Options options) {
super(null, new SimpleConfiguration(options,
EnumSet.of(DiagnosticPart.SUMMARY,
DiagnosticPart.DETAILS,
DiagnosticPart.SUBDIAGNOSTICS)));
}
@Override
protected String formatDiagnostic(JCDiagnostic d, Locale locale) {
return formatMessage(d, locale);
}
@Override
public String formatMessage(JCDiagnostic d, Locale l) {
StringBuilder buf = new StringBuilder();
formatMessage(d, buf);
return buf.toString();
}
private void formatMessage(JCDiagnostic d, StringBuilder buf) {
String key = d.getCode();
Object[] args = d.getArgs();
// report the primary arg types, without recursing into diag fragments
buf.append(getKeyArgsString(key, args));
// report details for any diagnostic fragments
for (Object arg: args) {
if (arg instanceof JCDiagnostic) {
buf.append("\n");
formatMessage((JCDiagnostic) arg, buf);
}
}
// report details for any subdiagnostics
for (String s: formatSubdiagnostics(d, null)) {
buf.append("\n");
buf.append(s);
}
}
@Override
public boolean isRaw() {
return true;
}
}
/**
* Diagnostic formatter which "localizes" a message as a line
* containing a key, and a possibly empty set of descriptive strings for the
* arg types.
*/
static class ArgTypeMessages extends JavacMessages {
static void preRegister(final Context c) {
c.put(JavacMessages.messagesKey, new Context.Factory<JavacMessages>() {
public JavacMessages make() {
return new ArgTypeMessages(c) {
@Override
public String getLocalizedString(Locale l, String key, Object... args) {
return getKeyArgsString(key, args);
}
};
}
});
}
ArgTypeMessages(Context context) {
super(context);
}
}
/**
* Utility method to generate a string for key and args
*/
static String getKeyArgsString(String key, Object... args) {
StringBuilder buf = new StringBuilder();
buf.append(key);
String sep = ": ";
for (Object o : args) {
buf.append(sep);
buf.append(getArgTypeOrStringValue(o));
sep = ", ";
}
return buf.toString();
}
static boolean showStringValues = false;
static String getArgTypeOrStringValue(Object o) {
if (showStringValues && o instanceof String)
return "\"" + o + "\"";
return getArgType(o);
}
static String getArgType(Object o) {
if (o == null)
return "null";
if (o instanceof Name)
return "name";
if (o instanceof Boolean)
return "boolean";
if (o instanceof Integer)
return "number";
if (o instanceof String)
return "string";
if (o instanceof Flag)
return "modifier";
if (o instanceof KindName)
return "symbol kind";
if (o instanceof Token)
return "token";
if (o instanceof Symbol)
return "symbol";
if (o instanceof Type)
return "type";
if (o instanceof List) {
List<?> l = (List<?>) o;
if (l.isEmpty())
return "list";
else
return "list of " + getArgType(l.get(0));
}
if (o instanceof ListBuffer)
return getArgType(((ListBuffer) o).toList());
if (o instanceof Set) {
Set<?> s = (Set<?>) o;
if (s.isEmpty())
return "set";
else
return "set of " + getArgType(s.iterator().next());
}
if (o instanceof SourceVersion)
return "source version";
if (o instanceof FileObject || o instanceof File)
return "file name";
if (o instanceof JCDiagnostic)
return "message segment";
if (o instanceof LocalizedString)
return "message segment"; // only instance is "no arguments"
String s = o.getClass().getSimpleName();
return (s.isEmpty() ? o.getClass().getName() : s);
}
// </editor-fold>
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -168,7 +168,7 @@ class Example implements Comparable<Example> {
try { try {
run(null, keys, true, verbose); run(null, keys, true, verbose);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace(System.err);
} }
return keys; return keys;
} }
@ -293,10 +293,15 @@ class Example implements Comparable<Example> {
} }
abstract static class Compiler { abstract static class Compiler {
static Compiler getCompiler(List<String> opts, boolean verbose) { interface Factory {
Compiler getCompiler(List<String> opts, boolean verbose);
}
static class DefaultFactory implements Factory {
public Compiler getCompiler(List<String> opts, boolean verbose) {
String first; String first;
String[] rest; String[] rest;
if (opts == null || opts.size() == 0) { if (opts == null || opts.isEmpty()) {
first = null; first = null;
rest = new String[0]; rest = new String[0];
} else { } else {
@ -312,6 +317,16 @@ class Example implements Comparable<Example> {
else else
throw new IllegalArgumentException(first); throw new IllegalArgumentException(first);
} }
}
static Factory factory;
static Compiler getCompiler(List<String> opts, boolean verbose) {
if (factory == null)
factory = new DefaultFactory();
return factory.getCompiler(opts, verbose);
}
protected Compiler(boolean verbose) { protected Compiler(boolean verbose) {
this.verbose = verbose; this.verbose = verbose;

View file

@ -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<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();
}
}

View file

@ -0,0 +1,406 @@
/*
* 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.
*/
/**
* @test
* @bug 7013272
* @summary Automatically generate info about how compiler resource keys are used
* @build Example ArgTypeCompilerFactory MessageFile MessageInfo
* @run main MessageInfo
*/
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* Utility to manipulate compiler.properties, and suggest info comments based
* on information derived from running examples.
*
* Options:
* -examples dir location of examples directory
* -o file output file
* -check just check message file
* -ensureNewlines ensure newline after each entry
* -fixIndent fix indentation of continuation lines
* -sort sort messages
* -verbose verbose output
* -replace replace comments instead of merging comments
* file javac compiler.properties file
*
*/
public class MessageInfo {
public static void main(String... args) throws Exception {
jtreg = (System.getProperty("test.src") != null);
File tmpDir;
if (jtreg) {
// use standard jtreg scratch directory: the current directory
tmpDir = new File(System.getProperty("user.dir"));
} else {
tmpDir = new File(System.getProperty("java.io.tmpdir"),
MessageInfo.class.getName()
+ (new SimpleDateFormat("yyMMddHHmmss")).format(new Date()));
}
Example.setTempDir(tmpDir);
Example.Compiler.factory = new ArgTypeCompilerFactory();
MessageInfo mi = new MessageInfo();
try {
if (mi.run(args))
return;
} finally {
/* VERY IMPORTANT NOTE. In jtreg mode, tmpDir is set to the
* jtreg scratch directory, which is the current directory.
* In case someone is faking jtreg mode, make sure to only
* clean tmpDir when it is reasonable to do so.
*/
if (tmpDir.isDirectory() &&
tmpDir.getName().startsWith(MessageInfo.class.getName())) {
if (clean(tmpDir))
tmpDir.delete();
}
}
if (jtreg)
throw new Exception(mi.errors + " errors occurred");
else
System.exit(1);
}
void usage() {
System.out.println("Usage:");
System.out.println(" java MessageInfo [options] [file]");
System.out.println("where options include");
System.out.println(" -examples dir location of examples directory");
System.out.println(" -o file output file");
System.out.println(" -check just check message file");
System.out.println(" -ensureNewlines ensure newline after each entry");
System.out.println(" -fixIndent fix indentation of continuation lines");
System.out.println(" -sort sort messages");
System.out.println(" -verbose verbose output");
System.out.println(" -replace replace comments instead of merging comments");
System.out.println(" file javac compiler.properties file");
}
boolean run(String... args) {
File testSrc = new File(System.getProperty("test.src", "."));
File examplesDir = new File(testSrc, "examples");
File notYetFile = null;
File msgFile = null;
File outFile = null;
boolean verbose = false;
boolean ensureNewlines = false;
boolean fixIndent = false;
boolean sort = false;
boolean replace = false;
boolean check = jtreg; // default true in jtreg mode
for (int i = 0; i < args.length; i++) {
String arg = args[i];
if (arg.equals("-examples") && (i + 1) < args.length)
examplesDir = new File(args[++i]);
else if(arg.equals("-notyet") && (i + 1) < args.length)
notYetFile = new File(args[++i]);
else if (arg.equals("-ensureNewlines"))
ensureNewlines = true;
else if (arg.equals("-fixIndent"))
fixIndent = true;
else if (arg.equals("-sort"))
sort = true;
else if (arg.equals("-verbose"))
verbose = true;
else if (arg.equals("-replace"))
replace = true;
else if (arg.equals("-check"))
check = true;
else if (arg.equals("-o") && (i + 1) < args.length)
outFile = new File(args[++i]);
else if (arg.startsWith("-")) {
error("unknown option: " + arg);
return false;
} else if (i == args.length - 1) {
msgFile = new File(arg);
} else {
error("unknown arg: " + arg);
return false;
}
}
if (!check && outFile == null) {
usage();
return true;
}
if ((ensureNewlines || fixIndent || sort) && outFile == null) {
error("must set output file for these options");
return false;
}
if (notYetFile == null) {
notYetFile = new File(examplesDir.getParentFile(), "examples.not-yet.txt");
}
if (msgFile == null) {
for (File d = testSrc; d != null; d = d.getParentFile()) {
if (new File(d, "TEST.ROOT").exists()) {
d = d.getParentFile();
File f = new File(d, "src/share/classes/com/sun/tools/javac/resources/compiler.properties");
if (f.exists()) {
msgFile = f;
break;
}
}
}
if (msgFile == null) {
error("no message file available");
return false;
}
}
MessageFile mf;
try {
mf = new MessageFile(msgFile);
} catch (IOException e) {
error("problem reading message file: " + e);
return false;
}
Map<String, Set<String>> msgInfo = runExamples(examplesDir, verbose);
if (ensureNewlines)
ensureNewlines(mf);
if (fixIndent)
fixIndent(mf);
if (sort)
sort(mf, true);
for (Map.Entry<String, Set<String>> e: msgInfo.entrySet()) {
String k = e.getKey();
Set<String> suggestions = e.getValue();
MessageFile.Message m = mf.messages.get(k);
if (m == null) {
error("Can't find message for " + k + " in message file");
continue;
}
MessageFile.Info info = m.getInfo();
Set<Integer> placeholders = m.getPlaceholders();
MessageFile.Info suggestedInfo = new MessageFile.Info(suggestions);
suggestedInfo.markUnused(placeholders);
if (!info.isEmpty()) {
if (info.contains(suggestedInfo))
continue;
if (!replace) {
if (info.fields.size() != suggestedInfo.fields.size())
error("Cannot merge info for " + k);
else
suggestedInfo.merge(info);
}
}
if (outFile == null) {
System.err.println("suggest for " + k);
System.err.println(suggestedInfo.toComment());
} else
m.setInfo(suggestedInfo);
}
if (check)
check(mf, notYetFile);
try {
if (outFile != null)
mf.write(outFile);
} catch (IOException e) {
error("problem writing file: " + e);
return false;
}
return (errors == 0);
}
void check(MessageFile mf, File notYetFile) {
Set<String> notYetList = null;
for (Map.Entry<String, MessageFile.Message> e: mf.messages.entrySet()) {
String key = e.getKey();
MessageFile.Message m = e.getValue();
if (m.needInfo() && m.getInfo().isEmpty()) {
if (notYetList == null)
notYetList = getNotYetList(notYetFile);
if (notYetList.contains(key))
System.err.println("Warning: no info for " + key);
else
error("no info for " + key);
}
}
}
void ensureNewlines(MessageFile mf) {
for (MessageFile.Message m: mf.messages.values()) {
MessageFile.Line l = m.firstLine;
while (l.text.endsWith("\\"))
l = l.next;
if (l.next != null && !l.next.text.isEmpty())
l.insertAfter("");
}
}
void fixIndent(MessageFile mf) {
for (MessageFile.Message m: mf.messages.values()) {
MessageFile.Line l = m.firstLine;
while (l.text.endsWith("\\") && l.next != null) {
if (!l.next.text.matches("^ \\S.*"))
l.next.text = " " + l.next.text.trim();
l = l.next;
}
}
}
void sort(MessageFile mf, boolean includePrecedingNewlines) {
for (MessageFile.Message m: mf.messages.values()) {
for (MessageFile.Line l: m.getLines(includePrecedingNewlines)) {
l.remove();
mf.lastLine.insertAfter(l);
}
}
}
Map<String, Set<String>> runExamples(File examplesDir, boolean verbose) {
Map<String, Set<String>> map = new TreeMap<String, Set<String>>();
for (Example e: getExamples(examplesDir)) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.run(pw, true, verbose);
pw.close();
String[] lines = sw.toString().split("\n");
for (String line: lines) {
if (!line.startsWith("compiler."))
continue;
int colon = line.indexOf(":");
if (colon == -1)
continue;
String key = line.substring(0, colon);
StringBuilder sb = new StringBuilder();
sb.append("# ");
int i = 0;
String[] descs = line.substring(colon + 1).split(", *");
for (String desc: descs) {
if (i > 0) sb.append(", ");
sb.append(i++);
sb.append(": ");
sb.append(desc.trim());
}
Set<String> set = map.get(key);
if (set == null)
map.put(key, set = new TreeSet<String>());
set.add(sb.toString());
}
}
return map;
}
/**
* Get the complete set of examples to be checked.
*/
Set<Example> getExamples(File examplesDir) {
Set<Example> results = new TreeSet<Example>();
for (File f: examplesDir.listFiles()) {
if (isValidExample(f))
results.add(new Example(f));
}
return results;
}
boolean isValidExample(File f) {
return (f.isDirectory() && (!jtreg || f.list().length > 0)) ||
(f.isFile() && f.getName().endsWith(".java"));
}
/**
* Get the contents of the "not-yet" list.
*/
Set<String> getNotYetList(File file) {
Set<String> results = new TreeSet<String>();
try {
String[] lines = read(file).split("[\r\n]");
for (String line: lines) {
int hash = line.indexOf("#");
if (hash != -1)
line = line.substring(0, hash).trim();
if (line.matches("[A-Za-z0-9-_.]+"))
results.add(line);
}
} catch (IOException e) {
throw new Error(e);
}
return results;
}
/**
* Read the contents of a file.
*/
String read(File f) throws IOException {
byte[] bytes = new byte[(int) f.length()];
DataInputStream in = new DataInputStream(new FileInputStream(f));
try {
in.readFully(bytes);
} finally {
in.close();
}
return new String(bytes);
}
/**
* Report an error.
*/
void error(String msg) {
System.err.println("Error: " + msg);
errors++;
}
static boolean jtreg;
int errors;
/**
* Clean the contents of a directory.
*/
static boolean clean(File dir) {
boolean ok = true;
for (File f: dir.listFiles()) {
if (f.isDirectory())
ok &= clean(f);
ok &= f.delete();
}
return ok;
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,7 @@
* @test * @test
* @bug 6968063 * @bug 6968063
* @summary provide examples of code that generate diagnostics * @summary provide examples of code that generate diagnostics
* @build Example HTMLWriter RunExamples * @build ArgTypeCompilerFactory Example HTMLWriter RunExamples
* @run main RunExamples * @run main RunExamples
*/ */
@ -97,6 +97,7 @@ public class RunExamples {
boolean raw = false; boolean raw = false;
boolean showFiles = false; boolean showFiles = false;
boolean verbose = false; boolean verbose = false;
boolean argTypes = false;
String title = null; String title = null;
for (int i = 0; i < args.length; i++) { for (int i = 0; i < args.length; i++) {
@ -115,6 +116,8 @@ public class RunExamples {
outFile = new File(args[++i]); outFile = new File(args[++i]);
else if (arg.equals("-title") && (i + 1) < args.length) else if (arg.equals("-title") && (i + 1) < args.length)
title = args[++i]; title = args[++i];
else if (arg.equals("-argtypes"))
argTypes = true;
else if (arg.startsWith("-")) { else if (arg.startsWith("-")) {
error("unknown option: " + arg); error("unknown option: " + arg);
return false; return false;
@ -127,6 +130,11 @@ public class RunExamples {
} }
} }
// special mode to show message keys and the types of the args that
// are used.
if (argTypes)
Example.Compiler.factory = new ArgTypeCompilerFactory();
if (selectedKeys.size() > 0) { if (selectedKeys.size() > 0) {
Set<Example> examples = getExamples(examplesDir); Set<Example> examples = getExamples(examplesDir);
nextKey: nextKey:
@ -138,7 +146,7 @@ public class RunExamples {
error("Key " + k + ": no examples found"); error("Key " + k + ": no examples found");
} }
} else { } else {
if (selectedExamples.size() == 0) if (selectedExamples.isEmpty())
selectedExamples = getExamples(examplesDir); selectedExamples = getExamples(examplesDir);
} }