initial commit
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import org.springframework.cloud.function.compiler.java.CompilationFailedException;
|
||||
import org.springframework.cloud.function.compiler.java.CompilationMessage;
|
||||
import org.springframework.cloud.function.compiler.java.CompilationResult;
|
||||
import org.springframework.cloud.function.compiler.java.RuntimeJavaCompiler;
|
||||
|
||||
/**
|
||||
* @author Andy Clement
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class FunctionCompiler {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(FunctionCompiler.class);
|
||||
|
||||
// Newlines in the property are escaped
|
||||
private static final String NEWLINE_ESCAPE = Matcher.quoteReplacement("\\n");
|
||||
|
||||
// Individual double-quote characters are represented by two double quotes in the DSL
|
||||
private static final String DOUBLE_DOUBLE_QUOTE = Matcher.quoteReplacement("\"\"");
|
||||
|
||||
private final static String PACKAGE = "org.springframework.cloud.function.compiler";
|
||||
|
||||
private final static String MAIN_COMPILED_CLASS_NAME = PACKAGE + ".GeneratedFunctionFactory";
|
||||
|
||||
/**
|
||||
* The user supplied code snippet is inserted into the template and then the result is compiled
|
||||
*/
|
||||
private static String SOURCE_CODE_TEMPLATE =
|
||||
"package " + PACKAGE + ";\n" +
|
||||
"import java.util.*;\n" + // Helpful to include this
|
||||
"import java.util.function.*;\n" +
|
||||
"import reactor.core.publisher.Flux;\n" +
|
||||
"public class GeneratedFunctionFactory implements FunctionFactory {\n" +
|
||||
" public Function<Flux<Object>, Flux<Object>> getFunction() {\n" +
|
||||
" %s\n" +
|
||||
" }\n" +
|
||||
"}\n";
|
||||
|
||||
private final RuntimeJavaCompiler compiler = new RuntimeJavaCompiler();
|
||||
|
||||
/**
|
||||
* Produce a Function instance by:<ul>
|
||||
* <li>Decoding the code String to process any newlines/double-double-quotes
|
||||
* <li>Insert the code into the source code template for a class
|
||||
* <li>Compiling the class using the JDK provided Java Compiler
|
||||
* <li>Loading the compiled class
|
||||
* <li>Invoking a well known method on the factory class to produce a Function instance
|
||||
* <li>Returning that instance.
|
||||
* </ul>
|
||||
*
|
||||
* @return a Function instance
|
||||
*/
|
||||
public <T, R> Function<T, R> compile(String code) {
|
||||
logger.info("Initial code property value :'{}'", code);
|
||||
code = decode(code);
|
||||
if (code.startsWith("\"") && code.endsWith("\"")) {
|
||||
code = code.substring(1,code.length()-1);
|
||||
}
|
||||
if (!code.startsWith("return ") && !code.endsWith(";")) {
|
||||
code = "return " + code + ";";
|
||||
}
|
||||
logger.info("Processed code property value :\n{}\n", code);
|
||||
CompilationResult compilationResult = buildAndCompileSourceCode(code);
|
||||
if (compilationResult.wasSuccessful()) {
|
||||
List<Class<?>> clazzes = compilationResult.getCompiledClasses();
|
||||
logger.info("Compilation resulted in #{} classes", clazzes.size());
|
||||
for (Class<?> clazz: clazzes) {
|
||||
if (clazz.getName().equals(MAIN_COMPILED_CLASS_NAME)) {
|
||||
try {
|
||||
FunctionFactory functionFactory = (FunctionFactory) clazz.newInstance();
|
||||
return functionFactory.getFunction();
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Unexpected problem during retrieval of Function from compiled class", e);
|
||||
}
|
||||
}
|
||||
System.out.println(clazz.getName());
|
||||
}
|
||||
logger.error("Failed to find the expected compiled class");
|
||||
}
|
||||
List<CompilationMessage> compilationMessages = compilationResult.getCompilationMessages();
|
||||
throw new CompilationFailedException(compilationMessages);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the source for and then compile and load a class that embodies
|
||||
* the supplied methodBody. The methodBody is inserted into a class template that
|
||||
* returns a <tt>Function<Flux<Object>,Flux<Object>></tt>.
|
||||
* This method can return more than one class if the method body includes local class
|
||||
* declarations. An example methodBody would be <tt>return input -> input.buffer(5).map(list->list.get(0));</tt>.
|
||||
*
|
||||
* @param methodBody the source code for a method that should return a
|
||||
* <tt>Function<Flux<Object>,Flux<Object>></tt>
|
||||
* @return the list of Classes produced by compiling and then loading the snippet of code
|
||||
*/
|
||||
private CompilationResult buildAndCompileSourceCode(String methodBody) {
|
||||
String sourceCode = makeSourceClassDefinition(methodBody);
|
||||
return compiler.compile(MAIN_COMPILED_CLASS_NAME, sourceCode);
|
||||
}
|
||||
|
||||
private static String decode(String input) {
|
||||
return input.replaceAll(NEWLINE_ESCAPE, "\n").replaceAll(DOUBLE_DOUBLE_QUOTE, "\"");
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a full source code definition for a class by applying the specified method body
|
||||
* to the Reactive template.
|
||||
*
|
||||
* @param methodBody the code to insert into the Reactive source class template
|
||||
* @return a complete Java Class definition
|
||||
*/
|
||||
public static String makeSourceClassDefinition(String methodBody) {
|
||||
return String.format(SOURCE_CODE_TEMPLATE, methodBody);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public interface FunctionFactory {
|
||||
|
||||
<T, R> Function<T, R> getFunction();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
* Common superclass for iterables that need to handle closing when finished
|
||||
* with and that need to handle possible constraints on the values that
|
||||
* are iterated over.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public abstract class CloseableFilterableJavaFileObjectIterable implements Iterable<JavaFileObject> {
|
||||
|
||||
// private final static Logger logger = LoggerFactory.getLogger(CloseableFilterableJavaFileObjectIterable.class);
|
||||
|
||||
private final static boolean BOOT_PACKAGING_AWARE = true;
|
||||
private final static String BOOT_PACKAGING_PREFIX_FOR_CLASSES = "BOOT-INF/classes/";
|
||||
|
||||
// If set specifies the package the iterator consumer is interested in. Only
|
||||
// return results in this package.
|
||||
private String packageNameFilter;
|
||||
|
||||
// Indicates whether the consumer of the iterator wants to see classes
|
||||
// that are in subpackages of those matching the filter.
|
||||
private boolean includeSubpackages;
|
||||
|
||||
public CloseableFilterableJavaFileObjectIterable(String packageNameFilter, boolean includeSubpackages) {
|
||||
if (packageNameFilter!=null && packageNameFilter.contains(File.separator)) {
|
||||
throw new IllegalArgumentException("Package name filters should use dots to separate components: "+packageNameFilter);
|
||||
}
|
||||
this.packageNameFilter = packageNameFilter==null?null:packageNameFilter.replace('.', File.separatorChar) + "/";
|
||||
this.includeSubpackages = includeSubpackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by subclasses to check values against any specified constraints.
|
||||
*
|
||||
* @param name the name to check against the criteria
|
||||
* @return true if the name is a valid iterator result based on the specified criteria
|
||||
*/
|
||||
protected boolean accept(String name) {
|
||||
// logger.debug("checking {} against constraints packageNameFilter={} includeSubpackages={}",name,packageNameFilter,includeSubpackages);
|
||||
if (!name.endsWith(".class")) {
|
||||
return false;
|
||||
}
|
||||
if (packageNameFilter == null) {
|
||||
return true;
|
||||
}
|
||||
boolean accept;
|
||||
if (includeSubpackages == true) {
|
||||
accept = name.startsWith(packageNameFilter);
|
||||
if (!accept && BOOT_PACKAGING_AWARE) {
|
||||
accept = name.startsWith(BOOT_PACKAGING_PREFIX_FOR_CLASSES) &&
|
||||
name.indexOf(packageNameFilter)==BOOT_PACKAGING_PREFIX_FOR_CLASSES.length();
|
||||
}
|
||||
} else {
|
||||
accept = name.startsWith(packageNameFilter) && name.indexOf("/",packageNameFilter.length())==-1;
|
||||
if (!accept && BOOT_PACKAGING_AWARE) {
|
||||
accept = name.startsWith(BOOT_PACKAGING_PREFIX_FOR_CLASSES) &&
|
||||
name.indexOf(packageNameFilter)==BOOT_PACKAGING_PREFIX_FOR_CLASSES.length() &&
|
||||
name.indexOf("/",BOOT_PACKAGING_PREFIX_FOR_CLASSES.length()+packageNameFilter.length())==-1;
|
||||
}
|
||||
}
|
||||
return accept;
|
||||
}
|
||||
|
||||
abstract void close();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Mark Fisher
|
||||
*/
|
||||
public class CompilationFailedException extends RuntimeException {
|
||||
|
||||
public CompilationFailedException(List<CompilationMessage> messages) {
|
||||
super(consolidateMessages(messages));
|
||||
}
|
||||
|
||||
private static String consolidateMessages(List<CompilationMessage> messages) {
|
||||
if (messages == null || messages.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (CompilationMessage message : messages) {
|
||||
sb.append(message.toString());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
/**
|
||||
* Encapsulate information produced during compilation. A message may be an error
|
||||
* or something less serious (warning/informational). The <tt>toString()</tt> method
|
||||
* will produce a formatted error include source context indicating the precise
|
||||
* location of the problem.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompilationMessage {
|
||||
|
||||
private Kind kind;
|
||||
private String message;
|
||||
private String sourceCode;
|
||||
private int startPosition;
|
||||
private int endPosition;
|
||||
|
||||
enum Kind {
|
||||
ERROR, OTHER
|
||||
};
|
||||
|
||||
public CompilationMessage(Kind kind, String message, String sourceCode, int startPosition, int endPosition) {
|
||||
this.kind = kind;
|
||||
this.message = message;
|
||||
this.sourceCode = sourceCode;
|
||||
this.startPosition = startPosition;
|
||||
this.endPosition = endPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the type of message
|
||||
*/
|
||||
public Kind getKind() {
|
||||
return this.kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the message text
|
||||
*/
|
||||
public String getMessage() {
|
||||
return this.message;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the source code for the file associated with the message
|
||||
*/
|
||||
public String getSourceCode() {
|
||||
return this.sourceCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return offset from start of source file where the error begins
|
||||
*/
|
||||
public int getStartPosition() {
|
||||
return this.startPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return offset from start of source file where the error ends
|
||||
*/
|
||||
public int getEndPosition() {
|
||||
return this.endPosition;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("==========\n");
|
||||
if (sourceCode != null) { // Cannot include source context if no source available
|
||||
int[] lineStartEnd = getLineStartEnd(startPosition);
|
||||
s.append(sourceCode.substring(lineStartEnd[0], lineStartEnd[1])).append("\n");
|
||||
int col = lineStartEnd[0];
|
||||
// When inserting the whitespace, ensure tabs in the source line are respected
|
||||
while ((col) < startPosition) {
|
||||
s.append(sourceCode.charAt(col++)=='\t'?"\t":" ");
|
||||
}
|
||||
// Want at least one ^
|
||||
s.append("^");
|
||||
col++;
|
||||
while ((col++) < endPosition) {
|
||||
s.append("^");
|
||||
}
|
||||
s.append("\n");
|
||||
}
|
||||
s.append(kind).append(":").append(message).append("\n");
|
||||
s.append("==========\n");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* For a given position in the source code this method returns a pair of int
|
||||
* that indicate the start and end of the line within the source code that
|
||||
* contain the position.
|
||||
*
|
||||
* @param searchPos the position of interest in the source code
|
||||
* @return an int array of length 2 containing the start and end positions of the line
|
||||
*/
|
||||
private int[] getLineStartEnd(int searchPos) {
|
||||
int previousPos = -1;
|
||||
int pos = 0;
|
||||
do {
|
||||
pos = sourceCode.indexOf('\n', previousPos + 1);
|
||||
if (searchPos < pos) {
|
||||
return new int[] { previousPos + 1, pos };
|
||||
}
|
||||
previousPos = pos;
|
||||
} while (pos != -1);
|
||||
return new int[] { previousPos + 1, sourceCode.length() };
|
||||
}
|
||||
// TODO test coverage for first line/last line situations
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager.Location;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
|
||||
/**
|
||||
* During compilation instances of this class will collect up the output files from the compilation process.
|
||||
* Any kind of file is collected upon but access is only currently provided to retrieve classes produced
|
||||
* during compilation. Annotation processors that run may create other kinds of artifact.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompilationOutputCollector {
|
||||
|
||||
private List<InMemoryJavaFileObject> outputFiles = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Retrieve compiled classes that have been collected since this collector
|
||||
* was built. Due to annotation processing it is possible other source files
|
||||
* or metadata files may be produced during compilation - those are not included
|
||||
* in the returned list.
|
||||
*
|
||||
* @return list of compiled classes
|
||||
*/
|
||||
public List<CompiledClassDefinition> getCompiledClasses() {
|
||||
List<CompiledClassDefinition> compiledClassDefinitions = new ArrayList<>();
|
||||
for (InMemoryJavaFileObject outputFile : outputFiles) {
|
||||
if (outputFile.getKind() == Kind.CLASS) {
|
||||
CompiledClassDefinition compiledClassDefinition = new CompiledClassDefinition(outputFile.getName(),
|
||||
outputFile.getBytes());
|
||||
compiledClassDefinitions.add(compiledClassDefinition);
|
||||
}
|
||||
}
|
||||
return compiledClassDefinitions;
|
||||
}
|
||||
|
||||
public InMemoryJavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) {
|
||||
InMemoryJavaFileObject jfo = InMemoryJavaFileObject.getJavaFileObject(location, className, kind, sibling);
|
||||
outputFiles.add(jfo);
|
||||
return jfo;
|
||||
}
|
||||
|
||||
public InMemoryJavaFileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) {
|
||||
InMemoryJavaFileObject ojfo = InMemoryJavaFileObject.getFileObject(location, packageName, relativeName, sibling);
|
||||
outputFiles.add(ojfo);
|
||||
return ojfo;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holder for the results of compilation. If compilation was successful the set
|
||||
* of classes that resulted from compilation will be available. If compilation
|
||||
* was not successful the error messages should provide information about why.
|
||||
* Note that compilation may succeed and yet there will still be informational or
|
||||
* warning messages collected.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompilationResult {
|
||||
|
||||
private boolean successfulCompilation;
|
||||
|
||||
List<CompilationMessage> compilationMessages = new ArrayList<>();
|
||||
|
||||
List<Class<?>> compiledClasses = new ArrayList<>();
|
||||
|
||||
public CompilationResult(boolean successfulCompilation) {
|
||||
this.successfulCompilation = successfulCompilation;
|
||||
}
|
||||
|
||||
public boolean wasSuccessful() {
|
||||
return successfulCompilation;
|
||||
}
|
||||
|
||||
public List<Class<?>> getCompiledClasses() {
|
||||
return compiledClasses;
|
||||
}
|
||||
|
||||
public List<CompilationMessage> getCompilationMessages() {
|
||||
return Collections.unmodifiableList(compilationMessages);
|
||||
}
|
||||
|
||||
public void recordCompilationMessage(CompilationMessage compilationMessage) {
|
||||
this.compilationMessages.add(compilationMessage);
|
||||
}
|
||||
|
||||
public void setCompiledClasses(List<Class<?>> compiledClasses) {
|
||||
this.compiledClasses = compiledClasses;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append("Compilation result: #classes="+compiledClasses.size()+" #messages="+compilationMessages.size()+"\n");
|
||||
s.append("Compiled classes:\n").append(compiledClasses).append("\n");
|
||||
s.append("Compilation messages:\n").append(compilationMessages).append("\n");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Encapsulates a name with the bytes for its class definition.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class CompiledClassDefinition {
|
||||
|
||||
private byte[] bytes;
|
||||
private String filename;
|
||||
private String classname;
|
||||
|
||||
public CompiledClassDefinition(String filename, byte[] bytes) {
|
||||
this.filename = filename;
|
||||
this.bytes = bytes;
|
||||
this.classname = filename;
|
||||
if (classname.startsWith(File.separator)) {
|
||||
classname = classname.substring(1);
|
||||
}
|
||||
classname = classname.replace(File.separatorChar, '.').substring(0, classname.length()-6);//strip off .class
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "CompiledClassDefinition(name=" + getName() + ",#bytes=" + getBytes().length + ")";
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return this.classname;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
* A JavaFileObject that represents a file in a directory.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class DirEntryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private File file;
|
||||
private File basedir;
|
||||
|
||||
public DirEntryJavaFileObject(File basedir, File file) {
|
||||
this.basedir = basedir;
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
return file.toURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the path of the file relative to the base directory, for example: a/b/c/D.class
|
||||
*/
|
||||
@Override
|
||||
public String getName() {
|
||||
String basedirPath = basedir.getPath();
|
||||
String filePath = file.getPath();
|
||||
return filePath.substring(basedirPath.length()+1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
throw new IllegalStateException("Only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("openReader() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
throw new IllegalStateException("only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return file.lastModified();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false; // This object is for read only access to a class
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Kind.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
if (kind != Kind.CLASS) {
|
||||
return false;
|
||||
}
|
||||
String name = getName();
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
return name.substring(lastSlash + 1).equals(simpleName + ".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return file.getName().hashCode()*37+basedir.getName().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof DirEntryJavaFileObject)) {
|
||||
return false;
|
||||
}
|
||||
DirEntryJavaFileObject that = (DirEntryJavaFileObject)obj;
|
||||
return (basedir.getName().equals(that.basedir.getName())) && (file.getName().equals(that.file.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
/**
|
||||
* Walks a directory hierarchy from some base directory discovering files.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class DirEnumeration implements Enumeration<File> {
|
||||
|
||||
// private final static Logger logger = LoggerFactory.getLogger(DirEnumeration.class);
|
||||
|
||||
// The starting point
|
||||
private File basedir;
|
||||
|
||||
// Candidates collected so far
|
||||
private List<File> filesToReturn;
|
||||
|
||||
// Places still to explore for candidates
|
||||
private List<File> directoriesToExplore;
|
||||
|
||||
public DirEnumeration(File basedir) {
|
||||
this.basedir = basedir;
|
||||
}
|
||||
|
||||
private void computeValue() {
|
||||
if (filesToReturn == null) { // Indicates we haven't started yet
|
||||
filesToReturn = new ArrayList<>();
|
||||
directoriesToExplore = new ArrayList<>();
|
||||
visitDirectory(basedir);
|
||||
}
|
||||
if (filesToReturn.size() == 0) {
|
||||
while (filesToReturn.size() == 0 && directoriesToExplore.size() != 0) {
|
||||
File nextDir = directoriesToExplore.get(0);
|
||||
directoriesToExplore.remove(0);
|
||||
visitDirectory(nextDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
computeValue();
|
||||
return filesToReturn.size() != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File nextElement() {
|
||||
computeValue();
|
||||
if (filesToReturn.size()==0) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
File toReturn = filesToReturn.get(0);
|
||||
filesToReturn.remove(0);
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private void visitDirectory(File dir) {
|
||||
File[] files = dir.listFiles();
|
||||
if (files != null) {
|
||||
for (File file: files) {
|
||||
if (file.isDirectory()) {
|
||||
directoriesToExplore.add(file);
|
||||
} else {
|
||||
filesToReturn.add(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
// logger.debug("after visiting {} filesToReturn=#{} dirsToExplore=#{}",dir,filesToReturn.size(), directoriesToExplore.size());
|
||||
}
|
||||
|
||||
public File getDirectory() {
|
||||
return basedir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the relative path of this file to the base directory that the directory enumeration was
|
||||
* started for.
|
||||
* @param file a file discovered returned by this enumeration
|
||||
* @return the relative path of the file (for example: a/b/c/D.class)
|
||||
*/
|
||||
public String getName(File file) {
|
||||
String basedirPath = basedir.getPath();
|
||||
String filePath = file.getPath();
|
||||
if (!filePath.startsWith(basedirPath)) {
|
||||
throw new IllegalStateException("The file '"+filePath+"' is not nested below the base directory '"+basedirPath+"'");
|
||||
}
|
||||
return filePath.substring(basedirPath.length()+1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import org.apache.commons.collections.IteratorUtils;
|
||||
|
||||
/**
|
||||
* Simple iterable that can be used to return an iterator over no values.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
class EmptyIterable extends CloseableFilterableJavaFileObjectIterable {
|
||||
|
||||
static EmptyIterable instance = new EmptyIterable();
|
||||
|
||||
private EmptyIterable() {
|
||||
super(null,false);
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Iterator<JavaFileObject> iterator() {
|
||||
return IteratorUtils.emptyIterator();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.CharArrayWriter;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager.Location;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A JavaFileObject that represents a source artifact created for compilation or an output
|
||||
* artifact producing during compilation (a .class file or some other thing if an annotation
|
||||
* processor has run). In order to be clear what it is being used for there are static factory
|
||||
* methods that ask for specific types of file.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class InMemoryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private final static Logger logger = LoggerFactory.getLogger(InMemoryJavaFileObject.class);
|
||||
|
||||
private Location location;
|
||||
private String packageName;
|
||||
private String relativeName;
|
||||
private FileObject sibling;
|
||||
private String className;
|
||||
private Kind kind;
|
||||
|
||||
private byte[] content = null;
|
||||
private long lastModifiedTime = 0;
|
||||
private URI uri = null;
|
||||
|
||||
private InMemoryJavaFileObject() {}
|
||||
|
||||
public static InMemoryJavaFileObject getFileObject(Location location, String packageName, String relativeName, FileObject sibling) {
|
||||
InMemoryJavaFileObject retval = new InMemoryJavaFileObject();
|
||||
retval.kind = Kind.OTHER;
|
||||
retval.location = location;
|
||||
retval.packageName = packageName;
|
||||
retval.relativeName = relativeName;
|
||||
retval.sibling = sibling;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static InMemoryJavaFileObject getJavaFileObject(Location location, String className, Kind kind, FileObject sibling) {
|
||||
InMemoryJavaFileObject retval = new InMemoryJavaFileObject();
|
||||
retval.location = location;
|
||||
retval.className = className;
|
||||
retval.kind = kind;
|
||||
retval.sibling = sibling;
|
||||
return retval;
|
||||
}
|
||||
|
||||
public static InMemoryJavaFileObject getSourceJavaFileObject(String className, String content) {
|
||||
InMemoryJavaFileObject retval = new InMemoryJavaFileObject();
|
||||
retval.location = StandardLocation.SOURCE_PATH;
|
||||
retval.className = className;
|
||||
retval.kind = Kind.SOURCE;
|
||||
retval.content = content.getBytes();
|
||||
return retval;
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return content;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "OutputJavaFileObject: Location="+location+",className="+className+",kind="+kind+",relativeName="+relativeName+",sibling="+sibling+",packageName="+packageName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
// These memory based output files 'pretend' to be relative to the file system root
|
||||
if (uri == null) {
|
||||
String name = null;
|
||||
if (className != null) {
|
||||
name = className.replace('.', '/');
|
||||
} else if (packageName !=null && packageName.length()!=0) {
|
||||
name = packageName.replace('.', '/')+'/'+relativeName;
|
||||
} else {
|
||||
name = relativeName;
|
||||
}
|
||||
|
||||
String uriString = null;
|
||||
try {
|
||||
uriString = "file:/"+name+kind.extension;
|
||||
uri = new URI(uriString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Unexpected URISyntaxException for string '" + uriString + "'", e);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return toUri().getPath();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
if (content == null) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
logger.debug("opening input stream for {}",getName());
|
||||
return new ByteArrayInputStream(content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
logger.debug("opening output stream for {}",getName());
|
||||
return new ByteArrayOutputStream() {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
super.close();
|
||||
lastModifiedTime = System.currentTimeMillis();
|
||||
content = this.toByteArray();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
return new InputStreamReader(openInputStream(), Charset.defaultCharset());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
if (kind!=Kind.SOURCE) {
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on file object: " + getName());
|
||||
}
|
||||
// Not yet supporting encodings
|
||||
return (content==null?null:new String(content));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
// Let's not enforce this restriction right now
|
||||
// if (kind == Kind.CLASS) {
|
||||
// throw new UnsupportedOperationException("openWriter() not supported on file object: " + getName());
|
||||
// }
|
||||
return new CharArrayWriter() {
|
||||
@Override
|
||||
public void close() {
|
||||
lastModifiedTime = System.currentTimeMillis();
|
||||
content = new String(toCharArray()).getBytes(); // Ignoring encoding...
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return lastModifiedTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
String baseName = simpleName + kind.extension;
|
||||
return kind.equals(getKind())
|
||||
&& (baseName.equals(toUri().getPath())
|
||||
|| toUri().getPath().endsWith("/" + baseName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Stack;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Iterable that will produce an iterator that returns classes found
|
||||
* on a specified classpath that meet specified criteria. For jars it finds, the
|
||||
* iterator will go into nested jars - this handles the situation with a
|
||||
* spring boot uberjar.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class IterableClasspath extends CloseableFilterableJavaFileObjectIterable {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(IterableClasspath.class);
|
||||
|
||||
private static final String BOOT_PACKAGING_PREFIX_FOR_LIBRARIES = "BOOT-INF/lib/";
|
||||
|
||||
private List<File> classpathEntries = new ArrayList<>();
|
||||
|
||||
private List<ZipFile> openArchives = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* @param classpath a classpath of jars/directories
|
||||
* @param packageNameFilter an optional package name if choosing to filter (e.g. com.example)
|
||||
* @param includeSubpackages if true, include results in subpackages of the specified package filter
|
||||
*/
|
||||
IterableClasspath(String classpath, String packageNameFilter, boolean includeSubpackages) {
|
||||
super(packageNameFilter, includeSubpackages);
|
||||
StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator);
|
||||
while (tokenizer.hasMoreElements()) {
|
||||
String nextEntry = tokenizer.nextToken();
|
||||
File f = new File(nextEntry);
|
||||
if (f.exists()) {
|
||||
classpathEntries.add(f);
|
||||
} else {
|
||||
logger.debug("path element does not exist {}",f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (ZipFile openArchive : openArchives) {
|
||||
try {
|
||||
openArchive.close();
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Unexpected error closing archive {}",openArchive,ioe);
|
||||
}
|
||||
}
|
||||
openArchives.clear();
|
||||
}
|
||||
|
||||
public Iterator<JavaFileObject> iterator() {
|
||||
return new ClasspathEntriesIterator();
|
||||
}
|
||||
|
||||
class ClasspathEntriesIterator implements Iterator<JavaFileObject> {
|
||||
private int currentClasspathEntriesIndex = 0;
|
||||
|
||||
// Either a directory or an archive will be open at any one time
|
||||
private File openDirectory = null;
|
||||
private DirEnumeration openDirectoryEnumeration = null;
|
||||
|
||||
private ZipFile openArchive = null;
|
||||
private File openFile = null;
|
||||
private ZipEntry nestedZip = null;
|
||||
private Stack<Enumeration<? extends ZipEntry>> openArchiveEnumeration = null;
|
||||
|
||||
private JavaFileObject nextEntry = null;
|
||||
|
||||
private void findNext() {
|
||||
if (nextEntry == null) {
|
||||
try {
|
||||
while (openArchive!=null || openDirectory!=null || currentClasspathEntriesIndex < classpathEntries.size()) {
|
||||
if (openArchive == null && openDirectory == null) {
|
||||
// Open the next item
|
||||
File nextFile = classpathEntries.get(currentClasspathEntriesIndex);
|
||||
if (nextFile.isDirectory()) {
|
||||
openDirectory = nextFile;
|
||||
openDirectoryEnumeration = new DirEnumeration(nextFile);
|
||||
} else {
|
||||
openFile = nextFile;
|
||||
openArchive = new ZipFile(nextFile);
|
||||
openArchives.add(openArchive);
|
||||
openArchiveEnumeration = new Stack<Enumeration<? extends ZipEntry>>();
|
||||
openArchiveEnumeration.push(openArchive.entries());
|
||||
}
|
||||
currentClasspathEntriesIndex++;
|
||||
}
|
||||
if (openArchiveEnumeration != null) {
|
||||
while (!openArchiveEnumeration.isEmpty()) {
|
||||
while (openArchiveEnumeration.peek().hasMoreElements()) {
|
||||
ZipEntry entry = openArchiveEnumeration.peek().nextElement();
|
||||
String entryName = entry.getName();
|
||||
if (accept(entryName)) {
|
||||
if (nestedZip!=null) {
|
||||
nextEntry = new NestedZipEntryJavaFileObject(openFile, openArchive,nestedZip, entry);
|
||||
} else {
|
||||
nextEntry = new ZipEntryJavaFileObject(openFile, openArchive, entry);
|
||||
}
|
||||
return;
|
||||
} else if (nestedZip == null && entryName.startsWith(BOOT_PACKAGING_PREFIX_FOR_LIBRARIES) && entryName.endsWith(".jar")) {
|
||||
// nested jar in uber jar
|
||||
logger.debug("opening nested archive {}",entry.getName());
|
||||
ZipInputStream zis = new ZipInputStream(openArchive.getInputStream(entry));
|
||||
// nextEntry = new NestedZipEntryJavaFileObject(openArchive.firstElement(),openArchive.peek(),entry);
|
||||
Enumeration<? extends ZipEntry> nestedZipEnumerator = new ZipEnumerator(zis);
|
||||
nestedZip = entry;
|
||||
openArchiveEnumeration.push(nestedZipEnumerator);
|
||||
}
|
||||
}
|
||||
openArchiveEnumeration.pop();
|
||||
if (nestedZip ==null) { openArchive = null; openFile = null; }
|
||||
else nestedZip = null;
|
||||
}
|
||||
openArchiveEnumeration = null;
|
||||
openArchive = null;
|
||||
openFile = null;
|
||||
} else if (openDirectoryEnumeration != null) {
|
||||
while (openDirectoryEnumeration.hasMoreElements()) {
|
||||
File entry = openDirectoryEnumeration.nextElement();
|
||||
String name = openDirectoryEnumeration.getName(entry);
|
||||
if (accept(name)) {
|
||||
nextEntry = new DirEntryJavaFileObject(openDirectoryEnumeration.getDirectory(), entry);
|
||||
return;
|
||||
}
|
||||
}
|
||||
openDirectoryEnumeration = null;
|
||||
openDirectory = null;
|
||||
}
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Unexpected error whilst processing classpath entries",ioe);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
findNext();
|
||||
return nextEntry != null;
|
||||
}
|
||||
|
||||
public JavaFileObject next() {
|
||||
findNext();
|
||||
if (nextEntry == null) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
JavaFileObject retval = nextEntry;
|
||||
nextEntry = null;
|
||||
return retval;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class ZipEnumerator implements Enumeration<ZipEntry>{
|
||||
|
||||
private ZipInputStream zis;
|
||||
private ZipEntry nextEntry = null;
|
||||
|
||||
public ZipEnumerator(ZipInputStream zis) {
|
||||
this.zis = zis;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasMoreElements() {
|
||||
try {
|
||||
nextEntry = zis.getNextEntry();
|
||||
} catch (IOException ioe) {
|
||||
nextEntry=null;
|
||||
}
|
||||
return nextEntry!=null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ZipEntry nextElement() {
|
||||
ZipEntry retval = nextEntry;
|
||||
nextEntry = null;
|
||||
return retval;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.tools.FileObject;
|
||||
import javax.tools.JavaFileManager;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.JavaFileObject.Kind;
|
||||
import javax.tools.StandardLocation;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A file manager that serves source code from in memory and ensures output results are kept in memory
|
||||
* rather than being flushed out to disk. The JavaFileManager is also used as a lookup mechanism
|
||||
* for resolving types.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class MemoryBasedJavaFileManager implements JavaFileManager {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(MemoryBasedJavaFileManager.class);
|
||||
|
||||
private CompilationOutputCollector outputCollector;
|
||||
|
||||
private List<CloseableFilterableJavaFileObjectIterable> toClose = new ArrayList<>();
|
||||
|
||||
public MemoryBasedJavaFileManager() {
|
||||
outputCollector = new CompilationOutputCollector();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int isSupportedOption(String option) {
|
||||
logger.debug("isSupportedOption({})",option);
|
||||
return -1; // Not yet supporting options
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader getClassLoader(Location location) {
|
||||
// Do not simply return the context classloader as it may get closed and then
|
||||
// be unusable for loading any further classes
|
||||
logger.debug("getClassLoader({})",location);
|
||||
return null; // Do not currently need to load plugins
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse)
|
||||
throws IOException {
|
||||
logger.debug("list({},{},{},{})",location,packageName,kinds,recurse);
|
||||
CloseableFilterableJavaFileObjectIterable resultIterable = null;
|
||||
if (location == StandardLocation.PLATFORM_CLASS_PATH && (kinds==null || kinds.contains(Kind.CLASS))) {
|
||||
String sunBootClassPath = System.getProperty("sun.boot.class.path");
|
||||
logger.debug("Creating iterable for boot class path: {}",sunBootClassPath);
|
||||
resultIterable = new IterableClasspath(sunBootClassPath, packageName, recurse);
|
||||
toClose.add(resultIterable);
|
||||
} else if (location == StandardLocation.CLASS_PATH && (kinds==null || kinds.contains(Kind.CLASS))) {
|
||||
String javaClassPath = System.getProperty("java.class.path");
|
||||
logger.debug("Creating iterable for class path: {}",javaClassPath);
|
||||
resultIterable = new IterableClasspath(javaClassPath, packageName, recurse);
|
||||
toClose.add(resultIterable);
|
||||
} else if (location == StandardLocation.SOURCE_PATH) {
|
||||
// There are no 'extra sources'
|
||||
resultIterable = EmptyIterable.instance;
|
||||
} else {
|
||||
// Nothing to list
|
||||
resultIterable = EmptyIterable.instance;
|
||||
}
|
||||
return resultIterable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasLocation(Location location) {
|
||||
logger.debug("hasLocation({})",location);
|
||||
return (location == StandardLocation.SOURCE_PATH ||
|
||||
location == StandardLocation.CLASS_PATH ||
|
||||
location == StandardLocation.PLATFORM_CLASS_PATH);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String inferBinaryName(Location location, JavaFileObject file) {
|
||||
if (location == StandardLocation.SOURCE_PATH) {
|
||||
return null;
|
||||
}
|
||||
// Kind of ignoring location here... assuming we want basically the FQ type name
|
||||
// Example value from getName(): javax/validation/bootstrap/GenericBootstrap.class
|
||||
String classname = file.getName().replace('/', '.');
|
||||
return classname.substring(0, classname.lastIndexOf(".class"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSameFile(FileObject a, FileObject b) {
|
||||
logger.debug("isSameFile({},{})",a,b);
|
||||
return a.equals(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleOption(String current, Iterator<String> remaining) {
|
||||
logger.debug("handleOption({},{})",current,remaining);
|
||||
return false; // This file manager does not manage any options
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
|
||||
logger.debug("getJavaFileForInput({},{},{})",location,className,kind);
|
||||
throw new IllegalStateException("Not expected to be used in this context");
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)
|
||||
throws IOException {
|
||||
logger.debug("getJavaFileForOutput({},{},{},{})",location,className,kind,sibling);
|
||||
// Example parameters: CLASS_OUTPUT, Foo, CLASS, StringBasedJavaSourceFileObject[string:///a/b/c/Foo.java]
|
||||
return outputCollector.getJavaFileForOutput(location, className, kind, sibling);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
|
||||
logger.debug("getFileForInput({},{},{})",location,packageName,relativeName);
|
||||
throw new IllegalStateException("Not expected to be used in this context");
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling)
|
||||
throws IOException {
|
||||
logger.debug("getFileForOutput({},{},{},{})",location,packageName,relativeName,sibling);
|
||||
// This can be called when the annotation config processor runs
|
||||
// Example parameters: CLASS_OUTPUT, , META-INF/spring-configuration-metadata.json, null
|
||||
return outputCollector.getFileForOutput(location, packageName, relativeName, sibling);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
for (CloseableFilterableJavaFileObjectIterable closeable: toClose) {
|
||||
closeable.close();
|
||||
}
|
||||
}
|
||||
|
||||
public List<CompiledClassDefinition> getCompiledClasses() {
|
||||
return outputCollector.getCompiledClasses();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
/**
|
||||
* Represents an element inside in zip which is itself inside a zip. These objects are
|
||||
* not initially created with the content of the file they represent,
|
||||
* only enough information to find that content because many will
|
||||
* typically be created but only few will be opened.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class NestedZipEntryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private File outerFile;
|
||||
private ZipFile outerZipFile;
|
||||
private ZipEntry innerZipFile;
|
||||
private ZipEntry innerZipFileEntry;
|
||||
|
||||
private URI uri;
|
||||
|
||||
public NestedZipEntryJavaFileObject(File outerFile, ZipFile outerZipFile, ZipEntry innerZipFile, ZipEntry innerZipFileEntry) {
|
||||
this.outerFile = outerFile;
|
||||
this.outerZipFile = outerZipFile;
|
||||
this.innerZipFile = innerZipFile;
|
||||
this.innerZipFileEntry = innerZipFileEntry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return innerZipFileEntry.getName(); // Example: a/b/C.class
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
if (uri == null) {
|
||||
String uriString = null;
|
||||
try {
|
||||
uriString = "zip:"+outerFile.getAbsolutePath()+"!"+innerZipFile.getName()+"!"+innerZipFileEntry.getName();
|
||||
uri = new URI(uriString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Unexpected URISyntaxException for string '"+uriString+"'",e);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
// Find the inner zip file inside the outer zip file, then
|
||||
// find the relevant entry, then return the stream.
|
||||
InputStream innerZipFileInputStream = this.outerZipFile.getInputStream(innerZipFile);
|
||||
ZipInputStream innerZipInputStream = new ZipInputStream(innerZipFileInputStream);
|
||||
ZipEntry nextEntry = innerZipInputStream.getNextEntry();
|
||||
while (nextEntry != null) {
|
||||
if (nextEntry.getName().equals(innerZipFileEntry.getName())) {
|
||||
return innerZipInputStream;
|
||||
}
|
||||
nextEntry = innerZipInputStream.getNextEntry();
|
||||
}
|
||||
throw new IllegalStateException("Unable to locate nested zip entry "+innerZipFileEntry.getName()+" in zip "+innerZipFile.getName()+" inside zip "+outerZipFile.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return innerZipFileEntry.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
// The filtering before this object was created ensure it is only used for classes
|
||||
return Kind.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false; // Cannot delete entries inside nested zips
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
throw new IllegalStateException("cannot write to nested zip entry: "+toUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
throw new IllegalStateException("cannot write to nested zip entry: "+toUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
if (kind != Kind.CLASS) {
|
||||
return false;
|
||||
}
|
||||
String name = getName();
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
return name.substring(lastSlash+1).equals(simpleName+".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null; // nesting level not known
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null; // access level not known
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = outerFile.getName().hashCode();
|
||||
hc = hc * 37 + innerZipFile.getName().hashCode();
|
||||
hc = hc * 37 + innerZipFileEntry.getName().hashCode();
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof NestedZipEntryJavaFileObject)) {
|
||||
return false;
|
||||
}
|
||||
NestedZipEntryJavaFileObject that = (NestedZipEntryJavaFileObject)obj;
|
||||
return (outerFile.getName().equals(that.outerFile.getName())) &&
|
||||
(innerZipFile.getName().equals(that.innerZipFile.getName())) &&
|
||||
(innerZipFileEntry.getName().equals(that.innerZipFileEntry.getName()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import javax.tools.DiagnosticCollector;
|
||||
import javax.tools.JavaCompiler;
|
||||
import javax.tools.JavaCompiler.CompilationTask;
|
||||
import javax.tools.JavaFileObject;
|
||||
import javax.tools.ToolProvider;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Compile Java source at runtime and load it.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class RuntimeJavaCompiler {
|
||||
|
||||
private JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger(RuntimeJavaCompiler.class);
|
||||
|
||||
/**
|
||||
* Compile the named class consisting of the supplied source code. If successful load the class
|
||||
* and return it. Multiple classes may get loaded if the source code included anonymous/inner/local
|
||||
* classes.
|
||||
* @param className the name of the class (dotted form, e.g. com.foo.bar.Goo)
|
||||
* @param classSourceCode the full source code for the class
|
||||
* @return a CompilationResult that encapsulates what happened during compilation (classes/messages produced)
|
||||
*/
|
||||
public CompilationResult compile(String className, String classSourceCode) {
|
||||
logger.info("Compiling source for class {} using compiler {}",className,compiler.getClass().getName());
|
||||
|
||||
DiagnosticCollector<JavaFileObject> diagnosticCollector = new DiagnosticCollector<JavaFileObject>();
|
||||
MemoryBasedJavaFileManager fileManager = new MemoryBasedJavaFileManager();
|
||||
// JavaFileObject sourceFile = new StringBasedJavaSourceFileObject(className, classSourceCode);
|
||||
JavaFileObject sourceFile = InMemoryJavaFileObject.getSourceJavaFileObject(className, classSourceCode);
|
||||
// new InMemoryJavaFileObject(StandardLocation.SOURCE_PATH, className, javax.tools.JavaFileObject.Kind.SOURCE, null);
|
||||
// try (Writer w = sourceFile.openWriter()) {
|
||||
// w.write(classSourceCode);
|
||||
// } catch (IOException ioe) {
|
||||
// ioe.printStackTrace();
|
||||
// }
|
||||
Iterable<? extends JavaFileObject> compilationUnits = Arrays.asList(sourceFile);
|
||||
CompilationTask task = compiler.getTask(null, fileManager , diagnosticCollector, null, null, compilationUnits);
|
||||
|
||||
boolean success = task.call();
|
||||
CompilationResult compilationResult = new CompilationResult(success);
|
||||
|
||||
// If successful there may be no errors but there might be info/warnings
|
||||
for (Diagnostic<? extends JavaFileObject> diagnostic : diagnosticCollector.getDiagnostics()) {
|
||||
CompilationMessage.Kind kind = (diagnostic.getKind()==Kind.ERROR?CompilationMessage.Kind.ERROR:CompilationMessage.Kind.OTHER);
|
||||
// String sourceCode = ((StringBasedJavaSourceFileObject)diagnostic.getSource()).getSourceCode();
|
||||
String sourceCode =null;
|
||||
try {
|
||||
sourceCode = (String)diagnostic.getSource().getCharContent(true);
|
||||
} catch (IOException ioe) {
|
||||
// Unexpected, but leave sourceCode null to indicate it was not retrievable
|
||||
}
|
||||
int startPosition = (int)diagnostic.getPosition();
|
||||
if (startPosition == Diagnostic.NOPOS) {
|
||||
startPosition = (int)diagnostic.getStartPosition();
|
||||
}
|
||||
CompilationMessage compilationMessage = new CompilationMessage(kind,diagnostic.getMessage(null),sourceCode,startPosition,(int)diagnostic.getEndPosition());
|
||||
compilationResult.recordCompilationMessage(compilationMessage);
|
||||
}
|
||||
if (success) {
|
||||
List<CompiledClassDefinition> ccds = fileManager.getCompiledClasses();
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
try (SimpleClassLoader ccl = new SimpleClassLoader(this.getClass().getClassLoader())) {
|
||||
for (CompiledClassDefinition ccd: ccds) {
|
||||
Class<?> clazz = ccl.defineClass(ccd.getClassName(), ccd.getBytes());
|
||||
classes.add(clazz);
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
logger.debug("Unexpected exception defining classes",ioe);
|
||||
}
|
||||
compilationResult.setCompiledClasses(classes);
|
||||
}
|
||||
return compilationResult;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.net.URL;
|
||||
import java.net.URLClassLoader;
|
||||
|
||||
/**
|
||||
* Very simple classloader that can be used to load the compiled types.
|
||||
*
|
||||
* @author Andy Clement
|
||||
*/
|
||||
public class SimpleClassLoader extends URLClassLoader {
|
||||
|
||||
private static final URL[] NO_URLS = new URL[0];
|
||||
|
||||
public SimpleClassLoader(ClassLoader classLoader) {
|
||||
super(NO_URLS, classLoader);
|
||||
}
|
||||
|
||||
public Class<?> defineClass(String name, byte[] bytes) {
|
||||
return super.defineClass(name, bytes, 0, bytes.length);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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 org.springframework.cloud.function.compiler.java;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.NestingKind;
|
||||
import javax.tools.JavaFileObject;
|
||||
|
||||
public class ZipEntryJavaFileObject implements JavaFileObject {
|
||||
|
||||
private File containingFile;
|
||||
private ZipFile zf;
|
||||
private ZipEntry ze;
|
||||
|
||||
private URI uri;
|
||||
|
||||
public ZipEntryJavaFileObject(File containingFile, ZipFile zipFile, ZipEntry entry) {
|
||||
this.containingFile = containingFile;
|
||||
this.zf = zipFile;
|
||||
this.ze = entry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI toUri() {
|
||||
if (uri == null) {
|
||||
String uriString = null;
|
||||
try {
|
||||
uriString = "zip:" + containingFile.getAbsolutePath() + "!" + ze.getName();
|
||||
uri = new URI(uriString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("Unexpected URISyntaxException for string '" + uriString + "'", e);
|
||||
}
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return ze.getName(); // a/b/C.class
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return zf.getInputStream(ze);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OutputStream openOutputStream() throws IOException {
|
||||
throw new IllegalStateException("only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("openReader() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||
// It is bytecode
|
||||
throw new UnsupportedOperationException("getCharContent() not supported on class file: " + getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Writer openWriter() throws IOException {
|
||||
throw new IllegalStateException("only expected to be used for input");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastModified() {
|
||||
return ze.getTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean delete() {
|
||||
return false; // Cannot delete entries inside zips
|
||||
}
|
||||
|
||||
@Override
|
||||
public Kind getKind() {
|
||||
return Kind.CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNameCompatible(String simpleName, Kind kind) {
|
||||
if (kind != Kind.CLASS) {
|
||||
return false;
|
||||
}
|
||||
String name = getName();
|
||||
int lastSlash = name.lastIndexOf('/');
|
||||
return name.substring(lastSlash + 1).equals(simpleName + ".class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public NestingKind getNestingKind() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Modifier getAccessLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hc = containingFile.getName().hashCode();
|
||||
hc = hc * 37 + ze.getName().hashCode();
|
||||
return hc;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj instanceof ZipEntryJavaFileObject)) {
|
||||
return false;
|
||||
}
|
||||
ZipEntryJavaFileObject that = (ZipEntryJavaFileObject)obj;
|
||||
return (containingFile.getName().equals(that.containingFile.getName())) &&
|
||||
(ze.getName().equals(that.ze.getName()));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user