Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception handling PoC #45

Merged
merged 14 commits into from
Nov 16, 2017
147 changes: 48 additions & 99 deletions java-vtl-script/src/main/java/no/ssb/vtl/script/VTLScriptEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,42 +20,21 @@
* =========================LICENSE_END==================================
*/

/*-
* #%L
* java-vtl-script
* %%
* Copyright (C) 2016 Hadrien Kohl
* %%
* 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.
* #L%
*/

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import no.ssb.vtl.model.Dataset;
import no.ssb.vtl.connectors.Connector;
import no.ssb.vtl.parser.VTLLexer;
import no.ssb.vtl.parser.VTLParser;
import no.ssb.vtl.script.error.SyntaxException;
import no.ssb.vtl.script.error.WrappedException;
import no.ssb.vtl.script.error.ContextualRuntimeException;
import no.ssb.vtl.script.error.VTLCompileException;
import no.ssb.vtl.script.error.VTLScriptException;
import no.ssb.vtl.script.support.SyntaxErrorListener;
import no.ssb.vtl.script.visitors.AssignmentVisitor;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.misc.ParseCancellationException;

import javax.script.AbstractScriptEngine;
import javax.script.Bindings;
Expand All @@ -67,7 +46,9 @@
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.TimeZone;
import java.util.function.Consumer;

/**
* A VTL {@link ScriptEngine} implementation.
Expand Down Expand Up @@ -134,89 +115,57 @@ public Object eval(String script, ScriptContext context) throws ScriptException
return eval(new StringReader(script), context);
}

@Override
public Object eval(Reader reader, ScriptContext context) throws ScriptException {
/*
Until compilation is done, this is the main method that allows
VTL execution.
Exceptions' hierarchy is as follow:
- ScriptException
- CompilationException
- SyntaxException
- TypeException
- ConstraintException
- ValidationException
- VTLRuntimeException
The WrappedScriptException is used to report errors that are not originating
from the VTL Parser.
*/
try {
VTLLexer lexer = new VTLLexer(new ANTLRInputStream(reader));
VTLParser parser = new VTLParser(new CommonTokenStream(lexer));

lexer.removeErrorListeners();
parser.removeErrorListeners();

BaseErrorListener errorListener = new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object offendingSymbol, int line, int column, String msg, RecognitionException e) {
WrappedException wrappedException;
if (e instanceof WrappedException) {
wrappedException = (WrappedException) e;
} else {
wrappedException = new WrappedException(
msg,
recognizer,
e != null ? e.getInputStream() : null,
e != null ? (ParserRuleContext) e.getCtx() : null,
new SyntaxException(
msg, null, line, column, "VTL-0199"
)
);
}
wrappedException.setLine(line);
wrappedException.setColumn(column);
throw new ParseCancellationException(msg, wrappedException);
}
};
lexer.addErrorListener(errorListener);
parser.addErrorListener(errorListener);
public VTLParser.StartContext parse(Reader reader, Consumer<VTLScriptException> errorConsumer) throws IOException {
// TODO: Change to CharStreams.fromString() when #1977 makes it to release.
VTLLexer lexer = new VTLLexer(new ANTLRInputStream(reader));
VTLParser parser = new VTLParser(new CommonTokenStream(lexer));

VTLParser.StartContext start = parser.start();
lexer.removeErrorListeners();
parser.removeErrorListeners();

// Run loop.
AssignmentVisitor assignmentVisitor = new AssignmentVisitor(context, connectors);
Object last = null;
for (VTLParser.StatementContext statementContext : start.statement()) {
last = assignmentVisitor.visit(statementContext);
}
return last;
BaseErrorListener errorListener = new SyntaxErrorListener(errorConsumer);

} catch (ParseCancellationException pce) {
if (pce.getCause() instanceof WrappedException) {
WrappedException cause = (WrappedException) pce.getCause();
lexer.addErrorListener(errorListener);
parser.addErrorListener(errorListener);

if (cause.getCause() instanceof ScriptException) {
throw ((ScriptException) cause.getCause());
} else {
throw new ScriptException(
pce.getMessage(),
null,
cause.getLineNumber(),
cause.getColumnNumber()
);
}
return parser.start();
}

@Override
public Object eval(Reader reader, ScriptContext context) throws ScriptException {
try {
ArrayList<VTLScriptException> errors = Lists.newArrayList();
VTLParser.StartContext start = parse(reader, errors::add);
Object returnValue = run(start, errors::add, context);
if (!errors.isEmpty()) {
throw new VTLCompileException(errors);
} else {
if (pce.getCause() != null) {
throw new ScriptException(pce.getCause().getMessage());
return returnValue;
}
} catch (IOException | RuntimeException unknownException) {
throw new ScriptException(unknownException);
}
}

/**
* Run loop
*/
private Object run(VTLParser.StartContext start, Consumer<VTLScriptException> errorConsumer, ScriptContext context) throws VTLScriptException {
AssignmentVisitor assignmentVisitor = new AssignmentVisitor(context, connectors);
Object last = null;
for (VTLParser.StatementContext statementContext : start.statement()) {
try {
last = assignmentVisitor.visit(statementContext);
} catch (ContextualRuntimeException cre) {
ParserRuleContext ctx = cre.getContext();
if (cre.getCause() != null) {
errorConsumer.accept(new VTLScriptException((Exception) cre.getCause(), ctx));
} else {
throw new ScriptException(pce.getMessage());
errorConsumer.accept(new VTLScriptException(cre.getMessage(), ctx));
}
}
} catch (IOException | RuntimeException ioe) {
throw new ScriptException(ioe);
}
return last;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package no.ssb.vtl.script.error;

/*
* ========================LICENSE_START=================================
* Java VTL
* %%
* Copyright (C) 2016 - 2017 Hadrien Kohl
* %%
* 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.
* =========================LICENSE_END==================================
*/

import org.antlr.v4.runtime.ParserRuleContext;

import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;

/**
* A {@link RuntimeException} that contains the context in which it happened.
*/
public class ContextualRuntimeException extends RuntimeException {

private final ParserRuleContext context;

public ContextualRuntimeException(ParserRuleContext context) {
this.context = checkNotNull(context);
}

public ContextualRuntimeException(String message, ParserRuleContext context) {
super(message);
this.context = checkNotNull(context);
}

public ContextualRuntimeException(String message, Throwable cause, ParserRuleContext context) {
super(message, cause);
this.context = checkNotNull(context);
}

public ContextualRuntimeException(Throwable cause, ParserRuleContext context) {
super(cause.getMessage(), cause);
this.context = checkNotNull(context);
}

public ContextualRuntimeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, ParserRuleContext context) {
super(message, cause, enableSuppression, writableStackTrace);
this.context = checkNotNull(context);
}

public static void checkArgument(ParserRuleContext context, boolean expression) throws ContextualRuntimeException {
if (!expression) {
throw new ContextualRuntimeException(context);
}
}

public static void checkArgument(ParserRuleContext context, boolean expression, String template, Object... arguments) throws ContextualRuntimeException {
if (!expression) {
throw new ContextualRuntimeException(format(template, arguments), context);
}
}

public ParserRuleContext getContext() {
return context;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@
/**
* Mark the exception as positionable.
*/
@Deprecated
public interface PositionableError {

void setLine(int line);

void setColumn(int column);

int getLineNumber();

int getColumnNumber();

int getStartLine();
int getStopLine();
int getStartColumn();
int getStopColumn();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package no.ssb.vtl.script.error;

/*
* ========================LICENSE_START=================================
* Java VTL
* %%
* Copyright (C) 2016 - 2017 Hadrien Kohl
* %%
* 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.
* =========================LICENSE_END==================================
*/

import com.google.common.collect.Lists;

import javax.script.ScriptException;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;

public class VTLCompileException extends ScriptException {

public List<VTLScriptException> getErrors() {
return errors;
}

private List<VTLScriptException> errors = Lists.newArrayList();

public VTLCompileException(List<VTLScriptException> errors) {
super("compilation errors");
this.errors = checkNotNull(errors);
}

@Override
public String getMessage() {
StringBuilder message = new StringBuilder(super.getMessage());
for (VTLScriptException exception : getErrors()) {
message.append("\n\t - ").append(exception.getMessage());
}
return message.toString();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
/**
* Utility class for error codes.
*/
@Deprecated
public class VTLErrorCodeUtil {

public static String checkVTLCode(String vtlCode, String prefix) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
/**
* Thrown when the VTL execution failed at runtime.
*/
@Deprecated
public class VTLRuntimeException extends RuntimeException implements VTLThrowable {

private static final long serialVersionUID = 7253953869019949964L;
Expand Down
Loading