package org.netbeans.gradle.project.view; import java.awt.Image; import java.beans.BeanInfo; import java.io.File; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.jtrim.concurrent.UpdateTaskExecutor; import org.jtrim.property.MutableProperty; import org.jtrim.property.PropertySource; import org.jtrim.property.swing.SwingForwarderFactory; import org.jtrim.property.swing.SwingProperties; import org.jtrim.property.swing.SwingPropertySource; import org.jtrim.swing.concurrent.SwingUpdateTaskExecutor; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.model.util.CollectionUtils; import org.netbeans.gradle.project.properties.NbProperties; import org.netbeans.gradle.project.util.ListenerRegistrations; import org.netbeans.gradle.project.util.NbFunction; import org.netbeans.gradle.project.util.NbTaskExecutors; import org.openide.filesystems.FileObject; import org.openide.filesystems.FileStateInvalidException; import org.openide.filesystems.FileStatusEvent; import org.openide.filesystems.FileStatusListener; import org.openide.filesystems.FileSystem; import org.openide.filesystems.FileUIUtils; import org.openide.filesystems.FileUtil; import org.openide.filesystems.ImageDecorator; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import static org.jtrim.property.PropertyFactory.*; public final class BadgeAwareNode extends FilterNode { private static final Logger LOGGER = Logger.getLogger(BadgeAwareNode.class.getName()); private final PropertySource<? extends Collection<File>> files; private final PropertySource<ImageDecorator> statusProperty; private final UpdateTaskExecutor fileUpdater; private final UpdateTaskExecutor iconChangeNotifier; private final MutableProperty<FileObjects> fileObjs; private final ListenerRegistrations listenerRegs; private BadgeAwareNode(Node original, PropertySource<? extends Collection<File>> files) { super(original); ExceptionHelper.checkNotNullArgument(files, "files"); this.files = files; this.listenerRegs = new ListenerRegistrations(); this.fileObjs = lazilySetProperty(memProperty(new FileObjects())); this.fileUpdater = NbTaskExecutors.newDefaultUpdateExecutor(); this.iconChangeNotifier = new SwingUpdateTaskExecutor(true); this.statusProperty = NbProperties.propertyOfProperty(fileObjs, new NbFunction<FileObjects, PropertySource<ImageDecorator>>() { @Override public PropertySource<ImageDecorator> apply(FileObjects arg) { return new FileSystemStatusProperty(arg).toStandard(); } }); } public static Node makeBadgeAware(Node original, PropertySource<? extends Collection<File>> files) { BadgeAwareNode result = new BadgeAwareNode(original, files); result.init(); return result; } private static FileSystem getFileSystem(Set<FileObject> fileObjs) { if (fileObjs.isEmpty()) { return null; } for (FileObject fileObj: fileObjs) { try { return fileObj.getFileSystem(); } catch (FileStateInvalidException ex) { LOGGER.log(Level.INFO, "FileSystem is unavailable for file: " + fileObj, ex); } } return null; } private void updateFilesNow(Collection<File> currentFiles) { fileObjs.setValue(new FileObjects(currentFiles)); } private void updateFiles() { fileUpdater.execute(new Runnable() { @Override public void run() { Collection<File> currentFiles = files.getValue(); updateFilesNow(currentFiles != null ? currentFiles : Collections.<File>emptySet()); } }); } private void updateIcons() { iconChangeNotifier.execute(new Runnable() { @Override public void run() { updateIconsNow(); } }); } private void updateIconsNow() { fireIconChange(); fireOpenedIconChange(); } public void init() { listenerRegs.add(NbProperties.weakListenerProperty(files).addChangeListener(new Runnable() { @Override public void run() { updateFiles(); } })); updateFiles(); listenerRegs.add(NbProperties.weakListenerProperty(statusProperty).addChangeListener(new Runnable() { @Override public void run() { updateIcons(); } })); } private Image annotate(Image src) { ImageDecorator status = statusProperty.getValue(); return status != null ? status.annotateIcon(src, BeanInfo.ICON_COLOR_16x16, fileObjs.getValue().fileObjs) : src; } @Override public Image getOpenedIcon(int type) { return annotate(super.getOpenedIcon(type)); } @Override public Image getIcon(int type) { return annotate(super.getIcon(type)); } @Override public void destroy() throws IOException { listenerRegs.unregisterAll(); super.destroy(); } private static final class FileObjects { private final Set<FileObject> fileObjs; private final FileSystem fileSystem; public FileObjects() { this(Collections.<File>emptySet()); } public FileObjects(Collection<File> files) { Set<FileObject> newFileObjs = CollectionUtils.newHashSet(files.size()); for (File file: files) { FileObject fileObj = file != null ? FileUtil.toFileObject(file) : null; if (fileObj != null) { newFileObjs.add(fileObj); } } this.fileObjs = Collections.unmodifiableSet(newFileObjs); this.fileSystem = getFileSystem(newFileObjs); } @Override public int hashCode() { int hash = 5; hash = 97 * hash + Objects.hashCode(this.fileObjs); hash = 97 * hash + System.identityHashCode(this.fileSystem); return hash; } @Override public boolean equals(Object obj) { if (obj == null) return false; if (getClass() != obj.getClass()) return false; final FileObjects other = (FileObjects)obj; return this.fileSystem == other.fileSystem && Objects.equals(this.fileObjs, other.fileObjs); } } private static final class FileSystemStatusProperty implements SwingPropertySource<ImageDecorator, FileStatusListener> { private final FileObjects fileObjects; public FileSystemStatusProperty(FileObjects fileObjects) { assert fileObjects != null; this.fileObjects = fileObjects; } public PropertySource<ImageDecorator> toStandard() { return SwingProperties.fromSwingSource(this, new SwingForwarderFactory<FileStatusListener>() { @Override public FileStatusListener createForwarder(final Runnable listener) { ExceptionHelper.checkNotNullArgument(listener, "listener"); return new FileStatusListener() { @Override public void annotationChanged(FileStatusEvent ev) { if (anythingChanged(ev)) { listener.run(); } } }; } }); } private boolean anythingChanged(FileStatusEvent ev) { for (FileObject fileObj: fileObjects.fileObjs) { if (ev.hasChanged(fileObj)) { return true; } } return false; } @Override public ImageDecorator getValue() { FileSystem fs = fileObjects.fileSystem; return FileUIUtils.getImageDecorator(fs); } @Override public void addChangeListener(FileStatusListener listener) { FileSystem fs = fileObjects.fileSystem; if (fs != null) { fs.addFileStatusListener(listener); } } @Override public void removeChangeListener(FileStatusListener listener) { FileSystem fs = fileObjects.fileSystem; if (fs != null) { fs.removeFileStatusListener(listener); } } } }