Class CompileString
This class can be used by a JUnit based assignment grader to compile some program generated source code and load the byte code into the running JVM.
Example assignment
Consider the following example assignment.
Create a text document 'expression.txt' (UTF8 encoding, no BOM) and write an expression, that generates the following sequence of values when evaluated for some loop variable i:
i | expected expression value |
|---|---|
| 1 | 5 |
| 2 | 8 |
| 3 | 11 |
| 4 | 14 |
| 5 | 17 |
Grader code
We can provide a short program text and insert the student submitted expression
in the text. Then we can use the class CompileString in order to compile
that little program. CompileString is not used directly, but we use the convenience
method de.hsh.graja.graderapi.GraderContext#readClassIntoSubmissionProtectionDomain(String, String, Path, StringBuilder)
that will call CompileString internally.
Here is the program code. You can find that example in SampleGrajaAssignments as assignment dom.expression
@RunWith(GrajaRunner.class)
public class Grader {
@InjectContext public static GrajaContext ctx;
/**
* submission file name
*/
public static final String SRC_FILENAME= "expression.txt";
/**
* class name of the generated class
*/
public static final String CLASS_NAME= "ExpressionTester";
/**
* This is the main test.
*/
@Test
public void shouldWork() {
String expected= "5 8 11 14 17 ";
String observed= getResultFor(loadSubmission());
org.junit.Assert.assertTrue("Your expression returns unexpected values. Expected:<"+expected+">. Observed:<"+observed+">.", expected.equals(observed));
}
/**
* Read submitted text file and extract the expression from it.
* @return expression as a string
*/
private String loadSubmission() {
String solution= null;
String srcFilename= null;
try {
srcFilename= getCurrentWorkspacePath().resolve(SRC_FILENAME).toString();
String observed= FileUtils.readUTF8FileCompletely(srcFilename) + "\n";
Scanner scanner= new Scanner(observed);
while (scanner.hasNextLine()) {
solution= scanner.nextLine();
if (solution.length() > 0 && solution.charAt(0) == '') { // strip BOM
solution= solution.substring(1);
}
if (solution.trim().length() != 0) break; // skip empty lines
}
scanner.close();
} catch (IOException e) {
Assert.fail("Cannot read file "+srcFilename);
}
return solution;
}
/**
* Execute a short program that calls the <code>expr</code> inside a loop
* and collects the results.
* @param expr the expression
* @return a space separated sequence of values after expression evaluation
*/
private String getResultFor(String expr) {
String result= null;
try {
String code= getExpressionRunnerSourceCode(expr);
StringBuilder errorLog= new StringBuilder();
Class<?> expressionRunnerClass= ctx.readClassIntoSubmissionProtectionDomain(code, CLASS_NAME, getCurrentWorkspacePath(), errorLog);
if (expressionRunnerClass == null) {
SequenceComment sequence= new SequenceComment();
sequence.addItem(new ParagraphComment(InlineComment.format(Level.SEVERE, Audience.BOTH, "Cannot evaluate expression: '%s'%n", expr)));
sequence.addItem(new VerbatimComment(Level.SEVERE, Audience.BOTH, errorLog.toString()));
throw new CommentAssertionError(sequence);
}
result= Support.invokeStatic(expressionRunnerClass, String.class, "run");
} catch (IOException | ClassNotFoundException e) {
fail("Cannot evaluate expression '"+expr+"'");
}
return result;
}
/**
* Create sourcecode.
* @param expr the expression
* @return source code
*/
private static String getExpressionRunnerSourceCode(String expr) {
return
"public class "+CLASS_NAME+" { \n"+
" public static String run() { \n"+
" StringBuilder sb= new StringBuilder(); \n"+
" int value; \n"+
" for (int i=1; i<=5; i++) { \n"+
" value= (int)("+expr+"); \n"+
" sb.append(value).append(\" \"); \n"+
" } \n"+
" return(sb.toString()); \n"+
" } \n"+
"} \n";
}
}
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic ClassFileObjectreadClass(String code, String fullyQualifiedClassname, Path workspace, List<String> classPath, StringBuilder errorLog) This method compiles code and loads the resulting bytecode.
-
Constructor Details
-
CompileString
public CompileString()
-
-
Method Details
-
readClass
public static ClassFileObject readClass(String code, String fullyQualifiedClassname, Path workspace, List<String> classPath, StringBuilder errorLog) throws IOException, ClassNotFoundException This method compiles code and loads the resulting bytecode. A client must add the loaded class afterwards to the submission class loader.- Parameters:
code- program source code that must specify a single classfullyQualifiedClassname- the name of the classworkspace- path to where to put the temporary source fileerrorLog- out parameter- Returns:
- the compiled byte code
- Throws:
IOException- in case of I/O errors when generating / reading filesClassNotFoundException- if the source code does not specify a single class
-