0001: /*
0002: * The contents of this file are subject to the terms of the Common Development
0003: * and Distribution License (the License). You may not use this file except in
0004: * compliance with the License.
0005: *
0006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
0007: * or http://www.netbeans.org/cddl.txt.
0008: *
0009: * When distributing Covered Code, include this CDDL Header Notice in each file
0010: * and include the License file at http://www.netbeans.org/cddl.txt.
0011: * If applicable, add the following below the CDDL Header, with the fields
0012: * enclosed by brackets [] replaced by your own identifying information:
0013: * "Portions Copyrighted [year] [name of copyright owner]"
0014: *
0015: * The Original Software is NetBeans. The Initial Developer of the Original
0016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0017: * Microsystems, Inc. All Rights Reserved.
0018: */
0019:
0020: package org.netbeans.modules.sql.project.ui;
0021:
0022: import java.awt.Image;
0023: import java.awt.datatransfer.DataFlavor;
0024: import java.awt.datatransfer.Transferable;
0025: import java.awt.datatransfer.UnsupportedFlavorException;
0026: import java.awt.dnd.DnDConstants;
0027: import java.beans.PropertyChangeSupport;
0028: import java.io.IOException;
0029: import java.lang.reflect.InvocationTargetException;
0030: import java.text.MessageFormat;
0031: import java.util.ArrayList;
0032: import java.util.Collection;
0033: import java.util.Collections;
0034: import java.util.HashSet;
0035: import java.util.Iterator;
0036: import java.util.List;
0037: import java.util.Set;
0038: import java.util.StringTokenizer;
0039: import java.util.TreeMap;
0040: import javax.swing.Action;
0041: import javax.swing.SwingUtilities;
0042: import javax.swing.event.ChangeEvent;
0043: import javax.swing.event.ChangeListener;
0044: import javax.swing.event.EventListenerList;
0045: import org.netbeans.api.fileinfo.NonRecursiveFolder;
0046: import org.netbeans.api.queries.VisibilityQuery;
0047: import org.netbeans.modules.compapp.projects.base.PackageDisplayUtils; //import org.netbeans.modules.java.project.PackageDisplayUtils;
0048: import org.netbeans.spi.project.ActionProvider;
0049: import org.netbeans.spi.project.ui.support.FileSensitiveActions;
0050: import org.openide.DialogDisplayer;
0051: import org.openide.ErrorManager;
0052: import org.openide.NotifyDescriptor;
0053: import org.openide.filesystems.FileAttributeEvent;
0054: import org.openide.filesystems.FileChangeListener;
0055: import org.openide.filesystems.FileEvent;
0056: import org.openide.filesystems.FileObject;
0057: import org.openide.filesystems.FileRenameEvent;
0058: import org.openide.filesystems.FileStateInvalidException;
0059: import org.openide.filesystems.FileSystem;
0060: import org.openide.filesystems.FileUtil;
0061: import org.openide.loaders.ChangeableDataFilter;
0062: import org.openide.loaders.DataFilter;
0063: import org.openide.loaders.DataFolder;
0064: import org.openide.loaders.DataObject;
0065: import org.openide.nodes.Children;
0066: import org.openide.nodes.FilterNode;
0067: import org.openide.nodes.Node;
0068: import org.openide.nodes.PropertySupport;
0069: import org.openide.nodes.Sheet;
0070: import org.openide.util.Lookup;
0071: import org.openide.util.NbBundle;
0072: import org.openide.util.RequestProcessor;
0073: import org.openide.util.WeakListeners;
0074: import org.openide.util.datatransfer.ExTransferable;
0075: import org.openide.util.datatransfer.MultiTransferObject;
0076: import org.openide.util.datatransfer.PasteType;
0077: import org.openide.util.lookup.Lookups;
0078: import org.openide.util.lookup.ProxyLookup;
0079: import org.openidex.search.FileObjectFilter;
0080: import org.openidex.search.SearchInfoFactory;
0081:
0082: /**
0083: * Display of Java sources in a package structure rather than folder structure.
0084: * @author Adam Sotona, Jesse Glick, Petr Hrebejk, Tomas Zezula
0085: */
0086: final class PackageViewChildren extends Children.Keys
0087: /*<String>*/implements FileChangeListener, ChangeListener,
0088: Runnable {
0089:
0090: private static final String NODE_NOT_CREATED = "NNC"; // NOI18N
0091: private static final String NODE_NOT_CREATED_EMPTY = "NNC_E"; //NOI18N
0092:
0093: private static final MessageFormat PACKAGE_FLAVOR = new MessageFormat(
0094: "application/x-java-org-netbeans-modules-java-project-packagenodednd; class=org.netbeans.spi.java.project.support.ui.PackageViewChildren$PackageNode; mask={0}"); //NOI18N
0095:
0096: static final String PRIMARY_TYPE = "application"; //NOI18N
0097: static final String SUBTYPE = "x-java-org-netbeans-modules-java-project-packagenodednd"; //NOI18N
0098: static final String MASK = "mask"; //NOI18N
0099:
0100: private java.util.Map/*<String,NODE_NOT_CREATED|NODE_NOT_CREATED_EMPTY|PackageNode>*/names2nodes;
0101: private final FileObject root;
0102: private FileChangeListener wfcl; // Weak listener on the system filesystem
0103: private ChangeListener wvqcl; // Weak listener on the VisibilityQuery
0104:
0105: /**
0106: * Creates children based on a single source root.
0107: * @param root the folder where sources start (must be a package root)
0108: */
0109: public PackageViewChildren(FileObject root) {
0110:
0111: // Sem mas dat cache a bude to uplne nejrychlejsi na svete
0112:
0113: if (root == null) {
0114: throw new NullPointerException();
0115: }
0116: this .root = root;
0117: }
0118:
0119: FileObject getRoot() {
0120: return root; // Used from PackageRootNode
0121: }
0122:
0123: protected Node[] createNodes(Object obj) {
0124: FileObject fo = root.getFileObject((String) obj);
0125: if (fo != null && fo.isValid()) {
0126: Object o = names2nodes.get(obj);
0127: PackageNode n;
0128: if (o == NODE_NOT_CREATED) {
0129: n = new PackageNode(root, DataFolder.findFolder(fo),
0130: false);
0131: } else if (o == NODE_NOT_CREATED_EMPTY) {
0132: n = new PackageNode(root, DataFolder.findFolder(fo),
0133: true);
0134: } else {
0135: n = new PackageNode(root, DataFolder.findFolder(fo));
0136: }
0137: names2nodes.put(obj, n);
0138: return new Node[] { n };
0139: } else {
0140: return new Node[0];
0141: }
0142:
0143: }
0144:
0145: RequestProcessor.Task task = RequestProcessor.getDefault().create(
0146: this );
0147:
0148: protected void addNotify() {
0149: // System.out.println("ADD NOTIFY" + root + " : " + this );
0150: super .addNotify();
0151: task.schedule(0);
0152: }
0153:
0154: public Node[] getNodes(boolean optimal) {
0155: if (optimal) {
0156: Node[] garbage = super .getNodes(false);
0157: task.waitFinished();
0158: }
0159: return super .getNodes(false);
0160: }
0161:
0162: public Node findChild(String name) {
0163: getNodes(true);
0164: return super .findChild(name);
0165: }
0166:
0167: public void run() {
0168: computeKeys();
0169: refreshKeys();
0170: try {
0171: FileSystem fs = root.getFileSystem();
0172: wfcl = (FileChangeListener) WeakListeners.create(
0173: FileChangeListener.class, this , fs);
0174: fs.addFileChangeListener(wfcl);
0175: } catch (FileStateInvalidException e) {
0176: ErrorManager.getDefault().notify(
0177: ErrorManager.INFORMATIONAL, e);
0178: }
0179: wvqcl = WeakListeners
0180: .change(this , VisibilityQuery.getDefault());
0181: VisibilityQuery.getDefault().addChangeListener(wvqcl);
0182: }
0183:
0184: protected void removeNotify() {
0185: // System.out.println("REMOVE NOTIFY" + root + " : " + this );
0186: VisibilityQuery.getDefault().removeChangeListener(wvqcl);
0187: try {
0188: root.getFileSystem().removeFileChangeListener(wfcl);
0189: } catch (FileStateInvalidException e) {
0190: ErrorManager.getDefault().notify(
0191: ErrorManager.INFORMATIONAL, e);
0192: }
0193: setKeys(Collections.EMPTY_SET);
0194: names2nodes.clear();
0195: super .removeNotify();
0196: }
0197:
0198: // Private methods ---------------------------------------------------------
0199:
0200: private void refreshKeys() {
0201: setKeys(names2nodes.keySet());
0202: }
0203:
0204: /* #70097: workaround of a javacore deadlock
0205: * See related issue: #61027
0206: */
0207: private void refreshKeysAsync() {
0208: SwingUtilities.invokeLater(new Runnable() {
0209: public void run() {
0210: refreshKeys();
0211: }
0212: });
0213: }
0214:
0215: private void computeKeys() {
0216: // XXX this is not going to perform too well for a huge source root...
0217: // However we have to go through the whole hierarchy in order to find
0218: // all packages (Hrebejk)
0219: names2nodes = new TreeMap();
0220: findNonExcludedPackages(root);
0221: }
0222:
0223: /**
0224: * Collect all recursive subfolders, except those which have subfolders
0225: * but no files.
0226: */
0227: private void findNonExcludedPackages(FileObject fo) {
0228: PackageView.findNonExcludedPackages(this , fo);
0229: }
0230:
0231: /** Finds all empty parents of given package and deletes them
0232: */
0233: private void cleanEmptyKeys(FileObject fo) {
0234: FileObject parent = fo.getParent();
0235:
0236: // Special case for default package
0237: if (root.equals(parent)) {
0238: PackageNode n = get(parent);
0239: // the default package is considered empty if it only contains folders,
0240: // regardless of the contents of these folders (empty or not)
0241: if (n != null && PackageDisplayUtils.isEmpty(root, false)) {
0242: remove(root);
0243: }
0244: return;
0245: }
0246:
0247: while (FileUtil.isParentOf(root, parent)) {
0248: PackageNode n = get(parent);
0249: if (n != null && n.isLeaf()) {
0250: // System.out.println("Cleaning " + parent);
0251: remove(parent);
0252: }
0253: parent = parent.getParent();
0254: }
0255: }
0256:
0257: // Non private only to be able to have the findNonExcludedPackages impl
0258: // in on place (PackageView)
0259: void add(FileObject fo, boolean empty) {
0260: String path = FileUtil.getRelativePath(root, fo);
0261: assert path != null : "Adding wrong folder " + fo + "(valid="
0262: + fo.isValid() + ")" + "under root" + this .root
0263: + "(valid=" + this .root.isValid() + ")";
0264: if (get(fo) == null) {
0265: names2nodes.put(path, empty ? NODE_NOT_CREATED_EMPTY
0266: : NODE_NOT_CREATED);
0267: }
0268: }
0269:
0270: private void remove(FileObject fo) {
0271: String path = FileUtil.getRelativePath(root, fo);
0272: assert path != null : "Removing wrong folder" + fo;
0273: names2nodes.remove(path);
0274: }
0275:
0276: private void removeSubTree(FileObject fo) {
0277: String path = FileUtil.getRelativePath(root, fo);
0278: assert path != null : "Removing wrong folder" + fo;
0279: Collection keys = new HashSet(names2nodes.keySet());
0280: names2nodes.remove(path);
0281: path = path + '/'; //NOI18N
0282: for (Iterator it = keys.iterator(); it.hasNext();) {
0283: String key = (String) it.next();
0284: if (key.startsWith(path)) {
0285: names2nodes.remove(key);
0286: }
0287: }
0288: }
0289:
0290: private PackageNode get(FileObject fo) {
0291: String path = FileUtil.getRelativePath(root, fo);
0292: assert path != null : "Asking for wrong folder" + fo;
0293: Object o = names2nodes.get(path);
0294: return !isNodeCreated(o) ? null : (PackageNode) o;
0295: }
0296:
0297: private boolean contains(FileObject fo) {
0298: String path = FileUtil.getRelativePath(root, fo);
0299: assert path != null : "Asking for wrong folder" + fo;
0300: Object o = names2nodes.get(path);
0301: return o != null;
0302: }
0303:
0304: private boolean exists(FileObject fo) {
0305: String path = FileUtil.getRelativePath(root, fo);
0306: return names2nodes.get(path) != null;
0307: }
0308:
0309: private boolean isNodeCreated(Object o) {
0310: return o instanceof Node;
0311: }
0312:
0313: private PackageNode updatePath(String oldPath, String newPath) {
0314: Object o = names2nodes.get(oldPath);
0315: if (o == null) {
0316: return null;
0317: }
0318: names2nodes.remove(oldPath);
0319: names2nodes.put(newPath, o);
0320: return !isNodeCreated(o) ? null : (PackageNode) o;
0321: }
0322:
0323: // Implementation of FileChangeListener ------------------------------------
0324:
0325: public void fileAttributeChanged(FileAttributeEvent fe) {
0326: }
0327:
0328: public void fileChanged(FileEvent fe) {
0329: }
0330:
0331: public void fileFolderCreated(FileEvent fe) {
0332: FileObject fo = fe.getFile();
0333: if (FileUtil.isParentOf(root, fo) && isVisible(root, fo)) {
0334: cleanEmptyKeys(fo);
0335: // add( fo, false);
0336: findNonExcludedPackages(fo);
0337: refreshKeys();
0338: }
0339: }
0340:
0341: public void fileDataCreated(FileEvent fe) {
0342: FileObject fo = fe.getFile();
0343: if (FileUtil.isParentOf(root, fo) && isVisible(root, fo)) {
0344: FileObject parent = fo.getParent();
0345: if (!VisibilityQuery.getDefault().isVisible(parent)) {
0346: return; // Adding file into ignored directory
0347: }
0348: PackageNode n = get(parent);
0349: if (n == null && !contains(parent)) {
0350: add(parent, false);
0351: refreshKeys();
0352: } else if (n != null) {
0353: n.updateChildren();
0354: }
0355: }
0356: }
0357:
0358: public void fileDeleted(FileEvent fe) {
0359: FileObject fo = fe.getFile();
0360:
0361: // System.out.println("FILE DELETED " + FileUtil.getRelativePath( root, fo ) );
0362:
0363: if (FileUtil.isParentOf(root, fo) && isVisible(root, fo)) {
0364:
0365: // System.out.println("IS FOLDER? " + fo + " : " + fo.isFolder() );
0366: /* Hack for MasterFS see #42464 */
0367: if (fo.isFolder() || get(fo) != null) {
0368: // System.out.println("REMOVING FODER " + fo );
0369: removeSubTree(fo);
0370: // Now add the parent if necessary
0371: FileObject parent = fo.getParent();
0372: if ((FileUtil.isParentOf(root, parent) || root
0373: .equals(parent))
0374: && get(parent) == null && parent.isValid()) {
0375: // Candidate for adding
0376: if (!toBeRemoved(parent)) {
0377: // System.out.println("ADDING PARENT " + parent );
0378: add(parent, true);
0379: }
0380: }
0381: refreshKeysAsync();
0382: } else {
0383: FileObject parent = fo.getParent();
0384: final PackageNode n = get(parent);
0385: if (n != null) {
0386: //#61027: workaround to a deadlock when the package is being changed from non-leaf to leaf:
0387: boolean leaf = n.isLeaf();
0388: DataFolder df = n.getDataFolder();
0389: boolean empty = n.isEmpty(df);
0390:
0391: if (leaf != empty) {
0392: SwingUtilities.invokeLater(new Runnable() {
0393: public void run() {
0394: n.updateChildren();
0395: }
0396: });
0397: } else {
0398: n.updateChildren();
0399: }
0400: }
0401: // If the parent folder only contains folders remove it
0402: if (toBeRemoved(parent)) {
0403: remove(parent);
0404: refreshKeysAsync();
0405: }
0406:
0407: }
0408: }
0409: // else {
0410: // System.out.println("NOT A PARENT " + fo );
0411: // }
0412: }
0413:
0414: /** Returns true if the folder should be removed from the view
0415: * i.e. it has some unignored children and the children are folders only
0416: */
0417: private boolean toBeRemoved(FileObject folder) {
0418: boolean ignoredOnly = true;
0419: boolean foldersOnly = true;
0420: FileObject kids[] = folder.getChildren();
0421: for (int i = 0; i < kids.length; i++) {
0422: if (VisibilityQuery.getDefault().isVisible(kids[i])) {
0423: ignoredOnly = false;
0424: if (!kids[i].isFolder()) {
0425: foldersOnly = false;
0426: break;
0427: }
0428: }
0429: }
0430: if (ignoredOnly) {
0431: return false; // It is either empty or it only contains ignored files
0432: // thus is leaf and it means package
0433: } else {
0434: return foldersOnly;
0435: }
0436: }
0437:
0438: public void fileRenamed(FileRenameEvent fe) {
0439: FileObject fo = fe.getFile();
0440: if (FileUtil.isParentOf(root, fo) && fo.isFolder()) {
0441: String rp = FileUtil.getRelativePath(root, fo.getParent());
0442: String oldPath = rp + (rp.length() == 0 ? "" : "/")
0443: + fe.getName() + fe.getExt(); // NOI18N
0444:
0445: boolean visible = VisibilityQuery.getDefault()
0446: .isVisible(fo);
0447: boolean doUpdate = false;
0448:
0449: // Find all entries which have to be updated
0450: ArrayList needsUpdate = new ArrayList();
0451: for (Iterator it = names2nodes.keySet().iterator(); it
0452: .hasNext();) {
0453: String p = (String) it.next();
0454: if (p.startsWith(oldPath)) {
0455: if (visible) {
0456: needsUpdate.add(p);
0457: } else {
0458: names2nodes.remove(p);
0459: doUpdate = true;
0460: }
0461: }
0462: }
0463:
0464: // If the node does not exists then there might have been update
0465: // from ignored to non ignored
0466: if (get(fo) == null && visible) {
0467: cleanEmptyKeys(fo);
0468: findNonExcludedPackages(fo);
0469: doUpdate = true; // force refresh
0470: }
0471:
0472: int oldPathLen = oldPath.length();
0473: String newPath = FileUtil.getRelativePath(root, fo);
0474: for (Iterator it = needsUpdate.iterator(); it.hasNext();) {
0475: String p = (String) it.next();
0476: StringBuffer np = new StringBuffer(p);
0477: np.replace(0, oldPathLen, newPath);
0478: PackageNode n = updatePath(p, np.toString()); // Replace entries in cache
0479: if (n != null) {
0480: n.updateDisplayName(); // Update nodes
0481: }
0482: }
0483:
0484: if (needsUpdate.size() > 1 || doUpdate) {
0485: // Sorting might change
0486: refreshKeys();
0487: }
0488: }
0489: /*
0490: else if ( FileUtil.isParentOf( root, fo ) && fo.isFolder() ) {
0491: FileObject parent = fo.getParent();
0492: PackageNode n = get( parent );
0493: if ( n != null && VisibilityQuery.getDefault().isVisible( parent ) ) {
0494: n.updateChildren();
0495: }
0496:
0497: }
0498: */
0499:
0500: }
0501:
0502: /** Test whether file and all it's parent up to parent paremeter
0503: * are visible
0504: */
0505: private boolean isVisible(FileObject parent, FileObject file) {
0506:
0507: do {
0508: if (!VisibilityQuery.getDefault().isVisible(file)) {
0509: return false;
0510: }
0511: file = file.getParent();
0512: } while (file != null && file != parent);
0513:
0514: return true;
0515: }
0516:
0517: // Implementation of ChangeListener ------------------------------------
0518:
0519: public void stateChanged(ChangeEvent e) {
0520: computeKeys();
0521: refreshKeys();
0522: }
0523:
0524: /*
0525: private void debugKeySet() {
0526: for( Iterator it = names2nodes.keySet().iterator(); it.hasNext(); ) {
0527: String k = (String)it.next();
0528: System.out.println( " " + k + " -> " + names2nodes.get( k ) );
0529: }
0530: }
0531: */
0532:
0533: static final class PackageNode extends FilterNode {
0534:
0535: private static final DataFilter NO_FOLDERS_FILTER = new NoFoldersDataFilter();
0536:
0537: private final FileObject root;
0538: private DataFolder dataFolder;
0539: private boolean isDefaultPackage;
0540:
0541: private static Action actions[];
0542:
0543: public PackageNode(FileObject root, DataFolder dataFolder) {
0544: this (root, dataFolder, isEmpty(dataFolder));
0545: }
0546:
0547: public PackageNode(FileObject root, DataFolder dataFolder,
0548: boolean empty) {
0549: super (
0550: dataFolder.getNodeDelegate(),
0551: empty ? Children.LEAF : dataFolder
0552: .createNodeChildren(NO_FOLDERS_FILTER),
0553: new ProxyLookup(
0554: new Lookup[] {
0555: Lookups
0556: .singleton(new NoFoldersContainer(
0557: dataFolder)),
0558: dataFolder.getNodeDelegate()
0559: .getLookup(),
0560: Lookups
0561: .singleton(PackageRootNode
0562: .alwaysSearchableSearchInfo(SearchInfoFactory
0563: .createSearchInfo(
0564: dataFolder
0565: .getPrimaryFile(),
0566: false, //not recursive
0567: new FileObjectFilter[] { SearchInfoFactory.VISIBILITY_FILTER }))), }));
0568: this .root = root;
0569: this .dataFolder = dataFolder;
0570: this .isDefaultPackage = root.equals(dataFolder
0571: .getPrimaryFile());
0572: }
0573:
0574: FileObject getRoot() {
0575: return root; // Used from PackageRootNode
0576: }
0577:
0578: public String getName() {
0579: String relativePath = FileUtil.getRelativePath(root,
0580: dataFolder.getPrimaryFile());
0581: return relativePath == null ? null : relativePath.replace(
0582: '/', '.'); // NOI18N
0583: }
0584:
0585: public Action[] getActions(boolean context) {
0586:
0587: if (!context) {
0588: if (actions == null) {
0589: // Copy actions and leave out the PropertiesAction and FileSystemAction.
0590: Action super Actions[] = super .getActions(context);
0591: ArrayList actionList = new ArrayList(
0592: super Actions.length);
0593:
0594: for (int i = 0; i < super Actions.length; i++) {
0595:
0596: if (super Actions[i] == null
0597: && super Actions[i + 1] instanceof org.openide.actions.PropertiesAction) {
0598: i++;
0599: continue;
0600: } else if (super Actions[i] instanceof org.openide.actions.PropertiesAction) {
0601: continue;
0602: }
0603: // else if ( superActions[i] instanceof org.openide.actions.FileSystemAction ) {
0604: // actionList.add (null); // insert separator and new action
0605: // actionList.add (FileSensitiveActions.fileCommandAction(ActionProvider.COMMAND_COMPILE_SINGLE,
0606: // NbBundle.getMessage( PackageViewChildren.class, "LBL_CompilePackage_Action" ), // NOI18N
0607: // null ));
0608: // }
0609:
0610: actionList.add(super Actions[i]);
0611: }
0612:
0613: actions = new Action[actionList.size()];
0614: actionList.toArray(actions);
0615: }
0616: return actions;
0617: } else {
0618: return super .getActions(context);
0619: }
0620: }
0621:
0622: public boolean canRename() {
0623: if (isDefaultPackage) {
0624: return false;
0625: } else {
0626: return true;
0627: }
0628: }
0629:
0630: public boolean canCut() {
0631: return !isDefaultPackage;
0632: }
0633:
0634: /**
0635: * Copy handling
0636: */
0637: public Transferable clipboardCopy() throws IOException {
0638: try {
0639: return new PackageTransferable(this ,
0640: DnDConstants.ACTION_COPY);
0641: } catch (ClassNotFoundException e) {
0642: throw new AssertionError(e);
0643: }
0644: }
0645:
0646: public Transferable clipboardCut() throws IOException {
0647: try {
0648: return new PackageTransferable(this ,
0649: DnDConstants.ACTION_MOVE);
0650: } catch (ClassNotFoundException e) {
0651: throw new AssertionError(e);
0652: }
0653: }
0654:
0655: public/*@Override*/Transferable drag() throws IOException {
0656: try {
0657: return new PackageTransferable(this ,
0658: DnDConstants.ACTION_NONE);
0659: } catch (ClassNotFoundException e) {
0660: throw new AssertionError(e);
0661: }
0662: }
0663:
0664: public PasteType[] getPasteTypes(Transferable t) {
0665: if (t.isDataFlavorSupported(ExTransferable.multiFlavor)) {
0666: try {
0667: MultiTransferObject mto = (MultiTransferObject) t
0668: .getTransferData(ExTransferable.multiFlavor);
0669: boolean hasPackageFlavor = false;
0670: for (int i = 0; i < mto.getCount(); i++) {
0671: DataFlavor[] flavors = mto
0672: .getTransferDataFlavors(i);
0673: if (isPackageFlavor(flavors)) {
0674: hasPackageFlavor = true;
0675: }
0676: }
0677: return hasPackageFlavor ? new PasteType[0] : super
0678: .getPasteTypes(t);
0679: } catch (UnsupportedFlavorException e) {
0680: ErrorManager.getDefault().notify(e);
0681: return new PasteType[0];
0682: } catch (IOException e) {
0683: ErrorManager.getDefault().notify(e);
0684: return new PasteType[0];
0685: }
0686: } else {
0687: DataFlavor[] flavors = t.getTransferDataFlavors();
0688: if (isPackageFlavor(flavors)) {
0689: return new PasteType[0];
0690: } else {
0691: return super .getPasteTypes(t);
0692: }
0693: }
0694: }
0695:
0696: public/*@Override*/PasteType getDropType(Transferable t,
0697: int action, int index) {
0698: if (t.isDataFlavorSupported(ExTransferable.multiFlavor)) {
0699: try {
0700: MultiTransferObject mto = (MultiTransferObject) t
0701: .getTransferData(ExTransferable.multiFlavor);
0702: boolean hasPackageFlavor = false;
0703: for (int i = 0; i < mto.getCount(); i++) {
0704: DataFlavor[] flavors = mto
0705: .getTransferDataFlavors(i);
0706: if (isPackageFlavor(flavors)) {
0707: hasPackageFlavor = true;
0708: }
0709: }
0710: return hasPackageFlavor ? null : super .getDropType(
0711: t, action, index);
0712: } catch (UnsupportedFlavorException e) {
0713: ErrorManager.getDefault().notify(e);
0714: return null;
0715: } catch (IOException e) {
0716: ErrorManager.getDefault().notify(e);
0717: return null;
0718: }
0719: } else {
0720: DataFlavor[] flavors = t.getTransferDataFlavors();
0721: if (isPackageFlavor(flavors)) {
0722: return null;
0723: } else {
0724: return super .getDropType(t, action, index);
0725: }
0726: }
0727: }
0728:
0729: private boolean isPackageFlavor(DataFlavor[] flavors) {
0730: for (int i = 0; i < flavors.length; i++) {
0731: if (SUBTYPE.equals(flavors[i].getSubType())
0732: && PRIMARY_TYPE.equals(flavors[i]
0733: .getPrimaryType())) {
0734: //Disable pasting into package, only paste into root is allowed
0735: return true;
0736: }
0737: }
0738: return false;
0739: }
0740:
0741: private static synchronized PackageRenameHandler getRenameHandler() {
0742: Lookup.Result renameImplementations = Lookup.getDefault()
0743: .lookup(
0744: new Lookup.Template(
0745: PackageRenameHandler.class));
0746: List handlers = (List) renameImplementations.allInstances();
0747: if (handlers.size() == 0)
0748: return null;
0749: if (handlers.size() > 1)
0750: ErrorManager
0751: .getDefault()
0752: .log(
0753: ErrorManager.WARNING,
0754: "Multiple instances of PackageRenameHandler found in Lookup; only using first one: "
0755: + handlers); //NOI18N
0756: return (PackageRenameHandler) handlers.get(0);
0757: }
0758:
0759: public void setName(String name) {
0760: PackageRenameHandler handler = getRenameHandler();
0761: if (handler != null) {
0762: handler.handleRename(this , name);
0763: return;
0764: }
0765:
0766: if (isDefaultPackage) {
0767: return;
0768: }
0769: String oldName = getName();
0770: if (oldName.equals(name)) {
0771: return;
0772: }
0773: if (!isValidPackageName(name)) {
0774: DialogDisplayer.getDefault().notify(
0775: new NotifyDescriptor.Message(NbBundle
0776: .getMessage(PackageViewChildren.class,
0777: "MSG_InvalidPackageName"),
0778: NotifyDescriptor.INFORMATION_MESSAGE));
0779: return;
0780: }
0781: name = name.replace('.', '/') + '/'; //NOI18N
0782: oldName = oldName.replace('.', '/') + '/'; //NOI18N
0783: int i;
0784: for (i = 0; i < oldName.length() && i < name.length(); i++) {
0785: if (oldName.charAt(i) != name.charAt(i)) {
0786: break;
0787: }
0788: }
0789: i--;
0790: int index = oldName.lastIndexOf('/', i); //NOI18N
0791: String commonPrefix = index == -1 ? null : oldName
0792: .substring(0, index);
0793: String toCreate = (index + 1 == name.length()) ? "" : name
0794: .substring(index + 1); //NOI18N
0795: try {
0796: FileObject commonFolder = commonPrefix == null ? this .root
0797: : this .root.getFileObject(commonPrefix);
0798: FileObject destination = commonFolder;
0799: StringTokenizer dtk = new StringTokenizer(toCreate, "/"); //NOI18N
0800: while (dtk.hasMoreTokens()) {
0801: String pathElement = dtk.nextToken();
0802: FileObject tmp = destination
0803: .getFileObject(pathElement);
0804: if (tmp == null) {
0805: tmp = destination.createFolder(pathElement);
0806: }
0807: destination = tmp;
0808: }
0809: FileObject source = this .dataFolder.getPrimaryFile();
0810: DataFolder sourceFolder = DataFolder.findFolder(source);
0811: DataFolder destinationFolder = DataFolder
0812: .findFolder(destination);
0813: DataObject[] children = sourceFolder.getChildren();
0814: for (int j = 0; j < children.length; j++) {
0815: if (children[j].getPrimaryFile().isData()) {
0816: children[j].move(destinationFolder);
0817: }
0818: }
0819: while (!commonFolder.equals(source)) {
0820: if (source.getChildren().length == 0) {
0821: FileObject tmp = source;
0822: source = source.getParent();
0823: tmp.delete();
0824: } else {
0825: break;
0826: }
0827: }
0828: } catch (IOException ioe) {
0829: ErrorManager.getDefault().notify(ioe);
0830: }
0831: }
0832:
0833: public boolean canDestroy() {
0834: if (isDefaultPackage) {
0835: return false;
0836: } else {
0837: return true;
0838: }
0839: }
0840:
0841: public void destroy() throws IOException {
0842: FileObject parent = dataFolder.getPrimaryFile().getParent();
0843: // First; delete all files except packages
0844: DataObject ch[] = dataFolder.getChildren();
0845: boolean empty = true;
0846: for (int i = 0; ch != null && i < ch.length; i++) {
0847: if (!ch[i].getPrimaryFile().isFolder()) {
0848: ch[i].delete();
0849: } else {
0850: empty = false;
0851: }
0852: }
0853:
0854: // If empty delete itself
0855: if (empty) {
0856: super .destroy();
0857: }
0858:
0859: // Second; delete empty super packages
0860: while (!parent.equals(root)
0861: && parent.getChildren().length == 0) {
0862: FileObject newParent = parent.getParent();
0863: parent.delete();
0864: parent = newParent;
0865: }
0866: }
0867:
0868: /**
0869: * Initially overridden to support CVS status labels in package nodes.
0870: *
0871: * @return annotated display name
0872: */
0873: public String getHtmlDisplayName() {
0874: String name = getDisplayName();
0875: try {
0876: FileObject fo = dataFolder.getPrimaryFile();
0877: Set set = new NonResursiveFolderSet(fo);
0878: org.openide.filesystems.FileSystem.Status status = fo
0879: .getFileSystem().getStatus();
0880: if (status instanceof org.openide.filesystems.FileSystem.HtmlStatus) {
0881: name = ((org.openide.filesystems.FileSystem.HtmlStatus) status)
0882: .annotateNameHtml(name, set);
0883: } else {
0884: name = status.annotateName(name, set);
0885: }
0886: } catch (FileStateInvalidException e) {
0887: // no fs, do nothing
0888: }
0889: return name;
0890: }
0891:
0892: public String getDisplayName() {
0893: FileObject folder = dataFolder.getPrimaryFile();
0894: String path = FileUtil.getRelativePath(root, folder);
0895: if (path == null) {
0896: // ???
0897: return "";
0898: }
0899: return PackageDisplayUtils.getDisplayLabel(path.replace(
0900: '/', '.'));
0901: }
0902:
0903: public String getShortDescription() {
0904: FileObject folder = dataFolder.getPrimaryFile();
0905: String path = FileUtil.getRelativePath(root, folder);
0906: if (path == null) {
0907: // ???
0908: return "";
0909: }
0910: return PackageDisplayUtils.getToolTip(folder, path.replace(
0911: '/', '.'));
0912: }
0913:
0914: public java.awt.Image getIcon(int type) {
0915: java.awt.Image img = getMyIcon(type);
0916:
0917: try {
0918: FileObject fo = dataFolder.getPrimaryFile();
0919: Set set = new NonResursiveFolderSet(fo);
0920: img = fo.getFileSystem().getStatus().annotateIcon(img,
0921: type, set);
0922: } catch (FileStateInvalidException e) {
0923: // no fs, do nothing
0924: }
0925:
0926: return img;
0927: }
0928:
0929: public java.awt.Image getOpenedIcon(int type) {
0930: java.awt.Image img = getMyOpenedIcon(type);
0931:
0932: try {
0933: FileObject fo = dataFolder.getPrimaryFile();
0934: Set set = new NonResursiveFolderSet(fo);
0935: img = fo.getFileSystem().getStatus().annotateIcon(img,
0936: type, set);
0937: } catch (FileStateInvalidException e) {
0938: // no fs, do nothing
0939: }
0940:
0941: return img;
0942: }
0943:
0944: private Image getMyIcon(int type) {
0945: FileObject folder = dataFolder.getPrimaryFile();
0946: String path = FileUtil.getRelativePath(root, folder);
0947: if (path == null) {
0948: // ???
0949: return null;
0950: }
0951: return PackageDisplayUtils.getIcon(folder, path.replace(
0952: '/', '.'), isLeaf());
0953: }
0954:
0955: private Image getMyOpenedIcon(int type) {
0956: return getIcon(type);
0957: }
0958:
0959: public void update() {
0960: fireIconChange();
0961: fireOpenedIconChange();
0962: }
0963:
0964: public void updateDisplayName() {
0965: fireNameChange(null, null);
0966: fireDisplayNameChange(null, null);
0967: fireShortDescriptionChange(null, null);
0968: }
0969:
0970: public void updateChildren() {
0971: boolean leaf = isLeaf();
0972: DataFolder df = getDataFolder();
0973: boolean empty = isEmpty(df);
0974: if (leaf != empty) {
0975: setChildren(empty ? Children.LEAF : df
0976: .createNodeChildren(NO_FOLDERS_FILTER));
0977: update();
0978: }
0979: }
0980:
0981: public/*@Override*/Node.PropertySet[] getPropertySets() {
0982: Node.PropertySet[] properties = super .getPropertySets();
0983: for (int i = 0; i < properties.length; i++) {
0984: if (Sheet.PROPERTIES.equals(properties[i].getName())) {
0985: //Replace the Sheet.PROPERTIES by the new one
0986: //having only the name property which does refactoring
0987: properties[i] = Sheet.createPropertiesSet();
0988: ((Sheet.Set) properties[i])
0989: .put(new PropertySupport.ReadWrite(
0990: DataObject.PROP_NAME, String.class,
0991: NbBundle.getMessage(
0992: PackageViewChildren.class,
0993: "PROP_name"),
0994: NbBundle.getMessage(
0995: PackageViewChildren.class,
0996: "HINT_name")) {
0997:
0998: public/*@Override*/Object getValue() {
0999: return PackageViewChildren.PackageNode.this
1000: .getName();
1001: }
1002:
1003: public/*@Override*/void setValue(
1004: Object val)
1005: throws IllegalAccessException,
1006: IllegalArgumentException,
1007: InvocationTargetException {
1008: if (!canRename())
1009: throw new IllegalAccessException();
1010: if (!(val instanceof String))
1011: throw new IllegalArgumentException();
1012: PackageViewChildren.PackageNode.this
1013: .setName((String) val);
1014: }
1015:
1016: public/*@Override*/boolean canWrite() {
1017: return PackageViewChildren.PackageNode.this
1018: .canRename();
1019: }
1020: });
1021: }
1022: }
1023: return properties;
1024: }
1025:
1026: private DataFolder getDataFolder() {
1027: return (DataFolder) getCookie(DataFolder.class);
1028: }
1029:
1030: private static boolean isEmpty(DataFolder dataFolder) {
1031: if (dataFolder == null) {
1032: return true;
1033: }
1034: return PackageDisplayUtils.isEmpty(dataFolder
1035: .getPrimaryFile());
1036: }
1037:
1038: private static boolean isValidPackageName(String name) {
1039: if (name.length() == 0) {
1040: //Fast check of default pkg
1041: return true;
1042: }
1043: StringTokenizer tk = new StringTokenizer(name, ".", true); //NOI18N
1044: boolean delimExpected = false;
1045: while (tk.hasMoreTokens()) {
1046: String namePart = tk.nextToken();
1047: if (!delimExpected) {
1048: if (namePart.equals(".")) { //NOI18N
1049: return false;
1050: }
1051: for (int i = 0; i < namePart.length(); i++) {
1052: char c = namePart.charAt(i);
1053: if (i == 0) {
1054: if (!Character.isJavaIdentifierStart(c)) {
1055: return false;
1056: }
1057: } else {
1058: if (!Character.isJavaIdentifierPart(c)) {
1059: return false;
1060: }
1061: }
1062: }
1063: } else {
1064: if (!namePart.equals(".")) { //NOI18N
1065: return false;
1066: }
1067: }
1068: delimExpected = !delimExpected;
1069: }
1070: return delimExpected;
1071: }
1072: }
1073:
1074: private static final class NoFoldersContainer implements
1075: DataObject.Container, java.beans.PropertyChangeListener,
1076: NonRecursiveFolder {
1077: private DataFolder folder;
1078: private PropertyChangeSupport prop = new PropertyChangeSupport(
1079: this );
1080:
1081: public NoFoldersContainer(DataFolder folder) {
1082: this .folder = folder;
1083: }
1084:
1085: public FileObject getFolder() {
1086: return folder.getPrimaryFile();
1087: }
1088:
1089: public DataObject[] getChildren() {
1090: DataObject[] arr = folder.getChildren();
1091: ArrayList list = new ArrayList(arr.length);
1092: for (int i = 0; i < arr.length; i++) {
1093: if (arr[i] instanceof DataFolder)
1094: continue;
1095:
1096: list.add(arr[i]);
1097: }
1098: return list.size() == arr.length ? arr
1099: : (DataObject[]) list.toArray(new DataObject[0]);
1100: }
1101:
1102: public void addPropertyChangeListener(
1103: java.beans.PropertyChangeListener l) {
1104: prop.addPropertyChangeListener(l);
1105: }
1106:
1107: public void removePropertyChangeListener(
1108: java.beans.PropertyChangeListener l) {
1109: prop.removePropertyChangeListener(l);
1110: }
1111:
1112: public void propertyChange(java.beans.PropertyChangeEvent evt) {
1113: if (DataObject.Container.PROP_CHILDREN.equals(evt
1114: .getPropertyName())) {
1115: prop.firePropertyChange(PROP_CHILDREN, null, null);
1116: }
1117: }
1118: }
1119:
1120: static final class NoFoldersDataFilter implements ChangeListener,
1121: ChangeableDataFilter {
1122:
1123: EventListenerList ell = new EventListenerList();
1124:
1125: public NoFoldersDataFilter() {
1126: VisibilityQuery.getDefault().addChangeListener(this );
1127: }
1128:
1129: public boolean acceptDataObject(DataObject obj) {
1130: FileObject fo = obj.getPrimaryFile();
1131: return VisibilityQuery.getDefault().isVisible(fo)
1132: && !(obj instanceof DataFolder);
1133: }
1134:
1135: public void stateChanged(ChangeEvent e) {
1136: Object[] listeners = ell.getListenerList();
1137: ChangeEvent event = null;
1138: for (int i = listeners.length - 2; i >= 0; i -= 2) {
1139: if (listeners[i] == ChangeListener.class) {
1140: if (event == null) {
1141: event = new ChangeEvent(this );
1142: }
1143: ((ChangeListener) listeners[i + 1])
1144: .stateChanged(event);
1145: }
1146: }
1147: }
1148:
1149: public void addChangeListener(ChangeListener listener) {
1150: ell.add(ChangeListener.class, listener);
1151: }
1152:
1153: public void removeChangeListener(ChangeListener listener) {
1154: ell.remove(ChangeListener.class, listener);
1155: }
1156:
1157: }
1158:
1159: static class PackageTransferable extends ExTransferable.Single {
1160:
1161: private PackageNode node;
1162:
1163: public PackageTransferable(PackageNode node, int operation)
1164: throws ClassNotFoundException {
1165: super (new DataFlavor(PACKAGE_FLAVOR
1166: .format(new Object[] { new Integer(operation) }),
1167: null, PackageNode.class.getClassLoader()));
1168: this .node = node;
1169: }
1170:
1171: protected Object getData() throws IOException,
1172: UnsupportedFlavorException {
1173: return this .node;
1174: }
1175: }
1176:
1177: static class PackagePasteType extends PasteType {
1178:
1179: private int op;
1180: private PackageNode[] nodes;
1181: private FileObject srcRoot;
1182:
1183: public PackagePasteType(FileObject srcRoot, PackageNode[] node,
1184: int op) {
1185: assert op == DnDConstants.ACTION_COPY
1186: || op == DnDConstants.ACTION_MOVE
1187: || op == DnDConstants.ACTION_NONE : "Invalid DnD operation"; //NOI18N
1188: this .nodes = node;
1189: this .op = op;
1190: this .srcRoot = srcRoot;
1191: }
1192:
1193: public void setOperation(int op) {
1194: this .op = op;
1195: }
1196:
1197: public Transferable paste() throws IOException {
1198: assert this .op != DnDConstants.ACTION_NONE;
1199: for (int ni = 0; ni < nodes.length; ni++) {
1200: FileObject fo = srcRoot;
1201: if (!nodes[ni].isDefaultPackage) {
1202: String pkgName = nodes[ni].getName();
1203: StringTokenizer tk = new StringTokenizer(pkgName,
1204: "."); //NOI18N
1205: while (tk.hasMoreTokens()) {
1206: String name = tk.nextToken();
1207: FileObject tmp = fo.getFileObject(name, null);
1208: if (tmp == null) {
1209: tmp = fo.createFolder(name);
1210: }
1211: fo = tmp;
1212: }
1213: }
1214: DataFolder dest = DataFolder.findFolder(fo);
1215: DataObject[] children = nodes[ni].dataFolder
1216: .getChildren();
1217: boolean cantDelete = false;
1218: for (int i = 0; i < children.length; i++) {
1219: if (children[i].getPrimaryFile().isData()
1220: && VisibilityQuery.getDefault().isVisible(
1221: children[i].getPrimaryFile())) {
1222: //Copy only the package level
1223: children[i].copy(dest);
1224: if (this .op == DnDConstants.ACTION_MOVE) {
1225: try {
1226: children[i].delete();
1227: } catch (IOException ioe) {
1228: cantDelete = true;
1229: }
1230: }
1231: } else {
1232: cantDelete = true;
1233: }
1234: }
1235: if (this .op == DnDConstants.ACTION_MOVE && !cantDelete) {
1236: try {
1237: FileObject tmpFo = nodes[ni].dataFolder
1238: .getPrimaryFile();
1239: FileObject originalRoot = nodes[ni].root;
1240: assert tmpFo != null && originalRoot != null;
1241: while (!tmpFo.equals(originalRoot)) {
1242: if (tmpFo.getChildren().length == 0) {
1243: FileObject tmpFoParent = tmpFo
1244: .getParent();
1245: tmpFo.delete();
1246: tmpFo = tmpFoParent;
1247: } else {
1248: break;
1249: }
1250: }
1251: } catch (IOException ioe) {
1252: //Not important
1253: }
1254: }
1255: }
1256: return ExTransferable.EMPTY;
1257: }
1258:
1259: public String getName() {
1260: return NbBundle.getMessage(PackageViewChildren.class,
1261: "TXT_PastePackage");
1262: }
1263: }
1264:
1265: /**
1266: * FileObject set that represents package. It means
1267: * that it's content must not be processed recursively.
1268: */
1269: private static class NonResursiveFolderSet extends HashSet
1270: implements NonRecursiveFolder {
1271:
1272: private final FileObject folder;
1273:
1274: /**
1275: * Creates set with one element, the folder.
1276: */
1277: public NonResursiveFolderSet(FileObject folder) {
1278: this .folder = folder;
1279: add(folder);
1280: }
1281:
1282: public FileObject getFolder() {
1283: return folder;
1284: }
1285: }
1286: }
|