Class 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= ReflectionSupport.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 Detail

      • CompileString

        public CompileString()
    • Method Detail

      • 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