0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.openide.loaders;
0043:
0044: import java.beans.PropertyVetoException;
0045: import java.io.*;
0046: import java.lang.ref.WeakReference;
0047: import java.util.*;
0048: import java.util.logging.*;
0049: import javax.swing.event.*;
0050: import org.openide.filesystems.*;
0051: import org.openide.nodes.*;
0052: import org.openide.util.*;
0053:
0054: /** Provides support for handling of data objects with multiple files.
0055: * One file is represented by one {@link Entry}. Each handler
0056: * has one {@link #getPrimaryEntry primary} entry and zero or more secondary entries.
0057: *
0058: * @author Ales Novak, Jaroslav Tulach, Ian Formanek
0059: */
0060: public class MultiDataObject extends DataObject {
0061: /** generated Serialized Version UID */
0062: static final long serialVersionUID = -7750146802134210308L;
0063:
0064: /** Synchronization object used in getCookieSet and setCookieSet methods.
0065: */
0066: private static final Object cookieSetLock = new Object();
0067:
0068: /** Lock used for lazy creation of secondary field (in method getSecondary()) */
0069: private static final Object secondaryCreationLock = new Object();
0070:
0071: /** A RequestProceccor used for firing property changes asynchronously */
0072: private static final RequestProcessor firingProcessor = new RequestProcessor(
0073: "MDO PropertyChange processor");
0074:
0075: /** A RequestProceccor used for waiting for finishing refresh */
0076: private static final RequestProcessor delayProcessor = new RequestProcessor(
0077: "MDO Firing delayer");
0078: /** a task waiting for the FolderList task to finish scanning of the folder */
0079: private RequestProcessor.Task delayedPropFilesTask;
0080: /** lock used in firePropFilesAfterFinishing */
0081: private static final Object delayedPropFilesLock = new Object();
0082: /** logging of operations in multidataobject */
0083: static final Logger ERR = Logger.getLogger(MultiDataObject.class
0084: .getName());
0085:
0086: /** getPrimaryEntry() is intended to have all inetligence for copy/move/... */
0087: private Entry primary;
0088:
0089: /** Map of secondary entries and its files. (FileObject, Entry) */
0090: private HashMap<FileObject, Entry> secondary;
0091:
0092: /** array of cookies for this object */
0093: private CookieSet cookieSet;
0094:
0095: /** flag when to call checkFiles(this) */
0096: boolean checked = false;
0097:
0098: /** Create a MultiFileObject.
0099: * @see DataObject#DataObject(org.openide.filesystems.FileObject,org.openide.loaders.DataLoader)
0100: * @param fo the primary file object
0101: * @param loader loader of this data object
0102: */
0103: public MultiDataObject(FileObject fo, MultiFileLoader loader)
0104: throws DataObjectExistsException {
0105: super (fo, loader);
0106: primary = createPrimaryEntry(this , getPrimaryFile());
0107: }
0108:
0109: /** This constructor is added for backward compatibility, MultiDataObject should be
0110: * properly constructed using the MultiFileLoader.
0111: * @param fo the primary file object
0112: * @param loader loader of this data object
0113: * @deprecated do not use this constructor, it is for backward compatibility of
0114: * {@link #DataShadow} and {@link #DataFolder} only
0115: * @since 1.13
0116: */
0117: @Deprecated
0118: MultiDataObject(FileObject fo, DataLoader loader)
0119: throws DataObjectExistsException {
0120: super (fo, loader);
0121: primary = createPrimaryEntry(this , getPrimaryFile());
0122: }
0123:
0124: /** Getter for the multi file loader that created this
0125: * object.
0126: *
0127: * @return the multi loader for the object
0128: */
0129: public final MultiFileLoader getMultiFileLoader() {
0130: DataLoader loader = getLoader();
0131:
0132: if (!(loader instanceof MultiFileLoader))
0133: return null;
0134:
0135: return (MultiFileLoader) loader;
0136: }
0137:
0138: @Override
0139: public Set<FileObject> files() {
0140: // move lazy initialization to FilesSet
0141: return new FilesSet(this );
0142: }
0143:
0144: /* Getter for delete action.
0145: * @return true if the object can be deleted
0146: */
0147: public boolean isDeleteAllowed() {
0148: return !getPrimaryFile().isReadOnly()
0149: && !existReadOnlySecondary();
0150: }
0151:
0152: private boolean existReadOnlySecondary() {
0153: synchronized (synchObjectSecondary()) {
0154: for (FileObject f : getSecondary().keySet()) {
0155: if (f.isReadOnly()) {
0156: return true;
0157: }
0158: }
0159: }
0160: return false;
0161: }
0162:
0163: /** Performs checks by calling checkFiles
0164: * @return getSecondary() method result
0165: */
0166: private Map<FileObject, Entry> checkSecondary() {
0167: // enumeration of all files
0168: if (!checked) {
0169: checkFiles(this );
0170: checked = true;
0171: }
0172: return getSecondary();
0173: }
0174:
0175: /** Lazy getter for secondary property
0176: * @return secondary object
0177: */
0178: /* package-private */Map<FileObject, Entry> getSecondary() {
0179: synchronized (secondaryCreationLock) {
0180: if (secondary == null) {
0181: secondary = new HashMap<FileObject, Entry>(4);
0182: }
0183: if (ERR.isLoggable(Level.FINE)) {
0184: ERR.fine("getSecondary for " + this + " is "
0185: + secondary); // NOI18N
0186: }
0187: return secondary;
0188: }
0189: }
0190:
0191: /* Getter for copy action.
0192: * @return true if the object can be copied
0193: */
0194: public boolean isCopyAllowed() {
0195: return true;
0196: }
0197:
0198: /* Getter for move action.
0199: * @return true if the object can be moved
0200: */
0201: public boolean isMoveAllowed() {
0202: return !getPrimaryFile().isReadOnly()
0203: && !existReadOnlySecondary();
0204: }
0205:
0206: /* Getter for rename action.
0207: * @return true if the object can be renamed
0208: */
0209: public boolean isRenameAllowed() {
0210: return !getPrimaryFile().isReadOnly()
0211: && !existReadOnlySecondary();
0212: }
0213:
0214: /* Help context for this object.
0215: * @return help context
0216: */
0217: public HelpCtx getHelpCtx() {
0218: return HelpCtx.DEFAULT_HELP;
0219: }
0220:
0221: /** Provide object used for synchronization of methods working with
0222: * Secondaries.
0223: * @return The private field <CODE>secondary</CODE>.
0224: */
0225: Object synchObjectSecondary() {
0226: Object lock = checkSecondary();
0227: if (lock == null)
0228: throw new IllegalStateException(
0229: "checkSecondary was null from " + this ); // NOI18N
0230: return checkSecondary();
0231: }
0232:
0233: /** Provides node that should represent this data object.
0234: *
0235: * @return the node representation
0236: * @see DataNode
0237: */
0238: protected Node createNodeDelegate() {
0239: DataNode dataNode = (DataNode) super .createNodeDelegate();
0240: return dataNode;
0241: }
0242:
0243: /** Add a new secondary entry to the list.
0244: * @param fe the entry to add
0245: */
0246: protected final void addSecondaryEntry(Entry fe) {
0247: synchronized (getSecondary()) {
0248: getSecondary().put(fe.getFile(), fe);
0249: if (ERR.isLoggable(Level.FINE)) {
0250: ERR.fine("addSecondaryEntry: " + fe + " for " + this ); // NOI18N
0251: }
0252: }
0253:
0254: // Fire PROP_FILES only if we have actually finished making the folder.
0255: // It is dumb to fire this if we do not yet even know what all of our
0256: // initial secondary files are going to be.
0257: FolderList l = getFolderList();
0258: if (l == null) {
0259: firePropertyChangeLater(PROP_FILES, null, null);
0260: } else { // l != null
0261: if (l.isCreated()) {
0262: firePropertyChangeLater(PROP_FILES, null, null);
0263: } else {
0264: firePropFilesAfterFinishing();
0265: }
0266: }
0267: }
0268:
0269: /** Finds FolderList object for the primary file's parent folder
0270: * @return FolderList object or <code>null</code>
0271: */
0272: private FolderList getFolderList() {
0273: FileObject parent = primary.file.getParent();
0274: if (parent != null) {
0275: return FolderList.find(parent, false);
0276: }
0277: return null;
0278: }
0279:
0280: /** Remove a secondary entry from the list.
0281: * @param fe the entry to remove
0282: */
0283: protected final void removeSecondaryEntry(Entry fe) {
0284: synchronized (getSecondary()) {
0285: getSecondary().remove(fe.getFile());
0286: if (ERR.isLoggable(Level.FINE)) {
0287: ERR
0288: .fine("removeSecondaryEntry: " + fe + " for "
0289: + this ); // NOI18N
0290: }
0291: }
0292:
0293: firePropertyChangeLater(PROP_FILES, null, null);
0294: updateFilesInCookieSet();
0295:
0296: if (fe.isImportant()) {
0297: checkConsistency(this );
0298: }
0299: }
0300:
0301: /** All secondary entries are recognized. Called from multi file object.
0302: * @param recognized object to mark recognized file to
0303: */
0304: final void markSecondaryEntriesRecognized(
0305: DataLoader.RecognizedFiles recognized) {
0306: synchronized (getSecondary()) {
0307: for (FileObject fo : getSecondary().keySet()) {
0308: recognized.markRecognized(fo);
0309: }
0310: }
0311: }
0312:
0313: /** Tests whether this file is between entries and if not,
0314: * creates a secondary entry for it and adds it into set of
0315: * secondary entries.
0316: * <P>
0317: * This method should be used in constructor of MultiDataObject to
0318: * register all the important files, that could belong to this data object.
0319: * As example, our XMLDataObject, tries to locate its <CODE>xmlinfo</CODE>
0320: * file and then do register it
0321: *
0322: * @param fo the file to register (can be null, then the action is ignored)
0323: * @return the entry associated to this file object (returns primary entry if the fo is null)
0324: */
0325: protected final Entry registerEntry(FileObject fo) {
0326: synchronized (getSecondary()) {
0327: if (fo == null) {
0328: // is it ok, to do this or somebody would like to see different behavour?
0329: return primary;
0330: }
0331: if (fo.equals(getPrimaryFile())) {
0332: return primary;
0333: }
0334:
0335: Entry e = getSecondary().get(fo);
0336: if (e != null) {
0337: return e;
0338: }
0339:
0340: // add it into set of entries
0341: e = createSecondaryEntry(this , fo);
0342: addSecondaryEntry(e);
0343:
0344: return e;
0345: }
0346: }
0347:
0348: /** Removes the entry from the set of secondary entries.
0349: * Called from the notifyFileDeleted
0350: */
0351: final void removeFile(FileObject fo) {
0352: synchronized (getSecondary()) {
0353: Entry e = getSecondary().get(fo);
0354: if (e != null) {
0355: removeSecondaryEntry(e);
0356: }
0357: }
0358: }
0359:
0360: /** Get the primary entry.
0361: * @return the entry
0362: */
0363: public final Entry getPrimaryEntry() {
0364: return primary;
0365: }
0366:
0367: /** Get secondary entries.
0368: * @return immutable set of entries
0369: */
0370: public final Set<Entry> secondaryEntries() {
0371: synchronized (synchObjectSecondary()) {
0372: removeAllInvalid();
0373:
0374: return new HashSet<Entry>(getSecondary().values());
0375: }
0376: }
0377:
0378: /** For a given file, find the associated secondary entry.
0379: * @param fo file object
0380: * @return the entry associated with the file object, or <code>null</code> if there is no
0381: * such entry
0382: */
0383: public final Entry findSecondaryEntry(FileObject fo) {
0384: Entry e;
0385: synchronized (synchObjectSecondary()) {
0386: removeAllInvalid();
0387: e = getSecondary().get(fo);
0388: }
0389: return e;
0390: }
0391:
0392: /** Removes all FileObjects that are not isValid from the
0393: * set of objects.
0394: */
0395: private void removeAllInvalid() {
0396: if (ERR.isLoggable(Level.FINE)) {
0397: ERR.fine("removeAllInvalid, started " + this ); // NOI18N
0398: }
0399: Iterator it = checkSecondary().entrySet().iterator();
0400: while (it.hasNext()) {
0401: Map.Entry e = (Map.Entry) it.next();
0402: FileObject fo = (FileObject) e.getKey();
0403: if (!fo.isValid()) {
0404: it.remove();
0405: if (ERR.isLoggable(Level.FINE)) {
0406: ERR.fine("removeAllInvalid, removed: " + fo
0407: + " for " + this ); // NOI18N
0408: }
0409: firePropertyChangeLater(PROP_FILES, null, null);
0410: }
0411: }
0412: if (ERR.isLoggable(Level.FINE)) {
0413: ERR.fine("removeAllInvalid, finished " + this ); // NOI18N
0414: }
0415: }
0416:
0417: //methods overriding DataObjectHandler's abstract methods
0418:
0419: /* Obtains lock for primary file by asking getPrimaryEntry() entry.
0420: *
0421: * @return the lock for primary file
0422: * @exception IOException if it is not possible to set the template
0423: * state.
0424: */
0425: protected FileLock takePrimaryFileLock() throws IOException {
0426: return getPrimaryEntry().takeLock();
0427: }
0428:
0429: // XXX does nothing of the sort --jglick
0430: /** Check if in specific folder exists fileobject with the same name.
0431: * If it exists user is asked for confirmation to rewrite, rename or cancel operation.
0432: * @param folder destination folder
0433: * @return the suffix which should be added to the name or null if operation is cancelled
0434: */
0435: private String existInFolder(FileObject fo, FileObject folder) {
0436: // merge folders when neccessary
0437: if (fo.isFolder() && isMergingFolders(fo, folder))
0438: return ""; // NOI18N
0439:
0440: String orig = fo.getName();
0441: String name = FileUtil.findFreeFileName(folder, orig, fo
0442: .getExt());
0443: if (name.length() <= orig.length()) {
0444: return ""; // NOI18N
0445: } else {
0446: return name.substring(orig.length());
0447: }
0448: }
0449:
0450: /** Override to change default handling of name collisions detected during the
0451: * copy, move operations. Reasonable for MultiDataObjects having folder their
0452: * primary file (e.g. DataFolder, CompoundDataObject).
0453: * @return <code>false</code> means, that new folder name should be synthetized when
0454: * the same folder already exists in the target location of copy, move operation, otherwise
0455: * existing falder will be used. Default implementation returns <code>false</code>.
0456: */
0457: boolean isMergingFolders(FileObject who, FileObject targetFolder) {
0458: return false;
0459: }
0460:
0461: /** Copies primary and secondary files to new folder.
0462: * May ask for user confirmation before overwriting.
0463: * @param df the new folder
0464: * @return data object for the new primary
0465: * @throws IOException if there was a problem copying
0466: * @throws UserCancelException if the user cancelled the copy
0467: */
0468: protected DataObject handleCopy(DataFolder df) throws IOException {
0469: FileObject fo;
0470:
0471: String suffix = existInFolder(getPrimaryEntry().getFile(), df
0472: .getPrimaryFile());
0473: if (suffix == null)
0474: throw new org.openide.util.UserCancelException();
0475:
0476: Iterator it = secondaryEntries().iterator();
0477: while (it.hasNext()) {
0478: ((Entry) it.next()).copy(df.getPrimaryFile(), suffix);
0479: }
0480: //#33244 - copy primary file after the secondary ones
0481: fo = getPrimaryEntry().copy(df.getPrimaryFile(), suffix);
0482:
0483: boolean fullRescan = getMultiFileLoader() == null
0484: || getMultiFileLoader().findPrimaryFile(fo) != fo;
0485: try {
0486: return fullRescan ? DataObject.find(fo)
0487: : createMultiObject(fo);
0488: } catch (DataObjectExistsException ex) {
0489: return ex.getDataObject();
0490: }
0491: }
0492:
0493: /* Deletes all secondary entries, removes them from the set of
0494: * secondary entries and then deletes the getPrimaryEntry() entry.
0495: */
0496: protected void handleDelete() throws IOException {
0497: List<FileObject> toRemove = new ArrayList<FileObject>();
0498: Iterator<Map.Entry<FileObject, Entry>> it;
0499: synchronized (synchObjectSecondary()) {
0500: removeAllInvalid();
0501: it = new ArrayList<Map.Entry<FileObject, Entry>>(
0502: getSecondary().entrySet()).iterator();
0503: }
0504:
0505: while (it.hasNext()) {
0506: Map.Entry<FileObject, Entry> e = it.next();
0507: e.getValue().delete();
0508: toRemove.add(e.getKey());
0509: }
0510:
0511: synchronized (synchObjectSecondary()) {
0512: for (FileObject f : toRemove) {
0513: getSecondary().remove(f);
0514: if (ERR.isLoggable(Level.FINE)) {
0515: ERR.fine(" handleDelete, removed entry: " + f);
0516: }
0517: }
0518: }
0519:
0520: getPrimaryEntry().delete();
0521: }
0522:
0523: /* Renames all entries and changes their files to new ones.
0524: */
0525: protected FileObject handleRename(String name) throws IOException {
0526: getPrimaryEntry().changeFile(getPrimaryEntry().rename(name));
0527:
0528: Map<FileObject, Entry> add = null;
0529:
0530: List<FileObject> toRemove = new ArrayList<FileObject>();
0531:
0532: Iterator<Map.Entry<FileObject, Entry>> it;
0533: synchronized (synchObjectSecondary()) {
0534: removeAllInvalid();
0535: it = new ArrayList<Map.Entry<FileObject, Entry>>(
0536: getSecondary().entrySet()).iterator();
0537: }
0538:
0539: while (it.hasNext()) {
0540: Map.Entry<FileObject, Entry> e = it.next();
0541: FileObject fo = e.getValue().rename(name);
0542: if (fo == null) {
0543: // remove the entry
0544: toRemove.add(e.getKey());
0545: } else {
0546: if (!fo.equals(e.getKey())) {
0547: // put the new one into change table
0548: if (add == null)
0549: add = new HashMap<FileObject, Entry>();
0550: Entry entry = e.getValue();
0551: entry.changeFile(fo);
0552: // using getFile to let the entry correctly annotate
0553: // the file by isImportant flag
0554: add.put(entry.getFile(), entry);
0555:
0556: // changed the file => remove the file
0557: toRemove.add(e.getKey());
0558: }
0559: }
0560: }
0561:
0562: // if there has been a change in files, apply it
0563: if ((add != null) || (!toRemove.isEmpty())) {
0564: synchronized (synchObjectSecondary()) {
0565: // remove entries
0566: if (!toRemove.isEmpty()) {
0567: for (FileObject f : toRemove) {
0568: getSecondary().remove(f);
0569: if (ERR.isLoggable(Level.FINE)) {
0570: ERR.fine("handleRename, removed: " + f
0571: + " for " + this ); // NOI18N
0572: }
0573: }
0574: }
0575: // add entries
0576: if (add != null) {
0577: getSecondary().putAll(add);
0578: if (ERR.isLoggable(Level.FINE)) {
0579: ERR.fine("handleRename, putAll: " + add
0580: + " for " + this ); // NOI18N
0581: }
0582: }
0583: }
0584: firePropertyChangeLater(PROP_FILES, null, null);
0585: }
0586:
0587: return getPrimaryEntry().getFile();
0588: }
0589:
0590: /** Moves primary and secondary files to a new folder.
0591: * May ask for user confirmation before overwriting.
0592: * @param df the new folder
0593: * @return the moved primary file object
0594: * @throws IOException if there was a problem moving
0595: * @throws UserCancelException if the user cancelled the move
0596: */
0597: protected FileObject handleMove(DataFolder df) throws IOException {
0598: String suffix = existInFolder(getPrimaryEntry().getFile(), df
0599: .getPrimaryFile());
0600: if (suffix == null)
0601: throw new org.openide.util.UserCancelException();
0602:
0603: List<Pair> backup = saveEntries();
0604:
0605: try {
0606: HashMap<FileObject, Entry> add = null;
0607:
0608: ArrayList<FileObject> toRemove = new ArrayList<FileObject>();
0609: Iterator<Map.Entry<FileObject, Entry>> it;
0610: int count;
0611: synchronized (synchObjectSecondary()) {
0612: removeAllInvalid();
0613: ArrayList<Map.Entry<FileObject, Entry>> list = new ArrayList<Map.Entry<FileObject, Entry>>(
0614: getSecondary().entrySet());
0615: count = list.size();
0616: it = list.iterator();
0617: }
0618:
0619: if (ERR.isLoggable(Level.FINE)) {
0620: ERR.fine("move " + this + " to " + df
0621: + " number of secondary entries: " + count); // NOI18N
0622: ERR.fine("moving primary entry: " + getPrimaryEntry()); // NOI18N
0623: }
0624: getPrimaryEntry()
0625: .changeFile(
0626: getPrimaryEntry().move(df.getPrimaryFile(),
0627: suffix));
0628: if (ERR.isLoggable(Level.FINE))
0629: ERR.fine(" moved: "
0630: + getPrimaryEntry().getFile()); // NOI18N
0631:
0632: while (it.hasNext()) {
0633: Map.Entry<FileObject, Entry> e = it.next();
0634: if (ERR.isLoggable(Level.FINE))
0635: ERR.fine("moving entry :" + e); // NOI18N
0636: FileObject fo = (e.getValue()).move(
0637: df.getPrimaryFile(), suffix);
0638: if (ERR.isLoggable(Level.FINE))
0639: ERR.fine(" moved to :" + fo); // NOI18N
0640: if (fo == null) {
0641: // remove the entry
0642: toRemove.add(e.getKey());
0643: } else {
0644: if (!fo.equals(e.getKey())) {
0645: // put the new one into change table
0646: if (add == null)
0647: add = new HashMap<FileObject, Entry>();
0648: Entry entry = e.getValue();
0649: entry.changeFile(fo);
0650: // using entry.getFile, so the file has correctly
0651: // associated its isImportant flag
0652: add.put(entry.getFile(), entry);
0653:
0654: // changed the file => remove the file
0655: toRemove.add(e.getKey());
0656: }
0657: }
0658: }
0659:
0660: // if there has been a change in files, apply it
0661: if ((add != null) || (!toRemove.isEmpty())) {
0662: synchronized (synchObjectSecondary()) {
0663: // remove entries
0664: if (!toRemove.isEmpty()) {
0665: Object[] objects = toRemove.toArray();
0666: for (int i = 0; i < objects.length; i++) {
0667: getSecondary().remove(objects[i]);
0668: if (ERR.isLoggable(Level.FINE)) {
0669: ERR.fine("handleMove, remove: "
0670: + objects[i] + " for " + this ); // NOI18N
0671: }
0672: }
0673: }
0674: // add entries
0675: if (add != null) {
0676: getSecondary().putAll(add);
0677: if (ERR.isLoggable(Level.FINE)) {
0678: ERR.fine("handleMove, putAll: " + add
0679: + " for " + this ); // NOI18N
0680: }
0681: }
0682: }
0683: firePropertyChangeLater(PROP_FILES, null, null);
0684: }
0685:
0686: if (ERR.isLoggable(Level.FINE)) {
0687: ERR.fine("successfully moved " + this ); // NOI18N
0688: }
0689: return getPrimaryEntry().getFile();
0690: } catch (IOException e) {
0691: if (ERR.isLoggable(Level.FINE)) {
0692: ERR
0693: .fine("exception is here, restoring entries "
0694: + this ); // NOI18N
0695: ERR.log(Level.FINE, null, e);
0696: }
0697: restoreEntries(backup);
0698: if (ERR.isLoggable(Level.FINE)) {
0699: ERR.fine("entries restored " + this ); // NOI18N
0700: }
0701: throw e;
0702: }
0703: }
0704:
0705: /* Creates new object from template.
0706: * @exception IOException
0707: */
0708: protected DataObject handleCreateFromTemplate(DataFolder df,
0709: String name) throws IOException {
0710: if (name == null) {
0711: name = FileUtil.findFreeFileName(df.getPrimaryFile(),
0712: getPrimaryFile().getName(), getPrimaryFile()
0713: .getExt());
0714: }
0715:
0716: FileObject primary = null;
0717: Map<String, Object> params = null;
0718: for (CreateFromTemplateHandler h : Lookup.getDefault()
0719: .lookupAll(CreateFromTemplateHandler.class)) {
0720: FileObject current = getPrimaryEntry().getFile();
0721: if (h.accept(current)) {
0722: if (params == null) {
0723: params = DataObject.CreateAction
0724: .findParameters(name);
0725: }
0726: primary = h.createFromTemplate(current, df
0727: .getPrimaryFile(), name,
0728: DataObject.CreateAction.enhanceParameters(
0729: params, name, current.getExt()));
0730: assert primary != null;
0731: break;
0732: }
0733: }
0734: if (params == null) {
0735: // do the regular creation
0736: primary = getPrimaryEntry().createFromTemplate(
0737: df.getPrimaryFile(), name);
0738: }
0739:
0740: Iterator<Entry> it = secondaryEntries().iterator();
0741: NEXT_ENTRY: while (it.hasNext()) {
0742: Entry entry = it.next();
0743: for (CreateFromTemplateHandler h : Lookup.getDefault()
0744: .lookupAll(CreateFromTemplateHandler.class)) {
0745: FileObject current = entry.getFile();
0746: if (h.accept(current)) {
0747: if (params == null) {
0748: params = DataObject.CreateAction
0749: .findParameters(name);
0750: }
0751: FileObject fo = h.createFromTemplate(current, df
0752: .getPrimaryFile(), name,
0753: DataObject.CreateAction.enhanceParameters(
0754: params, name, current.getExt()));
0755: assert fo != null;
0756: continue NEXT_ENTRY;
0757: }
0758: }
0759: entry.createFromTemplate(df.getPrimaryFile(), name);
0760: }
0761:
0762: try {
0763: // #61600: not very object oriented, but covered by DefaultVersusXMLDataObjectTest
0764: if (this instanceof DefaultDataObject) {
0765: return DataObject.find(primary);
0766: }
0767:
0768: return createMultiObject(primary);
0769: } catch (DataObjectExistsException ex) {
0770: return ex.getDataObject();
0771: }
0772: }
0773:
0774: @Override
0775: protected DataObject handleCopyRename(DataFolder df, String name,
0776: String ext) throws IOException {
0777: if (getLoader() instanceof UniFileLoader) {
0778: //allow the operation for single file DataObjects
0779: FileObject fo = getPrimaryEntry().copyRename(
0780: df.getPrimaryFile(), name, ext);
0781: return DataObject.find(fo);
0782: }
0783:
0784: throw new IOException(
0785: "SaveAs operation not supported for this file type.");
0786: }
0787:
0788: /** Set the set of cookies.
0789: * To the provided cookie set a listener is attached,
0790: * and any change to the set is propagated by
0791: * firing a change on {@link #PROP_COOKIE}.
0792: *
0793: * @param s the cookie set to use
0794: * @deprecated just use getCookieSet().add(...) instead
0795: */
0796: @Deprecated
0797: protected final void setCookieSet(CookieSet s) {
0798: setCookieSet(s, true);
0799: }
0800:
0801: /** Set the set of cookies.
0802: *
0803: * @param s the cookie set to use
0804: * @param fireChange used when called from getter. In this case event shouldn't
0805: * be fired.
0806: */
0807: private void setCookieSet(CookieSet s, boolean fireChange) {
0808: synchronized (cookieSetLock) {
0809: ChangeListener ch = getChangeListener();
0810:
0811: if (cookieSet != null) {
0812: cookieSet.removeChangeListener(ch);
0813: }
0814:
0815: s.addChangeListener(ch);
0816: cookieSet = s;
0817: }
0818:
0819: if (fireChange) {
0820: fireCookieChange();
0821: }
0822: }
0823:
0824: /** Get the set of cookies.
0825: * If the set had been
0826: * previously set by {@link #setCookieSet}, that set
0827: * is returned. Otherwise an empty set is
0828: * returned.
0829: *
0830: * @return the cookie set (never <code>null</code>)
0831: */
0832: protected final CookieSet getCookieSet() {
0833: CookieSet s = cookieSet;
0834: if (s != null)
0835: return s;
0836: synchronized (cookieSetLock) {
0837: if (cookieSet != null)
0838: return cookieSet;
0839:
0840: // generic cookie set with reference to data object and
0841: // a callback that updates FileObjects in its list.
0842: CookieSet g = CookieSet.createGeneric(getChangeListener());
0843: g.assign(DataObject.class, this );
0844: setCookieSet(g, false);
0845: return cookieSet;
0846: }
0847: }
0848:
0849: /** Look for a cookie in the current cookie set matching the requested class.
0850: *
0851: * @param type the class to look for
0852: * @return an instance of that class, or <code>null</code> if this class of cookie
0853: * is not supported
0854: */
0855: @Override
0856: public <T extends Node.Cookie> T getCookie(Class<T> type) {
0857: CookieSet c = cookieSet;
0858: if (c != null) {
0859: T cookie = c.getCookie(type);
0860: if (cookie != null)
0861: return cookie;
0862: }
0863: return super .getCookie(type);
0864: }
0865:
0866: /** Fires cookie change.
0867: */
0868: final void fireCookieChange() {
0869: firePropertyChange(PROP_COOKIE, null, null);
0870: }
0871:
0872: /** Fires property change but in event thread.
0873: */
0874: private void firePropertyChangeLater(final String name,
0875: final Object oldV, final Object newV) {
0876: firingProcessor.post(new Runnable() {
0877: public void run() {
0878: firePropertyChange(name, oldV, newV);
0879: if (PROP_FILES.equals(name)
0880: || PROP_PRIMARY_FILE.equals(name)) {
0881: updateFilesInCookieSet();
0882: }
0883: }
0884: });
0885: }
0886:
0887: /**
0888: * Posts a task to delayProcessor such that task
0889: * 1. waits for the FolderList to finish
0890: * 2. calls firePropertyChangeLater with PROP_FILES
0891: * Second time this method is called (delayedPropFilesTask is not null)
0892: * the new task is not created - the old one is rescheduled to run again.
0893: *
0894: * NOTE: this method should be improved not to fire twice in some cases.
0895: */
0896: private void firePropFilesAfterFinishing() {
0897: synchronized (delayedPropFilesLock) {
0898: if (delayedPropFilesTask == null) {
0899: delayedPropFilesTask = delayProcessor
0900: .post(new Runnable() {
0901: public void run() {
0902: FolderList l = getFolderList();
0903: if (l != null) {
0904: l.waitProcessingFinished();
0905: }
0906: firePropertyChangeLater(PROP_FILES,
0907: null, null);
0908: }
0909: });
0910: } else {
0911: delayedPropFilesTask.schedule(0);
0912: }
0913: }
0914: }
0915:
0916: /** sets checked to true */
0917: final void recognizedByFolder() {
0918: checked = true;
0919: }
0920:
0921: private ChangeAndBefore chLis;
0922:
0923: final ChangeAndBefore getChangeListener() {
0924: if (chLis == null) {
0925: chLis = new ChangeAndBefore();
0926: }
0927: return chLis;
0928: }
0929:
0930: // -- Following methods were added in order to wrap calls to MultiFileLoader
0931: // and check if the loader is really of this type. This hack was added to
0932: // keep backward compatibility of DataFolder and DataShadow classes, which
0933: // were originally subclassing DataObject, but was changed to subclass
0934: // MultiDataObject. Methods can be removed as the deprecated constructor
0935: // MultiDataObject(FileObject, DataLoader) disappears.
0936:
0937: private final MultiDataObject.Entry createPrimaryEntry(
0938: MultiDataObject obj, FileObject fo) {
0939: MultiFileLoader loader = getMultiFileLoader();
0940:
0941: if (loader != null)
0942: return loader.createPrimaryEntry(obj, fo);
0943:
0944: Entry e;
0945: if (fo.isFolder())
0946: e = new FileEntry.Folder(obj, fo);
0947: else
0948: e = new FileEntry(obj, fo);
0949:
0950: return e;
0951: }
0952:
0953: private final MultiDataObject.Entry createSecondaryEntry(
0954: MultiDataObject obj, FileObject fo) {
0955: MultiFileLoader loader = getMultiFileLoader();
0956:
0957: if (loader != null)
0958: return loader.createSecondaryEntryImpl(obj, fo);
0959:
0960: Entry e;
0961: if (fo.isFolder())
0962: e = new FileEntry.Folder(obj, fo);
0963: else
0964: e = new FileEntry(obj, fo);
0965:
0966: return e;
0967: }
0968:
0969: private final MultiDataObject createMultiObject(FileObject fo)
0970: throws DataObjectExistsException, IOException {
0971: MultiFileLoader loader = getMultiFileLoader();
0972:
0973: MultiDataObject obj;
0974:
0975: if (loader != null) {
0976: obj = DataObjectPool.createMultiObject(loader, fo);
0977: } else {
0978: obj = (MultiDataObject) getLoader().findDataObject(fo,
0979: RECOGNIZER);
0980: }
0981: return obj;
0982: }
0983:
0984: private final void checkConsistency(MultiDataObject obj) {
0985: MultiFileLoader loader = getMultiFileLoader();
0986:
0987: if (loader != null)
0988: loader.checkConsistency(obj);
0989: }
0990:
0991: private final void checkFiles(MultiDataObject obj) {
0992: MultiFileLoader loader = getMultiFileLoader();
0993:
0994: if (loader != null)
0995: loader.checkFiles(obj);
0996: }
0997:
0998: private static EmptyRecognizer RECOGNIZER = new EmptyRecognizer();
0999:
1000: private static class EmptyRecognizer implements
1001: DataLoader.RecognizedFiles {
1002: EmptyRecognizer() {
1003: }
1004:
1005: public void markRecognized(FileObject fo) {
1006: }
1007: }
1008:
1009: // End of compatibility hack. --^
1010:
1011: /** Save pairs Entry <-> Entry.getFile () in the list
1012: * @return list of saved pairs
1013: */
1014: final List<Pair> saveEntries() {
1015: synchronized (synchObjectSecondary()) {
1016: LinkedList<Pair> ll = new LinkedList<Pair>();
1017:
1018: ll.add(new Pair(getPrimaryEntry()));
1019: for (MultiDataObject.Entry en : secondaryEntries()) {
1020: ll.add(new Pair(en));
1021: }
1022: return ll;
1023: }
1024: }
1025:
1026: /** Restore entries from the list. If Entry.getFile () has changed from
1027: * time when backup list was created, original file is restored and
1028: * Entry is re-assigned to it.
1029: * @param backup list obtained from {@link #saveEntries ()} function
1030: */
1031: final void restoreEntries(List<Pair> backup) {
1032: for (Pair p : backup) {
1033: if (p.entry.getFile().equals(p.file))
1034: continue;
1035: if (p.file.isValid()) {
1036: p.entry.changeFile(p.file);
1037: } else {
1038: // copy back
1039: try {
1040: if (p.entry.getFile().isData())
1041: p.entry.changeFile(p.entry.getFile().copy(
1042: p.file.getParent(), p.file.getName(),
1043: p.file.getExt()));
1044: else {
1045: FileObject fo = p.file.getParent()
1046: .createFolder(p.file.getName());
1047: FileUtil.copyAttributes(p.entry.getFile(), fo);
1048: p.entry.changeFile(fo);
1049: }
1050: } catch (IOException e) {
1051: // should not occure
1052: }
1053: }
1054: }
1055: }
1056:
1057: final static class Pair {
1058: MultiDataObject.Entry entry;
1059: FileObject file;
1060:
1061: Pair(MultiDataObject.Entry e) {
1062: entry = e;
1063: file = e.getFile();
1064: }
1065: }
1066:
1067: /** Represents one file in a {@link MultiDataObject group data object}. */
1068: public abstract class Entry implements java.io.Serializable {
1069: /** generated Serialized Version UID */
1070: static final long serialVersionUID = 6024795908818133571L;
1071:
1072: /** modified from MultiDataObject operations, that is why it is package
1073: * private. Do not assign anything to this object, use changeFile method
1074: */
1075: private FileObject file;
1076:
1077: /** This factory is used for creating new clones of the holding lock for internal
1078: * use of this DataObject. It factory is null it means that the file entry is not
1079: */
1080: private transient WeakReference<FileLock> lock;
1081:
1082: protected Entry(FileObject file) {
1083: this .file = file;
1084: if (!isImportant()) {
1085: file.setImportant(false);
1086: }
1087: }
1088:
1089: /** A method to change the entry file to some else.
1090: * @param newFile
1091: */
1092: final void changeFile(FileObject newFile) {
1093: assert newFile != null : "NPE for " + file;
1094: if (newFile.equals(file)) {
1095: return;
1096: }
1097: if (ERR.isLoggable(Level.FINE)) {
1098: ERR.fine("changeFile: " + newFile + " for " + this
1099: + " of " + getDataObject()); // NOI18N
1100: }
1101: newFile.setImportant(isImportant());
1102: this .file = newFile;
1103:
1104: // release lock for old file
1105: FileLock l = lock == null ? null : (FileLock) lock.get();
1106: if (l != null && l.isValid()) {
1107: if (ERR.isLoggable(Level.FINE)) {
1108: ERR.fine("releasing old lock: " + this + " was: "
1109: + l);
1110: }
1111: l.releaseLock();
1112: }
1113: lock = null;
1114: }
1115:
1116: /** Get the file this entry works with.
1117: */
1118: public final FileObject getFile() {
1119: return file;
1120: }
1121:
1122: /** Get the multi data object this entry is assigned to.
1123: * @return the data object
1124: */
1125: public final MultiDataObject getDataObject() {
1126: return MultiDataObject.this ;
1127: }
1128:
1129: /** Method that allows to check whether an entry is important or is not.
1130: * Should be overriden by subclasses, the current implementation returns
1131: * true.
1132: *
1133: * @return true if this entry is important or false if not
1134: */
1135: public boolean isImportant() {
1136: return true;
1137: }
1138:
1139: /** Called when the entry is to be copied.
1140: * Depending on the entry type, it should either copy the underlying <code>FileObject</code>,
1141: * or do nothing (if it cannot be copied).
1142: * @param f the folder to create this entry in
1143: * @param suffix the suffix to add to the name of original file
1144: * @return the copied <code>FileObject</code> or <code>null</code> if it cannot be copied
1145: * @exception IOException when the operation fails
1146: */
1147: public abstract FileObject copy(FileObject f, String suffix)
1148: throws IOException;
1149:
1150: /** Called when the entry is to be renamed.
1151: * Depending on the entry type, it should either rename the underlying <code>FileObject</code>,
1152: * or delete it (if it cannot be renamed).
1153: * @param name the new name
1154: * @return the renamed <code>FileObject</code> or <code>null</code> if it has been deleted
1155: * @exception IOException when the operation fails
1156: */
1157: public abstract FileObject rename(String name)
1158: throws IOException;
1159:
1160: /** Called when the entry is to be moved.
1161: * Depending on the entry type, it should either move the underlying <code>FileObject</code>,
1162: * or delete it (if it cannot be moved).
1163: * @param f the folder to move this entry to
1164: * @param suffix the suffix to use
1165: * @return the moved <code>FileObject</code> or <code>null</code> if it has been deleted
1166: * @exception IOException when the operation fails
1167: */
1168: public abstract FileObject move(FileObject f, String suffix)
1169: throws IOException;
1170:
1171: /** Called when the entry is to be deleted.
1172: * @exception IOException when the operation fails
1173: */
1174: public abstract void delete() throws IOException;
1175:
1176: /** Called when the entry is to be created from a template.
1177: * Depending on the entry type, it should either copy the underlying <code>FileObject</code>,
1178: * or do nothing (if it cannot be copied).
1179: * @param f the folder to create this entry in
1180: * @param name the new name to use
1181: * @return the copied <code>FileObject</code> or <code>null</code> if it cannot be copied
1182: * @exception IOException when the operation fails
1183: */
1184: public abstract FileObject createFromTemplate(FileObject f,
1185: String name) throws IOException;
1186:
1187: /**
1188: * Called when the entry is to be copied and renamed.
1189: * @param f the folder to create this entry in
1190: * @param name new file name
1191: * @param ext new file extension
1192: * @return the copied and renamed <code>FileObject</code>, never null
1193: * @exception IOException when the operation fails
1194: * @since 6.3
1195: */
1196: public FileObject copyRename(FileObject f, String name,
1197: String ext) throws IOException {
1198: throw new IOException("Unsupported operation");
1199: }
1200:
1201: /** Try to lock this file entry.
1202: * @return the lock if the operation was successful; otherwise <code>null</code>
1203: * @throws IOException if the lock could not be taken
1204: */
1205: public FileLock takeLock() throws IOException {
1206: FileLock l = lock == null ? null : lock.get();
1207: if (l == null || !l.isValid()) {
1208: l = getFile().lock();
1209: lock = new WeakReference<FileLock>(l);
1210: }
1211: if (ERR.isLoggable(Level.FINE)) {
1212: ERR.fine("takeLock: " + this + " is: " + l);
1213: }
1214: return l;
1215: }
1216:
1217: /** Tests whether the entry is locked.
1218: * @return <code>true</code> if so
1219: */
1220: public boolean isLocked() {
1221: FileLock l = lock == null ? null : lock.get();
1222: return l != null && l.isValid();
1223: }
1224:
1225: public boolean equals(Object o) {
1226: if (!(o instanceof Entry))
1227: return false;
1228: return getFile().equals(((Entry) o).getFile());
1229: }
1230:
1231: public int hashCode() {
1232: return getFile().hashCode();
1233: }
1234:
1235: /** Make a Serialization replacement.
1236: * The entry is identified by the
1237: * file object is holds. When serialized, it stores the
1238: * file object and the data object. On deserialization
1239: * it finds the data object and creates the right entry
1240: * for it.
1241: */
1242: protected Object writeReplace() {
1243: return new EntryReplace(getFile());
1244: }
1245: }
1246:
1247: void notifyFileDeleted(FileEvent fe) {
1248: removeFile(fe.getFile());
1249: if (fe.getFile().equals(getPrimaryFile())) {
1250: try {
1251: MultiDataObject.this .markInvalid0();
1252: } catch (PropertyVetoException ex) {
1253: // silently ignore?
1254: Logger.getLogger(MultiDataObject.class.getName()).log(
1255: Level.WARNING, null, ex);
1256: }
1257: }
1258: }
1259:
1260: /** Fired when a file has been added to the same folder
1261: * @param fe the event describing context where action has taken place
1262: */
1263: void notifyFileDataCreated(FileEvent fe) {
1264: checked = false;
1265: }
1266:
1267: final void updateFilesInCookieSet() {
1268: getCookieSet().assign(FileObject.class,
1269: files().toArray(new FileObject[0]));
1270: }
1271:
1272: void checkCookieSet(Class<?> c) {
1273: }
1274:
1275: /** Change listener and implementation of before.
1276: */
1277: private final class ChangeAndBefore implements ChangeListener,
1278: CookieSet.Before {
1279: public void stateChanged(ChangeEvent ev) {
1280: fireCookieChange();
1281: }
1282:
1283: public void beforeLookup(Class<?> clazz) {
1284: if (clazz.isAssignableFrom(FileObject.class)) {
1285: updateFilesInCookieSet();
1286: }
1287: checkCookieSet(clazz);
1288: }
1289: }
1290:
1291: /** Entry replace.
1292: */
1293: private static final class EntryReplace extends Object implements
1294: java.io.Serializable {
1295: /** generated Serialized Version UID */
1296: static final long serialVersionUID = -1498798537289529182L;
1297:
1298: /** file object of the entry */
1299: private FileObject file;
1300: /** entry to be used during read */
1301: private transient Entry entry;
1302:
1303: public EntryReplace(FileObject fo) {
1304: file = fo;
1305: }
1306:
1307: private void readObject(ObjectInputStream ois)
1308: throws IOException, ClassNotFoundException {
1309: ois.defaultReadObject();
1310: try {
1311: DataObject obj = DataObject.find(file);
1312: if (obj instanceof MultiDataObject) {
1313: MultiDataObject m = (MultiDataObject) obj;
1314:
1315: if (file.equals(m.getPrimaryFile())) {
1316: // primary entry
1317: entry = m.getPrimaryEntry();
1318: } else {
1319: // secondary entry
1320: Entry e = (Entry) m.findSecondaryEntry(file);
1321: if (e == null) {
1322: throw new InvalidObjectException(obj
1323: .toString());
1324: }
1325: // remember the entry
1326: entry = e;
1327: }
1328: }
1329: } catch (DataObjectNotFoundException ex) {
1330: throw new InvalidObjectException(ex.getMessage());
1331: }
1332: }
1333:
1334: public Object readResolve() {
1335: return entry;
1336: }
1337: }
1338: }
|