/*******************************************************************************
* Copyright (c) 2007, 2008 Edgar Espina.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
*******************************************************************************/
package org.deved.antlride.core.build;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.antlr.tool.ErrorManager;
import org.deved.antlride.core.model.IGrammar;
import org.deved.antlride.core.model.IImport;
import org.deved.antlride.core.model.IOption;
import org.deved.antlride.core.model.IOptionValue;
import org.deved.antlride.core.model.IRule;
import org.deved.antlride.core.model.ISourceElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.dltk.compiler.problem.ProblemSeverity;
import org.eclipse.dltk.core.ISourceRange;
import org.eclipse.dltk.core.builder.ISourceLineTracker;
import org.eclipse.dltk.utils.TextUtils;
public class AntlrProblemFactory {
/**
* Extract the raw message error reported by ANTLR from the message package
*
*/
private static interface RawMessageStrategy {
String getRawMessage(String[] message);
}
private static class DefaultRawMessageStrategy implements
RawMessageStrategy {
public String getRawMessage(String[] message) {
return message[MESSAGE].replace("\r", "").replace("\n\n", "\n");
}
}
private static interface RelativePathStrategy {
String getRelativePath(AntlrBuildUnit unit);
}
private static class DefaultRelativePathStrategy implements
RelativePathStrategy {
public String getRelativePath(AntlrBuildUnit unit) {
return unit.getFolderPath().toString();
}
}
private static class CannotFindTokenFileRelativePathStrategy implements
RelativePathStrategy {
public String getRelativePath(AntlrBuildUnit unit) {
return unit.getLibraryPath().toString();
}
}
private static interface ShortMessageStrategy {
String getShortMessage(String rawMessage);
}
private static class DefaultShortMessageStrategy implements
ShortMessageStrategy {
private int id;
public DefaultShortMessageStrategy(int id) {
this.id = id;
}
public String getShortMessage(String rawMessage) {
String shortMessage = null;
try {
Matcher matcher = SHORT_MESSAGE_PATTERN.matcher(rawMessage);
// match for .g:d:d:
if (matcher.find()) {
StringBuilder builder = new StringBuilder();
builder.append("(").append(id).append("): ");
builder.append(rawMessage.substring(matcher.end()).trim());
shortMessage = builder.toString();
} else {
// the file name is not reported
// remove the error type prefix
if (rawMessage.startsWith("error")) {
shortMessage = rawMessage.substring("error".length());
} else if (rawMessage.startsWith("warning")) {
shortMessage = rawMessage.substring("warning".length());
} else {
shortMessage = rawMessage;
}
}
} catch (Exception ex) {
// if any problem happen, use the raw message
shortMessage = rawMessage;
}
return shortMessage.trim();
}
}
private static interface ProblemFileStrategy {
IPath getOriginatingFilePath(String rawMessage, AntlrBuildUnit unit);
}
private static class DefaultProblemFileStrategy implements
ProblemFileStrategy {
public IPath getOriginatingFilePath(String rawMessage,
AntlrBuildUnit unit) {
IPath originatingFilePath = unit.getPath();
try {
Matcher matcher = PATH_PATTERN.matcher(rawMessage);
String path1 = originatingFilePath.toString();
if (matcher.find()) {
String path2 = matcher.group();
path2 = path2.replaceAll(":\\d+:\\d+:", "");
if (!path1.equals(path2)) {
originatingFilePath = new Path(path2);
}
} else {
Matcher smm = SHORT_MESSAGE_PATTERN.matcher(rawMessage);
if (smm.find()) {
Matcher mpm = MSG_PREFIX_PATTERN.matcher(rawMessage);
if (mpm.find()) {
int start = mpm.start() + mpm.group().length();
int end = smm.start();
String path2 = rawMessage.substring(start, end)
.trim()
+ ".g";
if (!path1.equals(path2)) {
originatingFilePath = new Path(path2);
if (originatingFilePath.segmentCount() == 1) {
originatingFilePath = unit.getLibraryPath()
.append(originatingFilePath);
}
}
}
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return originatingFilePath;
}
}
private static interface ProblemPostProcessor {
int start();
int end();
int line();
void process(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end);
}
private static class DefaultProblemPostProcessor implements
ProblemPostProcessor {
private final Collection<Character> IGNORED_CHARS = Arrays.asList('{',
'}', '?', ';', '*', '+', ':', '(', ')', '[', ']', '|', '^',
'.', '!');
private int line;
private int start;
private int end;
public final void process(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
this.start = next(source, start);
this.end = prev(source, end) + 1;
if (this.end < this.start) {
// restore the previous offsets
this.start = start;
this.end = end;
if (Character.isWhitespace(source[end])) {
this.end--;
}
}
this.line = line;
doProcess(unit, source, lineTracker, rawMessage, this.line,
this.start, this.end);
}
protected void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
}
private int next(char[] source, int offset) {
int offs = offset;
try {
while (offs < source.length && ignoreChar(source[offs])) {
offs++;
}
return offs;
} catch (Exception ex) {
ex.printStackTrace();
return offset;
}
}
private int prev(char[] source, int offset) {
int offs = offset;
try {
while ((offs >= 0 && offs < source.length)
&& ignoreChar(source[offs])) {
offs--;
}
return offs;
} catch (Exception ex) {
ex.printStackTrace();
return offset;
}
}
private boolean ignoreChar(char ch) {
if (Character.isWhitespace(ch))
return true;
return IGNORED_CHARS.contains(ch);
}
public int start() {
return start;
}
public int end() {
return end;
}
public int line() {
return line;
}
public void setEnd(int end) {
this.end = end;
}
public void setLine(int line) {
this.line = line;
}
public void setStart(int start) {
this.start = start;
}
}
private static class LeftRecursionCyclesPostProcessor extends
DefaultProblemPostProcessor {
@Override
public void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
try {
// The following sets of rules are mutually left-recursive
// [rulename]
int bracketIndex = rawMessage.lastIndexOf('[');
String ruleName = rawMessage.substring(bracketIndex + 1,
rawMessage.indexOf(']', bracketIndex));
IGrammar grammar = unit.getGrammar();
IRule rule = grammar.findRule(ruleName);
setStart(rule.getName().sourceStart());
setEnd(rule.getName().sourceEnd());
setLine(lineTracker.getLineNumberOfOffset(start()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private static class CannotFindTokenFilePostProcessor extends
DefaultProblemPostProcessor {
@Override
public void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
try {
// the *.tokens reference in a parser/tree grammar doesn't
// exists. adjust the offset to the declaration
IGrammar grammar = unit.getGrammar();
IOption tokenVocab = grammar.findOption("tokenVocab");
IOptionValue value = tokenVocab.getValue();
setStart(value.sourceStart());
setEnd(value.sourceEnd());
setLine(lineTracker.getLineNumberOfOffset(start()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private static class FileAndGrammarNameDifferPostProcessor extends
DefaultProblemPostProcessor {
@Override
public void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
try {
IGrammar grammar = unit.getGrammar();
ISourceElement name = grammar.getName();
setStart(name.sourceStart());
setEnd(name.sourceEnd());
setLine(lineTracker.getLineNumberOfOffset(start()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private static class MissingCodeGenPostProcessor extends
DefaultProblemPostProcessor {
@Override
public void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
try {
IGrammar grammar = unit.getGrammar();
IOption language = grammar.findOption("language");
IOptionValue value = language.getValue();
setStart(value.sourceStart());
setEnd(value.sourceEnd());
setLine(lineTracker.getLineNumberOfOffset(start()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private static class NonRegularDecisionPostProcessor extends
DefaultProblemPostProcessor {
private static final String pattern = "[fatal] rule ";
@Override
public void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
try {
int istart = rawMessage.indexOf(pattern) + pattern.length();
int iend = rawMessage.indexOf(" ", istart);
String ruleName = rawMessage.substring(istart, iend);
IGrammar grammar = unit.getGrammar();
IRule rule = grammar.findRule(ruleName);
ISourceElement name = rule.getName();
setStart(name.sourceStart());
setEnd(name.sourceEnd());
setLine(lineTracker.getLineNumberOfOffset(start()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private static class TokenNonDeterminismPostProcessor extends
DefaultProblemPostProcessor {
@Override
public void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
try {
String ruleName = new String(source, start, end - start);
int colonIdx = ruleName.indexOf(':');
if (colonIdx > 0) {
ruleName = ruleName.substring(0, colonIdx);
}
IGrammar grammar = unit.getGrammar();
IRule rule = grammar.findRule(ruleName.trim());
ISourceElement name = rule.getName();
setStart(name.sourceStart());
setEnd(name.sourceEnd());
setLine(lineTracker.getLineNumberOfOffset(start()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
private static class CannotOpenFilePostProcessor extends
DefaultProblemPostProcessor {
private static final String pattern = "cannot find or open file: ";
@Override
public void doProcess(AntlrBuildUnit unit, char[] source,
ISourceLineTracker lineTracker, String rawMessage, int line,
int start, int end) {
try {
int istart = rawMessage.indexOf(pattern) + pattern.length();
int iend = rawMessage.indexOf(';', istart);
if (iend == -1) {
iend = rawMessage.length();
}
String filename = rawMessage.substring(istart, iend).trim()
.replace(".g", "");
IGrammar grammar = unit.getGrammar();
IImport imp = grammar.findImport(filename);
ISourceElement name = imp.getImportedGrammar();
setStart(name.sourceStart());
setEnd(name.sourceEnd());
setLine(lineTracker.getLineNumberOfOffset(start()));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
// private static class NoTokenDefinitionPostProcessor extends
// DefaultProblemPostProcessor {
// @Override
// public void doProcess(AntlrBuildUnit unit, char[] source,
// ISourceLineTracker lineTracker, String rawMessage, int line,
// int start, int end) {
// try {
// //"no lexer rule corresponding to token: <arg>"
// String tokenName = rawMessage.substring(rawMessage.lastIndexOf(':') +
// 1).trim();
// String content = new String(source, 0, source.length);
// setStart( content.indexOf(tokenName, start) );
// setEnd(start() + tokenName.length());
// setLine(lineTracker.getLineNumberOfOffset(start()));
// } catch (Exception ex) {
// ex.printStackTrace();
// }
// }
// }
private static Map<Integer, RawMessageStrategy> rawMessageMap;
private static Map<Integer, RelativePathStrategy> relativePathMap;
private static Map<Integer, ProblemPostProcessor> problemPostProcessors;
private static final int SEVERITY = 1;
private static final int ID = 2;
private static final int LINE = 3;
private static final int COLUMN = 4;
private static final int START = 5;
private static final int END = 6;
private static final int OFF_SET = 7;
private static final int MESSAGE = 8;
private static final Pattern SHORT_MESSAGE_PATTERN = Pattern
.compile("\\.g:\\d+:\\d+:");
private static final Pattern PATH_PATTERN = Pattern
.compile("/.*(/.*)*\\.g:\\d+:\\d+:");
private static final Pattern MSG_PREFIX_PATTERN = Pattern
.compile("(warning|error)\\(\\d+\\):");
private static final Integer DEFAULT_STRATEGY = new Integer(
Integer.MIN_VALUE);
private static Map<Integer, RawMessageStrategy> getRawMessageMap() {
if (rawMessageMap == null) {
rawMessageMap = new HashMap<Integer, RawMessageStrategy>() {
@Override
public RawMessageStrategy get(Object key) {
RawMessageStrategy rawMessageStrategy = super.get(key);
return rawMessageStrategy == null ? super
.get(DEFAULT_STRATEGY) : rawMessageStrategy;
}
private static final long serialVersionUID = 1L;
{
put(DEFAULT_STRATEGY, new DefaultRawMessageStrategy());
}
};
}
return rawMessageMap;
}
private static Map<Integer, ProblemPostProcessor> getProblemPostProcessors() {
if (problemPostProcessors == null) {
problemPostProcessors = new HashMap<Integer, ProblemPostProcessor>() {
@Override
public ProblemPostProcessor get(Object key) {
ProblemPostProcessor strategy = super.get(key);
return strategy == null ? super.get(DEFAULT_STRATEGY)
: strategy;
}
private static final long serialVersionUID = 1L;
{
put(DEFAULT_STRATEGY, new DefaultProblemPostProcessor());
put(ErrorManager.MSG_LEFT_RECURSION_CYCLES,
new LeftRecursionCyclesPostProcessor());
put(ErrorManager.MSG_CANNOT_FIND_TOKENS_FILE,
new CannotFindTokenFilePostProcessor());
put(ErrorManager.MSG_FILE_AND_GRAMMAR_NAME_DIFFER,
new FileAndGrammarNameDifferPostProcessor());
put(ErrorManager.MSG_MISSING_CODE_GEN_TEMPLATES,
new MissingCodeGenPostProcessor());
put(ErrorManager.MSG_NONREGULAR_DECISION,
new NonRegularDecisionPostProcessor());
put(ErrorManager.MSG_TOKEN_NONDETERMINISM,
new TokenNonDeterminismPostProcessor());
put(ErrorManager.MSG_CANNOT_OPEN_FILE,
new CannotOpenFilePostProcessor());
// put(ErrorManager.MSG_NO_TOKEN_DEFINITION,
// new NoTokenDefinitionPostProcessor());
}
};
}
return problemPostProcessors;
}
private static Map<Integer, RelativePathStrategy> getRelativePathMap() {
if (relativePathMap == null) {
relativePathMap = new HashMap<Integer, RelativePathStrategy>() {
@Override
public RelativePathStrategy get(Object key) {
RelativePathStrategy strategy = super.get(key);
return strategy == null ? super.get(DEFAULT_STRATEGY)
: strategy;
}
private static final long serialVersionUID = 1L;
{
put(ErrorManager.MSG_CANNOT_FIND_TOKENS_FILE,
new CannotFindTokenFileRelativePathStrategy());
put(DEFAULT_STRATEGY, new DefaultRelativePathStrategy());
}
};
}
return relativePathMap;
}
public static AntlrProblem create(AntlrBuildUnit unit, String[] message) {
ProblemSeverity severity = ProblemSeverity.values()[Integer.parseInt(message[SEVERITY])];
int id = Integer.parseInt(message[ID]);
int line = Integer.parseInt(message[LINE]);
int column = Integer.parseInt(message[COLUMN]);
int start = Integer.parseInt(message[START]);
int end = Integer.parseInt(message[END]);
int offset = Integer.parseInt(message[OFF_SET]);
// extract & process the raw message form package(message[])
String rawMessage = getRawMessageMap().get(id).getRawMessage(message);
// replace the full location for a relative location
String containerLocation = unit.getAbsoluteFolderPath().toOSString()
+ File.separator;
String relativePath = getRelativePathMap().get(id)
.getRelativePath(unit);
rawMessage = rawMessage.replace(containerLocation, relativePath + "/");
// try to get a short version of the raw message (without the line
// information, absolute file location, etc..)
ShortMessageStrategy shortMessageStrategy = new DefaultShortMessageStrategy(
id);
String shortMessage = shortMessageStrategy.getShortMessage(rawMessage);
char[] source = unit.getContents();
ISourceLineTracker lineTracker = unit.getLineTracker();
IPath filepath = unit.getPath();
IPath originatingFilepath = new DefaultProblemFileStrategy()
.getOriginatingFilePath(rawMessage, unit);
if (!filepath.equals(originatingFilepath)) {
// used for composite grammars
try {
source = getSource(originatingFilepath);
lineTracker = TextUtils.createLineTracker(source);
} catch (CoreException ex) {
ex.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
if (line > 0) {
// adjust positions: start, end and offset
try {
start = column + lineTracker.getLineOffset(line - 1) - 1;
} catch (Exception ex) {
ex.printStackTrace();
}
if (offset == 0) {
try {
if (line == lineTracker.getNumberOfLines()) {
offset = lineTracker.getLength() - 1;
} else {
offset = lineTracker.getLineOffset(line) - 1;
}
if (offset > start) {
String pattern = new String(source, start, offset
- start);
int commentOffset = pattern.indexOf("//");
if (commentOffset == -1) {
commentOffset = pattern.indexOf("/*");
}
if (commentOffset >= 0) {
offset -= (pattern.length() - commentOffset + 1);
}
}
end = offset;
} catch (Exception ex) {
ex.printStackTrace();
}
} else {
end = start + offset;
}
}
String lineWithProblem = getLineWithProblem(start, source, lineTracker);
// handle error where ANTLR doesn't report the problem location
ProblemPostProcessor problemPostProcessor = getProblemPostProcessors()
.get(id);
problemPostProcessor.process(unit, source, lineTracker, rawMessage,
line, start, end);
return new AntlrProblem(originatingFilepath, severity, id,
problemPostProcessor.line() == 0 ? 1 : problemPostProcessor
.line(), column, problemPostProcessor.start(),
problemPostProcessor.end(), rawMessage, shortMessage,
lineWithProblem);
}
private static String getLineWithProblem(int start, char[] source,
ISourceLineTracker lineTracker) {
String lineWithProblem = "";
try {
if (start > 0) {
ISourceRange sourceRange = lineTracker
.getLineInformationOfOffset(start);
if (sourceRange != ISourceLineTracker.NULL_RANGE) {
lineWithProblem = new String(source, sourceRange
.getOffset(), sourceRange.getLength());
}
}
} catch (Exception ex) {
ex.printStackTrace();
}
return lineWithProblem;
}
private static char[] getSource(IPath path) throws CoreException,
IOException {
InputStream in = null;
try {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IFile originatingFile = root.getFile(path);
in = new BufferedInputStream(originatingFile.getContents(true));
StringBuilder buffer = new StringBuilder();
int ch = in.read();
while (ch != -1) {
buffer.append((char) ch);
ch = in.read();
}
char[] source = new char[buffer.length()];
buffer.getChars(0, source.length, source, 0);
return source;
} finally {
if (in != null) {
try {
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}