
com.qwazr.compiler.JavaCompiler Maven / Gradle / Ivy
/**
* Copyright 2014-2016 Emmanuel Keller / QWAZR
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
package com.qwazr.compiler;
import com.qwazr.classloader.ClassLoaderManager;
import com.qwazr.utils.DirectoryWatcher;
import com.qwazr.utils.IOUtils;
import com.qwazr.utils.LockUtils;
import com.qwazr.utils.StringUtils;
import com.qwazr.server.ServerException;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.FileFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.tools.*;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
public class JavaCompiler implements Closeable {
private final static Logger LOGGER = LoggerFactory.getLogger(JavaCompiler.class);
private final ClassLoaderManager classLoaderManager;
private final int javaSourcePrefixSize;
private final File javaSourceDirectory;
private final File javaClassesDirectory;
private final LinkedHashSet classPathSet;
private final String compiledClassPath;
private final LockUtils.ReadWriteLock compilerLock;
private final DirectoryWatcher directorWatcher;
private final ConcurrentHashMap compilableMap;
private final ConcurrentHashMap diagnosticMap;
private JavaCompiler(final ExecutorService executorService, final ClassLoaderManager classLoaderManager,
final File javaSourceDirectory, final File javaClassesDirectory, final LinkedHashSet classPathSet)
throws IOException {
this.classLoaderManager = classLoaderManager;
this.classPathSet = classPathSet;
this.compiledClassPath = classPathSet != null && !classPathSet.isEmpty() ?
StringUtils.join(classPathSet, File.pathSeparatorChar) :
null;
this.compilableMap = new ConcurrentHashMap<>();
this.diagnosticMap = new ConcurrentHashMap<>();
this.javaSourceDirectory = javaSourceDirectory;
String javaSourcePrefix = javaSourceDirectory.getAbsolutePath();
javaSourcePrefixSize =
javaSourcePrefix.endsWith("/") ? javaSourcePrefix.length() : javaSourcePrefix.length() + 1;
this.javaClassesDirectory = javaClassesDirectory;
if (this.javaClassesDirectory != null && !this.javaClassesDirectory.exists())
this.javaClassesDirectory.mkdir();
compilerLock = new LockUtils.ReadWriteLock();
if (javaSourceDirectory.exists() && javaSourceDirectory.isDirectory()) {
compileDirectory(javaSourceDirectory);
directorWatcher = DirectoryWatcher.register(javaSourceDirectory.toPath(), path -> {
try {
compileDirectory(path.toFile());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
executorService.execute(directorWatcher);
} else {
LOGGER.warn("No java source directory: " + javaSourceDirectory.getAbsolutePath());
directorWatcher = null;
}
}
@Override
public void close() {
IOUtils.close(directorWatcher);
}
static JavaCompiler newInstance(final ExecutorService executorService, final ClassLoaderManager classLoaderManager,
final File javaSourceDirectory, final File javaClassesDirectory, final File... classPathDirectories)
throws IOException, URISyntaxException {
Objects.requireNonNull(javaSourceDirectory, "No source directory given (null)");
Objects.requireNonNull(javaClassesDirectory, "No class directory given (null)");
final LinkedHashSet urlList = new LinkedHashSet<>();
urlList.add(javaClassesDirectory.toURI().toURL());
final LinkedHashSet classPath = buildClassPath(classPathDirectories, urlList);
return new JavaCompiler(executorService, classLoaderManager, javaSourceDirectory, javaClassesDirectory,
classPath);
}
private static LinkedHashSet buildClassPath(final File[] classPathArray, final Collection urlCollection)
throws MalformedURLException, URISyntaxException {
final LinkedHashSet classPathes = new LinkedHashSet<>();
final String classPath = System.getProperty("java.class.path");
if (!StringUtils.isEmpty(classPath))
for (String cp : StringUtils.split(classPath, File.pathSeparatorChar))
classPathes.add(cp);
if (classPathArray != null) {
for (File classPathFile : classPathArray) {
if (classPathFile.isDirectory()) {
for (File f : classPathFile.listFiles((FileFilter) FileFileFilter.FILE)) {
classPathes.add(f.getAbsolutePath());
urlCollection.add(f.toURI().toURL());
}
} else if (classPathFile.isFile()) {
classPathes.add(classPathFile.getAbsolutePath());
urlCollection.add(classPathFile.toURI().toURL());
}
}
}
return classPathes;
}
private boolean compile(final javax.tools.JavaCompiler compiler, final Collection javaFiles)
throws IOException {
final DiagnosticCollector diagnostics = new DiagnosticCollector<>();
try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
final Iterable extends JavaFileObject> sourceFileObjects =
fileManager.getJavaFileObjectsFromFiles(javaFiles);
final List options = new ArrayList<>();
if (compiledClassPath != null && !compiledClassPath.isEmpty()) {
options.add("-classpath");
options.add(compiledClassPath);
}
options.add("-d");
options.add(javaClassesDirectory.getAbsolutePath());
options.add("-sourcepath");
options.add(javaSourceDirectory.getAbsolutePath());
javax.tools.JavaCompiler.CompilationTask task =
compiler.getTask(null, fileManager, diagnostics, options, null, sourceFileObjects);
final Date date = new Date();
final boolean compilationSuccess = task.call();
for (File file : javaFiles) {
final URI fileUri = file.toURI();
compilableMap.put(fileUri, date);
diagnosticMap.remove(fileUri);
}
for (final Diagnostic extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
if (diagnostic == null)
continue;
final JavaFileObject source = diagnostic.getSource();
if (source == null)
continue;
final URI uri = source.toUri();
if (uri != null)
diagnosticMap.put(uri, new CompilerStatus.DiagnosticStatus(date, diagnostic));
if (LOGGER.isWarnEnabled())
LOGGER.warn(
String.format("Error on line %d in %s%n%s", diagnostic.getLineNumber(), source.getName(),
diagnostic.getMessage(null)));
}
return compilationSuccess;
}
}
private Collection filterUptodate(final File parentDir, final File[] javaSourceFiles) {
if (javaSourceFiles == null)
return null;
final Collection finalJavaFiles = new ArrayList<>();
if (javaSourceFiles.length == 0)
return finalJavaFiles;
final File parentClassDir =
new File(javaClassesDirectory, parentDir.getAbsolutePath().substring(javaSourcePrefixSize));
for (File javaSourceFile : javaSourceFiles) {
final File classFile =
new File(parentClassDir, FilenameUtils.removeExtension(javaSourceFile.getName()) + ".class");
if (classFile.exists() && classFile.lastModified() > javaSourceFile.lastModified()) {
compilableMap.put(javaSourceFile.toURI(), new Date(classFile.lastModified()));
continue;
}
finalJavaFiles.add(javaSourceFile);
}
return finalJavaFiles;
}
private boolean compileDirectory(javax.tools.JavaCompiler compiler, File sourceDirectory) {
final Collection javaFiles = filterUptodate(sourceDirectory, sourceDirectory.listFiles(javaFileFilter));
if (javaFiles != null && javaFiles.size() > 0) {
if (LOGGER.isInfoEnabled())
LOGGER.info("Compile " + javaFiles.size() + " JAVA file(s) at " + sourceDirectory);
try {
if (!compile(compiler, javaFiles))
return false;
} catch (IOException e) {
throw new ServerException(e);
}
}
for (File dir : sourceDirectory.listFiles((FileFilter) DirectoryFileFilter.INSTANCE))
if (!compileDirectory(compiler, dir))
return false;
return true;
}
private void compileDirectory(File sourceDirectory) throws IOException {
if (sourceDirectory == null)
return;
if (!sourceDirectory.isDirectory())
return;
compilerLock.writeEx(() -> {
javax.tools.JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
Objects.requireNonNull(compiler, "No compiler is available. This feature requires a JDK (not a JRE).");
if (compileDirectory(compiler, sourceDirectory))
classLoaderManager.reload();
});
}
private final JavaFileFilter javaFileFilter = new JavaFileFilter();
private class JavaFileFilter implements FileFilter {
@Override
final public boolean accept(File file) {
if (!file.isFile())
return false;
return file.getName().endsWith(".java");
}
}
CompilerStatus getStatus() {
return new CompilerStatus(compilableMap, diagnosticMap, classPathSet);
}
}