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"?>
<!--
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.
This code is free software; you can redistribute it and/or modify it
@ -331,7 +331,7 @@
executable="${dist.bin.dir}/javac"
srcdir="test/tools/javac/diags"
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=""
classpath="${dist.lib.dir}/javac.jar"
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.
*
* 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 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.
*
* This code is free software; you can redistribute it and/or modify it
@ -168,7 +168,7 @@ class Example implements Comparable<Example> {
try {
run(null, keys, true, verbose);
} catch (IOException e) {
e.printStackTrace();
e.printStackTrace(System.err);
}
return keys;
}
@ -293,10 +293,15 @@ class Example implements Comparable<Example> {
}
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[] rest;
if (opts == null || opts.size() == 0) {
if (opts == null || opts.isEmpty()) {
first = null;
rest = new String[0];
} else {
@ -311,6 +316,16 @@ class Example implements Comparable<Example> {
return new BackdoorCompiler(verbose);
else
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) {

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.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,7 +25,7 @@
* @test
* @bug 6968063
* @summary provide examples of code that generate diagnostics
* @build Example HTMLWriter RunExamples
* @build ArgTypeCompilerFactory Example HTMLWriter RunExamples
* @run main RunExamples
*/
@ -97,6 +97,7 @@ public class RunExamples {
boolean raw = false;
boolean showFiles = false;
boolean verbose = false;
boolean argTypes = false;
String title = null;
for (int i = 0; i < args.length; i++) {
@ -115,6 +116,8 @@ public class RunExamples {
outFile = new File(args[++i]);
else if (arg.equals("-title") && (i + 1) < args.length)
title = args[++i];
else if (arg.equals("-argtypes"))
argTypes = true;
else if (arg.startsWith("-")) {
error("unknown option: " + arg);
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) {
Set<Example> examples = getExamples(examplesDir);
nextKey:
@ -138,7 +146,7 @@ public class RunExamples {
error("Key " + k + ": no examples found");
}
} else {
if (selectedExamples.size() == 0)
if (selectedExamples.isEmpty())
selectedExamples = getExamples(examplesDir);
}