2016-08-09 7 views
1

Я использовал this answer в качестве ссылки для компиляции класса во время выполнения и работает правильно. Итак, теперь мне нужно расширить скомпилированный класс, который фактически не может быть найден.Не удается расширить скомпилированный во время выполнения класс

Я попытался это:

import java.io.File; 
import java.net.URI; 
import java.net.URL; 
import java.net.URLClassLoader; 
import java.util.Arrays; 

import javax.tools.Diagnostic; 
import javax.tools.DiagnosticCollector; 
import javax.tools.JavaCompiler; 
import javax.tools.JavaCompiler.CompilationTask; 
import javax.tools.JavaFileObject; 
import javax.tools.SimpleJavaFileObject; 
import javax.tools.ToolProvider; 

public class CompileSourceInMemory { 

    public static Class<?> compile(String className, String sourceCode, URLClassLoader classLoader) throws Exception { 
     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
     DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); 

     JavaFileObject file = new JavaSourceFromString(className, sourceCode); 

     Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(file); 
     CompilationTask task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits); 

     boolean success = task.call(); 

     for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) { 
      System.out.println(diagnostic.getCode()); 
      System.out.println(diagnostic.getKind()); 
      System.out.println(diagnostic.getPosition()); 
      System.out.println(diagnostic.getStartPosition()); 
      System.out.println(diagnostic.getEndPosition()); 
      System.out.println(diagnostic.getSource()); 
      System.out.println(diagnostic.getMessage(null)); 
     } 

     System.out.println("Success: " + success); 

     if (success) { 
      return Class.forName(className.replace(".", "/"), true, classLoader); 
     } else { 
      throw new Exception("Didn't work!"); 
     } 
    } 

    public static void main(String args[]) throws Exception { 
     URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() }); 
     StringBuilder sourceCode = new StringBuilder(); 
     // sourceCode.append("package br.bla;"); 
     sourceCode.append("public class HelloWorld {"); 
     sourceCode.append(" public static void main(String args[]) {"); 
     sourceCode.append(" System.out.append(\"This is in another java file\");"); 
     sourceCode.append(" }"); 
     sourceCode.append("}"); 
     Class<?> helloWorld = compile("HelloWorld", sourceCode.toString(), classLoader); 

     sourceCode = new StringBuilder(); 
     // sourceCode.append("package br.bla;"); 
     sourceCode.append("public class ExtendedHelloWorld extends HelloWorld {"); 
     sourceCode.append(" public int i = 2;"); 
     sourceCode.append("}"); 

     Class<?> extendedHelloWorld = compile("ExtendedHelloWorld", sourceCode.toString(), classLoader); 
     Object object = extendedHelloWorld.newInstance(); 

     return; 
    } 
} 

class JavaSourceFromString extends SimpleJavaFileObject { 
    final String code; 

    JavaSourceFromString(String name, String code) { 
     super(URI.create("string:///" + name.replace('.', '/') + Kind.SOURCE.extension), Kind.SOURCE); 
     this.code = code; 
    } 

    @Override 
    public CharSequence getCharContent(boolean ignoreEncodingErrors) { 
     return code; 
    } 
} 

И выход был:

Success: true 
compiler.err.cant.resolve 
ERROR 
40 
40 
50 
JavaSourceFromString[string:///ExtendedHelloWorld.java] 
cannot find symbol 
    symbol: class HelloWorld 
Success: false 

Кто-нибудь знает, как собрать свой класс ExtendedHelloWorld?

ответ

2

Компиляция HelloWorld преуспевает, потому что ему нужен только собственный источник. Когда вы пытаетесь скомпилировать ExtendedHelloWorld, он терпит неудачу, потому что для этого требуется собственный источник и источник HelloWorld. Это может быть достигнуто путем хранения каждого класса в HashMap<String, String>, где key - это имя класса, а value - это исходный код класса.

Я бы посоветовал пару изменений вашего кода.

Я бы сдул ваш метод compile и разбил его на два разных метода компиляции. Первый будет использоваться, если вы хотите скомпилировать класс, который не расширяет класс, скомпилированный из памяти. Второй будет использоваться, если вы хотите скомпилировать класс, который расширяет класс, скомпилированный из памяти.

/* 
* Method to compile a class which doesn't extend a class that's been compiled from memory. 
*/ 
public static Class<?> compile(String className, String sourceCode, URLClassLoader classLoader) throws Exception { 
    return compileHelper(className, classLoader, Arrays.asList(new JavaSourceFromString(className, sourceCode))); 
} 

/* 
* Method to compile a class which extends a class that's been compiled from 
* memory. 
* 
* This method takes in the class name, a Set of Map.Entry<String, String>, 
* which contains class names and their sources, and a class loader. This 
* method iterates over the entries in the Set, creates JavaFileObjects from 
* the class names and their sources and adds each JavaFileObject to an 
* ArrayList which will be used in the 'compileHelper' method. 
*/ 
public static Class<?> compile(String className, Set<Map.Entry<String, String>> nameAndSource, URLClassLoader classLoader) throws Exception { 
    List<JavaFileObject> compilationUnits = new ArrayList<>(); 

    for(Entry<String, String> entry : nameAndSource) { 
     compilationUnits.add(new JavaSourceFromString(entry.getKey(), entry.getValue())); 
    } 

    return compileHelper(className, classLoader, compilationUnits); 
} 

Вышеуказанные методы затем вызывают метод-помощник, который фактически компилирует класс (ы). Этот метод очень похож на ваш метод compile, но вывод диагностики был перенесен на отдельный метод, printDiagnostics(diagnostics).

/* 
* Helper method that actually does the compiling. 
*/ 
private static Class<?> compileHelper(String className, URLClassLoader classLoader, Iterable<? extends JavaFileObject> compilationUnits) throws Exception { 
    JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
    DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); 
    CompilationTask task = null; 
    boolean success = false; 

    // debug compilation units section 
    System.out.println("Compiling " + className); 

    System.out.println(" - compilationUnits "); 
    for(JavaFileObject o : compilationUnits) { 
     System.out.println(" + " + o.toString()); 
    } 
    // end debug 

    task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits); 
    success = task.call(); 

    if (success) { 
     System.out.println("Successful compilation of " + className); 
     return Class.forName(className.replace(".", "/"), true, classLoader); 
    } else { 
     System.out.println("Failed while compiling " + className); 
     printDiagnostics(diagnostics); 
     throw new Exception("It didn't work!"); 
    } 
} 

Для того, чтобы использовать методы выше вы должны будете использовать HashMap<String, String> хранить имена классов и исходные коды каждого класса, который вы хотите скомпилировать. Затем, когда вы готовы к компиляции сделать вызов compile проходящий в entrySet() из HashMap, например: compile(className, nameAndSource.entrySet(), classLoader)

Например:

public static void main(String args[]) throws Exception { 
    Map<String, String> nameAndSource = new HashMap<>(); 
    URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() }); 
    String className; 
    StringBuilder sourceCode; 

    // HelloWorld class 
    className = "HelloWorld"; 
    sourceCode = new StringBuilder();   
    sourceCode.append("public class HelloWorld {"); 
    sourceCode.append(" public static void main(String args[]) {"); 
    sourceCode.append(" System.out.append(\"This is in another java file\");"); 
    sourceCode.append(" }"); 
    sourceCode.append("}"); 

    // pass the class name and source code to 'compile'   
    Class<?> helloWorld = compile(className, sourceCode.toString(), classLoader); 

    // add HelloWorld class name and source code to HashMap 
    nameAndSource.put(className, sourceCode.toString()); 

    // ExtendedHelloWorldClass 
    className = "ExtendedHelloWorld"; 
    sourceCode = new StringBuilder(); 
    sourceCode.append("public class ExtendedHelloWorld extends HelloWorld {"); 
    sourceCode.append(" public int num = 2;"); 
    sourceCode.append("}"); 

    // add ExtendedHelloWorld class name and source code to HashMap 
    nameAndSource.put(className, sourceCode.toString()); 

    // here's where we pass in the nameAndSource entrySet() 
    Class<?> extendedHelloWorld = compile(className, nameAndSource.entrySet(), classLoader); 

    return; 
} 

Вот полный исходный код, что Я попытался описать выше:

public class CompileSourceInMemory { 

    /* 
    * Method to compile a class which extends a class that's been compiled from 
    * memory. 
    * 
    * This method takes in the class name, a Set of Map.Entry<String, String>, 
    * which contains class names and their sources, and a class loader. This 
    * method iterates over the entries in the Set, creates JavaFileObjects from 
    * the class names and their sources and adds each JavaFileObject to an 
    * ArrayList which will be used the private compile method. 
    */ 
    public static Class<?> compile(String className, Set<Map.Entry<String, String>> nameAndSource, URLClassLoader classLoader) throws Exception { 
     List<JavaFileObject> compilationUnits = new ArrayList<>(); 

     for (Entry<String, String> entry : nameAndSource) { 
      compilationUnits.add(newJavaSourceFromString(entry.getKey(), entry.getValue())); 
     } 

     return compileHelper(className, classLoader, compilationUnits); 
    } 

    /* 
    * Method to compile a class which doesn't extend a class that's been 
    * compiled from memory. 
    */ 
    public static Class<?> compile(String className, String sourceCode, URLClassLoader classLoader) throws Exception { 
     return compileHelper(className, classLoader, Arrays.asList(new JavaSourceFromString(className, sourceCode))); 
    } 

    /* 
    * Helper method that actually does the compiling. 
    */ 
    private static Class<?> compileHelper(String className, URLClassLoader classLoader, Iterable<? extends JavaFileObject> compilationUnits) throws Exception { 
     JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); 
     DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>(); 
     CompilationTask task = null; 
     boolean success = false; 

     // debug compilation units section 
     System.out.println("Compiling " + className); 

     System.out.println(" - compilationUnits "); 
     for (JavaFileObject o : compilationUnits) { 
      System.out.println(" + " + o.toString()); 
     } 
     // end debug 

     task = compiler.getTask(null, null, diagnostics, null, null, compilationUnits); 
     success = task.call(); 

     if (success) { 
      System.out.println("Successful compilation of " + className); 
      return Class.forName(className.replace(".", "/"), true, classLoader); 
     } else { 
      System.out.println("Failed while compiling " + className); 
      printDiagnostics(diagnostics); 
      throw new Exception("It didn't work!"); 
     } 
    } 

    public static void main(String args[]) throws Exception { 
     Map<String, String> nameAndSource = new HashMap<>(); 
     URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { new File("").toURI().toURL() }); 
     String className; 
     StringBuilder sourceCode; 

     // HelloWorld Class 
     className = "HelloWorld"; 
     sourceCode = new StringBuilder(); 
     sourceCode.append("public class HelloWorld {"); 
     sourceCode.append(" public static void main(String args[]) {"); 
     sourceCode.append(" System.out.append(\"This is in another java file\");"); 
     sourceCode.append(" }"); 
     sourceCode.append("}"); 

     // pass the class name and source code to 'compile' 
     Class<?> helloWorld = compile(className, sourceCode.toString(), classLoader); 

     // add HelloWorld class name and source code to HashMap 
     nameAndSource.put(className, sourceCode.toString()); 

     // ExtendedHelloWorld Class 
     className = "ExtendedHelloWorld"; 
     sourceCode = new StringBuilder(); 
     sourceCode.append("public class ExtendedHelloWorld extends HelloWorld {"); 
     sourceCode.append(" public int num = 2;"); 
     sourceCode.append("}"); 

     // add ExtendedHelloWorld class name and source code to HashMap 
     nameAndSource.put(className, sourceCode.toString()); 

     // pass the nameAndSource entrySet() to 'compile' 
     Class<?> extendedHelloWorld = compile(className, nameAndSource.entrySet(), classLoader); 

     // ExtendedExtendedHelloWorld Class 
     className = "ExtendedExtendedHelloWorld"; 
     sourceCode = new StringBuilder(); 
     sourceCode.append("public class ExtendedExtendedHelloWorld extends ExtendedHelloWorld {"); 
     sourceCode.append(" public void printNum() { System.out.println(num); }"); 
     sourceCode.append("}"); 

     // add ExtendedExtendedHelloWorld class name and source code to HashMap 
     nameAndSource.put(className, sourceCode.toString()); 

     // pass the nameAndSource entrySet() to 'compile' 
     Class<?> extendedExtendedHelloWorld = compile(className, nameAndSource.entrySet(), classLoader); 
     Object eehw = extendedExtendedHelloWorld.newInstance(); 

     return; 
    } 

    private static void printDiagnostics(DiagnosticCollector<JavaFileObject> diagnostics) { 
     StringBuilder sb = new StringBuilder("-- Diagnostics --\n"); 
     for (Diagnostic<?> d : diagnostics.getDiagnostics()) { 
      sb.append(String 
        .format("d.getCode() - %s%nd.getKind() - %s%nd.getPosition() - %d%nd.getStartPosition() - %d%nd.getEndPosition() - %d%nd.getSource() - %s%nd.getMessage(null) - %s%n", 
          d.getCode(), d.getKind().toString(), 
          d.getPosition(), d.getStartPosition(), 
          d.getEndPosition(), d.getSource().toString(), 
          d.getMessage(null))); 
     } 
     System.out.println(sb.append("--").toString()); 
    } 
} 

class JavaSourceFromString extends SimpleJavaFileObject { 
    final String code; 

    JavaSourceFromString(String name, String code) { 
     super(URI.create("string:///" + name.replace('.', '/') 
       + Kind.SOURCE.extension), Kind.SOURCE); 
     this.code = code; 
    } 

    @Override 
    public CharSequence getCharContent(boolean ignoreEncodingErrors) { 
     return code; 
    } 
} 

Вот вывод выполнения кода выше:

Compiling HelloWorld 
- compilationUnits 
    + JavaSourceFromString[string:///HelloWorld.java] 
Successful compilation of HelloWorld 
Compiling ExtendedHelloWorld 
- compilationUnits 
    + JavaSourceFromString[string:///ExtendedHelloWorld.java] 
    + JavaSourceFromString[string:///HelloWorld.java] 
Successful compilation of ExtendedHelloWorld 
Compiling ExtendedExtendedHelloWorld 
- compilationUnits 
    + JavaSourceFromString[string:///ExtendedHelloWorld.java] 
    + JavaSourceFromString[string:///ExtendedExtendedHelloWorld.java] 
    + JavaSourceFromString[string:///HelloWorld.java] 
Successful compilation of ExtendedExtendedHelloWorld 
+0

Он не работает с полностью квалифицированными именами классов, такими как br.bla.ExtendedHelloWorld. – hbelmiro

Смежные вопросы