org.gradle.tooling.internal.provider.serialization.ClientSidePayloadClassLoaderRegistry Maven / Gradle / Ivy
Show all versions of gradle-api Show documentation
/*
* 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.gradle.tooling.internal.provider.serialization;
import com.google.common.collect.Sets;
import net.jcip.annotations.ThreadSafe;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* A {@link PayloadClassLoaderRegistry} used in the client JVM that maps classes loaded by application ClassLoaders. Inspects each class to calculate a minimal classpath to send across to the daemon process to recreate the ClassLoaders.
*
* Delegates to another {@link PayloadClassLoaderRegistry} to take care of managing the classes serialized from the daemon.
*/
@ThreadSafe
public class ClientSidePayloadClassLoaderRegistry implements PayloadClassLoaderRegistry {
private static final short CLIENT_CLASS_LOADER_ID = 1;
private final PayloadClassLoaderRegistry delegate;
private final ClasspathInferer classpathInferer;
private final ClassLoaderCache classLoaderCache;
private final Lock lock = new ReentrantLock(); // protects the following state
// Contains only application owned ClassLoaders
private final Map classLoaders = new LinkedHashMap();
public ClientSidePayloadClassLoaderRegistry(PayloadClassLoaderRegistry delegate, ClasspathInferer classpathInferer, ClassLoaderCache classLoaderCache) {
this.delegate = delegate;
this.classpathInferer = classpathInferer;
this.classLoaderCache = classLoaderCache;
}
public SerializeMap newSerializeSession() {
final Set candidates = new LinkedHashSet();
final Set classPath = new LinkedHashSet();
final Map classLoaderIds = new HashMap();
final Map classLoaderDetails = new HashMap();
return new SerializeMap() {
public short visitClass(Class> target) {
ClassLoader classLoader = target.getClassLoader();
Short id = classLoaderIds.get(classLoader);
if (id != null) {
// Already seen this ClassLoader
return id;
}
ClassLoaderDetails details = classLoaderCache.maybeGetDetails(classLoader);
if (details != null) {
// A cached ClassLoader
id = (short) (classLoaderIds.size() + CLIENT_CLASS_LOADER_ID + 1);
classLoaderIds.put(classLoader, id);
classLoaderDetails.put(id, details);
return id;
}
// An application ClassLoader: Inspect class to collect up the classpath for it
classpathInferer.getClassPathFor(target, classPath);
candidates.add(target.getClassLoader());
return CLIENT_CLASS_LOADER_ID;
}
@Override
public void collectClassLoaderDefinitions(Map details) {
lock.lock();
UUID uuid;
try {
uuid = getUuidForLocalClassLoaders(candidates);
} finally {
lock.unlock();
}
details.putAll(classLoaderDetails);
details.put(CLIENT_CLASS_LOADER_ID, new ClassLoaderDetails(uuid, new ClientOwnedClassLoaderSpec(new ArrayList(classPath))));
}
};
}
public DeserializeMap newDeserializeSession() {
final DeserializeMap deserializeMap = delegate.newDeserializeSession();
return new DeserializeMap() {
public Class> resolveClass(ClassLoaderDetails classLoaderDetails, String className) throws ClassNotFoundException {
Set candidates;
lock.lock();
try {
candidates = getClassLoaders(classLoaderDetails.uuid);
} finally {
lock.unlock();
}
if (candidates != null) {
// TODO:ADAM - This isn't quite right
// MB: I think ^ refers to the first capable classloader loading the class. This could be different
// from the loader which originally loaded it, which could pose equality and lifecycle issues.
for (ClassLoader candidate : candidates) {
try {
return candidate.loadClass(className);
} catch (ClassNotFoundException e) {
// Ignore
}
}
throw new UnsupportedOperationException("Unexpected class received in response.");
}
return deserializeMap.resolveClass(classLoaderDetails, className);
}
};
}
private Set getClassLoaders(UUID uuid) {
LocalClassLoaderMapping localClassLoaderMapping = classLoaders.get(uuid);
if (localClassLoaderMapping == null) {
return null;
}
Set candidates = Sets.newLinkedHashSet();
for (WeakReference reference : localClassLoaderMapping.classLoaders) {
ClassLoader classLoader = reference.get();
if (classLoader != null) {
candidates.add(classLoader);
}
}
return candidates;
}
private UUID getUuidForLocalClassLoaders(Set candidates) {
for (LocalClassLoaderMapping localClassLoaderMapping : new ArrayList(classLoaders.values())) {
Set localCandidates = new LinkedHashSet();
for (WeakReference reference : localClassLoaderMapping.classLoaders) {
ClassLoader cl = reference.get();
if (cl != null) {
localCandidates.add(cl);
}
}
if (localCandidates.isEmpty()) {
classLoaders.remove(localClassLoaderMapping.uuid);
continue;
}
if (localCandidates.equals(candidates)) {
return localClassLoaderMapping.uuid;
}
}
LocalClassLoaderMapping details = new LocalClassLoaderMapping(UUID.randomUUID());
for (ClassLoader candidate : candidates) {
details.classLoaders.add(new WeakReference(candidate));
}
classLoaders.put(details.uuid, details);
return details.uuid;
}
private static class LocalClassLoaderMapping {
private final Set> classLoaders = Sets.newLinkedHashSet();
private final UUID uuid;
private LocalClassLoaderMapping(UUID uuid) {
this.uuid = uuid;
}
}
}