package org.netbeans.gradle.project.java.query; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Objects; import javax.swing.event.ChangeListener; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.model.java.JavaOutputDirs; import org.netbeans.gradle.model.java.JavaSourceGroup; import org.netbeans.gradle.model.java.JavaSourceSet; import org.netbeans.gradle.project.java.JavaExtension; import org.netbeans.gradle.project.java.JavaModelChangeListener; import org.netbeans.gradle.project.java.model.NbJavaModule; import org.netbeans.gradle.project.query.AbstractSourceForBinaryQuery; import org.netbeans.gradle.project.util.LazyChangeSupport; import org.netbeans.gradle.project.util.NbFileUtils; import org.netbeans.gradle.project.util.NbSupplier; import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation2; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileUtil; public final class GradleSourceForBinaryQuery extends AbstractSourceForBinaryQuery implements JavaModelChangeListener { // First, I thought this is an important query for the debugger but this // query is not really used by the debugger. // // Regardless, this query is important for the following reason: // NetBeans detects project dependencies the following way: // // First it queries the compile time dependencies, then it tries to look // up the sources of the copile time dependencies. If the sources should be // preferred over the binaries then it will not complain for the missing // binaries if the sources are available. Therefore the directory where the // compiled binaries of the project dependency will be stored are added to // the compile time classpath of the project. Adding sources of other // projects to the source path will confuse NetBeans. private static final FileObject[] NO_ROOTS = new FileObject[0]; private final NbSupplier<? extends NbJavaModule> moduleProvider; private final LazyChangeSupport changes; public GradleSourceForBinaryQuery(final JavaExtension javaExt) { this(new NbSupplier<NbJavaModule>() { @Override public NbJavaModule get() { return javaExt.getCurrentModel().getMainModule(); } }); ExceptionHelper.checkNotNullArgument(javaExt, "javaExt"); } public GradleSourceForBinaryQuery(NbSupplier<? extends NbJavaModule> moduleProvider) { ExceptionHelper.checkNotNullArgument(moduleProvider, "moduleProvider"); this.moduleProvider = moduleProvider; this.changes = LazyChangeSupport.createSwing(new EventSource()); } private static List<File> tryGetSourceRoots(NbJavaModule module, File binaryRoot) { List<JavaSourceSet> jarSources = module.getSourceSetsForJarOutput(binaryRoot); if (!jarSources.isEmpty()) { List<File> result = new ArrayList<>(); for (JavaSourceSet sourceSet: module.getSources()) { for (JavaSourceGroup sourceGroup: sourceSet.getSourceGroups()) { result.addAll(sourceGroup.getSourceRoots()); } } return result; } for (JavaSourceSet sourceSet: module.getSources()) { JavaOutputDirs outputDirs = sourceSet.getOutputDirs(); if (Objects.equals(outputDirs.getClassesDir(), binaryRoot)) { List<File> result = new ArrayList<>(); for (JavaSourceGroup sourceGroup: sourceSet.getSourceGroups()) { result.addAll(sourceGroup.getSourceRoots()); } return result; } } return null; } private static FileObject[] getSourceRoots( NbJavaModule module, File binaryRoot) { List<File> srcRoots = tryGetSourceRoots(module, binaryRoot); if (srcRoots == null) { return NO_ROOTS; } List<FileObject> result = new ArrayList<>(srcRoots.size()); for (File srcRoot: srcRoots) { FileObject rootObj = FileUtil.toFileObject(srcRoot); if (rootObj != null) { result.add(rootObj); } } return result.toArray(new FileObject[result.size()]); } @Override public void onModelChange() { changes.fireChange(); } @Override protected File normalizeBinaryPath(File binaryRoot) { NbJavaModule mainModule = moduleProvider.get(); // Is Jar output? if (!mainModule.getSourceSetsForJarOutput(binaryRoot).isEmpty()) { return binaryRoot; } for (JavaSourceSet sourceSet: mainModule.getSources()) { JavaOutputDirs outputDirs = sourceSet.getOutputDirs(); File classesDir = outputDirs.getClassesDir(); if (NbFileUtils.isParentOrSame(classesDir, binaryRoot)) { return classesDir; } } return null; } @Override protected Result tryFindSourceRoot(final File binaryRoot) { NbJavaModule mainModule = moduleProvider.get(); if (tryGetSourceRoots(mainModule, binaryRoot) == null) { return null; } return new SourceForBinaryQueryImplementation2.Result() { @Override public boolean preferSources() { return getRoots().length > 0; } @Override public FileObject[] getRoots() { NbJavaModule mainModule = moduleProvider.get(); return getSourceRoots(mainModule, binaryRoot); } @Override public void addChangeListener(ChangeListener listener) { changes.addChangeListener(listener); } @Override public void removeChangeListener(ChangeListener listener) { changes.removeChangeListener(listener); } @Override public String toString() { return Arrays.toString(getRoots()); } }; } private static final class EventSource implements SourceForBinaryQueryImplementation2.Result, LazyChangeSupport.Source { private volatile LazyChangeSupport changes; @Override public void init(LazyChangeSupport changes) { assert changes != null; this.changes = changes; } @Override public boolean preferSources() { return true; } @Override public FileObject[] getRoots() { return NO_ROOTS; } @Override public void addChangeListener(ChangeListener l) { changes.addChangeListener(l); } @Override public void removeChangeListener(ChangeListener l) { changes.removeChangeListener(l); } } }