package org.netbeans.gradle.project.view;
import java.awt.Image;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.Action;
import org.jtrim.event.ListenerRef;
import org.jtrim.swing.concurrent.SwingTaskExecutor;
import org.jtrim.utils.ExceptionHelper;
import org.netbeans.gradle.project.NbGradleProject;
import org.netbeans.gradle.project.NbIcons;
import org.netbeans.gradle.project.NbStrings;
import org.netbeans.gradle.project.api.event.NbListenerRef;
import org.netbeans.gradle.project.api.event.NbListenerRefs;
import org.netbeans.gradle.project.api.nodes.GradleProjectExtensionNodes;
import org.netbeans.gradle.project.api.nodes.ManualRefreshedNodes;
import org.netbeans.gradle.project.api.nodes.SingleNodeFactory;
import org.netbeans.gradle.project.event.GenericChangeListenerManager;
import org.netbeans.gradle.project.event.PausableChangeListenerManager;
import org.netbeans.gradle.project.model.ModelRefreshListener;
import org.netbeans.gradle.project.model.NbGradleModel;
import org.netbeans.gradle.project.model.NbGradleProjectTree;
import org.netbeans.gradle.project.util.ListenerRegistrations;
import org.netbeans.gradle.project.util.RefreshableChildren;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.lookup.Lookups;
public final class GradleProjectChildFactory
extends
ChildFactory.Detachable<SingleNodeFactory>
implements
RefreshableChildren {
private static final Logger LOGGER = Logger.getLogger(GradleProjectChildFactory.class.getName());
private final NbGradleProject project;
private final GradleProjectLogicalViewProvider parent;
private final AtomicReference<NodeExtensions> nodeExtensionsRef;
private final AtomicBoolean lastHasSubprojects;
private final ListenerRegistrations listenerRefs;
private final PausableChangeListenerManager refreshNotifier;
// Nodes comming from the NodeFactory.Registration annotation
private final AnnotationChildNodes annotationChildNodes;
private volatile boolean createdOnce;
public GradleProjectChildFactory(NbGradleProject project, GradleProjectLogicalViewProvider parent) {
ExceptionHelper.checkNotNullArgument(project, "project");
ExceptionHelper.checkNotNullArgument(parent, "parent");
this.project = project;
this.parent = parent;
this.nodeExtensionsRef = new AtomicReference<>(NodeExtensions.EMPTY);
this.lastHasSubprojects = new AtomicBoolean(false);
this.listenerRefs = new ListenerRegistrations();
this.annotationChildNodes = new AnnotationChildNodes(project);
this.createdOnce = false;
this.refreshNotifier = new GenericChangeListenerManager(SwingTaskExecutor.getStrictExecutor(false));
this.refreshNotifier.registerListener(new Runnable() {
@Override
public void run() {
if (createdOnce) {
refresh(false);
}
}
});
}
private NbGradleModel getShownModule() {
return project.currentModel().getValue();
}
private List<GradleProjectExtensionNodes> getExtensionNodes() {
List<GradleProjectExtensionNodes> result = new ArrayList<>(
project.getExtensions().lookupAllExtensionObjs(GradleProjectExtensionNodes.class));
return result;
}
@Override
public void refreshChildren() {
refreshNotifier.fireEventually();
}
private boolean hasSubProjects() {
return !getShownModule().getMainProject().getChildren().isEmpty();
}
private ListenerRef registerModelRefreshListener() {
return parent.addChildModelRefreshListener(new ModelRefreshListener() {
private final AtomicReference<PausableChangeListenerManager.PauseRef> pauseRef
= new AtomicReference<>(null);
@Override
public void startRefresh() {
PausableChangeListenerManager.PauseRef prevRef = pauseRef.getAndSet(refreshNotifier.pauseManager());
if (prevRef != null) {
prevRef.unpause();
}
}
@Override
public void endRefresh(boolean extensionsChanged) {
if (extensionsChanged) {
refreshNotifier.fireEventually();
}
PausableChangeListenerManager.PauseRef prevRef = pauseRef.getAndSet(null);
if (prevRef != null) {
prevRef.unpause();
}
}
});
}
private boolean tryReplaceNodeExtensionAndClose(NodeExtensions newExtensions) {
NodeExtensions prevValue;
do {
prevValue = nodeExtensionsRef.get();
if (prevValue == null) {
if (newExtensions != null) {
newExtensions.close();
}
return false;
}
} while (!nodeExtensionsRef.compareAndSet(prevValue, newExtensions));
prevValue.close();
return true;
}
private void updateNodesIfNeeded(NodeExtensions newNodeExtensions) {
if (!tryReplaceNodeExtensionAndClose(newNodeExtensions)) {
return;
}
boolean newHasSubProjects = hasSubProjects();
boolean prevHasSubProjects = lastHasSubprojects.getAndSet(newHasSubProjects);
if (newHasSubProjects != prevHasSubProjects) {
refreshChildren();
}
if (newNodeExtensions.isNeedRefreshOnProjectReload()) {
refreshChildren();
}
}
@Override
protected void addNotify() {
final Runnable simpleChangeListener = new Runnable() {
@Override
public void run() {
refreshChildren();
}
};
annotationChildNodes.addNotify();
listenerRefs.add(annotationChildNodes.nodeFactories().addChangeListener(simpleChangeListener));
listenerRefs.add(project.currentModel().addChangeListener(new Runnable() {
@Override
public void run() {
NodeExtensions newNodeExtensions
= NodeExtensions.create(getExtensionNodes(), simpleChangeListener);
updateNodesIfNeeded(newNodeExtensions);
}
}));
listenerRefs.add(NbListenerRefs.fromRunnable(new Runnable() {
@Override
public void run() {
tryReplaceNodeExtensionAndClose(null);
}
}));
listenerRefs.add(registerModelRefreshListener());
lastHasSubprojects.set(hasSubProjects());
List<GradleProjectExtensionNodes> extensionNodes = getExtensionNodes();
for (GradleProjectExtensionNodes singleExtensionNodes: extensionNodes) {
listenerRefs.add(singleExtensionNodes.addNodeChangeListener(simpleChangeListener));
}
}
@Override
protected void removeNotify() {
listenerRefs.unregisterAll();
annotationChildNodes.removeNotify();
}
@Override
protected Node createNodeForKey(SingleNodeFactory key) {
return key.createNode();
}
private void addProjectFiles(List<SingleNodeFactory> toPopulate) throws DataObjectNotFoundException {
toPopulate.add(BuildScriptsNode.getFactory(project));
}
private Node createSimpleNode() {
DataFolder projectFolder = DataFolder.findFolder(project.getProjectDirectory());
return projectFolder.getNodeDelegate().cloneNode();
}
private Children createSubprojectsChild() {
return Children.create(new SubProjectsChildFactory(project), true);
}
private static Action createOpenAction(String caption,
Collection<? extends NbGradleProjectTree> modules) {
return OpenProjectsAction.createFromModules(caption, modules);
}
private static void getAllChildren(NbGradleProjectTree module, List<NbGradleProjectTree> result) {
Collection<NbGradleProjectTree> children = module.getChildren();
result.addAll(children);
for (NbGradleProjectTree child: children) {
getAllChildren(child, result);
}
}
public static List<NbGradleProjectTree> getAllChildren(NbGradleProjectTree module) {
List<NbGradleProjectTree> result = new ArrayList<>();
getAllChildren(module, result);
return result;
}
private static List<NbGradleProjectTree> getAllChildren(NbGradleModel model) {
List<NbGradleProjectTree> result = new ArrayList<>();
getAllChildren(model.getMainProject(), result);
return result;
}
private void addChildren(List<SingleNodeFactory> toPopulate) {
final NbGradleModel shownModule = getShownModule();
final Collection<NbGradleProjectTree> immediateChildren
= shownModule.getMainProject().getChildren();
if (immediateChildren.isEmpty()) {
return;
}
final List<NbGradleProjectTree> children = getAllChildren(shownModule);
toPopulate.add(new SingleNodeFactory() {
@Override
public Node createNode() {
return new FilterNode(
createSimpleNode(),
createSubprojectsChild(),
Lookups.singleton(shownModule.getMainProject())) {
@Override
public String getName() {
return "SubProjectsNode_" + getShownModule().getMainProject().getProjectFullName().replace(':', '_');
}
@Override
public Action[] getActions(boolean context) {
return new Action[] {
createOpenAction(NbStrings.getOpenImmediateSubProjectsCaption(), immediateChildren),
createOpenAction(NbStrings.getOpenSubProjectsCaption(), children)
};
}
@Override
public String getDisplayName() {
return NbStrings.getSubProjectsCaption();
}
@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 void readKeys(List<SingleNodeFactory> toPopulate) throws DataObjectNotFoundException {
toPopulate.addAll(annotationChildNodes.nodeFactories().getValue());
List<GradleProjectExtensionNodes> extensionNodes = getExtensionNodes();
if (extensionNodes != null) {
for (GradleProjectExtensionNodes nodes: extensionNodes) {
toPopulate.addAll(nodes.getNodeFactories());
}
}
addChildren(toPopulate);
addProjectFiles(toPopulate);
}
@Override
protected boolean createKeys(List<SingleNodeFactory> toPopulate) {
createdOnce = true;
try {
readKeys(toPopulate);
} catch (DataObjectNotFoundException ex) {
throw new RuntimeException(ex);
}
return true;
}
private static boolean isAllAnnotatedWith(
Collection<?> objects,
Class<? extends Annotation> annotation) {
for (Object obj: objects) {
if (!obj.getClass().isAnnotationPresent(annotation)) {
return false;
}
}
return true;
}
private static final class NodeExtensions {
private static final NodeExtensions EMPTY = createEmpty();
private final List<GradleProjectExtensionNodes> nodeFactories;
private final List<NbListenerRef> listenerRefs;
private final boolean needRefreshOnProjectReload;
private NodeExtensions(
Collection<? extends GradleProjectExtensionNodes> nodeFactories,
List<NbListenerRef> listenerRefs) {
this.nodeFactories = Collections.unmodifiableList(
new ArrayList<>(nodeFactories));
this.listenerRefs = listenerRefs;
this.needRefreshOnProjectReload = !isAllAnnotatedWith(nodeFactories, ManualRefreshedNodes.class);
}
public boolean isNeedRefreshOnProjectReload() {
return needRefreshOnProjectReload;
}
public static NodeExtensions createEmpty() {
return create(Collections.<GradleProjectExtensionNodes>emptyList(), new Runnable() {
@Override
public void run() {
// Do nothing.
}
});
}
public static NodeExtensions create(
Collection<? extends GradleProjectExtensionNodes> nodeFactories,
Runnable changeListener) {
List<NbListenerRef> listenerRefs = new ArrayList<>(nodeFactories.size());
for (GradleProjectExtensionNodes nodeFactory: nodeFactories) {
listenerRefs.add(nodeFactory.addNodeChangeListener(changeListener));
}
NodeExtensions result = new NodeExtensions(nodeFactories, listenerRefs);
if (result.isNeedRefreshOnProjectReload()) {
for (GradleProjectExtensionNodes nodeFactory: nodeFactories) {
Class<?> nodeFactoryClass = nodeFactory.getClass();
if (!nodeFactoryClass.isAnnotationPresent(ManualRefreshedNodes.class)) {
LOGGER.log(Level.WARNING,
"{0} is not annotated with ManualRefreshedNodes and this will cause project node refresh on all model loads.",
nodeFactoryClass.getName());
}
}
}
return result;
}
public List<GradleProjectExtensionNodes> getFactories() {
return nodeFactories;
}
public void close() {
for (NbListenerRef ref: listenerRefs) {
ref.unregister();
}
}
}
}