package org.netbeans.gradle.project.query;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.File;
import java.io.FilenameFilter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.SwingUtilities;
import org.jtrim.concurrent.UpdateTaskExecutor;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.gradle.project.properties.ScriptPlatform;
import org.netbeans.gradle.project.properties.global.CommonGlobalSettings;
import org.netbeans.gradle.project.script.DefaultScriptFileProvider;
import org.netbeans.gradle.project.script.ScriptFileProvider;
import org.netbeans.gradle.project.util.NbTaskExecutors;
import org.netbeans.spi.java.classpath.ClassPathFactory;
import org.netbeans.spi.java.classpath.ClassPathImplementation;
import org.netbeans.spi.java.classpath.ClassPathProvider;
import org.netbeans.spi.java.classpath.PathResourceImplementation;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.openide.filesystems.FileObject;
import org.openide.util.lookup.ServiceProvider;
import org.openide.util.lookup.ServiceProviders;
@ServiceProviders({@ServiceProvider(service = ClassPathProvider.class)})
public final class GradleFilesClassPathProvider implements ClassPathProvider {
private static final Logger LOGGER = Logger.getLogger(GradleFilesClassPathProvider.class.getName());
private volatile boolean initialized;
private final ReentrantLock initLock;
private final ConcurrentMap<ClassPathType, List<PathResourceImplementation>> classpathResources;
private final Map<ClassPathType, ClassPath> classpaths;
private final UpdateTaskExecutor classpathUpdateExecutor;
private final ScriptFileProvider scriptProvider;
private final PropertyChangeSupport changes;
public GradleFilesClassPathProvider() {
this(new DefaultScriptFileProvider());
}
public GradleFilesClassPathProvider(ScriptFileProvider scriptProvider) {
ExceptionHelper.checkNotNullArgument(scriptProvider, "scriptProvider");
this.scriptProvider = scriptProvider;
this.initLock = new ReentrantLock();
this.initialized = false;
this.classpaths = new EnumMap<>(ClassPathType.class);
this.classpathResources = new ConcurrentHashMap<>();
this.classpathUpdateExecutor = NbTaskExecutors.newDefaultUpdateExecutor();
EventSource eventSource = new EventSource();
this.changes = new PropertyChangeSupport(eventSource);
eventSource.init(this.changes);
}
// These PropertyChangeListener methods are declared because
// for some reason, NetBeans want to use them through reflection.
public void addPropertyChangeListener(PropertyChangeListener listener) {
changes.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
changes.removePropertyChangeListener(listener);
}
private ClassPath createClassPath(ClassPathType classPathType) {
return ClassPathFactory.createClassPath(new GradleClassPaths(classPathType));
}
private static URL[] getGradleBinaries() {
FileObject gradleHome = CommonGlobalSettings.getDefault().tryGetGradleInstallation();
if (gradleHome == null) {
return new URL[0];
}
return GradleHomeClassPathProvider.getGradleLibs(gradleHome, new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
String lowerCaseName = name.toLowerCase(Locale.US);
return !lowerCaseName.startsWith("groovy-") && lowerCaseName.endsWith(".jar");
}
});
}
private void updateClassPathResources() {
URL[] jars = getGradleBinaries();
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,
"Updating the .gradle file classpaths to: {0}",
Arrays.toString(jars));
}
List<PathResourceImplementation> jarResources = new ArrayList<>(jars.length);
for (URL jar: jars) {
jarResources.add(ClassPathSupport.createResource(jar));
}
classpathResources.put(ClassPathType.COMPILE, jarResources);
classpathResources.put(ClassPathType.RUNTIME, jarResources);
ScriptPlatform platform = CommonGlobalSettings.getDefault().defaultJdk().getActiveValue();
if (platform != null) {
List<ClassPath.Entry> classpathEntries = platform.getJavaPlatform().getBootstrapLibraries().entries();
List<PathResourceImplementation> platformResources = new ArrayList<>(classpathEntries.size());
for (ClassPath.Entry entry: classpathEntries) {
platformResources.add(ClassPathSupport.createResource(entry.getURL()));
}
classpathResources.put(ClassPathType.BOOT, Collections.unmodifiableList(platformResources));
}
}
private void setupClassPaths() {
updateClassPathResources();
classpaths.put(ClassPathType.BOOT, createClassPath(ClassPathType.BOOT));
classpaths.put(ClassPathType.COMPILE, createClassPath(ClassPathType.COMPILE));
classpaths.put(ClassPathType.RUNTIME, createClassPath(ClassPathType.RUNTIME));
}
private void init() {
if (initialized) {
return;
}
initLock.lock();
try {
if (!initialized) {
unsafeInit();
}
} finally {
initialized = true;
initLock.unlock();
}
}
private void scheduleUpdateClassPath() {
classpathUpdateExecutor.execute(new Runnable() {
@Override
public void run() {
updateClassPathResources();
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
changes.firePropertyChange(ClassPathImplementation.PROP_RESOURCES, null, null);
}
});
}
});
}
private void unsafeInit() {
assert initLock.isHeldByCurrentThread();
Runnable changeListener = new Runnable() {
@Override
public void run() {
scheduleUpdateClassPath();
}
};
CommonGlobalSettings defaultSettings = CommonGlobalSettings.getDefault();
defaultSettings.gradleLocation().getActiveSource().addChangeListener(changeListener);
defaultSettings.defaultJdk().getActiveSource().addChangeListener(changeListener);
setupClassPaths();
}
@Override
public ClassPath findClassPath(FileObject file, String type) {
if (!scriptProvider.isScriptFileName(file.getNameExt())) {
return null;
}
init();
ClassPathType classPathType = getClassPathType(type);
if (classPathType == null) {
return null;
}
ClassPath classpath = classpaths.get(classPathType);
if (classpath != null) {
return classpath;
}
setupClassPaths();
return classpaths.get(classPathType);
}
private static ClassPathType getClassPathType(String type) {
if (type == null) {
return null;
}
switch (type) {
case ClassPath.SOURCE:
return null;
case ClassPath.BOOT:
return ClassPathType.BOOT;
case ClassPath.COMPILE:
return ClassPathType.COMPILE;
case ClassPath.EXECUTE:
return ClassPathType.RUNTIME;
case JavaClassPathConstants.PROCESSOR_PATH:
return ClassPathType.COMPILE;
default:
return null;
}
}
private class GradleClassPaths implements ClassPathImplementation {
private final ClassPathType classPathType;
public GradleClassPaths(ClassPathType classPathType) {
assert classPathType != null;
this.classPathType = classPathType;
}
@Override
public List<PathResourceImplementation> getResources() {
List<PathResourceImplementation> result = classpathResources.get(classPathType);
return result != null
? result
: Collections.<PathResourceImplementation>emptyList();
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
changes.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
changes.removePropertyChangeListener(listener);
}
}
private enum ClassPathType {
BOOT,
COMPILE,
RUNTIME
}
private static final class EventSource implements ClassPathImplementation {
private volatile PropertyChangeSupport changes;
public void init(PropertyChangeSupport changes) {
assert changes != null;
this.changes = changes;
}
@Override
public List<PathResourceImplementation> getResources() {
return Collections.emptyList();
}
@Override
public void addPropertyChangeListener(PropertyChangeListener listener) {
changes.addPropertyChangeListener(listener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener listener) {
changes.removePropertyChangeListener(listener);
}
}
}