/* * Copyright (c) 2012 Sam Harwell, Tunnel Vision Laboratories LLC * All rights reserved. * * The source code of this document is proprietary work, and is not licensed for * distribution. For information about licensing, contact Sam Harwell at: * sam@tunnelvisionlabs.com */ package org.tvl.goworks.project; import java.awt.EventQueue; import java.awt.Image; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.ImageIcon; import org.netbeans.api.progress.ProgressHandle; import org.netbeans.api.progress.ProgressHandleFactory; import org.netbeans.api.project.FileOwnerQuery; import org.netbeans.api.project.SourceGroup; import org.netbeans.api.queries.VisibilityQuery; 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.FilterNode; import org.openide.nodes.Node; import org.openide.util.NbBundle; import org.openide.util.WeakListeners; /** * * @author Sam Harwell */ public class PackageView { // -J-Dorg.tvl.goworks.project.PackageView.level=FINE private static final Logger LOGGER = Logger.getLogger(PackageView.class.getName()); private PackageView() { } public static Node createPackageView(SourceGroup group) { return new RootNode(group); } /** * Finds the node representing given object, if any. * The current implementation works only for {@link org.openide.filesystems.FileObject}s * and {@link org.openide.loaders.DataObject}s. * @param rootNode a node some descendant of which should contain the object * @param object object to find * @return a node representing the given object, or null if no such node was found */ public static Node findPath(Node rootNode, Object object) { final PathFinder pf = rootNode.getLookup().lookup(PathFinder.class); if ( pf != null ) { return pf.findPath( rootNode, object ); } return null; } /** Fills given collection with flattened packages under given folder *@param target The collection to be filled *@param fo The folder to be scanned * @param group the group to scan * @param createPackageItems if false the collection will be filled with file objects; if * true PackageItems will be created. * @param showProgress whether to show a progress handle or not */ @NbBundle.Messages({"# {0} - root folder", "PackageView.find_packages_progress=Finding packages in {0}"}) static void findNonExcludedPackages(PackageViewChildren children, Collection<PackageItem> target, FileObject fo, SourceGroup group, boolean showProgress) { if (showProgress) { ProgressHandle progress = ProgressHandleFactory.createHandle(Bundle.PackageView_find_packages_progress(FileUtil.getFileDisplayName(fo))); progress.start(1000); findNonExcludedPackages(children, target, fo, group, progress, 0, 1000); progress.finish(); } else { findNonExcludedPackages(children, target, fo, group, null, 0, 0); } } private static void findNonExcludedPackages(PackageViewChildren children, Collection<PackageItem> target, FileObject fo, SourceGroup group, ProgressHandle progress, int start, int end) { if (!fo.isValid() || fo.isVirtual()) { return; } if (!fo.isFolder()) { throw new IllegalArgumentException("Package view only accepts folders, given: " + FileUtil.getFileDisplayName(fo)); // NOI18N } if (progress != null) { String path = FileUtil.getRelativePath(children.getRoot(), fo); if (path == null) { if (!fo.isValid() || !children.getRoot().isValid()) { return; } else { throw new IllegalArgumentException( MessageFormat.format( "{0} in {1}", //NOI18N FileUtil.getFileDisplayName(fo), FileUtil.getFileDisplayName(children.getRoot()))); } } progress.progress(path.replace('/', '.'), start); } if ( !VisibilityQuery.getDefault().isVisible( fo ) ) { return; // Don't show hidden packages } boolean hasSubfolders = false; boolean hasFiles = false; List<FileObject> folders = new ArrayList<>(); for (FileObject kid : fo.getChildren()) { // XXX could use PackageDisplayUtils.isSignificant here if (kid.isValid() && VisibilityQuery.getDefault().isVisible(kid) && group.contains(kid)) { if (kid.isFolder()) { folders.add(kid); hasSubfolders = true; } else { hasFiles = true; } } } if (hasFiles || !hasSubfolders) { if (target != null) { target.add( new PackageItem(group, fo, !hasFiles ) ); } else { if (fo.isValid()) { children.add(fo, !hasFiles, false); } } } if (!folders.isEmpty()) { int diff = (end - start) / folders.size(); int c = 0; for (FileObject kid : folders) { // Do this after adding the parent, so we get a pre-order traversal. // Also see PackageViewChildren.findChild: prefer to get root first. findNonExcludedPackages(children, target, kid, group, progress, start + c * diff, start + (c + 1) * diff); c++; } } } private static final class RootNode extends FilterNode implements PropertyChangeListener { private final SourceGroup _sourceGroup; private RootNode(SourceGroup sourceGroup) { super(getOriginalNode(sourceGroup)); _sourceGroup = sourceGroup; sourceGroup.addPropertyChangeListener(WeakListeners.propertyChange(this, sourceGroup)); } @Override public void propertyChange(PropertyChangeEvent event) { String prop = event.getPropertyName(); if (SourceGroup.PROP_CONTAINERSHIP.equals(prop)) { EventQueue.invokeLater(new Runnable() { @Override public void run() { changeOriginal(getOriginalNode(_sourceGroup), true); } }); } } private static Node getOriginalNode(SourceGroup group) { FileObject root = group.getRootFolder(); if (root == null || !root.isValid()) { return new AbstractNode(Children.LEAF); } if (!VisibilityQuery.getDefault().isVisible(root)) { LOGGER.log( Level.WARNING, "Ignoring source group: {0} with non visible root: {1}", //NOI18N new Object[]{ group, FileUtil.getFileDisplayName(root) }); return new AbstractNode (Children.LEAF); } return new PackageRootNode(FileOwnerQuery.getOwner(root), group); } } /** * Model item representing one package. */ static final class PackageItem implements Comparable<PackageItem> { private static Map<Image,Icon> image2icon = new IdentityHashMap<>(); private final boolean empty; private final FileObject pkg; private final String pkgname; private Icon icon; public PackageItem(SourceGroup group, FileObject pkg, boolean empty) { this.pkg = pkg; this.empty = empty; String path = FileUtil.getRelativePath(group.getRootFolder(), pkg); assert path != null : "No " + pkg + " in " + group; pkgname = path.replace('/', '.'); } @Override public String toString() { return pkgname; } public String getLabel() { return PackageDisplayUtils.getDisplayLabel(pkgname); } public Icon getIcon() { if ( icon == null ) { Image image = PackageDisplayUtils.getIcon(pkg, empty); icon = image2icon.get(image); if ( icon == null ) { icon = new ImageIcon( image ); image2icon.put( image, icon ); } } return icon; } @Override public int compareTo(PackageItem p) { return pkgname.compareTo(p.pkgname); } } }