package org.netbeans.gradle.project.view;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import org.jtrim.cancel.Cancellation;
import org.jtrim.cancel.CancellationToken;
import org.jtrim.concurrent.CancelableTask;
import org.jtrim.event.EventListeners;
import org.jtrim.event.ListenerRef;
import org.jtrim.event.ProxyListenerRegistry;
import org.jtrim.event.SimpleListenerRegistry;
import org.jtrim.utils.ExceptionHelper;
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.event.NbListenerManagers;
import org.netbeans.gradle.project.script.CommonScripts;
import org.netbeans.gradle.project.script.ScriptFileProvider;
import org.netbeans.gradle.project.util.GradleFileUtils;
import org.netbeans.gradle.project.util.ListenerRegistrations;
import org.netbeans.gradle.project.util.NbFileUtils;
import org.netbeans.gradle.project.util.NbTaskExecutors;
import org.netbeans.gradle.project.util.RefreshableChildren;
import org.netbeans.gradle.project.util.StringUtils;
import org.netbeans.spi.project.ui.PathFinder;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.ChildFactory;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;
public final class GradleHomeNode extends AbstractNode {
// Though as of Gradle 3.2.1, Kotling init scripts are not supported, we will assume support
// because it is reasonable to expect support for them in the future.
private static final String INIT_GRADLE_BASE_NAME = "init";
private static final String INIT_D_NAME = "init.d";
private final GradleHomeNodeChildFactory childFactory;
private final ScriptFileProvider scriptProvider;
public GradleHomeNode(ScriptFileProvider scriptProvider) {
this(new GradleHomeNodeChildFactory(scriptProvider), scriptProvider);
}
private GradleHomeNode(GradleHomeNodeChildFactory childFactory, ScriptFileProvider scriptProvider) {
this(childFactory, scriptProvider, Children.create(childFactory, true));
}
private GradleHomeNode(GradleHomeNodeChildFactory childFactory, ScriptFileProvider scriptProvider, Children children) {
super(children, createLookup(childFactory, scriptProvider, children));
this.childFactory = childFactory;
this.scriptProvider = scriptProvider;
setName(getClass().getSimpleName());
}
private static Lookup createLookup(
GradleHomeNodeChildFactory childFactory,
ScriptFileProvider scriptProvider,
Children children) {
return Lookups.fixed(
new GradleHomePathFinder(scriptProvider),
NodeUtils.defaultNodeRefresher(children, childFactory));
}
public static SingleNodeFactory getFactory(ScriptFileProvider scriptProvider) {
return new FactoryImpl(scriptProvider);
}
private Action openGradleHomeFile(String name) {
Path userHome = getGradleUserHome();
Path file = userHome != null ? userHome.resolve(name) : Paths.get(name);
Action result = new OpenAlwaysFileAction(file);
if (userHome == null) {
result.setEnabled(false);
}
return result;
}
private Action openGradleHomeScriptFile(String baseName) {
Path userHome = getGradleUserHome();
Path baseDir = userHome != null ? userHome : Paths.get(".");
Action result = OpenAlwaysFileAction.openScriptAction(baseDir, baseName, scriptProvider);
if (userHome == null) {
result.setEnabled(false);
}
return result;
}
@Override
public Action[] getActions(boolean context) {
List<Action> result = new ArrayList<>();
result.add(openGradleHomeScriptFile(INIT_GRADLE_BASE_NAME));
result.add(openGradleHomeFile(CommonScripts.GRADLE_PROPERTIES_NAME));
if (!childFactory.hasInitDDirDisplayed) {
result.add(new CreateInitDAction());
}
result.add(null);
result.add(NodeUtils.getRefreshNodeAction(this));
return result.toArray(new Action[result.size()]);
}
@Override
public Image getIcon(int type) {
return NbIcons.getFolderIcon();
}
@Override
public Image getOpenedIcon(int type) {
return NbIcons.getOpenFolderIcon();
}
@Override
public String getDisplayName() {
return NbStrings.getGradleHomeNodeCaption();
}
private static File getGradleUserHomeFile() {
return GradleFileUtils.GRADLE_USER_HOME.getValue();
}
private static Path getGradleUserHome() {
File result = getGradleUserHomeFile();
return result != null ? result.toPath() : null;
}
private static final class GradleHomePathFinder implements PathFinder {
private final ScriptFileProvider scriptProvider;
public GradleHomePathFinder(ScriptFileProvider scriptProvider) {
ExceptionHelper.checkNotNullArgument(scriptProvider, "scriptProvider");
this.scriptProvider = scriptProvider;
}
@Override
public Node findPath(Node root, Object target) {
FileObject targetFile = NodeUtils.tryGetFileSearchTarget(target);
if (targetFile == null) {
return null;
}
String baseName = targetFile.getNameExt();
boolean canBeFound = CommonScripts.GRADLE_PROPERTIES_NAME.equalsIgnoreCase(baseName)
|| scriptProvider.isScriptFileName(baseName);
// We have only gradle files and the gradle.properties.
if (!canBeFound) {
return null;
}
File userHome = getGradleUserHomeFile();
if (userHome == null) {
// Most likely we could not create the nodes, so
// don't bother looking at subnodes.
return null;
}
FileObject userHomeObj = FileUtil.toFileObject(userHome);
if (userHomeObj == null) {
// The directory does not exist, so there should be
// no valid node.
return null;
}
if (!FileUtil.isParentOf(userHomeObj, targetFile)) {
return null;
}
Node result = NodeUtils.findFileChildNode(root.getChildren(), targetFile);
if (result != null) {
return result;
}
return NodeUtils.askChildrenForTarget(root.getChildren(), target);
}
}
private static class GradleHomeNodeChildFactory
extends
ChildFactory.Detachable<SingleNodeFactory>
implements
RefreshableChildren {
private final ScriptFileProvider scriptProvider;
private final ListenerRegistrations listenerRefs;
private final ProxyListenerRegistry<Runnable> userHomeChangeListeners;
private volatile boolean hasInitDDirDisplayed;
private volatile boolean createdOnce;
public GradleHomeNodeChildFactory(ScriptFileProvider scriptProvider) {
ExceptionHelper.checkNotNullArgument(scriptProvider, "scriptProvider");
this.scriptProvider = scriptProvider;
this.listenerRefs = new ListenerRegistrations();
this.userHomeChangeListeners = new ProxyListenerRegistry<>(NbListenerManagers.neverNotifingRegistry());
this.hasInitDDirDisplayed = false;
this.createdOnce = false;
}
@Override
public void refreshChildren() {
if (createdOnce) {
refresh(false);
}
}
private FileObject tryGetUserHomeObj() {
File userHome = GradleFileUtils.GRADLE_USER_HOME.getValue();
if (userHome == null) {
return null;
}
return FileUtil.toFileObject(userHome);
}
private void updateUserHome() {
final FileObject userHome = tryGetUserHomeObj();
if (userHome == null) {
userHomeChangeListeners.replaceRegistry(NbListenerManagers.neverNotifingRegistry());
}
else {
userHomeChangeListeners.replaceRegistry(new SimpleListenerRegistry<Runnable>() {
@Override
public ListenerRef registerListener(Runnable listener) {
return NbFileUtils.addDirectoryContentListener(userHome, true, listener);
}
});
}
userHomeChangeListeners.onEvent(EventListeners.runnableDispatcher(), null);
}
@Override
protected void addNotify() {
listenerRefs.add(GradleFileUtils.GRADLE_USER_HOME.addChangeListener(new Runnable() {
@Override
public void run() {
updateUserHome();
}
}));
updateUserHome();
listenerRefs.add(userHomeChangeListeners.registerListener(new Runnable() {
@Override
public void run() {
refresh(false);
}
}));
}
@Override
protected void removeNotify() {
listenerRefs.unregisterAll();
}
private static FileObject tryGetFile(Path dir, String name) {
return FileUtil.toFileObject(dir.resolve(name).toFile());
}
private List<FileObject> getScriptFiles(Path dir, String baseName) {
Iterable<Path> paths = scriptProvider.findScriptFiles(dir, baseName);
List<FileObject> result = new ArrayList<>();
for (Path path: paths) {
FileObject fileObj = FileUtil.toFileObject(path.toFile());
if (fileObj != null) {
result.add(fileObj);
}
}
return result;
}
private void addGradleProperties(Path userHome, List<SingleNodeFactory> toPopulate) {
FileObject gradleProperties = tryGetFile(userHome, CommonScripts.GRADLE_PROPERTIES_NAME);
if (gradleProperties != null) {
SingleNodeFactory node = NodeUtils.tryGetFileNode(gradleProperties);
if (node != null) {
toPopulate.add(node);
}
}
}
private Collection<FileObject> addInitGradle(Path userHome, List<SingleNodeFactory> toPopulate) {
List<FileObject> initGradles = getScriptFiles(userHome, INIT_GRADLE_BASE_NAME);
for (FileObject initGradle: initGradles) {
SingleNodeFactory node = NodeUtils.tryGetFileNode(
initGradle,
initGradle.getNameExt(),
NbIcons.getGradleIcon());
if (node != null) {
toPopulate.add(node);
}
}
return initGradles;
}
private void addInitDDir(Path userHome, List<SingleNodeFactory> toPopulate) {
final FileObject initD = tryGetFile(userHome, INIT_D_NAME);
if (initD != null && initD.isFolder()) {
hasInitDDirDisplayed = true;
toPopulate.add(GradleFolderNode.getFactory(
NbStrings.getGlobalInitScriptsNodeCaption(),
initD,
scriptProvider));
}
}
private void addOtherGradleFiles(
Path userHome,
Collection<FileObject> filtered,
List<SingleNodeFactory> toPopulate) {
FileObject userHomeObj = FileUtil.toFileObject(userHome.toFile());
if (userHomeObj == null) {
return;
}
List<FileObject> gradleFiles = new ArrayList<>();
for (FileObject file: userHomeObj.getChildren()) {
if (filtered.contains(file)) {
continue;
}
if (scriptProvider.isScriptFileName(file.getNameExt())) {
gradleFiles.add(file);
}
}
Collections.sort(gradleFiles, new Comparator<FileObject>() {
@Override
public int compare(FileObject o1, FileObject o2) {
return StringUtils.STR_CMP.compare(o1.getNameExt(), o2.getNameExt());
}
});
for (FileObject file: gradleFiles) {
SingleNodeFactory node = NodeUtils.tryGetFileNode(
file,
file.getNameExt(),
NbIcons.getGradleIcon());
if (node != null) {
toPopulate.add(node);
}
}
}
private void readKeys(List<SingleNodeFactory> toPopulate) {
hasInitDDirDisplayed = false;
Path userHome = getGradleUserHome();
if (userHome == null) {
return;
}
addGradleProperties(userHome, toPopulate);
Collection<FileObject> initGradles = addInitGradle(userHome, toPopulate);
addInitDDir(userHome, toPopulate);
addOtherGradleFiles(userHome, initGradles, toPopulate);
}
@Override
protected boolean createKeys(List<SingleNodeFactory> toPopulate) {
createdOnce = true;
readKeys(toPopulate);
return true;
}
@Override
protected Node createNodeForKey(SingleNodeFactory key) {
return key.createNode();
}
}
private static final class FactoryImpl implements SingleNodeFactory {
private final ScriptFileProvider scriptProvider;
public FactoryImpl(ScriptFileProvider scriptProvider) {
ExceptionHelper.checkNotNullArgument(scriptProvider, "scriptProvider");
this.scriptProvider = scriptProvider;
}
@Override
public Node createNode() {
return new GradleHomeNode(scriptProvider);
}
}
@SuppressWarnings("serial")
private static class CreateInitDAction extends AbstractAction {
public CreateInitDAction() {
super(NbStrings.getCreateInitDDir());
}
@Override
public void actionPerformed(ActionEvent e) {
NbTaskExecutors.DEFAULT_EXECUTOR.execute(Cancellation.UNCANCELABLE_TOKEN, new CancelableTask() {
@Override
public void execute(CancellationToken cancelToken) throws Exception {
Path userHome = getGradleUserHome();
if (userHome != null) {
Path initDPath = userHome.resolve(INIT_D_NAME);
Files.createDirectories(initDPath);
}
}
}, null);
}
}
}