Removed EmptyIterable

- Removed EmptyIterable as it's only used by _MemoryBasedJavaFileManager_
to ensure the contract of the _list(..)_ operation that must not return
null. The same contract is ensured with _new
IterableClasspath(classpath, packageName, recurse)_ while making
_MemoryBasedJavaFileManager.list(..)_ simpler and more consistent.
- Untill this fix the AbstractByteCodeLoadingProxy was building FQCN of
the byte-array defined class using Resource.getFileName() and then some,
which is not very reliable since if such name does not match the actual
name contained in the byte code, class loading will result in exception.
So, this fix reads FQCN from the actual byte code..
- Reduced visibility of AbstractByteCodeLoadingProxy
- Simplified ByteCodeLoadingFunctionTests

Closes gh-99
This commit is contained in:
Oleg Zhurakousky
2017-08-23 08:27:55 -04:00
committed by Dave Syer
parent 986e76610c
commit 37fc3e6c65
7 changed files with 43 additions and 123 deletions

View File

@@ -1,47 +0,0 @@
/*
* 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();
}
}

View File

@@ -1,12 +1,12 @@
/*
* Copyright 2016 the original author or authors.
*
* Copyright 2016-2017 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.
@@ -44,6 +44,7 @@ import org.slf4j.LoggerFactory;
* as a lookup mechanism for resolving types.
*
* @author Andy Clement
* @author Oleg Zhurakousky
*/
public class MemoryBasedJavaFileManager implements JavaFileManager {
@@ -55,7 +56,7 @@ public class MemoryBasedJavaFileManager implements JavaFileManager {
private List<CloseableFilterableJavaFileObjectIterable> toClose = new ArrayList<>();
private Map<String,File> resolvedAdditionalDependencies = new LinkedHashMap<>();
public MemoryBasedJavaFileManager() {
outputCollector = new CompilationOutputCollector();
}
@@ -78,14 +79,11 @@ public class MemoryBasedJavaFileManager implements JavaFileManager {
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;
String classpath = "";
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);
classpath = System.getProperty("sun.boot.class.path");
logger.debug("Creating iterable for boot class path: {}", classpath);
}
else if (location == StandardLocation.CLASS_PATH
&& (kinds == null || kinds.contains(Kind.CLASS))) {
@@ -95,18 +93,11 @@ public class MemoryBasedJavaFileManager implements JavaFileManager {
javaClassPath += File.pathSeparatorChar + resolvedAdditionalDependency.toURI().toString().substring("file:".length());
}
}
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;
classpath = javaClassPath;
logger.debug("Creating iterable for class path: {}", classpath);
}
CloseableFilterableJavaFileObjectIterable resultIterable = new IterableClasspath(classpath, packageName, recurse);
toClose.add(resultIterable);
return resultIterable;
}
@@ -224,7 +215,7 @@ public class MemoryBasedJavaFileManager implements JavaFileManager {
try {
File resolved = engine.resolve(new Dependency(new DefaultArtifact(coordinates), "runtime"));
// Example:
// dependency = maven://org.springframework:spring-expression:4.3.9.RELEASE
// dependency = maven://org.springframework:spring-expression:4.3.9.RELEASE
// resolved.toURI() = file:/Users/aclement/.m2/repository/org/springframework/spring-expression/4.3.9.RELEASE/spring-expression-4.3.9.RELEASE.jar
resolvedAdditionalDependencies.put(dependency, resolved);
} catch (RuntimeException re) {

View File

@@ -19,9 +19,9 @@ package org.springframework.cloud.function.compiler.proxy;
import java.lang.reflect.Method;
import java.util.concurrent.atomic.AtomicReference;
import org.springframework.asm.ClassReader;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.cloud.function.compiler.CompilationResultFactory;
import org.springframework.cloud.function.compiler.FunctionCompiler;
import org.springframework.cloud.function.compiler.java.SimpleClassLoader;
import org.springframework.cloud.function.support.FunctionFactoryMetadata;
import org.springframework.core.io.Resource;
@@ -30,36 +30,30 @@ import org.springframework.util.ReflectionUtils;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
*/
public abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBean, FunctionFactoryMetadata<T> {
abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBean, FunctionFactoryMetadata<T> {
private final Resource resource;
private final Class<?> type;
private CompilationResultFactory<T> factory;
private final SimpleClassLoader classLoader = new SimpleClassLoader(AbstractByteCodeLoadingProxy.class.getClassLoader());
private T target;
private Method method;
public AbstractByteCodeLoadingProxy(Resource resource, Class<?> type) {
AbstractByteCodeLoadingProxy(Resource resource) {
this.resource = resource;
this.type = type;
}
@Override
@SuppressWarnings("unchecked")
public void afterPropertiesSet() throws Exception {
byte[] bytes = FileCopyUtils.copyToByteArray(this.resource.getInputStream());
String filename = this.resource.getFilename();
String functionName = filename == null ? type.getSimpleName() : filename.replaceAll(".fun$", "");
String firstLetter = functionName.substring(0, 1).toUpperCase();
String upperCasedName = (functionName.length() > 1) ? firstLetter + functionName.substring(1) : firstLetter;
String className = String.format("%s.%s%sFactory", FunctionCompiler.class.getPackage().getName(), upperCasedName, this.type.getSimpleName());
String className = new ClassReader(bytes).getClassName().replace("/", ".");
Class<?> factoryClass = this.classLoader.defineClass(className, bytes);
try {
this.factory = (CompilationResultFactory<T>) factoryClass.newInstance();
this.target = ((CompilationResultFactory<T>) factoryClass.newInstance()).getResult();
this.method = findFactoryMethod(factoryClass);
}
catch (InstantiationException | IllegalAccessException e) {
@@ -67,6 +61,16 @@ public abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBea
}
}
@Override
public final T getTarget() {
return this.target;
}
@Override
public Method getFactoryMethod() {
return this.method;
}
private Method findFactoryMethod(Class<?> clazz) {
AtomicReference<Method> method = new AtomicReference<>();
ReflectionUtils.doWithLocalMethods(clazz, m -> {
@@ -77,14 +81,4 @@ public abstract class AbstractByteCodeLoadingProxy<T> implements InitializingBea
});
return method.get();
}
@Override
public final T getTarget() {
return this.factory.getResult();
}
@Override
public Method getFactoryMethod() {
return this.method;
}
}

View File

@@ -23,13 +23,14 @@ import org.springframework.core.io.Resource;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
*
* @param <T> type
*/
public class ByteCodeLoadingConsumer<T> extends AbstractByteCodeLoadingProxy<Consumer<T>> implements FunctionFactoryMetadata<Consumer<T>>, Consumer<T> {
public ByteCodeLoadingConsumer(Resource resource) {
super(resource, Consumer.class);
super(resource);
}
@Override

View File

@@ -23,6 +23,7 @@ import org.springframework.core.io.Resource;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
*
* @param <T> Function input type
* @param <R> Function result type
@@ -30,7 +31,7 @@ import org.springframework.core.io.Resource;
public class ByteCodeLoadingFunction<T, R> extends AbstractByteCodeLoadingProxy<Function<T, R>> implements FunctionFactoryMetadata<Function<T, R>>, Function<T, R> {
public ByteCodeLoadingFunction(Resource resource) {
super(resource, Function.class);
super(resource);
}
@Override

View File

@@ -23,13 +23,14 @@ import org.springframework.core.io.Resource;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
*
* @param <T> type
*/
public class ByteCodeLoadingSupplier<T> extends AbstractByteCodeLoadingProxy<Supplier<T>> implements FunctionFactoryMetadata<Supplier<T>>, Supplier<T> {
public ByteCodeLoadingSupplier(Resource resource) {
super(resource, Supplier.class);
super(resource);
}
@Override

View File

@@ -36,7 +36,7 @@ import reactor.core.publisher.Flux;
/**
* @author Dave Syer
*
* @author Oleg Zhurakousky
*/
public class ByteCodeLoadingFunctionTests {
@@ -44,12 +44,7 @@ public class ByteCodeLoadingFunctionTests {
public void compileConsumer() throws Exception {
CompiledFunctionFactory<Consumer<String>> compiled = new ConsumerCompiler<String>(
String.class.getName()).compile("foos", "System.out::println", "String");
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
@Override
public String getFilename() {
return "foos.fun";
}
};
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos");
ByteCodeLoadingConsumer<String> consumer = new ByteCodeLoadingConsumer<>(resource);
consumer.afterPropertiesSet();
assertThat(consumer instanceof FunctionFactoryMetadata);
@@ -61,12 +56,7 @@ public class ByteCodeLoadingFunctionTests {
public void compileSupplier() throws Exception {
CompiledFunctionFactory<Supplier<String>> compiled = new SupplierCompiler<String>(
String.class.getName()).compile("foos", "() -> \"foo\"", "String");
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
@Override
public String getFilename() {
return "foos.fun";
}
};
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos");
ByteCodeLoadingSupplier<String> supplier = new ByteCodeLoadingSupplier<>(resource);
supplier.afterPropertiesSet();
assertThat(supplier instanceof FunctionFactoryMetadata);
@@ -78,12 +68,7 @@ public class ByteCodeLoadingFunctionTests {
public void compileFunction() throws Exception {
CompiledFunctionFactory<Function<String, String>> compiled = new FunctionCompiler<String, String>(
String.class.getName()).compile("foos", "v -> v.toUpperCase()", "String", "String");
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
@Override
public String getFilename() {
return "foos.fun";
}
};
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos");
ByteCodeLoadingFunction<String, String> function = new ByteCodeLoadingFunction<>(resource);
function.afterPropertiesSet();
assertThat(function instanceof FunctionFactoryMetadata);
@@ -95,17 +80,11 @@ public class ByteCodeLoadingFunctionTests {
public void compileFluxFunction() throws Exception {
CompiledFunctionFactory<Function<Flux<String>, Flux<String>>> compiled = new FunctionCompiler<Flux<String>, Flux<String>>(
String.class.getName()).compile("foos", "flux -> flux.map(v -> v.toUpperCase())", "Flux<String>", "Flux<String>");
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos") {
@Override
public String getFilename() {
return "foos.fun";
}
};
ByteArrayResource resource = new ByteArrayResource(compiled.getGeneratedClassBytes(), "foos");
ByteCodeLoadingFunction<Flux<String>, Flux<String>> function = new ByteCodeLoadingFunction<>(resource);
function.afterPropertiesSet();
assertThat(function instanceof FunctionFactoryMetadata);
assertThat(FunctionFactoryUtils.isFluxFunction(function.getFactoryMethod())).isTrue();
assertThat(function.apply(Flux.just("foo")).blockFirst()).isEqualTo("FOO");
}
}