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 ClassFileObject
readClass
(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
-