package org.netbeans.gradle.project.view; import java.awt.Image; import java.awt.event.ActionEvent; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.Action; import org.jtrim.utils.ExceptionHelper; import org.netbeans.api.project.Project; import org.netbeans.gradle.model.util.CollectionUtils; import org.netbeans.gradle.project.NbGradleProject; import org.netbeans.gradle.project.NbGradleProjectFactory; import org.netbeans.gradle.project.NbIcons; import org.netbeans.gradle.project.NbStrings; import org.netbeans.gradle.project.api.nodes.SingleNodeFactory; import org.netbeans.gradle.project.model.NbGradleProjectTree; import org.netbeans.gradle.project.util.ListenerRegistrations; import org.netbeans.gradle.project.util.StringUtils; import org.openide.loaders.DataFolder; import org.openide.nodes.ChildFactory; import org.openide.nodes.Children; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.openide.util.ContextAwareAction; import org.openide.util.Lookup; import org.openide.util.lookup.Lookups; public final class SubProjectsChildFactory extends ChildFactory.Detachable<SingleNodeFactory> { private static final Logger LOGGER = Logger.getLogger(SubProjectsChildFactory.class.getName()); private final NbGradleProject project; private final List<NbGradleProjectTree> subProjects; private final AtomicReference<NbGradleProjectTree> lastTree; private final boolean root; private final ListenerRegistrations listenerRefs; public SubProjectsChildFactory(NbGradleProject project) { this(project, null, true); } private SubProjectsChildFactory( NbGradleProject project, Collection<? extends NbGradleProjectTree> subProjects, boolean root) { ExceptionHelper.checkNotNullArgument(project, "project"); this.root = root; this.project = project; this.listenerRefs = new ListenerRegistrations(); this.lastTree = new AtomicReference<>(null); if (subProjects != null) { this.subProjects = new ArrayList<>(subProjects); sortModules(this.subProjects); ExceptionHelper.checkNotNullElements(this.subProjects, "subProjects"); } else { this.subProjects = null; } } private static void sortModules(List<NbGradleProjectTree> modules) { Collections.sort(modules, new Comparator<NbGradleProjectTree>(){ @Override public int compare(NbGradleProjectTree o1, NbGradleProjectTree o2) { return StringUtils.STR_CMP.compare(o1.getProjectName(), o2.getProjectName()); } }); } private static boolean hasRelevantDifferences(NbGradleProjectTree tree1, NbGradleProjectTree tree2) { if (tree1 == tree2) { // In practice this happens only when they are nulls. return false; } if (tree1 == null || tree2 == null) { return true; } return !equalsTree(tree1, tree2); } private static boolean equalsTree(NbGradleProjectTree tree1, NbGradleProjectTree tree2) { Collection<NbGradleProjectTree> children1 = tree1.getChildren(); Collection<NbGradleProjectTree> children2 = tree2.getChildren(); if (children1.size() != children2.size()) { return false; } if (children1.isEmpty()) { return true; } Map<String, NbGradleProjectTree> children2Map = getChildrenMap2(tree2); for (NbGradleProjectTree subTree1: children1) { NbGradleProjectTree subTree2 = children2Map.get(subTree1.getProjectName()); if (subTree2 == null) { return false; } if (!equalsTree(subTree1, subTree2)) { return false; } } return true; } private static Map<String, NbGradleProjectTree> getChildrenMap2(NbGradleProjectTree tree) { Collection<NbGradleProjectTree> children = tree.getChildren(); Map<String, NbGradleProjectTree> result = CollectionUtils.newHashMap(children.size()); for (NbGradleProjectTree child: children) { result.put(child.getProjectName(), child); } return result; } private void modelChanged() { NbGradleProjectTree newTree = project.currentModel().getValue().getMainProject(); NbGradleProjectTree prevTree = lastTree.getAndSet(newTree); if (hasRelevantDifferences(prevTree, newTree)) { refresh(false); } } @Override protected void addNotify() { if (root) { listenerRefs.add(project.currentModel().addChangeListener(new Runnable() { @Override public void run() { modelChanged(); } })); } } @Override protected void removeNotify() { listenerRefs.unregisterAll(); } @Override protected Node createNodeForKey(SingleNodeFactory key) { return key.createNode(); } private List<NbGradleProjectTree> getSubProjects() { if (subProjects != null) { return subProjects; } List<NbGradleProjectTree> result = new ArrayList<>( project.currentModel().getValue().getMainProject().getChildren()); sortModules(result); return result; } @Override protected boolean createKeys(List<SingleNodeFactory> toPopulate) { for (final NbGradleProjectTree subProject: getSubProjects()) { toPopulate.add(new SingleNodeFactory() { @Override public Node createNode() { if (subProject.getChildren().isEmpty()) { return new SubModuleNode(project, subProject); } else { return new SubModuleWithChildren(project, subProject); } } }); } return true; } private static Children createSubprojectsChild( NbGradleProject project, Collection<? extends NbGradleProjectTree> children) { return Children.create(new SubProjectsChildFactory(project, children, false), true); } private static Node createSimpleNode(NbGradleProject project) { DataFolder projectFolder = DataFolder.findFolder(project.getProjectDirectory()); return projectFolder.getNodeDelegate().cloneNode(); } private static Action createOpenAction(String caption, Collection<NbGradleProjectTree> projects) { return OpenProjectsAction.createFromModules(caption, projects); } private static Action[] getSubProjectContextActions(NbGradleProjectTree module, Action... defaultActions) { Project project = NbGradleProjectFactory.tryLoadSafeProject(module.getProjectDir()); if (project == null) { return defaultActions; } return getSubProjectContextActions(project, defaultActions); } private static Action[] getSubProjectContextActions(Project project, Action... defaultActions) { ContextActionProvider actionProvider = project.getLookup().lookup(ContextActionProvider.class); if (actionProvider == null) { return defaultActions; } Action[] projectActions = actionProvider.getActions(); Action[] result = new Action[defaultActions.length + projectActions.length + 1]; System.arraycopy(defaultActions, 0, result, 0, defaultActions.length); result[defaultActions.length] = null; System.arraycopy(projectActions, 0, result, defaultActions.length + 1, projectActions.length); return result; } private static Lookup getSubProjectLookup(NbGradleProjectTree module) { Project project = NbGradleProjectFactory.tryLoadSafeProject(module.getProjectDir()); if (project == null) { return Lookups.fixed(module); } else { return Lookups.fixed(module, project); } } private static class SubModuleWithChildren extends FilterNode { private final NbGradleProjectTree module; private final List<NbGradleProjectTree> immediateChildren; private final List<NbGradleProjectTree> children; public SubModuleWithChildren(NbGradleProject project, NbGradleProjectTree module) { this(project, module, module.getChildren()); } private SubModuleWithChildren( NbGradleProject project, NbGradleProjectTree module, Collection<? extends NbGradleProjectTree> children) { super(createSimpleNode(project), createSubprojectsChild(project, children), getSubProjectLookup(module)); this.module = module; this.immediateChildren = Collections.unmodifiableList(GradleProjectChildFactory.getAllChildren(module)); this.children = Collections.unmodifiableList(new ArrayList<>(children)); } @Override public String getName() { return "SubProjectsNode_" + module.getProjectFullName().replace(':', '_'); } @Override public Action[] getActions(boolean context) { return getSubProjectContextActions(module, new OpenSubProjectAction(), createOpenAction(NbStrings.getOpenImmediateSubProjectsCaption(), immediateChildren), createOpenAction(NbStrings.getOpenSubProjectsCaption(), children)); } @Override public Action getPreferredAction() { return new OpenSubProjectAction(); } @Override public String getDisplayName() { return module.getProjectName(); } @Override public Image getIcon(int type) { return NbIcons.getGradleIcon(); } @Override public Image getOpenedIcon(int type) { return getIcon(type); } @Override public boolean canRename() { return false; } } private static class SubModuleNode extends FilterNode { private final NbGradleProjectTree module; public SubModuleNode(NbGradleProject project, NbGradleProjectTree module) { super(Node.EMPTY.cloneNode(), null, getSubProjectLookup(module)); this.module = module; } @Override public Action[] getActions(boolean context) { return getSubProjectContextActions(module, new OpenSubProjectAction()); } @Override public Action getPreferredAction() { return new OpenSubProjectAction(); } @Override public String getName() { return "SubModuleNode_" + module.getProjectFullName().replace(':', '_'); } @Override public String getDisplayName() { return module.getProjectName(); } @Override public Image getIcon(int type) { return NbIcons.getGradleIcon(); } @Override public Image getOpenedIcon(int type) { return getIcon(type); } @Override public boolean canRename() { return false; } } @SuppressWarnings("serial") // don't care private static class OpenSubProjectAction extends AbstractAction implements ContextAwareAction { @Override public int hashCode() { return 5 * getClass().hashCode(); } @Override public boolean equals(Object obj) { if (obj == null) { return false; } return getClass() == obj.getClass(); } @Override public void actionPerformed(ActionEvent e) { LOGGER.warning("SubProjectsChildFactory.OpenSubProjectAction.actionPerformed has been invoked."); } @Override public Action createContextAwareInstance(Lookup actionContext) { final Collection<? extends NbGradleProjectTree> projects = actionContext.lookupAll(NbGradleProjectTree.class); return createOpenAction( NbStrings.getOpenSubProjectCaption(projects), Collections.unmodifiableCollection(projects)); } } }