Class CompileString

java.lang.Object
de.hsh.graja.modules.compile.CompileString

public class CompileString extends Object

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:

iexpected expression value
15
28
311
414
517

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 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 class
      fullyQualifiedClassname - the name of the class
      workspace - path to where to put the temporary source file
      errorLog - out parameter
      Returns:
      the compiled byte code
      Throws:
      IOException - in case of I/O errors when generating / reading files
      ClassNotFoundException - if the source code does not specify a single class