Package org.netbeans.gradle.project.java.nodes

Source Code of org.netbeans.gradle.project.java.nodes.JavaDependenciesNode$DependenciesChildFactory

package org.netbeans.gradle.project.java.nodes;

import java.awt.Image;
import java.awt.event.ActionEvent;
import java.io.File;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.jtrim.concurrent.TaskExecutor;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.api.project.Project;
import org.netbeans.gradle.model.java.JavaClassPaths;
import org.netbeans.gradle.model.java.JavaSourceSet;
import org.netbeans.gradle.model.util.CollectionUtils;
import org.netbeans.gradle.project.NbGradleProject;
import org.netbeans.gradle.project.NbIcons;
import org.netbeans.gradle.project.NbStrings;
import org.netbeans.gradle.project.NbTaskExecutors;
import org.netbeans.gradle.project.api.nodes.SingleNodeFactory;
import org.netbeans.gradle.project.api.task.CommandCompleteListener;
import org.netbeans.gradle.project.java.JavaExtension;
import org.netbeans.gradle.project.java.model.JavaProjectDependency;
import org.netbeans.gradle.project.java.model.NbJavaModel;
import org.netbeans.gradle.project.java.model.NbJavaModule;
import org.netbeans.gradle.project.tasks.DaemonTaskDef;
import org.netbeans.gradle.project.tasks.DownloadSourcesTask;
import org.netbeans.gradle.project.tasks.GradleDaemonManager;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;

public final class JavaDependenciesNode extends AbstractNode {
    private static final Logger LOGGER = Logger.getLogger(JavaDependenciesNode.class.getName());
    private static final Collator STR_CMP = Collator.getInstance();

    private static final TaskExecutor SOURCES_DOWNLOADER
            = NbTaskExecutors.newExecutor("Sources-downloader", 1);

    private final JavaExtension javaExt;

    public JavaDependenciesNode(JavaExtension javaExt) {
        this(new DependenciesChildFactory(javaExt), javaExt);
    }

    private JavaDependenciesNode(DependenciesChildFactory childFactory, JavaExtension javaExt) {
        super(createChildren(childFactory));

        this.javaExt = javaExt;
    }

    private static Children createChildren(DependenciesChildFactory childFactory) {
        return Children.create(childFactory, true);
    }

    @Override
    public Image getIcon(int type) {
        return NbIcons.getLibrariesIcon();
    }

    @Override
    public Image getOpenedIcon(int type) {
        return getIcon(type);
    }

    @Override
    public String getDisplayName() {
        return NbStrings.getDependenciesNodeCaption();
    }

    @Override
    public Action[] getActions(boolean context) {
        NbGradleProject project = javaExt.getProject().getLookup().lookup(NbGradleProject.class);
        return new Action[]{
            new DownloadSourcesAction(project)
        };
    }

    private static class DependenciesChildFactory
    extends
            ChildFactory.Detachable<SingleNodeFactory>
    implements
            ChangeListener {

        private final JavaExtension javaExt;
        private final AtomicReference<NbJavaModule> lastModule;

        public DependenciesChildFactory(JavaExtension javaExt) {
            ExceptionHelper.checkNotNullArgument(javaExt, "javaExt");

            this.javaExt = javaExt;
            this.lastModule = new AtomicReference<>(null);
        }

        @Override
        public void stateChanged(ChangeEvent e) {
            NbJavaModule newModule = javaExt.getCurrentModel().getMainModule();
            NbJavaModule prevModule = lastModule.getAndSet(newModule);

            if (hasRelevantDifferences(newModule, prevModule)) {
                refresh(false);
            }
        }

        private static boolean hasRelevantDifferences(NbJavaModule module1, NbJavaModule module2) {
            if (module1 == module2) {
                // In practice this happens only when they are nulls.
                return false;
            }
            if (module1 == null || module2 == null) {
                return true;
            }

            if (module1.getSources().size() != module2.getSources().size()) {
                return true;
            }

            Map<String, JavaSourceSet> sources2 = getSourceSetMap(module2);
            for (JavaSourceSet sourceSet1: module1.getSources()) {
                JavaSourceSet sourceSet2 = sources2.get(sourceSet1.getName());
                if (sourceSet2 == null) {
                    return true;
                }

                if (!isClassPathSame(sourceSet1, sourceSet2)) {
                    return true;
                }
            }
            return false;
        }

        private static boolean isClassPathSame(JavaSourceSet sourceSet1, JavaSourceSet sourceSet2) {
            JavaClassPaths classpaths1 = sourceSet1.getClasspaths();
            JavaClassPaths classpaths2 = sourceSet2.getClasspaths();

            if (!classpaths1.getCompileClasspaths().equals(classpaths2.getCompileClasspaths())) {
                return false;
            }

            return classpaths1.getRuntimeClasspaths().equals(classpaths2.getRuntimeClasspaths());
        }

        private static Map<String, JavaSourceSet> getSourceSetMap(NbJavaModule module) {
            Map<String, JavaSourceSet> result = CollectionUtils.newHashMap(module.getSources().size());
            for (JavaSourceSet sourceSet: module.getSources()) {
                result.put(sourceSet.getName(), sourceSet);
            }
            return result;
        }

        @Override
        protected void addNotify() {
            lastModule.set(javaExt.getCurrentModel().getMainModule());
            javaExt.addModelChangeListener(this);
        }

        @Override
        protected void removeNotify() {
            javaExt.removeModelChangeListener(this);
        }

        private void addDependencyGroup(
                final String groupName,
                final Collection<? extends SingleNodeFactory> dependencies,
                List<SingleNodeFactory> toPopulate) {

            if (dependencies.isEmpty()) {
                return;
            }

            toPopulate.add(new SingleNodeFactory() {
                @Override
                public Node createNode() {
                    return new AbstractNode(Children.create(new DependencyGroupChildFactory(dependencies), true)) {
                        @Override
                        public Image getIcon(int type) {
                            return NbIcons.getLibrariesIcon();
                        }

                        @Override
                        public Image getOpenedIcon(int type) {
                            return getIcon(type);
                        }

                        @Override
                        public String getDisplayName() {
                            return groupName;
                        }
                    };
                }
            });
        }

        private static List<SingleNodeFactory> filesToNodes(NbJavaModel currentModel, Collection<File> files) {
            List<SingleNodeFactory> result = new ArrayList<>(files.size());
            for (File file: files) {
                JavaProjectDependency projectDep = currentModel.tryGetDepedency(file);
                if (projectDep == null) {
                    result.add(new FileDependency(file));
                }
                else {
                    result.add(new ProjectDependencyFactory(projectDep));
                }
            }
            return result;
        }

        private static String listToString(Collection<?> list) {
            StringBuilder result = new StringBuilder();
            boolean first = true;
            for (Object element: list) {
                if (first) {
                    first = false;
                }
                else {
                    result.append(", ");
                }
                result.append(element != null ? element.toString() : "null");
            }
            return result.toString();
        }

        private static String getBaseDependencyGroupName(
                DependencyType dependencyType,
                JavaSourceSet sourceSet) {

            String sourceSetName = sourceSet.getName();

            switch (dependencyType) {
                case COMPILE:
                    return NbStrings.getCompileForSourceSet(sourceSetName);
                case RUNTIME:
                    return NbStrings.getRuntimeForSourceSet(sourceSetName);
                case PROVIDED:
                    return NbStrings.getProvidedForSourceSet(sourceSetName);
                default:
                    throw new AssertionError(dependencyType.name());
            }
        }

        private String getNameForDependencyGroup(
                DependencyType dependencyType,
                JavaSourceSet sourceSet,
                Map<String, Set<String>> sourceSetDependencyGraph) {

            String baseName = getBaseDependencyGroupName(dependencyType, sourceSet);
            Set<String> dependencies = sourceSetDependencyGraph.get(sourceSet.getName());
            if (dependencies == null || dependencies.isEmpty()) {
                return baseName;
            }
            else {
                return NbStrings.getSourceSetInherits(baseName, listToString(dependencies));
            }
        }

        private static int compareProjectDependencyNodes(ProjectDependencyFactory node1, ProjectDependencyFactory node2) {
            NbJavaModule module1 = node1.projectDep.tryGetModule();
            NbJavaModule module2 = node2.projectDep.tryGetModule();

            if (module1 == module2) {
                return 0;
            }

            if (module1 == null) {
                return 1;
            }
            if (module2 == null) {
                return -1;
            }

            return STR_CMP.compare(module1.getShortName(), module2.getShortName());
        }

        private static int compareFileDependencyNodes(FileDependency node1, FileDependency node2) {
            String name1 = node1.file.getName();
            String name2 = node2.file.getName();

            return STR_CMP.compare(name1, name2);
        }

        private static int compareDependencyNodes(SingleNodeFactory node1, SingleNodeFactory node2) {
            if (node1 instanceof ProjectDependencyFactory) {
                if (node2 instanceof ProjectDependencyFactory) {
                    return compareProjectDependencyNodes(
                            (ProjectDependencyFactory)node1,
                            (ProjectDependencyFactory)node2);
                }
                else {
                    return -1;
                }
            }
            else if (node2 instanceof ProjectDependencyFactory) {
                return 1;
            }
            else if (node1 instanceof FileDependency && node2 instanceof FileDependency) {
                return compareFileDependencyNodes((FileDependency)node1, (FileDependency)node2);
            }
            else {
                return 0;
            }
        }

        private static List<SingleNodeFactory> sortDependencyNodes(List<SingleNodeFactory> nodes) {
            SingleNodeFactory[] nodesArray = nodes.toArray(new SingleNodeFactory[nodes.size()]);

            Arrays.sort(nodesArray, new Comparator<SingleNodeFactory>() {
                @Override
                public int compare(SingleNodeFactory o1, SingleNodeFactory o2) {
                    return compareDependencyNodes(o1, o2);
                }
            });

            return Arrays.asList(nodesArray);
        }

        private void addSourceSetDependencyNodes(
                NbJavaModel currentModel,
                String nodeGroupName,
                Collection<String> sourceSetDependencies,
                Set<File> classpaths, // IN/OUT
                List<SingleNodeFactory> toPopulate) {

            NbJavaModule mainModule = currentModel.getMainModule();
            classpaths.removeAll(mainModule.getAllBuildOutputs());

            for (String inheritedName: sourceSetDependencies) {
                JavaSourceSet inherited = mainModule.tryGetSourceSetByName(inheritedName);
                if (inherited != null) {
                    classpaths.removeAll(inherited.getClasspaths().getCompileClasspaths());
                    classpaths.removeAll(inherited.getClasspaths().getRuntimeClasspaths());
                    classpaths.remove(inherited.getOutputDirs().getClassesDir());
                    classpaths.remove(inherited.getOutputDirs().getResourcesDir());
                    classpaths.removeAll(inherited.getOutputDirs().getOtherDirs());
                }
            }

            List<SingleNodeFactory> dependencyNodes = filesToNodes(currentModel, classpaths);
            dependencyNodes = sortDependencyNodes(dependencyNodes);

            addDependencyGroup(nodeGroupName, dependencyNodes, toPopulate);
        }

        private static <T> Set<T> splitSets(Set<T> set1, Set<T> set2) {
            Set<T> smallerSet;
            Set<T> largerSet;

            if (set1.size() < set2.size()) {
                smallerSet = set1;
                largerSet = set2;
            }
            else {
                smallerSet = set2;
                largerSet = set1;
            }

            Set<T> intersect = new HashSet<>();
            for (T element: smallerSet) {
                if (largerSet.remove(element)) {
                    intersect.add(element);
                }
            }
            smallerSet.removeAll(intersect);

            return intersect;
        }

        private static Map<String, Set<String>> sourceSetDependencyGraph(
                NbJavaModule mainModule) {

            Map<File, String> buildOutput = new HashMap<>();
            for (JavaSourceSet sourceSet: mainModule.getSources()) {
                buildOutput.put(sourceSet.getOutputDirs().getClassesDir(), sourceSet.getName());
            }

            Map<String, Set<String>> result = new HashMap<>();
            for (JavaSourceSet sourceSet: mainModule.getSources()) {
                String sourceSetName = sourceSet.getName();

                Set<File> runtimeClasspaths = sourceSet.getClasspaths().getRuntimeClasspaths();

                Set<String> dependencies = new HashSet<>();
                for (Map.Entry<File, String> entry: buildOutput.entrySet()) {
                    if (runtimeClasspaths.contains(entry.getKey())) {
                        String dependencyName = entry.getValue();
                        if (!sourceSetName.equals(dependencyName)) {
                            dependencies.add(entry.getValue());
                        }
                    }
                }

                result.put(sourceSetName, dependencies);
            }

            // TODO: Remove redundant edges

            return result;
        }

        private void readKeys(List<SingleNodeFactory> toPopulate) throws DataObjectNotFoundException {
            NbJavaModel currentModel = javaExt.getCurrentModel();
            NbJavaModule mainModule = currentModel.getMainModule();

            Map<String, Set<String>> dependencyGraph = sourceSetDependencyGraph(mainModule);

            for (JavaSourceSet sourceSet: mainModule.getSources()) {
                JavaClassPaths classpaths = sourceSet.getClasspaths();

                Set<File> providedClassPaths = new HashSet<>(classpaths.getCompileClasspaths());
                Set<File> runtimeClassPaths = new HashSet<>(classpaths.getRuntimeClasspaths());
                Set<File> compileClassPaths = splitSets(providedClassPaths, runtimeClassPaths);

                Set<String> sourceDependencies = dependencyGraph.get(sourceSet.getName());
                if (sourceDependencies == null) {
                    sourceDependencies = Collections.emptySet();
                }

                addSourceSetDependencyNodes(
                        currentModel,
                        getNameForDependencyGroup(DependencyType.COMPILE, sourceSet, dependencyGraph),
                        sourceDependencies,
                        compileClassPaths,
                        toPopulate);

                addSourceSetDependencyNodes(
                        currentModel,
                        getNameForDependencyGroup(DependencyType.PROVIDED, sourceSet, dependencyGraph),
                        sourceDependencies,
                        providedClassPaths,
                        toPopulate);

                addSourceSetDependencyNodes(
                        currentModel,
                        getNameForDependencyGroup(DependencyType.RUNTIME, sourceSet, dependencyGraph),
                        sourceDependencies,
                        runtimeClassPaths,
                        toPopulate);
            }

            LOGGER.fine("Dependencies for the Gradle project were found.");
        }

        @Override
        protected boolean createKeys(List<SingleNodeFactory> toPopulate) {
            try {
                readKeys(toPopulate);
            } catch (DataObjectNotFoundException ex) {
                throw new RuntimeException(ex);
            }
            return true;
        }

        @Override
        protected Node createNodeForKey(SingleNodeFactory key) {
            return key.createNode();
        }
    }

    private static class DependencyGroupChildFactory extends ChildFactory<SingleNodeFactory> {
        private final List<SingleNodeFactory> dependencies;

        public DependencyGroupChildFactory(Collection<? extends SingleNodeFactory> dependencies) {
            this.dependencies = new ArrayList<>(dependencies);
        }

        protected void readKeys(List<SingleNodeFactory> toPopulate) throws DataObjectNotFoundException {
            toPopulate.addAll(dependencies);
        }

        @Override
        protected boolean createKeys(List<SingleNodeFactory> toPopulate) {
            try {
                readKeys(toPopulate);
            } catch (DataObjectNotFoundException ex) {
                throw new RuntimeException(ex);
            }
            return true;
        }

        @Override
        protected Node createNodeForKey(SingleNodeFactory key) {
            return key.createNode();
        }
    }

    private static final class FileDependency implements SingleNodeFactory {
        private final File file;

        public FileDependency(File file) {
            assert file != null;

            this.file = file;
        }

        private Node createPlainNode() {
            return new FilterNode(Node.EMPTY) {
                @Override
                public String getDisplayName() {
                    return file.getName();
                }

                @Override
                public Image getIcon(int type) {
                    return NbIcons.getFolderIcon();
                }

                @Override
                public Image getOpenedIcon(int type) {
                    return getIcon(type);
                }
            };
        }

        @Override
        public Node createNode() {
            File normalizedFile = FileUtil.normalizeFile(file);
            FileObject fileObj = normalizedFile != null ? FileUtil.toFileObject(normalizedFile) : null;
            if (fileObj == null) {
                LOGGER.log(Level.WARNING, "Dependency is not available: {0}", file);
                return createPlainNode();
            }

            final DataObject dataObj;
            try {
                dataObj = DataObject.find(fileObj);
            } catch (DataObjectNotFoundException ex) {
                LOGGER.log(Level.INFO, "Unexpected DataObjectNotFoundException for file: " + file, ex);
                return createPlainNode();
            }
            return dataObj.getNodeDelegate().cloneNode();
        }
    }

    private static final class ProjectDependencyFactory implements SingleNodeFactory {
        private final JavaProjectDependency projectDep;

        public ProjectDependencyFactory(JavaProjectDependency projectDep) {
            this.projectDep = projectDep;
        }

        private DataObject getProjectDirObj() {
            Project project = projectDep.getProjectReference().tryGetProject();
            if (project == null) {
                return null;
            }
            try {
                return DataObject.find(project.getProjectDirectory());
            } catch (DataObjectNotFoundException ex) {
                LOGGER.log(Level.INFO, "Failed to find node for project directory: " + project.getProjectDirectory(), ex);
                return null;
            }
        }

        @Override
        public Node createNode() {
            DataObject fileObject = getProjectDirObj();

            Node baseNode = fileObject != null
                    ? fileObject.getNodeDelegate()
                    : Node.EMPTY;

            // TODO: Update the created node if the underlying project is reloaded.
            return new FilterNode(baseNode.cloneNode()) {
                @Override
                public Image getIcon(int type) {
                    return NbIcons.getGradleIcon();
                }

                @Override
                public Image getOpenedIcon(int type) {
                    return getIcon(type);
                }

                @Override
                public String getDisplayName() {
                    NbJavaModule module = projectDep.tryGetModule();
                    return module != null
                            ? module.getShortName()
                            : super.getDisplayName();
                }
            };
        }
    }

    private enum DependencyType {
        COMPILE,
        RUNTIME,
        PROVIDED
    }

    @SuppressWarnings("serial")
    private static final class DownloadSourcesAction extends AbstractAction {
        private final NbGradleProject project;

        public DownloadSourcesAction(NbGradleProject project) {
            super(NbStrings.getDownloadSources());
            this.project = project;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            DaemonTaskDef taskDef = DownloadSourcesTask.createTaskDef(project);
            GradleDaemonManager.submitGradleTask(SOURCES_DOWNLOADER, taskDef, new CommandCompleteListener() {
                @Override
                public void onComplete(Throwable error) {
                    if (error != null) {
                        project.displayError(NbStrings.getDownloadSourcesFailure(), error);
                    }
                }
            });
        }
    }
}
TOP

Related Classes of org.netbeans.gradle.project.java.nodes.JavaDependenciesNode$DependenciesChildFactory

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.