com.baidu.bjf.remoting.protobuf.utils.compiler.JdkCompiler Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jprotobuf Show documentation
Show all versions of jprotobuf Show documentation
A useful utility library for java programmer using google protobuf.
/*
* Copyright (c) Baidu Inc. All rights reserved.
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.baidu.bjf.remoting.protobuf.utils.compiler;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.Charset;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.tools.DiagnosticCollector;
import javax.tools.FileObject;
import javax.tools.ForwardingJavaFileManager;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileManager;
import javax.tools.JavaFileObject;
import javax.tools.JavaFileObject.Kind;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.baidu.bjf.remoting.protobuf.utils.ClassHelper;
import com.baidu.bjf.remoting.protobuf.utils.OS;
import com.baidu.bjf.remoting.protobuf.utils.StringUtils;
import com.baidu.bjf.remoting.protobuf.utils.ZipUtils;
/**
* JdkCompiler. (SPI, Singleton, ThreadSafe)
*
* @author xiemalin
* @since 1.0.0
*/
public class JdkCompiler extends AbstractCompiler {
/** Logger for this class. */
private static final Logger LOGGER = LoggerFactory.getLogger(JdkCompiler.class.getName());
/** The compiler. */
private final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
/** The class loader. */
private final ClassLoaderImpl classLoader;
/** The java file manager. */
private final JavaFileManagerImpl javaFileManager;
/** The options. */
private volatile List options;
/** The Constant DEFAULT_JDK_VERSION. */
private static final String DEFAULT_JDK_VERSION = "1.8";
private static final String BOOT_INF_CLASSES = "BOOT-INF/classes/";
/** The Constant TEMP_PATH. */
private static final String TEMP_PATH =
System.getProperty("java.io.tmpdir") + File.separator + "JPROTOBUF_CACHE_DIR";
/**
* Instantiates a new jdk compiler.
*
* @param loader the loader
*/
public JdkCompiler(final ClassLoader loader) {
this(loader, DEFAULT_JDK_VERSION);
}
/**
* Instantiates a new jdk compiler.
*
* @param loader the loader
* @param jdkVersion the jdk version
*/
public JdkCompiler(final ClassLoader loader, final String jdkVersion) {
options = new ArrayList();
options.add("-source");
options.add(jdkVersion);
options.add("-target");
options.add(jdkVersion);
// set compiler's classpath to be same as the runtime's
if (compiler == null) {
throw new RuntimeException(
"compiler is null maybe you are on JRE enviroment please change to JDK environment.");
}
DiagnosticCollector diagnosticCollector = new DiagnosticCollector();
StandardJavaFileManager manager =
compiler.getStandardFileManager(diagnosticCollector, null, Charset.forName("utf-8"));
if (loader instanceof URLClassLoader
&& (!loader.getClass().getName().equals("sun.misc.Launcher$AppClassLoader"))) {
try {
URLClassLoader urlClassLoader = (URLClassLoader) loader;
List files = new ArrayList();
boolean isInternalJar = false;
String rootJar = null;
Set fileNames = new HashSet();
for (URL url : urlClassLoader.getURLs()) {
String file = url.getFile();
files.add(new File(file));
if (StringUtils.endsWith(file, "!/")) {
file = StringUtils.removeEnd(file, "!/");
}
if (file.startsWith("file:")) {
file = StringUtils.removeStart(file, "file:");
}
if (file.indexOf("!") != -1) {
// if has internal jar like
// file:/D:/develop/a.jar!/BOOT-INF/lib/spring-boot-starter-1.5.14.RELEASE.jar
isInternalJar = true;
rootJar = StringUtils.substringBefore(file, "!");
if (OS.isFamilyWindows() || OS.isFamilyWin9x()) {
rootJar = StringUtils.removeStart(rootJar, "/");
}
}
File f = new File(file);
fileNames.add(f.getName());
files.add(f);
}
if (isInternalJar && rootJar != null) {
ZipUtils.unZip(new File(rootJar), TEMP_PATH);
listFiles(TEMP_PATH, files, fileNames);
files.add(new File(TEMP_PATH, BOOT_INF_CLASSES));
}
manager.setLocation(StandardLocation.CLASS_PATH, files);
} catch (IOException e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
classLoader = AccessController.doPrivileged(new PrivilegedAction() {
public ClassLoaderImpl run() {
return new ClassLoaderImpl(loader);
}
});
javaFileManager = new JavaFileManagerImpl(manager, classLoader);
}
/*
* (non-Javadoc)
*
* @see com.baidu.bjf.remoting.protobuf.utils.compiler.AbstractCompiler#doCompile(java.lang.String,
* java.lang.String, java.io.OutputStream)
*/
@Override
public synchronized Class> doCompile(String name, String sourceCode, OutputStream os) throws Throwable {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Begin to compile source code: class is '{}'", name);
}
int i = name.lastIndexOf('.');
String packageName = i < 0 ? "" : name.substring(0, i);
String className = i < 0 ? name : name.substring(i + 1);
JavaFileObjectImpl javaFileObject = new JavaFileObjectImpl(className, sourceCode);
javaFileManager.putFileForInput(StandardLocation.SOURCE_PATH, packageName,
className + ClassUtils.JAVA_EXTENSION, javaFileObject);
DiagnosticCollector diagnosticCollector = new DiagnosticCollector();
Boolean result = compiler.getTask(null, javaFileManager, diagnosticCollector, options, null,
Arrays.asList(new JavaFileObject[] { javaFileObject })).call();
if (result == null || !result.booleanValue()) {
throw new IllegalStateException(
"Compilation failed. class: " + name + ", diagnostics: " + diagnosticCollector.getDiagnostics());
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("compile source code done: class is '{}'", name);
LOGGER.debug("loading class '{}'", name);
}
Class> retClass = classLoader.loadClass(name);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("loading class done '{}'", name);
}
if (os != null) {
byte[] bytes = classLoader.loadClassBytes(name);
if (bytes != null) {
os.write(bytes);
os.flush();
}
}
return retClass;
}
/*
* (non-Javadoc)
*
* @see com.baidu.bjf.remoting.protobuf.utils.compiler.Compiler#loadBytes(java.lang.String)
*/
@Override
public byte[] loadBytes(String className) {
byte[] bytes = classLoader.loadClassBytes(className);
return bytes;
}
/**
* The Class ClassLoaderImpl.
*/
private final class ClassLoaderImpl extends ClassLoader {
/** The classes. */
private final Map classes = new HashMap();
/**
* Instantiates a new class loader impl.
*
* @param parentClassLoader the parent class loader
*/
ClassLoaderImpl(final ClassLoader parentClassLoader) {
super(parentClassLoader);
}
/**
* Files.
*
* @return the collection
*/
Collection files() {
return Collections.unmodifiableCollection(classes.values());
}
/**
* Load class bytes.
*
* @param qualifiedClassName the qualified class name
* @return the byte[]
*/
public byte[] loadClassBytes(final String qualifiedClassName) {
JavaFileObject file = classes.get(qualifiedClassName);
if (file != null) {
byte[] bytes = ((JavaFileObjectImpl) file).getByteCode();
return bytes;
}
return null;
}
/*
* (non-Javadoc)
*
* @see java.lang.ClassLoader#findClass(java.lang.String)
*/
@Override
protected Class> findClass(final String qualifiedClassName) throws ClassNotFoundException {
JavaFileObject file = classes.get(qualifiedClassName);
if (file != null) {
byte[] bytes = ((JavaFileObjectImpl) file).getByteCode();
return defineClass(qualifiedClassName, bytes, 0, bytes.length);
}
try {
return ClassHelper.forNameWithCallerClassLoader(qualifiedClassName, getClass());
} catch (ClassNotFoundException nf) {
return super.findClass(qualifiedClassName);
}
}
/**
* Adds the.
*
* @param qualifiedClassName the qualified class name
* @param javaFile the java file
*/
void add(final String qualifiedClassName, final JavaFileObject javaFile) {
classes.put(qualifiedClassName, javaFile);
}
/*
* (non-Javadoc)
*
* @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
*/
@Override
protected synchronized Class> loadClass(final String name, final boolean resolve)
throws ClassNotFoundException {
return super.loadClass(name, resolve);
}
/*
* (non-Javadoc)
*
* @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
*/
@Override
public InputStream getResourceAsStream(final String name) {
if (name.endsWith(ClassUtils.CLASS_EXTENSION)) {
String qualifiedClassName =
name.substring(0, name.length() - ClassUtils.CLASS_EXTENSION.length()).replace('/', '.');
JavaFileObjectImpl file = (JavaFileObjectImpl) classes.get(qualifiedClassName);
if (file != null) {
return new ByteArrayInputStream(file.getByteCode());
}
}
return super.getResourceAsStream(name);
}
}
/**
* The Class JavaFileObjectImpl.
*/
private static final class JavaFileObjectImpl extends SimpleJavaFileObject {
/** The bytecode. */
private ByteArrayOutputStream bytecode;
/** The source. */
private final CharSequence source;
/**
* Instantiates a new java file object impl.
*
* @param baseName the base name
* @param source the source
*/
public JavaFileObjectImpl(final String baseName, final CharSequence source) {
super(ClassUtils.toURI(baseName + ClassUtils.JAVA_EXTENSION), Kind.SOURCE);
this.source = source;
}
/**
* Instantiates a new java file object impl.
*
* @param name the name
* @param kind the kind
*/
JavaFileObjectImpl(final String name, final Kind kind) {
super(ClassUtils.toURI(name), kind);
source = null;
}
/**
* Instantiates a new java file object impl.
*
* @param uri the uri
* @param kind the kind
*/
public JavaFileObjectImpl(URI uri, Kind kind) {
super(uri, kind);
source = null;
}
/*
* (non-Javadoc)
*
* @see javax.tools.SimpleJavaFileObject#getCharContent(boolean)
*/
@Override
public CharSequence getCharContent(final boolean ignoreEncodingErrors) throws UnsupportedOperationException {
if (source == null) {
throw new UnsupportedOperationException("source == null");
}
return source;
}
/*
* (non-Javadoc)
*
* @see javax.tools.SimpleJavaFileObject#openInputStream()
*/
@Override
public InputStream openInputStream() {
return new ByteArrayInputStream(getByteCode());
}
/*
* (non-Javadoc)
*
* @see javax.tools.SimpleJavaFileObject#openOutputStream()
*/
@Override
public OutputStream openOutputStream() {
return bytecode = new ByteArrayOutputStream();
}
/**
* Gets the byte code.
*
* @return the byte code
*/
public byte[] getByteCode() {
if (bytecode == null) {
return null;
}
return bytecode.toByteArray();
}
}
/**
* The Class JavaFileManagerImpl.
*/
private static final class JavaFileManagerImpl extends ForwardingJavaFileManager {
/** The class loader. */
private final ClassLoaderImpl classLoader;
/** The file objects. */
private final Map fileObjects = new HashMap();
/**
* Instantiates a new java file manager impl.
*
* @param fileManager the file manager
* @param classLoader the class loader
*/
public JavaFileManagerImpl(JavaFileManager fileManager, ClassLoaderImpl classLoader) {
super(fileManager);
this.classLoader = classLoader;
}
/*
* (non-Javadoc)
*
* @see javax.tools.ForwardingJavaFileManager#getFileForInput(javax.tools.JavaFileManager.Location,
* java.lang.String, java.lang.String)
*/
@Override
public FileObject getFileForInput(Location location, String packageName, String relativeName)
throws IOException {
FileObject o = fileObjects.get(uri(location, packageName, relativeName));
if (o != null) {
return o;
}
return super.getFileForInput(location, packageName, relativeName);
}
/**
* Put file for input.
*
* @param location the location
* @param packageName the package name
* @param relativeName the relative name
* @param file the file
*/
public void putFileForInput(StandardLocation location, String packageName, String relativeName,
JavaFileObject file) {
fileObjects.put(uri(location, packageName, relativeName), file);
}
/**
* Uri.
*
* @param location the location
* @param packageName the package name
* @param relativeName the relative name
* @return the uri
*/
private URI uri(Location location, String packageName, String relativeName) {
return ClassUtils.toURI(location.getName() + '/' + packageName + '/' + relativeName);
}
/*
* (non-Javadoc)
*
* @see javax.tools.ForwardingJavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location,
* java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
*/
@Override
public JavaFileObject getJavaFileForOutput(Location location, String qualifiedName, Kind kind,
FileObject outputFile) throws IOException {
JavaFileObject file = new JavaFileObjectImpl(qualifiedName, kind);
classLoader.add(qualifiedName, file);
return file;
}
/*
* (non-Javadoc)
*
* @see javax.tools.ForwardingJavaFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
*/
@Override
public ClassLoader getClassLoader(JavaFileManager.Location location) {
return classLoader;
}
/*
* (non-Javadoc)
*
* @see javax.tools.ForwardingJavaFileManager#inferBinaryName(javax.tools.JavaFileManager.Location,
* javax.tools.JavaFileObject)
*/
@Override
public String inferBinaryName(Location loc, JavaFileObject file) {
if (file instanceof JavaFileObjectImpl) {
return file.getName();
}
return super.inferBinaryName(loc, file);
}
/*
* (non-Javadoc)
*
* @see javax.tools.ForwardingJavaFileManager#list(javax.tools.JavaFileManager.Location, java.lang.String,
* java.util.Set, boolean)
*/
@Override
public Iterable list(Location location, String packageName, Set kinds, boolean recurse)
throws IOException {
Iterable result = super.list(location, packageName, kinds, recurse);
ArrayList files = new ArrayList();
if (location == StandardLocation.CLASS_PATH && kinds.contains(JavaFileObject.Kind.CLASS)) {
for (JavaFileObject file : fileObjects.values()) {
if (file.getKind() == Kind.CLASS && file.getName().startsWith(packageName)) {
files.add(file);
}
}
files.addAll(classLoader.files());
} else if (location == StandardLocation.SOURCE_PATH && kinds.contains(JavaFileObject.Kind.SOURCE)) {
for (JavaFileObject file : fileObjects.values()) {
if (file.getKind() == Kind.SOURCE && file.getName().startsWith(packageName)) {
files.add(file);
}
}
}
for (JavaFileObject file : result) {
files.add(file);
}
return files;
}
}
/**
* List URL files.
*
* @param classesPath the classes path
* @param ext the ext
* @param list the list
*/
private static void listFiles(String classesPath, final List list, final Set filters) {
Collection listFiles = FileUtils.listFiles(new File(classesPath), new IOFileFilter() {
@Override
public boolean accept(File dir, String name) {
return filters.contains(name);
}
@Override
public boolean accept(File file) {
return filters.contains(file.getName());
}
}, new IOFileFilter() {
@Override
public boolean accept(File dir, String name) {
return true;
}
@Override
public boolean accept(File file) {
return true;
}
});
if (listFiles != null) {
for (Object f : listFiles) {
try {
File file = (File) f;
list.add(file); // add jar file
} catch (Exception e) {
LOGGER.warn(e.getMessage());
}
}
}
}
}