Skip to content

Commit 3245557

Browse files
committed
Custom compilation class path (#2).
* ClassFactory now takes two additional arguments: additional compiler options and parent class loader that is used to load classes that lambda depends on. * InMemoryClassLoader can now use custom parent class loader. * InMemoryFileManager now implements StandardJavaFileManager instead of just JavaFileManager. It turns out that Eclipse compiler treats implementations of StandardJavaFileManager differently and it's the only way to make the custom class path work. * ClassPathExtractor static helper class contains two ways of automatically extracting the classpath of the current JVM, the simpler one is used by default. * LamdaFactory passes custom class path as an additional compiler option to the ClassFactory. * LambdaFactoryConfiguration has two new fields: parentClassLoader and compilationClassPath. * Responsibility of selecting the default java compiler has been moved from LambdaFactory to LambdaFactoryConfiguration.
1 parent d3b0ba3 commit 3245557

17 files changed

Lines changed: 317 additions & 76 deletions

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# 1.3
2+
* Custom compilation class path. User can now specify a class path and a class loader used when creating lambda. By default, current JVM class path is used. Thanks to that users can now use custom classes and interfaces in lambda codes (previously only standard library classes were available).
3+
14
# 1.2
25
* Dynamic type references. User can now pass a String type name to the constructor of the DynamicTypeReference class instead of statically creating a TypeReference instance with an appropriate generic type.
36

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ assertEquals("ABC101DEF", decorate.apply(101));
3131
```
3232
(You can visit [tests directory](https://github.com/greenjoe/lambdaFromString/tree/master/src/test/java/pl/joegreen/lambdaFromString) to see additional examples.)
3333

34-
By default only java.util.function.* is imported by the class, as it is needed by the library itself. If you would like to import additional classes, you can specify imports in the configuration, as shown below. Please note that only Java standard library classes are available on the compilation classpath in the current version of library, so you cannot import your own classes.
34+
By default only java.util.function.* is imported by the class, as it is needed by the library itself. If you would like to import additional classes, you can specify imports in the configuration, as shown below. Please note that classes used in a lambda need to be available on the classpath used by the library. It can be configured, by default the java.class.path system property is used (it should work fine in most cases).
3535

36-
Imports can be passed as `Class<?>` instances or strings (string form is the only way to use * wildcard). Static imports are also supported and can be passed as strings.
36+
Imports can be passed as `Class<?>` instances or strings (string form is the only way to use wildcards). Static imports are also supported and can be passed as strings.
3737

3838
```java
3939
LambdaFactory factory = LambdaFactory.get(
@@ -62,7 +62,7 @@ You can get it from Maven Central:
6262
<dependency>
6363
<groupId>pl.joegreen</groupId>
6464
<artifactId>lambda-from-string</artifactId>
65-
<version>1.2</version>
65+
<version>1.3</version>
6666
</dependency>
6767
```
6868
It has only one external Maven dependency: [Eclipse JDT Core Batch Compiler](http://mvnrepository.com/artifact/org.eclipse.jdt.core.compiler/ecj). That dependency was added because Java compiler is a part of JDK (located in tools.jar) and it's not available in pure JRE. When client applications were running on JRE then no Java compiler was available at runtime and the LambdaFromString library failed to compile lambda code. Eclipse ECJ makes it possible to use LambdaFromString even in cases when only JRE is available at runtime.
@@ -77,7 +77,7 @@ If you are sure that your application will be running on JDK and you want to use
7777
</exclusion>
7878
</exclusions>
7979
```
80-
In that case you can also use lambdaFromString without Maven by just downloading a [single jar](http://central.maven.org/maven2/pl/joegreen/lambda-from-string/1.2/lambda-from-string-1.2.jar).
80+
In that case you can also use lambdaFromString without Maven by just downloading a [single jar](http://central.maven.org/maven2/pl/joegreen/lambda-from-string/1.2/lambda-from-string-1.2.jar).
8181

8282
## How it works?
8383

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package pl.joegreen.lambdaFromString;
2+
3+
import java.io.File;
4+
import java.net.URL;
5+
import java.net.URLClassLoader;
6+
import java.util.Arrays;
7+
import java.util.Optional;
8+
import java.util.stream.Collectors;
9+
10+
public class ClassPathExtractor {
11+
public static String getJavaPropertyClassPath(){
12+
return Optional.ofNullable(System.getProperty("java.class.path")).orElse("");
13+
}
14+
15+
public static String getCurrentContextClassLoaderClassPath(){
16+
URLClassLoader contextClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader();
17+
return Arrays.stream(contextClassLoader.getURLs())
18+
.map(URL::getFile)
19+
.collect(Collectors.joining(File.pathSeparator));
20+
}
21+
}

src/main/java/pl/joegreen/lambdaFromString/LambdaCreationException.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@ public LambdaCreationException(ClassCompilationException classCompilationExcepti
1212
super(classCompilationException);
1313
compilationDetails = classCompilationException.getCompilationDetails();
1414
}
15-
public LambdaCreationException(Exception ex) {
16-
super(ex);
15+
16+
public LambdaCreationException(Throwable t) {
17+
super(t);
1718
compilationDetails = Optional.empty();
1819
}
1920

src/main/java/pl/joegreen/lambdaFromString/LambdaFactory.java

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,12 @@
55

66
import javax.tools.JavaCompiler;
77
import java.lang.reflect.Method;
8+
import java.util.Arrays;
89
import java.util.List;
910
import java.util.Optional;
1011

1112
public class LambdaFactory {
1213

13-
protected static Optional<JavaCompiler> DEFAULT_COMPILER = JavaCompilerProvider.findDefaultJavaCompiler();
14-
1514
/**
1615
* Returns a LambdaFactory instance with default configuration.
1716
* @throws JavaCompilerNotFoundException if the library cannot find any java compiler
@@ -26,34 +25,35 @@ public static LambdaFactory get() {
2625
* in the configuration
2726
*/
2827
public static LambdaFactory get(LambdaFactoryConfiguration configuration) {
29-
JavaCompiler compiler = getConfiguredOrDefaultCompiler(configuration);
28+
JavaCompiler compiler = Optional.ofNullable(configuration.getJavaCompiler()).orElseThrow(JavaCompilerNotFoundException::new);
3029
return new LambdaFactory(
3130
configuration.getDefaultHelperClassSourceProvider(),
3231
configuration.getClassFactory(),
3332
compiler,
3433
configuration.getImports(),
35-
configuration.getStaticImports());
36-
}
37-
38-
private static JavaCompiler getConfiguredOrDefaultCompiler(LambdaFactoryConfiguration configuration) {
39-
return configuration.getJavaCompiler()
40-
.orElse(DEFAULT_COMPILER
41-
.orElseThrow(JavaCompilerNotFoundException::new));
34+
configuration.getStaticImports(),
35+
configuration.getCompilationClassPath(),
36+
configuration.getParentClassLoader());
4237
}
4338

4439
private final HelperClassSourceProvider helperProvider;
4540
private final ClassFactory classFactory;
4641
private final JavaCompiler javaCompiler;
4742
private final List<String> imports;
4843
private final List<String> staticImports;
44+
private final String compilationClassPath;
45+
private final ClassLoader parentClassLoader;
4946

5047
private LambdaFactory(HelperClassSourceProvider helperProvider, ClassFactory classFactory,
51-
JavaCompiler javaCompiler, List<String> imports, List<String> staticImports) {
48+
JavaCompiler javaCompiler, List<String> imports, List<String> staticImports,
49+
String compilationClassPath, ClassLoader parentClassLoader) {
5250
this.helperProvider = helperProvider;
5351
this.classFactory = classFactory;
5452
this.javaCompiler = javaCompiler;
5553
this.imports = imports;
5654
this.staticImports = staticImports;
55+
this.compilationClassPath = compilationClassPath;
56+
this.parentClassLoader = parentClassLoader;
5757
}
5858

5959
/**
@@ -69,20 +69,25 @@ private LambdaFactory(HelperClassSourceProvider helperProvider, ClassFactory cla
6969
public <T> T createLambda(String code, TypeReference<T> typeReference) throws LambdaCreationException {
7070
String helperClassSource = helperProvider.getHelperClassSource(typeReference.toString(), code, imports, staticImports);
7171
try {
72-
Class<?> helperClass = classFactory.createClass(helperProvider.getHelperClassName(), helperClassSource, javaCompiler);
72+
Class<?> helperClass = classFactory.createClass(helperProvider.getHelperClassName(), helperClassSource, javaCompiler, createOptionsForCompilationClasspath(compilationClassPath), parentClassLoader);
7373
Method lambdaReturningMethod = helperClass.getMethod(helperProvider.getLambdaReturningMethodName());
7474
@SuppressWarnings("unchecked")
7575
// the whole point of the class template and runtime compilation is to make this cast work well :-)
7676
T lambda = (T) lambdaReturningMethod.invoke(null);
7777
return lambda;
78-
} catch (ReflectiveOperationException | RuntimeException e) {
78+
} catch (ReflectiveOperationException | RuntimeException | NoClassDefFoundError e) {
79+
// NoClassDefFoundError can be thrown if provided parent class loader cannot load classes used by the lambda
7980
throw new LambdaCreationException(e);
8081
} catch (ClassCompilationException classCompilationException) {
81-
// knows type of the cause so it get CompilationDetails
82+
// that catch differs from the catch above as the exact exception type is known and additional details can be extracted
8283
throw new LambdaCreationException(classCompilationException);
8384
}
8485
}
8586

87+
private List<String> createOptionsForCompilationClasspath(String compilationClassPath) {
88+
return Arrays.asList("-classpath", compilationClassPath);
89+
}
90+
8691
/**
8792
* Convenience wrapper for {@link #createLambda(String, TypeReference)}
8893
* which throws unchecked exception instead of checked one.

src/main/java/pl/joegreen/lambdaFromString/LambdaFactoryConfiguration.java

Lines changed: 83 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,15 @@
1010
import static java.util.stream.Stream.concat;
1111

1212
public class LambdaFactoryConfiguration {
13-
13+
protected static Optional<JavaCompiler> DEFAULT_COMPILER = JavaCompilerProvider.findDefaultJavaCompiler();
1414

1515
private HelperClassSourceProvider helperClassSourceProvider;
1616
private ClassFactory classFactory;
1717
private List<String> staticImports;
1818
private List<String> imports;
19-
private Optional<JavaCompiler> javaCompiler;
19+
private String compilationClassPath;
20+
private ClassLoader parentClassLoader;
21+
private JavaCompiler javaCompiler;
2022

2123
public static LambdaFactoryConfiguration get() {
2224
return new LambdaFactoryConfiguration();
@@ -27,18 +29,23 @@ private LambdaFactoryConfiguration() {
2729
classFactory = new DefaultClassFactory();
2830
staticImports = Collections.unmodifiableList(new ArrayList<>());
2931
imports = Collections.unmodifiableList(new ArrayList<>());
30-
javaCompiler = Optional.empty();
32+
compilationClassPath = ClassPathExtractor.getJavaPropertyClassPath();
33+
parentClassLoader = this.getClass().getClassLoader();
34+
javaCompiler = DEFAULT_COMPILER.orElse(null);
3135
}
3236

33-
3437
private LambdaFactoryConfiguration copy() {
3538
return new LambdaFactoryConfiguration()
36-
.setClassFactory(classFactory)
3739
.setDefaultHelperClassSourceProvider(helperClassSourceProvider)
40+
.setClassFactory(classFactory)
41+
.setStaticImports(staticImports)
3842
.setImports(imports)
39-
.setStaticImports(staticImports);
43+
.setCompilationClassPath(compilationClassPath)
44+
.setParentClassLoader(parentClassLoader)
45+
.setJavaCompiler(javaCompiler);
4046
}
4147

48+
4249
public HelperClassSourceProvider getDefaultHelperClassSourceProvider() {
4350
return helperClassSourceProvider;
4451
}
@@ -55,61 +62,93 @@ public List<String> getImports() {
5562
return imports;
5663
}
5764

58-
public Optional<JavaCompiler> getJavaCompiler() {
65+
public String getCompilationClassPath() {
66+
return compilationClassPath;
67+
}
68+
69+
public ClassLoader getParentClassLoader() {
70+
return parentClassLoader;
71+
}
72+
73+
public JavaCompiler getJavaCompiler() {
5974
return javaCompiler;
6075
}
76+
6177
/**
62-
* Changes classFactory which is responsible for compiling the helper class and loading it into the memory. <br>
63-
* Should be used only in rare cases when you cannot get the exact functionality
64-
* you want from the library and want to change some of its inner behavior. The interface accepted by this method
65-
* can change between library versions and cause compilation errors if you decide to use it.
78+
* Changes helperClassSourceProvider which provides a code template for the class to be compiled. <br>
79+
* Should be used only in rare cases when you cannot get the exact functionality
80+
* you want from the library and want to change some of its inner behavior. The interface accepted by this method
81+
* can change between library versions and cause compilation errors if you decide to use it.
6682
*/
67-
public LambdaFactoryConfiguration withClassFactory(ClassFactory classFactory){
68-
return copy().setClassFactory(classFactory);
83+
public LambdaFactoryConfiguration withHelperClassSourceProvider(HelperClassSourceProvider helperSourceProvider) {
84+
return copy().setDefaultHelperClassSourceProvider(helperSourceProvider);
6985
}
7086

7187
/**
72-
* Changes helperClassSourceProvider which provides a code template for the class to be compiled. <br>
73-
* Should be used only in rare cases when you cannot get the exact functionality
74-
* you want from the library and want to change some of its inner behavior. The interface accepted by this method
75-
* can change between library versions and cause compilation errors if you decide to use it.
88+
* Changes classFactory which is responsible for compiling the helper class and loading it into the memory. <br>
89+
* Should be used only in rare cases when you cannot get the exact functionality
90+
* you want from the library and want to change some of its inner behavior. The interface accepted by this method
91+
* can change between library versions and cause compilation errors if you decide to use it.
7692
*/
77-
public LambdaFactoryConfiguration withHelperClassSourceProvider(HelperClassSourceProvider helperSourceProvider){
78-
return copy().setDefaultHelperClassSourceProvider(helperSourceProvider);
93+
public LambdaFactoryConfiguration withClassFactory(ClassFactory classFactory) {
94+
return copy().setClassFactory(classFactory);
7995
}
8096

8197
/**
82-
* Overrides default JavaCompiler instance. <br>It can be used to force using JDK compiler when the Eclipse Compiler
83-
* is also available. Completely different compiler instance can also be passed but in that case it is possible
84-
* that some changes will have to be made in the class factory ({@link #withClassFactory(ClassFactory)}).
98+
* Adds static imports that will be visible in the lambda code. Each string should consist of what is
99+
* normally written between "import static " and ";" in the import statement. Imports can contain * wildcards.
85100
*/
86-
public LambdaFactoryConfiguration withJavaCompiler(JavaCompiler javaCompiler){
87-
return copy().setJavaCompiler(javaCompiler);
101+
public LambdaFactoryConfiguration withStaticImports(String... newStaticImports) {
102+
return copy().setStaticImports(listWithNewElements(staticImports, newStaticImports));
88103
}
89104

90105
/**
91106
* Adds imports that will be visible in the lambda code. Each string should consist of what is
92107
* normally written between "import " and ";" in the import statement. Imports can contain * wildcards.
93108
*/
94-
public LambdaFactoryConfiguration withImports(String... newImports){
109+
public LambdaFactoryConfiguration withImports(String... newImports) {
95110
return copy().setImports(listWithNewElements(imports, newImports));
96111
}
97112

98113
/**
99114
* Adds imports that will be visible in the lambda code. To add an import with * wildcard
100115
* please use{@link #withImports(String...)}.
101116
*/
102-
public LambdaFactoryConfiguration withImports(Class<?>... newImports){
117+
public LambdaFactoryConfiguration withImports(Class<?>... newImports) {
103118
String[] stringImports = Arrays.stream(newImports).map(Class::getCanonicalName).toArray(String[]::new);
104119
return withImports(stringImports);
105120
}
106121

122+
/** Overrides default compilation classpath (default is taken from the java.class.path system property).
123+
* Changing the classpath may sometimes result in a need to provide a custom parent class loader {@link #withParentClassLoader(ClassLoader)
124+
* so that it can load classes provided in the given class path.
125+
*/
126+
public LambdaFactoryConfiguration withCompilationClassPath(String compilationClassPath) {
127+
return copy().setCompilationClassPath(compilationClassPath);
128+
}
129+
107130
/**
108-
* Adds static imports that will be visible in the lambda code. Each string should consist of what is
109-
* normally written between "import static " and ";" in the import statement. Imports can contain * wildcards.
131+
* Overrides default parent class loader (by default a class loader loading this class is used).
132+
* Provided class loader has to be able to load all the classes used by compiled lambda expressions
133+
* (either itself or by calling its parent class loader).
110134
*/
111-
public LambdaFactoryConfiguration withStaticImports(String... newStaticImports){
112-
return copy().setStaticImports(listWithNewElements(staticImports, newStaticImports));
135+
public LambdaFactoryConfiguration withParentClassLoader(ClassLoader parentClassLoader) {
136+
return copy().setParentClassLoader(parentClassLoader);
137+
}
138+
139+
/**
140+
* Overrides default JavaCompiler instance. <br>It can be used to force using JDK compiler when the Eclipse Compiler
141+
* is also available. Completely different compiler instance can also be passed but in that case it is possible
142+
* that some changes will have to be made in the class factory ({@link #withClassFactory(ClassFactory)}).
143+
*/
144+
public LambdaFactoryConfiguration withJavaCompiler(JavaCompiler javaCompiler) {
145+
return copy().setJavaCompiler(javaCompiler);
146+
}
147+
148+
149+
private LambdaFactoryConfiguration setDefaultHelperClassSourceProvider(HelperClassSourceProvider helperClassSourceProvider) {
150+
this.helperClassSourceProvider = helperClassSourceProvider;
151+
return this;
113152
}
114153

115154
private LambdaFactoryConfiguration setClassFactory(ClassFactory classFactory) {
@@ -127,17 +166,22 @@ private LambdaFactoryConfiguration setImports(List<String> imports) {
127166
return this;
128167
}
129168

130-
private LambdaFactoryConfiguration setDefaultHelperClassSourceProvider(HelperClassSourceProvider helperClassSourceProvider) {
131-
this.helperClassSourceProvider = helperClassSourceProvider;
169+
private LambdaFactoryConfiguration setCompilationClassPath(String classPath) {
170+
this.compilationClassPath = classPath;
171+
return this;
172+
}
173+
174+
private LambdaFactoryConfiguration setParentClassLoader(ClassLoader parentClassLoader) {
175+
this.parentClassLoader = parentClassLoader;
132176
return this;
133177
}
134178

135179
private LambdaFactoryConfiguration setJavaCompiler(JavaCompiler javaCompiler) {
136-
this.javaCompiler = Optional.of(javaCompiler);
180+
this.javaCompiler = javaCompiler;
137181
return this;
138182
}
139183

140-
private static <T> List<T> listWithNewElements(List<T> oldList, T... newElements){
184+
private static <T> List<T> listWithNewElements(List<T> oldList, T... newElements) {
141185
return Collections.unmodifiableList(concat(oldList.stream(), Arrays.stream(newElements)).collect(toList()));
142186
}
143187

@@ -152,6 +196,8 @@ public boolean equals(Object o) {
152196
if (!classFactory.equals(that.classFactory)) return false;
153197
if (!staticImports.equals(that.staticImports)) return false;
154198
if (!imports.equals(that.imports)) return false;
199+
if (!compilationClassPath.equals(that.compilationClassPath)) return false;
200+
if (!parentClassLoader.equals(that.parentClassLoader)) return false;
155201
return javaCompiler.equals(that.javaCompiler);
156202

157203
}
@@ -162,7 +208,10 @@ public int hashCode() {
162208
result = 31 * result + classFactory.hashCode();
163209
result = 31 * result + staticImports.hashCode();
164210
result = 31 * result + imports.hashCode();
211+
result = 31 * result + compilationClassPath.hashCode();
212+
result = 31 * result + parentClassLoader.hashCode();
165213
result = 31 * result + javaCompiler.hashCode();
166214
return result;
167215
}
216+
168217
}

0 commit comments

Comments
 (0)