Package jfix.util

Source Code of jfix.util.Compiler

/*
    Copyright (C) 2010 maik.jablonski@gmail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package jfix.util;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.tools.ToolProvider;

import org.apache.commons.io.FileUtils;

/**
* A Java-Compiler which can compile Java-Source-Files on the fly and load them
* via an custom ClassLoader at runtime. If a source-file is changed, it is
* recompiled and the class gets reloaded automatically.
*
* - To add an file to the compiler, use {@link #add(File sourceFile)}.
*
* - To create an instance of the compiled class, use
* {@link #newInstance(String classname)}.
*
* A shortcut for add/newInstance is {@link #eval(File)}.
*/
public class Compiler {

  private static final Pattern CLASSNAME_PATTERN = Pattern.compile(
      "public\\s+class\\s+([^\\s]+)", Pattern.DOTALL | Pattern.MULTILINE);

  public static void main(String[] args) {

  }

  private final List<File> sourceFiles = new CopyOnWriteArrayList();
  private ClassLoader runtimeClassLoader;

  public Compiler() {
  }

  /**
   * Add given file to the compiler for compilation.
   */
  public void add(File file) {
    if (!sourceFiles.contains(file)) {
      for (File sourceFile : new ArrayList<File>(sourceFiles)) {
        if (sourceFile.getName().equals(file.getName())) {
          sourceFiles.remove(sourceFile);
          break;
        }
      }
      sourceFiles.add(file);
      reset();
    }
  }

  /**
   * Remove given file from the compiler.
   */
  public void remove(File file) {
    if (sourceFiles.contains(file)) {
      sourceFiles.remove(file);
      reset();
    }
  }

  /**
   * Check if compiler contains given file already.
   */
  public boolean contains(File file) {
    return sourceFiles.contains(file);
  }

  /**
   * Add given file to compiler, compile it and create a new instance
   * directly.
   */
  public Object eval(File file) {
    add(file);
    return newInstance(file.getName().replace(".java", ""));
  }

  /**
   * Create java file for given source, compile it and return a new instance.
   */
  public Object eval(String source) {
    try {
      Matcher matcher = CLASSNAME_PATTERN.matcher(source);
      if (matcher.find()) {
        File sourceFile = new File(Files.createTempDirectory(),
            matcher.group(1) + ".java");
        FileUtils.writeStringToFile(sourceFile, source, "UTF-8");
        return eval(sourceFile);
      } else {
        return new RuntimeException("No class declaration.");
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Return new instance of given classname.
   */
  public Object newInstance(String classname) {
    try {
      return loadClass(classname).newInstance();
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  /**
   * Return class-object for given classname.
   */
  public Class loadClass(String classname) throws ClassNotFoundException {
    if (isSourceFileModified()) {
      compile();
      reset();
    }
    if (runtimeClassLoader == null) {
      reload();
    }
    return runtimeClassLoader.loadClass(classname);
  }

  private boolean isSourceFileModified() {
    for (File sourceFile : sourceFiles) {
      File classFile = new File(sourceFile.getAbsolutePath().replace(
          ".java", ".class"));
      if (classFile.lastModified() < sourceFile.lastModified()) {
        return true;
      }
    }
    return false;
  }

  private void compile() {
    String[] args = new String[2 + sourceFiles.size()];
    args[0] = "-classpath";
    args[1] = buildClasspath();
    for (int i = 0; i < sourceFiles.size(); i++) {
      args[2 + i] = sourceFiles.get(i).getAbsolutePath();
    }
    ByteArrayOutputStream errors = new ByteArrayOutputStream();
    if (ToolProvider.getSystemJavaCompiler().run(null, null, errors, args) != 0) {
      throw new RuntimeException(errors.toString());
    }
  }

  private void reload() {
    List<URL> classLoaderDirectories = new ArrayList();
    for (int i = 0; i < sourceFiles.size(); i++) {
      try {
        URL sourceURL = sourceFiles.get(i).getParentFile().toURI()
            .toURL();
        if (!classLoaderDirectories.contains(sourceURL)) {
          classLoaderDirectories.add(sourceURL);
        }
      } catch (MalformedURLException e) {
        // should not happen
      }
    }
    runtimeClassLoader = new URLClassLoader(
        classLoaderDirectories.toArray(new URL[] {}), Thread
            .currentThread().getContextClassLoader());
  }

  private void reset() {
    runtimeClassLoader = null;
  }

  private String buildClasspath() {
    StringBuilder sb = new StringBuilder();
    for (ClassLoader classloader = Thread.currentThread()
        .getContextClassLoader(); classloader != null; classloader = classloader
        .getParent()) {
      if (classloader instanceof URLClassLoader) {
        for (URL url : ((URLClassLoader) classloader).getURLs()) {
          if (sb.length() > 0) {
            sb.append(File.pathSeparatorChar);
          }
          try {
            // URLDecoder needed to unquote %20
            // see: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4466485
            sb.append(URLDecoder.decode(url.getFile(), "UTF-8"));
          } catch (UnsupportedEncodingException e) {
            // should not happen
          }
        }
      }
    }
    return sb.toString();
  }

}
TOP

Related Classes of jfix.util.Compiler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.