/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.devtools.j2objc.util;
import com.google.common.collect.Lists;
import com.google.devtools.j2objc.ast.CompilationUnit;
import com.google.devtools.j2objc.ast.TreeNode;
import com.google.devtools.j2objc.ast.TreeUtil;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import javax.tools.DiagnosticListener;
import javax.tools.JavaFileObject;
/**
* Provides convenient static error and warning methods.
*
* @author Tom Ball, Keith Stanger
*/
public class ErrorUtil implements DiagnosticListener<JavaFileObject> {
private static int errorCount = 0;
private static int warningCount = 0;
private static int functionizedMethodCount = 0;
private static PrintStream errorStream = System.err;
private static List<String> errorMessages = Lists.newArrayList();
private static List<String> warningMessages = Lists.newArrayList();
// Captures whether the translator should emit clang style message. Clang style messages
// are particularly useful when the translator is being invoked by Xcode build rules.
// Xcode will be able to pick the file path and line number, hence make it easy to address
// compilation errors from within Xcode.
// Ideally this should be set by a command line switch, but for now we tell that by checking
// the DEVELOPER_DIR environment variable set by Xcode.
private static final boolean CLANG_STYLE_ERROR_MSG = (null != System.getenv("DEVELOPER_DIR"));
private static Pattern pathAndLinePattern = null;
public static void reset() {
errorCount = 0;
warningCount = 0;
errorMessages = Lists.newArrayList();
warningMessages = Lists.newArrayList();
}
public static int errorCount() {
return errorCount;
}
public static int warningCount() {
return warningCount;
}
public static List<String> getErrorMessages() {
return errorMessages;
}
public static List<String> getWarningMessages() {
return warningMessages;
}
/**
* To be called by unit tests. In test mode errors and warnings are not
* printed to System.err.
*/
public static void setTestMode() {
errorStream = new PrintStream(new OutputStream() {
@Override
public void write(int b) {}
});
}
public static String getFullMessage(String tag, String message, boolean clangStyle) {
String fullMessage = null;
if (clangStyle) {
// Try to find the file path and line number, and then insert the tag after that,
// in order to get a message in the following format.
// <file_path>:<line_number>: error: <detailed_message>
if (pathAndLinePattern == null) {
pathAndLinePattern = Pattern.compile(".+?\\.java:\\d+: ");
}
Matcher matcher = pathAndLinePattern.matcher(message);
if (matcher.find()) {
fullMessage = matcher.group(0) + matcher.replaceFirst(tag);
}
// Fall back to default message style if pattern was not matched.
}
if (fullMessage == null) {
fullMessage = tag + message;
}
return fullMessage;
}
public static void parserDiagnostic(Diagnostic<? extends JavaFileObject> diagnostic) {
Kind kind = diagnostic.getKind();
if (kind == Kind.ERROR) {
errorMessages.add(diagnostic.getMessage(null));
errorCount++;
} else if (kind == Kind.MANDATORY_WARNING || kind == Kind.WARNING) {
warningMessages.add(diagnostic.getMessage(null));
warningCount++;
} else {
return;
}
String msg;
if (CLANG_STYLE_ERROR_MSG) {
msg = String.format("error: %s:%d: %s", diagnostic.getSource().getName(),
diagnostic.getLineNumber(), diagnostic.getMessage(null).trim());
} else {
msg = diagnostic.toString().trim();
}
errorStream.println(msg);
}
// TODO(tball): Consider more ways to associate errors with GenerationUnits to aid debugging.
public static void error(String message) {
errorMessages.add(message);
errorStream.println(getFullMessage("error: ", message, CLANG_STYLE_ERROR_MSG));
errorCount++;
}
public static void warning(String message) {
warningMessages.add(message);
errorStream.println(getFullMessage("warning: ", message, CLANG_STYLE_ERROR_MSG));
warningCount++;
}
/**
* Report an error with a specific AST node.
*/
public static void error(TreeNode node, String message) {
error(formatMessage(node, message));
}
/**
* Report a warning with a specific AST node.
*/
public static void warning(TreeNode node, String message) {
warning(formatMessage(node, message));
}
/**
* Report that an internal error happened when translating a specific source.
*/
public static void fatalError(Throwable error, String path) {
StringWriter msg = new StringWriter();
PrintWriter writer = new PrintWriter(msg);
writer.println(String.format("internal error translating \"%s\"", path));
error.printStackTrace(writer);
writer.flush();
error(msg.toString());
}
private static String formatMessage(TreeNode node, String message) {
CompilationUnit unit = TreeUtil.getCompilationUnit(node);
return String.format("%s:%s: %s", unit.getSourceFilePath(), node.getLineNumber(), message);
}
public static void functionizedMethod() {
++functionizedMethodCount;
}
public static int functionizedMethodCount() {
return functionizedMethodCount;
}
@Override
public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
JavaFileObject sourceFile = diagnostic.getSource();
String text = diagnostic.getMessage(null);
String msg = diagnostic.getPosition() != Diagnostic.NOPOS
? String.format("%s:%s: %s", sourceFile.getName(), diagnostic.getLineNumber(), text)
: String.format("%s: %s", sourceFile.getName(), text);
switch (diagnostic.getKind()) {
case ERROR:
error(msg);
break;
case WARNING:
warning(msg);
break;
default:
Logger.getGlobal().info(msg);
}
}
}