Skip to content

Commit 1eb96d6

Browse files
committed
Address #2, remove compiler dependency, improve runtime compilation for container/osgi enviroments (copy-paste from Chronicle Values)
1 parent a75dbc5 commit 1eb96d6

File tree

8 files changed

+807
-8
lines changed

8 files changed

+807
-8
lines changed

pom.xml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,6 @@
4242
<scope>provided</scope>
4343
</dependency>
4444

45-
<dependency>
46-
<groupId>net.openhft</groupId>
47-
<artifactId>compiler</artifactId>
48-
<version>2.2.0</version>
49-
</dependency>
50-
5145
<!-- test dependencies -->
5246

5347
<dependency>
Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,280 @@
1+
/*
2+
* Copyright (C) 2015 higherfrequencytrading.com
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU Lesser General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU Lesser General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package net.openhft.smoothie;
18+
19+
import java.lang.ref.WeakReference;
20+
import java.lang.reflect.Modifier;
21+
import java.security.AccessController;
22+
import java.security.PrivilegedAction;
23+
import java.util.Arrays;
24+
import java.util.WeakHashMap;
25+
import java.util.logging.Logger;
26+
27+
/**
28+
* Stripped down version of classes
29+
* https://github.com/google/guice/blob/9867f9c2142355ae958f9eeb8fb96811082c8812/core/src/com/google/inject/internal/InternalFlags.java
30+
* and
31+
* https://github.com/google/guice/blob/09fec22916264e76d95333b81c69030b3b713e64/core/src/com/google/inject/internal/BytecodeGen.java
32+
*
33+
* <p>When loading classes, we need to be careful of:
34+
* <ul>
35+
* <li><strong>Memory leaks.</strong> Generated classes need to be garbage collected in long-lived
36+
* applications. Once an injector and any instances it created can be garbage collected, the
37+
* corresponding generated classes should be collectable.
38+
* <li><strong>Visibility.</strong> Containers like <code>OSGi</code> use class loader boundaries
39+
* to enforce modularity at runtime.
40+
* </ul>
41+
*
42+
* <p>For each generated class, there's multiple class loaders involved:
43+
* <ul>
44+
* <li><strong>The related class's class loader.</strong> Every generated class services exactly
45+
* one user-supplied class. This class loader must be used to access members with protected
46+
* and package visibility.
47+
* <li><strong>Smoothie's class loader.</strong>
48+
* <li><strong>Our bridge class loader.</strong> This is a child of the user's class loader. It
49+
* selectively delegates to either the user's class loader (for user classes) or the Smoothie
50+
* class loader (for internal classes that are used by the generated classes). This class
51+
* loader that owns the classes generated by Smoothie.
52+
* </ul>
53+
*
54+
* @author mcculls@gmail.com (Stuart McCulloch)
55+
* @author jessewilson@google.com (Jesse Wilson)
56+
*/
57+
final class BytecodeGen {
58+
59+
static final Logger logger = Logger.getLogger(BytecodeGen.class.getName());
60+
61+
private static final CustomClassLoadingOption CUSTOM_CLASS_LOADING =
62+
parseCustomClassLoadingOption();
63+
64+
/**
65+
* The options for Smoothie custom class loading.
66+
*/
67+
public enum CustomClassLoadingOption {
68+
/** No custom class loading */
69+
OFF,
70+
/** Automatically bridge between class loaders (Default) */
71+
BRIDGE
72+
}
73+
74+
public static CustomClassLoadingOption getCustomClassLoadingOption() {
75+
return CUSTOM_CLASS_LOADING;
76+
}
77+
78+
private static CustomClassLoadingOption parseCustomClassLoadingOption() {
79+
return getSystemOption("smoothie__custom_class_loading",
80+
CustomClassLoadingOption.BRIDGE, CustomClassLoadingOption.OFF);
81+
}
82+
83+
/**
84+
* Gets the system option indicated by the specified key; runs as a privileged action.
85+
*
86+
* @param name of the system option
87+
* @param defaultValue if the option is not set
88+
* @param secureValue if the security manager disallows access to the option
89+
*
90+
* @return value of the option, defaultValue if not set, secureValue if no access
91+
*/
92+
private static <T extends Enum<T>> T getSystemOption(final String name, T defaultValue,
93+
T secureValue) {
94+
Class<T> enumType = defaultValue.getDeclaringClass();
95+
String value = null;
96+
try {
97+
value = AccessController.doPrivileged(
98+
(PrivilegedAction<String>) () -> System.getProperty(name));
99+
return (value != null && value.length() > 0) ? Enum.valueOf(enumType, value) :
100+
defaultValue;
101+
} catch (SecurityException e) {
102+
return secureValue;
103+
} catch (IllegalArgumentException e) {
104+
logger.warning(value + " is not a valid flag value for " + name + ". "
105+
+ " Smoothie must be one of " + Arrays.asList(enumType.getEnumConstants()));
106+
return defaultValue;
107+
}
108+
}
109+
110+
static final ClassLoader SMOOTHIE_CLASS_LOADER = canonicalize(BytecodeGen.class.getClassLoader());
111+
112+
// initialization-on-demand...
113+
private static class SystemBridgeHolder {
114+
static final BridgeClassLoader SYSTEM_BRIDGE = new BridgeClassLoader();
115+
}
116+
117+
/** ie. "net.openhft.smoothie" */
118+
static final String SMOOTHIE_PACKAGE =
119+
BytecodeGen.class.getName().replaceFirst("\\.smoothie\\..*$", ".smoothie");
120+
121+
/**
122+
* Weak cache of bridge class loaders that make the Smoothie Map implementation
123+
* classes visible to various code-generated proxies of client classes.
124+
*/
125+
private static final WeakHashMap<ClassLoader, WeakReference<ClassLoader>> CLASS_LOADER_CACHE =
126+
new WeakHashMap<>();
127+
128+
private static ClassLoader getFromClassLoaderCache(ClassLoader typeClassLoader) {
129+
synchronized (CLASS_LOADER_CACHE) {
130+
return CLASS_LOADER_CACHE.compute(typeClassLoader, (k, ref) -> {
131+
if (ref == null || ref.get() == null) {
132+
logger.fine("Creating a bridge ClassLoader for " + typeClassLoader);
133+
return AccessController.doPrivileged(
134+
(PrivilegedAction<WeakReference<ClassLoader>>) () ->
135+
new WeakReference<>(new BridgeClassLoader(typeClassLoader)));
136+
} else {
137+
return ref;
138+
}
139+
}).get();
140+
}
141+
}
142+
143+
/**
144+
* Attempts to canonicalize null references to the system class loader.
145+
* May return null if for some reason the system loader is unavailable.
146+
*/
147+
private static ClassLoader canonicalize(ClassLoader classLoader) {
148+
return classLoader != null ? classLoader : SystemBridgeHolder.SYSTEM_BRIDGE.getParent();
149+
}
150+
151+
/**
152+
* Returns the class loader to host generated classes for {@code type}.
153+
*/
154+
public static ClassLoader getClassLoader(Class<?> type) {
155+
return getClassLoader(type, type.getClassLoader());
156+
}
157+
158+
private static ClassLoader getClassLoader(Class<?> type, ClassLoader delegate) {
159+
160+
// simple case: do nothing!
161+
if (getCustomClassLoadingOption() == CustomClassLoadingOption.OFF) {
162+
return delegate;
163+
}
164+
165+
// java.* types can be seen everywhere
166+
if (type.getName().startsWith("java.")) {
167+
return SMOOTHIE_CLASS_LOADER;
168+
}
169+
170+
delegate = canonicalize(delegate);
171+
172+
// no need for a bridge if using same class loader, or it's already a bridge
173+
if (delegate == SMOOTHIE_CLASS_LOADER || delegate instanceof BridgeClassLoader) {
174+
return delegate;
175+
}
176+
177+
// don't try bridging private types as it won't work
178+
if (Visibility.forType(type) == Visibility.PUBLIC) {
179+
if (delegate != SystemBridgeHolder.SYSTEM_BRIDGE.getParent()) {
180+
// delegate guaranteed to be non-null here
181+
return getFromClassLoaderCache(delegate);
182+
}
183+
// delegate may or may not be null here
184+
return SystemBridgeHolder.SYSTEM_BRIDGE;
185+
}
186+
187+
return delegate; // last-resort: do nothing!
188+
}
189+
190+
/**
191+
* The required visibility of a user's class from a Smoothie-generated class. Visibility of
192+
* package-private members depends on the loading classloader: only if two classes were loaded
193+
* by the same classloader can they see each other's package-private members. We need to be
194+
* careful when choosing which classloader to use for generated classes. We prefer our bridge
195+
* classloader, since it's OSGi-safe and doesn't leak permgen space. But often we cannot due to
196+
* visibility.
197+
*/
198+
public enum Visibility {
199+
200+
/**
201+
* Indicates that Smoothie-generated classes only need to call and override public members
202+
* of the target class. These generated classes may be loaded by our bridge classloader.
203+
*/
204+
PUBLIC {
205+
@Override
206+
public Visibility and(Visibility that) {
207+
return that;
208+
}
209+
},
210+
211+
/**
212+
* Indicates that Smoothie-generated classes need to call or override package-private
213+
* members. These generated classes must be loaded in the same classloader as the target
214+
* class. They won't work with OSGi, and won't get garbage collected until the target class'
215+
* classloader is garbage collected.
216+
*/
217+
SAME_PACKAGE {
218+
@Override
219+
public Visibility and(Visibility that) {
220+
return this;
221+
}
222+
};
223+
224+
public static Visibility forType(Class<?> type) {
225+
return (type.getModifiers() & (Modifier.PROTECTED | Modifier.PUBLIC)) != 0
226+
? PUBLIC
227+
: SAME_PACKAGE;
228+
}
229+
230+
public abstract Visibility and(Visibility that);
231+
}
232+
233+
/**
234+
* Loader for Smoothie-generated classes. For referenced classes, this delegates to either
235+
* the user's classloader (which is the parent of this classloader) or Smoothie's class loader.
236+
*/
237+
static class BridgeClassLoader extends ClassLoader {
238+
239+
BridgeClassLoader() {
240+
// use system loader as parent
241+
}
242+
243+
BridgeClassLoader(ClassLoader usersClassLoader) {
244+
super(usersClassLoader);
245+
}
246+
247+
@Override protected Class<?> loadClass(String name, boolean resolve)
248+
throws ClassNotFoundException {
249+
250+
if (name.startsWith("sun.reflect")) {
251+
// these reflection classes must be loaded from bootstrap class loader
252+
return SystemBridgeHolder.SYSTEM_BRIDGE.classicLoadClass(name, resolve);
253+
}
254+
255+
if (name.startsWith(SMOOTHIE_PACKAGE)) {
256+
if (null == SMOOTHIE_CLASS_LOADER) {
257+
// use special system bridge to load classes from bootstrap class loader
258+
return SystemBridgeHolder.SYSTEM_BRIDGE.classicLoadClass(name, resolve);
259+
}
260+
try {
261+
Class<?> clazz = SMOOTHIE_CLASS_LOADER.loadClass(name);
262+
if (resolve) {
263+
resolveClass(clazz);
264+
}
265+
return clazz;
266+
} catch (Throwable e) {
267+
// fall-back to classic delegation
268+
}
269+
}
270+
271+
return classicLoadClass(name, resolve);
272+
}
273+
274+
// make the classic delegating loadClass method visible
275+
Class<?> classicLoadClass(String name, boolean resolve)
276+
throws ClassNotFoundException {
277+
return super.loadClass(name, resolve);
278+
}
279+
}
280+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright (C) 2015 higherfrequencytrading.com
3+
*
4+
* This program is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Lesser General Public License as published by
6+
* the Free Software Foundation, either version 3 of the License.
7+
*
8+
* This program is distributed in the hope that it will be useful,
9+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
* GNU Lesser General Public License for more details.
12+
*
13+
* You should have received a copy of the GNU Lesser General Public License
14+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
*/
16+
17+
package net.openhft.smoothie;
18+
19+
import org.jetbrains.annotations.NotNull;
20+
21+
import javax.tools.Diagnostic;
22+
import javax.tools.JavaFileObject;
23+
import java.util.*;
24+
25+
@SuppressWarnings("StaticNonFinalField")
26+
class CachedCompiler {
27+
private static final Map<ClassLoader, Map<String, Class>> loadedClassesMap =
28+
new WeakHashMap<>();
29+
30+
private static final List<String> options =
31+
Arrays.asList("-XDenableSunApiLintControl", "-Xlint:-sunapi");
32+
33+
private final Map<String, JavaFileObject> javaFileObjects = new HashMap<>();
34+
private boolean errors;
35+
36+
@NotNull
37+
Map<String, byte[]> compileFromJava(@NotNull String className, @NotNull String javaCode) {
38+
Iterable<? extends JavaFileObject> compilationUnits;
39+
javaFileObjects.put(className, new JavaSourceFromString(className, javaCode));
40+
compilationUnits = javaFileObjects.values();
41+
42+
MyJavaFileManager fileManager =
43+
new MyJavaFileManager(CompilerUtils.s_standardJavaFileManager);
44+
errors = false;
45+
CompilerUtils.s_compiler.getTask(null, fileManager, diagnostic -> {
46+
if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
47+
errors = true;
48+
System.err.println(diagnostic);
49+
}
50+
}, options, null, compilationUnits).call();
51+
Map<String, byte[]> result = fileManager.getAllBuffers();
52+
if (errors) {
53+
// compilation error, so we want to exclude this file from future compilation passes
54+
javaFileObjects.remove(className);
55+
}
56+
return result;
57+
}
58+
59+
public Class loadFromJava(@NotNull ClassLoader classLoader, @NotNull String className,
60+
@NotNull String javaCode) throws ClassNotFoundException {
61+
Class clazz = null;
62+
Map<String, Class> loadedClasses;
63+
synchronized (loadedClassesMap) {
64+
loadedClasses = loadedClassesMap.get(classLoader);
65+
if (loadedClasses == null)
66+
loadedClassesMap.put(classLoader, loadedClasses = new LinkedHashMap<>());
67+
else
68+
clazz = loadedClasses.get(className);
69+
}
70+
if (clazz != null)
71+
return clazz;
72+
for (Map.Entry<String, byte[]> entry : compileFromJava(className, javaCode).entrySet()) {
73+
String className2 = entry.getKey();
74+
synchronized (loadedClassesMap) {
75+
if (loadedClasses.containsKey(className2))
76+
continue;
77+
}
78+
byte[] bytes = entry.getValue();
79+
Class clazz2 = CompilerUtils.defineClass(classLoader, className2, bytes);
80+
synchronized (loadedClassesMap) {
81+
loadedClasses.put(className2, clazz2);
82+
}
83+
}
84+
synchronized (loadedClassesMap) {
85+
loadedClasses.put(className, clazz = classLoader.loadClass(className));
86+
}
87+
return clazz;
88+
}
89+
}

0 commit comments

Comments
 (0)