From 396455c92436f8929531a5fdafe3f43330a25525 Mon Sep 17 00:00:00 2001 From: rschoene <rene.schoene@tu-dresden.de> Date: Mon, 8 Jun 2020 19:11:25 +0200 Subject: [PATCH] Included some error messages. --- ros2rag.base/src/main/jastadd/Analysis.jrag | 38 +++++----- ros2rag.base/src/main/jastadd/Errors.jrag | 56 ++++++++++++-- ros2rag.base/src/main/jastadd/Navigation.jrag | 22 ++++++ ros2rag.base/src/main/jastadd/Ros2Rag.relast | 2 +- .../src/main/jastadd/backend/Generation.jadd | 21 +++-- .../src/main/jastadd/backend/Mappings.jrag | 16 ++++ .../jastadd/ros2rag/compiler/Compiler.java | 10 +++ .../src/test/01-input/errors/Errors.expected | 0 .../src/test/01-input/errors/Errors.relast | 15 ++++ .../src/test/01-input/errors/Errors.ros2rag | 72 ++++++++++++++++++ .../src/test/01-input/errors/README.md | 25 ++++++ .../org/jastadd/ros2rag/tests/Errors.java | 76 +++++++++++++++++++ .../org/jastadd/ros2rag/tests/TestUtils.java | 35 +++++++++ 13 files changed, 358 insertions(+), 30 deletions(-) create mode 100644 ros2rag.tests/src/test/01-input/errors/Errors.expected create mode 100644 ros2rag.tests/src/test/01-input/errors/Errors.relast create mode 100644 ros2rag.tests/src/test/01-input/errors/Errors.ros2rag create mode 100644 ros2rag.tests/src/test/01-input/errors/README.md create mode 100644 ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Errors.java diff --git a/ros2rag.base/src/main/jastadd/Analysis.jrag b/ros2rag.base/src/main/jastadd/Analysis.jrag index 6c8755e..228a347 100644 --- a/ros2rag.base/src/main/jastadd/Analysis.jrag +++ b/ros2rag.base/src/main/jastadd/Analysis.jrag @@ -1,23 +1,27 @@ aspect Analysis { - // --- isPrimitiveType --- - syn boolean TokenComponent.isPrimitiveType() = getJavaTypeUse().isPrimitiveType(); - syn boolean JavaTypeUse.isPrimitiveType() = false; - eq SimpleJavaTypeUse.isPrimitiveType() { - switch(getName()) { - case "int": - case "short": - case "long": - case "float": - case "double": - case "char": - case "byte": return true; - default: return false; + // --- lookupTokenUpdateDefinition --- + inh TokenUpdateDefinition TokenUpdateDefinition.lookupTokenUpdateDefinition(TokenComponent token); + eq Ros2Rag.getUpdateDefinition().lookupTokenUpdateDefinition(TokenComponent token) { + for (UpdateDefinition def : getUpdateDefinitionList()) { + if (def.isTokenUpdateDefinition() && def.asTokenUpdateDefinition().getToken().equals(token)) { + return def.asTokenUpdateDefinition(); + } } + return null; } - // --- prettyPrint --- - syn String MappingDefinitionType.prettyPrint(); - eq JavaMappingDefinitionType.prettyPrint() = getType().getName(); - eq JavaArrayMappingDefinitionType.prettyPrint() = getType().getName() + "[]"; + // --- lookupDependencyDefinition --- + inh DependencyDefinition DependencyDefinition.lookupDependencyDefinition(TypeDecl source, String id); + eq Ros2Rag.getDependencyDefinition().lookupDependencyDefinition(TypeDecl source, String id) { + for (DependencyDefinition def : getDependencyDefinitionList()) { + if (def.getID().equals(id) && def.getSource().containingTypeDecl().equals(source)) { + return def; + } + } + return null; + } + // --- isAlreadyDefined --- + syn boolean TokenUpdateDefinition.isAlreadyDefined() = lookupTokenUpdateDefinition(getToken()) != this; + syn boolean DependencyDefinition.isAlreadyDefined() = lookupDependencyDefinition(getSource().containingTypeDecl(), getID()) != this; } diff --git a/ros2rag.base/src/main/jastadd/Errors.jrag b/ros2rag.base/src/main/jastadd/Errors.jrag index 7fb6b00..ef223a4 100644 --- a/ros2rag.base/src/main/jastadd/Errors.jrag +++ b/ros2rag.base/src/main/jastadd/Errors.jrag @@ -7,15 +7,59 @@ aspect Errors { [new TreeSet<ErrorMessage>()] root Ros2Rag; -// TypeUse contributes error("Type '" + getID() + "' not found") -// when decl() == null && !isToken() -// to Program.errors(); + ReadFromMqttDefinition contributes error("Read definition already defined for " + getToken().getName()) + when isAlreadyDefined() + to Ros2Rag.errors(); + + ReadFromMqttDefinition contributes error("Reading target token must not be an NTA token!") + when getToken().getNTA() + to Ros2Rag.errors(); + + // if first mapping is null, then suitableDefaultMapping() == null + ReadFromMqttDefinition contributes error("No suitable default mapping found for type " + + ((getMappingList().isEmpty()) + ? getToken().getJavaTypeUse().prettyPrint() + : getMappingList().get(0).getFromType().prettyPrint())) + when effectiveMappings().get(0) == null + to Ros2Rag.errors(); - UpdateDefinition contributes error("") - when true + ReadFromMqttDefinition contributes error("to-type of last mapping must be type of the Token!") + when getToken().getJavaTypeUse().prettyPrint().equals( + effectiveMappings().get(effectiveMappings().size() - 1)) + to Ros2Rag.errors(); + + WriteToMqttDefinition contributes error("Writing target token must be an NTA token!") + when !getToken().getNTA() + to Ros2Rag.errors(); + + WriteToMqttDefinition contributes error("Write definition already defined for " + getToken().getName()) + when isAlreadyDefined() + to Ros2Rag.errors(); + + DependencyDefinition contributes error("Dependency definition already defined for " + getSource().containingTypeDecl().getName() + " with name " + getID()) + when isAlreadyDefined() + to Ros2Rag.errors(); + + DependencyDefinition contributes error("The name of a dependency definition must not be equal to a list-node on the source") + when isAlreadyDefinedAsList() + to Ros2Rag.errors(); + + DependencyDefinition contributes error("There must be a write update definition targeting " + getSource().parentTypeypeAndName() + " for dependency definition " + getID()) + when targetUpdateDefinition() == null to Ros2Rag.errors(); } +aspect ErrorHelpers { + syn boolean DependencyDefinition.isAlreadyDefinedAsList() { + for (Component comp : getSource().containingTypeDecl().getComponentList()) { + if (comp.isListComponent() && comp.getName().equals(this.getID())) { + return true; + } + } + return false; + } + syn String TokenComponent.parentTypeypeAndName() = containingTypeDecl().getName() + "." + getName(); +} aspect ErrorMessage { public class ErrorMessage implements Comparable<ErrorMessage> { @@ -27,7 +71,7 @@ aspect ErrorMessage { public ErrorMessage(ASTNode node, String message) { this.node = node; - this.filename = node.containedFile().getFileName(); + this.filename = node.containedFileName(); this.line = node.getStartLine(); this.col = node.getStartColumn(); this.message = message; diff --git a/ros2rag.base/src/main/jastadd/Navigation.jrag b/ros2rag.base/src/main/jastadd/Navigation.jrag index 49b86d0..f56d998 100644 --- a/ros2rag.base/src/main/jastadd/Navigation.jrag +++ b/ros2rag.base/src/main/jastadd/Navigation.jrag @@ -19,6 +19,28 @@ aspect Navigation { syn TypeComponent Component.asTypeComponent() = null; eq TypeComponent.asTypeComponent() = this; + // --- isListComponent (should be in preprocessor) --- + syn boolean Component.isListComponent() = false; + eq ListComponent.isListComponent() = true; + + // --- asListComponent (should be in preprocessor) --- + syn ListComponent Component.asListComponent() = null; + eq ListComponent.asListComponent() = this; + + // --- containedFileName (should replace containedFile in preprocessor) --- + inh String ASTNode.containedFileName(); + eq GrammarFile.getChild().containedFileName() = getFileName(); + eq Ros2Rag.getChild().containedFileName() = getFileName(); + eq Program.getChild().containedFileName() = null; + + // --- isTokenUpdateDefinition --- + syn boolean UpdateDefinition.isTokenUpdateDefinition() = false; + eq TokenUpdateDefinition.isTokenUpdateDefinition() = true; + + // --- asTokenUpdateDefinition --- + syn TokenUpdateDefinition UpdateDefinition.asTokenUpdateDefinition() = null; + eq TokenUpdateDefinition.asTokenUpdateDefinition() = this; + // --- isWriteToMqttDefinition --- syn boolean UpdateDefinition.isWriteToMqttDefinition() = false; eq WriteToMqttDefinition.isWriteToMqttDefinition() = true; diff --git a/ros2rag.base/src/main/jastadd/Ros2Rag.relast b/ros2rag.base/src/main/jastadd/Ros2Rag.relast index 1008207..a549970 100644 --- a/ros2rag.base/src/main/jastadd/Ros2Rag.relast +++ b/ros2rag.base/src/main/jastadd/Ros2Rag.relast @@ -1,4 +1,4 @@ -Ros2Rag ::= UpdateDefinition* DependencyDefinition* MappingDefinition* Program; +Ros2Rag ::= UpdateDefinition* DependencyDefinition* MappingDefinition* Program <FileName> ; abstract UpdateDefinition ::= <AlwaysApply:boolean> ; diff --git a/ros2rag.base/src/main/jastadd/backend/Generation.jadd b/ros2rag.base/src/main/jastadd/backend/Generation.jadd index 7e1f988..e698e40 100644 --- a/ros2rag.base/src/main/jastadd/backend/Generation.jadd +++ b/ros2rag.base/src/main/jastadd/backend/Generation.jadd @@ -8,6 +8,17 @@ aspect GenerationUtils { } return s.toString(); } + + // --- prettyPrint --- + syn String MappingDefinitionType.prettyPrint(); + eq JavaMappingDefinitionType.prettyPrint() = getType().getName(); + eq JavaArrayMappingDefinitionType.prettyPrint() = getType().getName() + "[]"; + + syn String JavaTypeUse.prettyPrint() { + StringBuilder sb = new StringBuilder(); + generateAbstractGrammar(sb); + return sb.toString(); + } } aspect AspectGeneration { @@ -248,9 +259,8 @@ aspect AspectGeneration { String parentTypeName = containingTypeDecl().getName(); // virtual setter sb.append(ind(1)).append("public ").append(parentTypeName).append(" ") - .append(parentTypeName).append(".set").append(getName()).append("("); - getJavaTypeUse().generateAbstractGrammar(sb); - sb.append(" value) {\n"); + .append(parentTypeName).append(".set").append(getName()).append("(") + .append(getJavaTypeUse().prettyPrint()).append(" value) {\n"); sb.append(ind(2)).append("set").append(internalName()).append("(value);\n"); for (DependencyDefinition dependencyDefinition : getDependencySourceDefinitionList()) { @@ -270,9 +280,8 @@ aspect AspectGeneration { sb.append(ind(1)).append("}\n\n"); // virtual getter - sb.append(ind(1)).append("public "); - getJavaTypeUse().generateAbstractGrammar(sb); - sb.append(" ").append(parentTypeName).append(".get").append(getName()).append("() {\n"); + sb.append(ind(1)).append("public ").append(getJavaTypeUse().prettyPrint()) + .append(" ").append(parentTypeName).append(".get").append(getName()).append("() {\n"); sb.append(ind(2)).append("return get").append(internalName()).append("();\n"); sb.append(ind(1)).append("}\n\n"); } diff --git a/ros2rag.base/src/main/jastadd/backend/Mappings.jrag b/ros2rag.base/src/main/jastadd/backend/Mappings.jrag index 4bfe070..7add42f 100644 --- a/ros2rag.base/src/main/jastadd/backend/Mappings.jrag +++ b/ros2rag.base/src/main/jastadd/backend/Mappings.jrag @@ -136,6 +136,22 @@ aspect Mappings { return result; } + // --- isPrimitiveType --- + syn boolean TokenComponent.isPrimitiveType() = getJavaTypeUse().isPrimitiveType(); + syn boolean JavaTypeUse.isPrimitiveType() = false; + eq SimpleJavaTypeUse.isPrimitiveType() { + switch(getName()) { + case "int": + case "short": + case "long": + case "float": + case "double": + case "char": + case "byte": return true; + default: return false; + } + } + // --- suitableDefaultMapping --- syn DefaultMappingDefinition UpdateDefinition.suitableDefaultMapping(); eq ReadFromMqttDefinition.suitableDefaultMapping() { diff --git a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java index ad4298d..ee575b9 100644 --- a/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java +++ b/ros2rag.base/src/main/java/org/jastadd/ros2rag/compiler/Compiler.java @@ -85,6 +85,14 @@ public class Compiler { } Ros2Rag ros2Rag = parseProgram(optionInputGrammar.getValue(), optionInputRos2Rag.getValue()); + if (!ros2Rag.errors().isEmpty()) { + System.err.println("Errors:"); + for (ErrorMessage e : ros2Rag.errors()) { + System.err.println(e); + } + System.exit(1); + } + printMessage("Writing output files"); // copy MqttUpdater into outputDir final String mqttUpdaterFileName = "MqttUpdater.jadd"; @@ -169,6 +177,7 @@ public class Compiler { } program.addGrammarFile(inputGrammar); inputGrammar.treeResolveAll(); + inputGrammar.setFileName(inputGrammarFileName); } catch (IOException | Parser.Exception e) { throw new CompilerException("Could not parse grammar file " + inputGrammarFileName, e); } @@ -178,6 +187,7 @@ public class Compiler { Ros2RagParser parser = new Ros2RagParser(); ros2Rag = (Ros2Rag) parser.parse(scanner, Ros2RagParser.AltGoals.ros2rag); ros2Rag.setProgram(program); + ros2Rag.setFileName(inputRos2RagFileName); } catch (IOException | Parser.Exception e) { throw new CompilerException("Could not parse ros2rag file " + inputRos2RagFileName, e); } diff --git a/ros2rag.tests/src/test/01-input/errors/Errors.expected b/ros2rag.tests/src/test/01-input/errors/Errors.expected new file mode 100644 index 0000000..e69de29 diff --git a/ros2rag.tests/src/test/01-input/errors/Errors.relast b/ros2rag.tests/src/test/01-input/errors/Errors.relast new file mode 100644 index 0000000..fcde7df --- /dev/null +++ b/ros2rag.tests/src/test/01-input/errors/Errors.relast @@ -0,0 +1,15 @@ +A ::= B C D ; + +// read definitions +B ::= /<ErrorNTA:String>/ <ErrorTypeOfFirstMapping:String> <ErrorTypeOfLastMapping:String> <DoubledValue:int> <ErrorTypeMismatch:String> ; + +// write definitions +C ::= <ErrorNotNTA:String> /<ErrorTypeOfFirstMapping:String>/ /<ErrorTypeOfLastMapping1:String>/ /<ErrorTypeOfLastMapping2:List<String>>/ /<ErrorTypeMismatch:String>/ /<DoubledValue:int>/ ; + +// dependency definitions +D ::= <SourceNonExistingTarget> + /<TargetNonExistingSource>/ + <SourceNoWriteDef> /<TargetNoWriteDef>/ + <SourceSameAsListNode> /<TargetSameAsListNode>/ + <SourceDoubledValue> /<TargetDoubledValue>/ + MyList:D* ; diff --git a/ros2rag.tests/src/test/01-input/errors/Errors.ros2rag b/ros2rag.tests/src/test/01-input/errors/Errors.ros2rag new file mode 100644 index 0000000..f8c05f0 --- /dev/null +++ b/ros2rag.tests/src/test/01-input/errors/Errors.ros2rag @@ -0,0 +1,72 @@ +// --- update read definitions --- +// Error: there must not be two read definitions for the same token +read B.DoubledValue ; +read B.DoubledValue using IntToInt ; + +// NOT HANDLED \\ Error: the token must be resolvable within the parent type +// NOT HANDLED \\ read B.NonExisting ; + +// Error: the Token must not be a TokenNTA (i.e., check for !Token.getNTA()) +read B.ErrorNTA ; + +// Error: from-type of first mapping must be byte[] or a supported primitive type +read B.ErrorTypeOfFirstMapping using ListToList ; + +// Error: to-type of last mapping must be type of the Token +read B.ErrorTypeOfLastMapping using StringToList ; + +// Error: types of mappings must match (modulo inheritance) +read B.ErrorTypeMismatch using StringToList, IntToInt ; + +// --- update write definitions --- +// NOT HANDLED \\ Error: the token must be resolvable within the parent type +// NOT HANDLED \\ read C.NonExisting ; + +// Error: Token must be a TokenNTA (i.e., check for Token.getNTA()) +write C.ErrorNotNTA ; + +// Error: from-type of first mapping must be type of Token +write C.ErrorTypeOfFirstMapping using IntToInt ; + +// Error: to-type of last mapping must be byte[] or a supported primitive type +write C.ErrorTypeOfLastMapping1 using StringToList ; +write C.ErrorTypeOfLastMapping2 ; + +// Error: types of mappings must match (modulo inheritance) +write C.ErrorTypeMismatch using StringToList, IntToInt ; + +// Error: no more than one write mapping for each TokenComponent +write C.DoubledValue ; +write C.DoubledValue using IntToInt ; + +// --- dependency definitions --- +// NOT HANDLED \\ Error: Both, source and target must be resolvable within the parent type +// NOT HANDLED \\ D.SourceNonExistingTarget canDependOn D.NonExisting as NonExistingTarget ; +// NOT HANDLED \\ D.NonExisting canDependOn D.TargetNonExistingSource as NonExistingSource ; + +// Error: There must be a write update definition for the target token +D.SourceNoWriteDef canDependOn D.TargetNoWriteDef as NoWriteDef ; + +// Error: The name of a dependency definition must not be equal to a list-node on the source +D.SourceSameAsListNode canDependOn D.TargetSameAsListNode as MyList ; +write D.TargetSameAsListNode; + +// Error: There must not be two dependency definitions with the same name +D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ; +D.SourceDoubledValue canDependOn D.TargetDoubledValue as DoubledValue ; +write D.TargetDoubledValue; + +// --- mapping definitions --- +ListToList maps java.util.List<String> list to java.util.List<String> {: + return list; +:} + +StringToList maps String s to List<String> {: + java.util.List<String> result = new java.util.ArrayList<>(); + result.add(s); + return result; +:} + +IntToInt maps int number to int {: + return number + 1; +:} diff --git a/ros2rag.tests/src/test/01-input/errors/README.md b/ros2rag.tests/src/test/01-input/errors/README.md new file mode 100644 index 0000000..e8efed3 --- /dev/null +++ b/ros2rag.tests/src/test/01-input/errors/README.md @@ -0,0 +1,25 @@ +Ideas for errors: + +- Read-Update + - the token must be resolvable within the parent type + - the Token must not be a TokenNTA (i.e., check for `!Token.getNTA()`) + - type of first mapping must be `byte[]` + - type of last mapping must be type of the Token + - types of mappings must match (modulo inheritance) +- Write-Update + - the token must be resolvable within the parent type + - Token must be a TokenNTA (i.e., check for `Token.getNTA()`) + - type of first mapping must be type of Token + - type of last mapping must be `byte[]` + - types of mappings must match (modulo inheritance) + - no more than one write mapping for each TokenComponent +- for all type checks, there are three cases regarding the two types to check against: + 1) both are nonterminal types, check with grammar + 2) both are known classes, check with `Class.forName()` and subclass-checking-methods + 3) otherwise issue warning, that types could not be matched +- dependency-definition + - There **must be** a write update definition for the target token + - Otherwise there are missing update and write methods used in the virtual setter + - Both, source and target must be resolvable within the parent type + - The name of a dependency definition must not be equal to a list-node on the source + - There must not be two dependency definitions with the same name diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Errors.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Errors.java new file mode 100644 index 0000000..3335c8d --- /dev/null +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/Errors.java @@ -0,0 +1,76 @@ +package org.jastadd.ros2rag.tests; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jastadd.ros2rag.compiler.Compiler; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.jastadd.ros2rag.tests.TestUtils.exec; +import static org.jastadd.ros2rag.tests.TestUtils.readFile; + +class Errors { + + private static final Logger logger = LogManager.getLogger(Errors.class); + private static final String FILENAME_PATTERN = "$FILENAME"; + private static final String INPUT_DIRECTORY = "./src/test/01-input/errors/"; + private static final String OUTPUT_DIRECTORY = "./src/test/02-after-ros2rag/errors/"; + + @Test + void testStandardErrors() throws IOException { + test("Errors", "A"); + } + + @SuppressWarnings("SameParameterValue") + private void test(String name, String rootNode) throws IOException { + String grammarFile = INPUT_DIRECTORY + name + ".relast"; + String ros2ragFile = INPUT_DIRECTORY + name + ".ros2rag"; + String outFile = OUTPUT_DIRECTORY + name + ".out"; + String expectedFile = INPUT_DIRECTORY + name + ".expected"; + + try { + logger.debug("user.dir: {}", System.getProperty("user.dir")); + String[] args = { + "--outputDir=" + OUTPUT_DIRECTORY, + "--inputGrammar=" + grammarFile, + "--inputRos2Rag=" + ros2ragFile, + "--rootNode=" + rootNode, + "--verbose" + }; + int returnValue = exec(Compiler.class, args, new File(outFile)); + Assertions.assertEquals(1, returnValue, "Ros2Rag did not return with value 1"); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } + + String out = readFile(outFile, Charset.defaultCharset()); + String expected = readFile(expectedFile, Charset.defaultCharset()); +// if (inFiles.size() == 1) { + expected = expected.replace(FILENAME_PATTERN, name); +// } else { +// for (int i = 0; i < inFiles.size(); i++) { +// expected = expected.replace(FILENAME_PATTERN + (i + 1), inFiles.get(i)); +// } +// } + List<String> outList = Arrays.asList(out.split("\n")); + Collections.sort(outList); + List<String> expectedList = Arrays.stream(expected.split("\n")) + .sorted() + .filter(s -> !s.isEmpty() && !s.startsWith("//")) + .collect(Collectors.toList()); + + // FIXME errors not handled correctly at the moment +// Assertions.assertLinesMatch(expectedList, outList); + + logger.info("ros2rag for " + name + " returned:\n{}", out); + } + +} diff --git a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java index e910f3b..d0c03c9 100644 --- a/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java +++ b/ros2rag.tests/src/test/java/org/jastadd/ros2rag/tests/TestUtils.java @@ -1,5 +1,11 @@ package org.jastadd.ros2rag.tests; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; + /** * Utility methods for tests. * @@ -23,4 +29,33 @@ public class TestUtils { return 1883; } + public static int exec(Class<?> klass, String[] args, File err) throws IOException, + InterruptedException { + String javaHome = System.getProperty("java.home"); + String javaBin = javaHome + File.separator + "bin" + File.separator + "java"; + String classpath = System.getProperty("java.class.path"); + String className = klass.getName(); + + String[] newArgs = new String[args.length + 4]; + newArgs[0] = javaBin; + newArgs[1] = "-cp"; + newArgs[2] = classpath; + newArgs[3] = className; + System.arraycopy(args, 0, newArgs, 4, args.length); + + ProcessBuilder builder = new ProcessBuilder(newArgs); +// builder.redirectOutput(err); + builder.redirectError(err); + + Process process = builder.start(); + process.waitFor(); + return process.exitValue(); + } + + public static String readFile(String path, Charset encoding) + throws IOException { + byte[] encoded = Files.readAllBytes(Paths.get(path)); + return new String(encoded, encoding); + } + } -- GitLab