KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > loaders > MultiDataObject


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.openide.loaders;
21
22
23 import java.beans.PropertyVetoException JavaDoc;
24 import java.io.*;
25 import java.lang.ref.WeakReference JavaDoc;
26 import java.util.*;
27 import java.util.logging.*;
28 import javax.swing.event.*;
29 import org.openide.filesystems.*;
30 import org.openide.nodes.*;
31 import org.openide.util.*;
32
33 /** Provides support for handling of data objects with multiple files.
34 * One file is represented by one {@link Entry}. Each handler
35 * has one {@link #getPrimaryEntry primary} entry and zero or more secondary entries.
36 *
37 * @author Ales Novak, Jaroslav Tulach, Ian Formanek
38 */

39 public class MultiDataObject extends DataObject {
40     /** generated Serialized Version UID */
41     static final long serialVersionUID = -7750146802134210308L;
42
43     /** Synchronization object used in getCookieSet and setCookieSet methods.
44      */

45     private static final Object JavaDoc cookieSetLock = new Object JavaDoc();
46     
47     /** Lock used for lazy creation of secondary field (in method getSecondary()) */
48     private static final Object JavaDoc secondaryCreationLock = new Object JavaDoc();
49     
50     /** A RequestProceccor used for firing property changes asynchronously */
51     private static final RequestProcessor firingProcessor =
52         new RequestProcessor( "MDO PropertyChange processor");
53     
54     /** A RequestProceccor used for waiting for finishing refresh */
55     private static final RequestProcessor delayProcessor =
56         new RequestProcessor( "MDO Firing delayer");
57     /** a task waiting for the FolderList task to finish scanning of the folder */
58     private RequestProcessor.Task delayedPropFilesTask;
59     /** lock used in firePropFilesAfterFinishing */
60     private static final Object JavaDoc delayedPropFilesLock = new Object JavaDoc();
61     /** logging of operations in multidataobject */
62     static final Logger ERR = Logger.getLogger(MultiDataObject.class.getName());
63     
64     /** getPrimaryEntry() is intended to have all inetligence for copy/move/... */
65     private Entry primary;
66
67     /** Map of secondary entries and its files. (FileObject, Entry) */
68     private HashMap<FileObject,Entry> secondary;
69
70     /** array of cookies for this object */
71     private CookieSet cookieSet;
72
73     /** flag when to call checkFiles(this) */
74     boolean checked = false;
75
76     /** Create a MultiFileObject.
77     * @see DataObject#DataObject(org.openide.filesystems.FileObject,org.openide.loaders.DataLoader)
78     * @param fo the primary file object
79     * @param loader loader of this data object
80     */

81     public MultiDataObject(FileObject fo, MultiFileLoader loader) throws DataObjectExistsException {
82         super(fo, loader);
83         primary = createPrimaryEntry (this, getPrimaryFile ());
84     }
85
86     /** This constructor is added for backward compatibility, MultiDataObject should be
87     * properly constructed using the MultiFileLoader.
88     * @param fo the primary file object
89     * @param loader loader of this data object
90     * @deprecated do not use this constructor, it is for backward compatibility of
91     * {@link #DataShadow} and {@link #DataFolder} only
92     * @since 1.13
93     */

94     @Deprecated JavaDoc
95     MultiDataObject(FileObject fo, DataLoader loader) throws DataObjectExistsException {
96         super(fo, loader);
97         primary = createPrimaryEntry (this, getPrimaryFile ());
98     }
99     
100     /** Getter for the multi file loader that created this
101     * object.
102     *
103     * @return the multi loader for the object
104     */

105     public final MultiFileLoader getMultiFileLoader () {
106         DataLoader loader = getLoader ();
107         
108         if (!(loader instanceof MultiFileLoader))
109             return null;
110         
111         return (MultiFileLoader)loader;
112     }
113
114     @Override JavaDoc
115     public Set<FileObject> files () {
116         // move lazy initialization to FilesSet
117
return new FilesSet (this);
118     }
119
120     /* Getter for delete action.
121     * @return true if the object can be deleted
122     */

123     public boolean isDeleteAllowed() {
124         return !getPrimaryFile ().isReadOnly () && !existReadOnlySecondary();
125     }
126     
127     private boolean existReadOnlySecondary() {
128         synchronized ( synchObjectSecondary() ) {
129             for (FileObject f : getSecondary().keySet()) {
130                 if (f.isReadOnly()) {
131                     return true;
132                 }
133             }
134         }
135         return false;
136     }
137
138     /** Performs checks by calling checkFiles
139      * @return getSecondary() method result
140      */

141     private Map<FileObject,Entry> checkSecondary () {
142         // enumeration of all files
143
if (! checked) {
144             checkFiles (this);
145             checked = true;
146         }
147         return getSecondary();
148     }
149         
150     /** Lazy getter for secondary property
151      * @return secondary object
152      */

153     /* package-private */ Map<FileObject,Entry> getSecondary() {
154         synchronized (secondaryCreationLock) {
155             if (secondary == null) {
156                 secondary = new HashMap<FileObject,Entry>(4);
157             }
158             if (ERR.isLoggable(Level.FINE)) {
159                 ERR.fine("getSecondary for " + this + " is " + secondary); // NOI18N
160
}
161             return secondary;
162         }
163     }
164     
165     /* Getter for copy action.
166     * @return true if the object can be copied
167     */

168     public boolean isCopyAllowed() {
169         return true;
170     }
171
172     /* Getter for move action.
173     * @return true if the object can be moved
174     */

175     public boolean isMoveAllowed() {
176         return !getPrimaryFile ().isReadOnly () && !existReadOnlySecondary();
177     }
178
179     /* Getter for rename action.
180     * @return true if the object can be renamed
181     */

182     public boolean isRenameAllowed () {
183         return !getPrimaryFile ().isReadOnly () && !existReadOnlySecondary();
184     }
185
186     /* Help context for this object.
187     * @return help context
188     */

189     public HelpCtx getHelpCtx() {
190         return HelpCtx.DEFAULT_HELP;
191     }
192
193     /** Provide object used for synchronization of methods working with
194      * Secondaries.
195      * @return The private field <CODE>secondary</CODE>.
196      */

197     Object JavaDoc synchObjectSecondary() {
198         Object JavaDoc lock = checkSecondary();
199         if (lock == null) throw new IllegalStateException JavaDoc("checkSecondary was null from " + this); // NOI18N
200
return checkSecondary();
201     }
202     
203     /** Provides node that should represent this data object.
204     *
205     * @return the node representation
206     * @see DataNode
207     */

208     protected Node createNodeDelegate () {
209         DataNode dataNode = (DataNode) super.createNodeDelegate ();
210         return dataNode;
211     }
212
213     /** Add a new secondary entry to the list.
214     * @param fe the entry to add
215     */

216     protected final void addSecondaryEntry (Entry fe) {
217         synchronized ( getSecondary() ) {
218             getSecondary().put (fe.getFile (), fe);
219             if (ERR.isLoggable(Level.FINE)) {
220                 ERR.fine("addSecondaryEntry: " + fe + " for " + this); // NOI18N
221
}
222         }
223
224         // Fire PROP_FILES only if we have actually finished making the folder.
225
// It is dumb to fire this if we do not yet even know what all of our
226
// initial secondary files are going to be.
227
FolderList l = getFolderList();
228         if (l == null) {
229             firePropertyChangeLater (PROP_FILES, null, null);
230         } else { // l != null
231
if (l.isCreated()) {
232                 firePropertyChangeLater (PROP_FILES, null, null);
233             } else {
234                 firePropFilesAfterFinishing();
235             }
236         }
237     }
238
239     /** Finds FolderList object for the primary file's parent folder
240      * @return FolderList object or <code>null</code>
241      */

242     private FolderList getFolderList() {
243         FileObject parent = primary.file.getParent();
244         if (parent != null) {
245             return FolderList.find(parent, false);
246         }
247         return null;
248     }
249     
250     /** Remove a secondary entry from the list.
251      * @param fe the entry to remove
252     */

253     protected final void removeSecondaryEntry (Entry fe) {
254         synchronized (getSecondary()) {
255             getSecondary().remove (fe.getFile ());
256             if (ERR.isLoggable(Level.FINE)) {
257                 ERR.fine("removeSecondaryEntry: " + fe + " for " + this); // NOI18N
258
}
259         }
260         
261         firePropertyChangeLater (PROP_FILES, null, null);
262         updateFilesInCookieSet();
263
264         if (fe.isImportant ()) {
265             checkConsistency(this);
266         }
267     }
268
269     /** All secondary entries are recognized. Called from multi file object.
270     * @param recognized object to mark recognized file to
271     */

272     final void markSecondaryEntriesRecognized (DataLoader.RecognizedFiles recognized) {
273         synchronized (getSecondary()) {
274             for (FileObject fo : getSecondary().keySet()) {
275                 recognized.markRecognized (fo);
276             }
277         }
278     }
279
280
281     /** Tests whether this file is between entries and if not,
282     * creates a secondary entry for it and adds it into set of
283     * secondary entries.
284     * <P>
285     * This method should be used in constructor of MultiDataObject to
286     * register all the important files, that could belong to this data object.
287     * As example, our XMLDataObject, tries to locate its <CODE>xmlinfo</CODE>
288     * file and then do register it
289     *
290     * @param fo the file to register (can be null, then the action is ignored)
291     * @return the entry associated to this file object (returns primary entry if the fo is null)
292     */

293     protected final Entry registerEntry (FileObject fo) {
294         synchronized (getSecondary()) {
295             if (fo == null) {
296                 // is it ok, to do this or somebody would like to see different behavour?
297
return primary;
298             }
299             if (fo.equals (getPrimaryFile ())) {
300                 return primary;
301             }
302
303             Entry e = getSecondary().get(fo);
304             if (e != null) {
305                 return e;
306             }
307
308             // add it into set of entries
309
e = createSecondaryEntry (this, fo);
310             addSecondaryEntry (e);
311
312             return e;
313         }
314     }
315
316     /** Removes the entry from the set of secondary entries.
317      * Called from the notifyFileDeleted
318      */

319     final void removeFile (FileObject fo) {
320         synchronized (getSecondary()) {
321             Entry e = getSecondary().get(fo);
322             if (e != null) {
323                 removeSecondaryEntry (e);
324             }
325         }
326     }
327
328     /** Get the primary entry.
329     * @return the entry
330     */

331     public final Entry getPrimaryEntry () {
332         return primary;
333     }
334
335     /** Get secondary entries.
336     * @return immutable set of entries
337     */

338     public final Set<Entry> secondaryEntries () {
339         synchronized ( synchObjectSecondary() ) {
340             removeAllInvalid ();
341
342             return new HashSet<Entry>(getSecondary().values());
343         }
344     }
345
346     /** For a given file, find the associated secondary entry.
347     * @param fo file object
348     * @return the entry associated with the file object, or <code>null</code> if there is no
349     * such entry
350     */

351     public final Entry findSecondaryEntry (FileObject fo) {
352         Entry e;
353         synchronized ( synchObjectSecondary() ) {
354             removeAllInvalid ();
355             e = getSecondary().get(fo);
356         }
357         return e;
358     }
359     
360     /** Removes all FileObjects that are not isValid from the
361      * set of objects.
362      */

363     private void removeAllInvalid () {
364         if (ERR.isLoggable(Level.FINE)) {
365             ERR.fine("removeAllInvalid, started " + this); // NOI18N
366
}
367         Iterator it = checkSecondary ().entrySet ().iterator ();
368         while (it.hasNext ()) {
369             Map.Entry e = (Map.Entry)it.next ();
370             FileObject fo = (FileObject)e.getKey ();
371             if (!fo.isValid ()) {
372                 it.remove ();
373                 if (ERR.isLoggable(Level.FINE)) {
374                     ERR.fine("removeAllInvalid, removed: " + fo + " for " + this); // NOI18N
375
}
376                 firePropertyChangeLater (PROP_FILES, null, null);
377             }
378         }
379         if (ERR.isLoggable(Level.FINE)) {
380             ERR.fine("removeAllInvalid, finished " + this); // NOI18N
381
}
382     }
383
384
385     //methods overriding DataObjectHandler's abstract methods
386

387     /* Obtains lock for primary file by asking getPrimaryEntry() entry.
388     *
389     * @return the lock for primary file
390     * @exception IOException if it is not possible to set the template
391     * state.
392     */

393     protected FileLock takePrimaryFileLock () throws IOException {
394         return getPrimaryEntry ().takeLock ();
395     }
396
397     // XXX does nothing of the sort --jglick
398
/** Check if in specific folder exists fileobject with the same name.
399     * If it exists user is asked for confirmation to rewrite, rename or cancel operation.
400     * @param folder destination folder
401     * @return the suffix which should be added to the name or null if operation is cancelled
402     */

403     private String JavaDoc existInFolder(FileObject fo, FileObject folder) {
404         // merge folders when neccessary
405
if (fo.isFolder () && isMergingFolders (fo, folder))
406             return ""; // NOI18N
407

408         String JavaDoc orig = fo.getName ();
409         String JavaDoc name = FileUtil.findFreeFileName(
410                           folder, orig, fo.getExt ()
411                       );
412         if (name.length () <= orig.length ()) {
413             return ""; // NOI18N
414
} else {
415             return name.substring (orig.length ());
416         }
417     }
418
419     /** Override to change default handling of name collisions detected during the
420      * copy, move operations. Reasonable for MultiDataObjects having folder their
421      * primary file (e.g. DataFolder, CompoundDataObject).
422      * @return <code>false</code> means, that new folder name should be synthetized when
423      * the same folder already exists in the target location of copy, move operation, otherwise
424      * existing falder will be used. Default implementation returns <code>false</code>.
425      */

426     boolean isMergingFolders(FileObject who, FileObject targetFolder) {
427         return false;
428     }
429     
430     /** Copies primary and secondary files to new folder.
431      * May ask for user confirmation before overwriting.
432      * @param df the new folder
433      * @return data object for the new primary
434      * @throws IOException if there was a problem copying
435      * @throws UserCancelException if the user cancelled the copy
436     */

437     protected DataObject handleCopy (DataFolder df) throws IOException {
438         FileObject fo;
439
440         String JavaDoc suffix = existInFolder(
441                             getPrimaryEntry().getFile(),
442                             df.getPrimaryFile ()
443                         );
444         if (suffix == null)
445             throw new org.openide.util.UserCancelException();
446
447         Iterator it = secondaryEntries().iterator();
448         while (it.hasNext ()) {
449             ((Entry)it.next()).copy (df.getPrimaryFile (), suffix);
450         }
451         //#33244 - copy primary file after the secondary ones
452
fo = getPrimaryEntry ().copy (df.getPrimaryFile (), suffix);
453
454         boolean fullRescan = getMultiFileLoader() == null ||
455             getMultiFileLoader().findPrimaryFile(fo) != fo;
456         try {
457             return fullRescan ? DataObject.find(fo) : createMultiObject (fo);
458         } catch (DataObjectExistsException ex) {
459             return ex.getDataObject ();
460         }
461     }
462
463     /* Deletes all secondary entries, removes them from the set of
464     * secondary entries and then deletes the getPrimaryEntry() entry.
465     */

466     protected void handleDelete() throws IOException {
467         List<FileObject> toRemove = new ArrayList<FileObject>();
468         Iterator<Map.Entry<FileObject,Entry>> it;
469         synchronized ( synchObjectSecondary() ) {
470             removeAllInvalid ();
471             it = new ArrayList<Map.Entry<FileObject,Entry>>(getSecondary().entrySet()).iterator();
472         }
473         
474         while (it.hasNext ()) {
475             Map.Entry<FileObject,Entry> e = it.next ();
476             e.getValue().delete();
477             toRemove.add(e.getKey());
478         }
479         
480         synchronized ( synchObjectSecondary() ) {
481             for (FileObject f : toRemove) {
482                 getSecondary().remove(f);
483                 if (ERR.isLoggable(Level.FINE)) {
484                     ERR.fine(" handleDelete, removed entry: " + f);
485                 }
486             }
487         }
488         
489         getPrimaryEntry().delete();
490     }
491
492     /* Renames all entries and changes their files to new ones.
493     */

494     protected FileObject handleRename (String JavaDoc name) throws IOException {
495         getPrimaryEntry ().changeFile (getPrimaryEntry().rename (name));
496
497         Map<FileObject,Entry> add = null;
498
499         List<FileObject> toRemove = new ArrayList<FileObject>();
500         
501         Iterator<Map.Entry<FileObject,Entry>> it;
502         synchronized ( synchObjectSecondary() ) {
503             removeAllInvalid ();
504             it = new ArrayList<Map.Entry<FileObject,Entry>>(getSecondary().entrySet ()).iterator();
505         }
506         
507         while (it.hasNext ()) {
508             Map.Entry<FileObject,Entry> e = it.next();
509             FileObject fo = e.getValue().rename(name);
510             if (fo == null) {
511                 // remove the entry
512
toRemove.add (e.getKey());
513             } else {
514                 if (!fo.equals (e.getKey ())) {
515                     // put the new one into change table
516
if (add == null) add = new HashMap<FileObject,Entry>();
517                     Entry entry = e.getValue();
518                     entry.changeFile (fo);
519                     // using getFile to let the entry correctly annotate
520
// the file by isImportant flag
521
add.put (entry.getFile (), entry);
522
523                     // changed the file => remove the file
524
toRemove.add(e.getKey());
525                 }
526             }
527         }
528
529         // if there has been a change in files, apply it
530
if ((add != null) || (!toRemove.isEmpty())) {
531             synchronized ( synchObjectSecondary() ) {
532                 // remove entries
533
if (!toRemove.isEmpty()) {
534                     for (FileObject f : toRemove) {
535                         getSecondary().remove(f);
536                         if (ERR.isLoggable(Level.FINE)) {
537                             ERR.fine("handleRename, removed: " + f + " for " + this); // NOI18N
538
}
539                     }
540                 }
541                 // add entries
542
if (add != null) {
543                     getSecondary().putAll (add);
544                     if (ERR.isLoggable(Level.FINE)) {
545                         ERR.fine("handleRename, putAll: " + add + " for " + this); // NOI18N
546
}
547                 }
548             }
549             firePropertyChangeLater (PROP_FILES, null, null);
550         }
551
552         return getPrimaryEntry ().getFile ();
553     }
554
555     /** Moves primary and secondary files to a new folder.
556      * May ask for user confirmation before overwriting.
557      * @param df the new folder
558      * @return the moved primary file object
559      * @throws IOException if there was a problem moving
560      * @throws UserCancelException if the user cancelled the move
561     */

562     protected FileObject handleMove (DataFolder df) throws IOException {
563         String JavaDoc suffix = existInFolder(getPrimaryEntry().getFile(), df.getPrimaryFile ());
564         if (suffix == null)
565             throw new org.openide.util.UserCancelException();
566
567         List<Pair> backup = saveEntries();
568
569         try {
570             HashMap<FileObject,Entry> add = null;
571
572             ArrayList<FileObject> toRemove = new ArrayList<FileObject>();
573             Iterator<Map.Entry<FileObject,Entry>> it;
574             int count;
575             synchronized ( synchObjectSecondary() ) {
576                 removeAllInvalid ();
577                 ArrayList<Map.Entry<FileObject,Entry>> list =
578                         new ArrayList<Map.Entry<FileObject,Entry>>(getSecondary().entrySet ());
579                 count = list.size();
580                 it = list.iterator();
581             }
582             
583             if (ERR.isLoggable(Level.FINE)) {
584                 ERR.fine("move " + this + " to " + df + " number of secondary entries: " + count); // NOI18N
585
ERR.fine("moving primary entry: " + getPrimaryEntry()); // NOI18N
586
}
587             getPrimaryEntry ().changeFile (getPrimaryEntry ().move (df.getPrimaryFile (), suffix));
588             if (ERR.isLoggable(Level.FINE)) ERR.fine(" moved: " + getPrimaryEntry().getFile()); // NOI18N
589

590             
591             while (it.hasNext ()) {
592                 Map.Entry<FileObject,Entry> e = it.next ();
593                 if (ERR.isLoggable(Level.FINE)) ERR.fine("moving entry :" + e); // NOI18N
594
FileObject fo = (e.getValue ()).move (df.getPrimaryFile (), suffix);
595                 if (ERR.isLoggable(Level.FINE)) ERR.fine(" moved to :" + fo); // NOI18N
596
if (fo == null) {
597                     // remove the entry
598
toRemove.add(e.getKey());
599                 } else {
600                     if (!fo.equals (e.getKey ())) {
601                         // put the new one into change table
602
if (add == null) add = new HashMap<FileObject,Entry> ();
603                         Entry entry = e.getValue ();
604                         entry.changeFile (fo);
605                         // using entry.getFile, so the file has correctly
606
// associated its isImportant flag
607
add.put (entry.getFile (), entry);
608
609                         // changed the file => remove the file
610
toRemove.add(e.getKey());
611                     }
612                 }
613             }
614
615             // if there has been a change in files, apply it
616
if ((add != null) || (!toRemove.isEmpty())) {
617                 synchronized ( synchObjectSecondary() ) {
618                     // remove entries
619
if (!toRemove.isEmpty()) {
620                         Object JavaDoc[] objects = toRemove.toArray();
621                         for (int i = 0; i < objects.length; i++) {
622                             getSecondary().remove(objects[i]);
623                             if (ERR.isLoggable(Level.FINE)) {
624                                 ERR.fine("handleMove, remove: " + objects[i] + " for " + this); // NOI18N
625
}
626                         }
627                     }
628                     // add entries
629
if (add != null) {
630                         getSecondary().putAll (add);
631                         if (ERR.isLoggable(Level.FINE)) {
632                             ERR.fine("handleMove, putAll: " + add + " for " + this); // NOI18N
633
}
634                     }
635                 }
636                 firePropertyChangeLater (PROP_FILES, null, null);
637             }
638
639             if (ERR.isLoggable(Level.FINE)) {
640                 ERR.fine("successfully moved " + this); // NOI18N
641
}
642             return getPrimaryEntry ().getFile ();
643         } catch (IOException e) {
644             if (ERR.isLoggable(Level.FINE)) {
645                 ERR.fine("exception is here, restoring entries " + this); // NOI18N
646
ERR.log(Level.FINE, null, e);
647             }
648             restoreEntries(backup);
649             if (ERR.isLoggable(Level.FINE)) {
650                 ERR.fine("entries restored " + this); // NOI18N
651
}
652             throw e;
653         }
654     }
655
656     /* Creates new object from template.
657     * @exception IOException
658     */

659     protected DataObject handleCreateFromTemplate (
660         DataFolder df, String JavaDoc name
661     ) throws IOException {
662         if (name == null) {
663             name = FileUtil.findFreeFileName(
664                        df.getPrimaryFile (), getPrimaryFile ().getName (), getPrimaryFile ().getExt ()
665                    );
666         }
667
668         FileObject fo = null;
669         Map<String JavaDoc,Object JavaDoc> params = null;
670         for (CreateFromTemplateHandler h : Lookup.getDefault().lookupAll(CreateFromTemplateHandler.class)) {
671             FileObject current = getPrimaryEntry().getFile();
672             if (h.accept(current)) {
673                 if (params == null) {
674                     params = DataObject.CreateAction.findParameters(name);
675                 }
676                 fo = h.createFromTemplate(current, df.getPrimaryFile(), name, params);
677                 assert fo != null;
678                 break;
679             }
680         }
681         if (params == null) {
682             // do the regular creation
683
fo = getPrimaryEntry().createFromTemplate (df.getPrimaryFile (), name);
684         }
685         
686         
687         Iterator it = secondaryEntries().iterator();
688         NEXT_ENTRY: while (it.hasNext ()) {
689             for (CreateFromTemplateHandler h : Lookup.getDefault().lookupAll(CreateFromTemplateHandler.class)) {
690                 FileObject current = getPrimaryEntry().getFile();
691                 if (h.accept(current)) {
692                     if (params == null) {
693                         params = DataObject.CreateAction.findParameters(name);
694                     }
695                     fo = h.createFromTemplate(current, df.getPrimaryFile(), name, params);
696                     assert fo != null;
697                     continue NEXT_ENTRY;
698                 }
699             }
700             ((Entry)it.next()).createFromTemplate (df.getPrimaryFile (), name);
701         }
702         
703         try {
704             // #61600: not very object oriented, but covered by DefaultVersusXMLDataObjectTest
705
if (this instanceof DefaultDataObject) {
706                 return DataObject.find(fo);
707             }
708             
709             return createMultiObject (fo);
710         } catch (DataObjectExistsException ex) {
711             return ex.getDataObject ();
712         }
713     }
714
715     /** Set the set of cookies.
716      * To the provided cookie set a listener is attached,
717     * and any change to the set is propagated by
718     * firing a change on {@link #PROP_COOKIE}.
719     *
720     * @param s the cookie set to use
721     * @deprecated just use getCookieSet().add(...) instead
722     */

723     @Deprecated JavaDoc
724     protected final void setCookieSet (CookieSet s) {
725         setCookieSet(s, true);
726     }
727
728     /** Set the set of cookies.
729      *
730      * @param s the cookie set to use
731      * @param fireChange used when called from getter. In this case event shouldn't
732      * be fired.
733      */

734     private void setCookieSet (CookieSet s, boolean fireChange) {
735         synchronized (cookieSetLock) {
736             ChangeListener ch = getChangeListener();
737
738             if (cookieSet != null) {
739                 cookieSet.removeChangeListener (ch);
740             }
741
742             s.addChangeListener (ch);
743             cookieSet = s;
744         }
745         
746         if (fireChange) {
747             fireCookieChange ();
748         }
749     }
750     
751     /** Get the set of cookies.
752      * If the set had been
753     * previously set by {@link #setCookieSet}, that set
754     * is returned. Otherwise an empty set is
755     * returned.
756     *
757     * @return the cookie set (never <code>null</code>)
758     */

759     protected final CookieSet getCookieSet () {
760         CookieSet s = cookieSet;
761         if (s != null) return s;
762         synchronized (cookieSetLock) {
763             if (cookieSet != null) return cookieSet;
764
765             // generic cookie set with reference to data object and
766
// a callback that updates FileObjects in its list.
767
CookieSet g = CookieSet.createGeneric(getChangeListener());
768             g.assign(DataObject.class, this);
769             setCookieSet (g, false);
770             return cookieSet;
771         }
772     }
773
774     /** Look for a cookie in the current cookie set matching the requested class.
775     *
776     * @param type the class to look for
777     * @return an instance of that class, or <code>null</code> if this class of cookie
778     * is not supported
779     */

780     @Override JavaDoc
781     public <T extends Node.Cookie> T getCookie(Class JavaDoc<T> type) {
782         CookieSet c = cookieSet;
783         if (c != null) {
784             T cookie = c.getCookie (type);
785             if (cookie != null) return cookie;
786         }
787         return super.getCookie (type);
788     }
789
790     /** Fires cookie change.
791     */

792     final void fireCookieChange () {
793         firePropertyChange (PROP_COOKIE, null, null);
794     }
795
796     /** Fires property change but in event thread.
797     */

798     private void firePropertyChangeLater (
799         final String JavaDoc name, final Object JavaDoc oldV, final Object JavaDoc newV
800     ) {
801         firingProcessor.post(new Runnable JavaDoc () {
802             public void run () {
803                 firePropertyChange (name, oldV, newV);
804                 if (PROP_FILES.equals(name) || PROP_PRIMARY_FILE.equals(name)) {
805                     updateFilesInCookieSet();
806                 }
807             }
808         });
809     }
810
811     /**
812      * Posts a task to delayProcessor such that task
813      * 1. waits for the FolderList to finish
814      * 2. calls firePropertyChangeLater with PROP_FILES
815      * Second time this method is called (delayedPropFilesTask is not null)
816      * the new task is not created - the old one is rescheduled to run again.
817      *
818      * NOTE: this method should be improved not to fire twice in some cases.
819      */

820     private void firePropFilesAfterFinishing() {
821         synchronized (delayedPropFilesLock) {
822             if (delayedPropFilesTask == null) {
823                 delayedPropFilesTask = delayProcessor.post(new Runnable JavaDoc() {
824                     public void run() {
825                         FolderList l = getFolderList();
826                         if (l != null) {
827                             l.waitProcessingFinished();
828                         }
829                         firePropertyChangeLater(PROP_FILES, null, null);
830                     }
831                 });
832             } else {
833                 delayedPropFilesTask.schedule(0);
834             }
835         }
836     }
837     
838     /** sets checked to true */
839     final void recognizedByFolder() {
840         checked = true;
841     }
842
843     private ChangeAndBefore chLis;
844
845     final ChangeAndBefore getChangeListener() {
846         if (chLis == null) {
847             chLis = new ChangeAndBefore();
848         }
849         return chLis;
850     }
851
852     // -- Following methods were added in order to wrap calls to MultiFileLoader
853
// and check if the loader is really of this type. This hack was added to
854
// keep backward compatibility of DataFolder and DataShadow classes, which
855
// were originally subclassing DataObject, but was changed to subclass
856
// MultiDataObject. Methods can be removed as the deprecated constructor
857
// MultiDataObject(FileObject, DataLoader) disappears.
858

859     private final MultiDataObject.Entry createPrimaryEntry(MultiDataObject obj, FileObject fo) {
860         MultiFileLoader loader = getMultiFileLoader ();
861         
862         if (loader != null)
863             return loader.createPrimaryEntry (obj, fo);
864         
865         Entry e;
866         if (fo.isFolder ())
867             e = new FileEntry.Folder(obj, fo);
868         else
869             e = new FileEntry (obj, fo);
870         
871         return e;
872     }
873
874     private final MultiDataObject.Entry createSecondaryEntry(MultiDataObject obj, FileObject fo) {
875         MultiFileLoader loader = getMultiFileLoader ();
876         
877         if (loader != null)
878             return loader.createSecondaryEntryImpl (obj, fo);
879         
880         Entry e;
881         if (fo.isFolder ())
882             e = new FileEntry.Folder(obj, fo);
883         else
884             e = new FileEntry (obj, fo);
885         
886         return e;
887     }
888
889     private final MultiDataObject createMultiObject(FileObject fo) throws DataObjectExistsException, IOException {
890         MultiFileLoader loader = getMultiFileLoader ();
891
892         MultiDataObject obj;
893
894         if (loader != null) {
895             obj = DataObjectPool.createMultiObject(loader, fo);
896         } else {
897             obj = (MultiDataObject)getLoader ().findDataObject (fo, RECOGNIZER);
898         }
899         return obj;
900     }
901
902     private final void checkConsistency (MultiDataObject obj) {
903         MultiFileLoader loader = getMultiFileLoader ();
904
905         if (loader != null)
906             loader.checkConsistency (obj);
907     }
908
909     private final void checkFiles (MultiDataObject obj) {
910         MultiFileLoader loader = getMultiFileLoader ();
911
912         if (loader != null)
913             loader.checkFiles (obj);
914     }
915
916     private static EmptyRecognizer RECOGNIZER = new EmptyRecognizer();
917     
918     private static class EmptyRecognizer implements DataLoader.RecognizedFiles {
919         EmptyRecognizer() {}
920         public void markRecognized (FileObject fo) {
921         }
922     }
923     
924     // End of compatibility hack. --^
925

926     /** Save pairs Entry <-> Entry.getFile () in the list
927      * @return list of saved pairs
928      */

929     final List<Pair> saveEntries() {
930         synchronized ( synchObjectSecondary() ) {
931             LinkedList<Pair> ll = new LinkedList<Pair>();
932
933             ll.add (new Pair(getPrimaryEntry ()));
934             for (MultiDataObject.Entry en: secondaryEntries()) {
935                 ll.add (new Pair(en));
936             }
937             return ll;
938         }
939     }
940     
941     /** Restore entries from the list. If Entry.getFile () has changed from
942      * time when backup list was created, original file is restored and
943      * Entry is re-assigned to it.
944      * @param backup list obtained from {@link #saveEntries ()} function
945      */

946     final void restoreEntries(List<Pair> backup) {
947         for (Pair p: backup) {
948             if (p.entry.getFile ().equals (p.file))
949                 continue;
950             if (p.file.isValid()) {
951                 p.entry.changeFile (p.file);
952             } else {
953                 // copy back
954
try {
955                     if (p.entry.getFile ().isData ())
956                         p.entry.changeFile (p.entry.getFile ().copy (p.file.getParent (), p.file.getName (), p.file.getExt ()));
957                     else {
958                         FileObject fo = p.file.getParent ().createFolder (p.file.getName ());
959                         FileUtil.copyAttributes (p.entry.getFile (), fo);
960                         p.entry.changeFile (fo);
961                     }
962                 } catch (IOException e) {
963                     // should not occure
964
}
965             }
966         }
967     }
968     
969     final static class Pair {
970         MultiDataObject.Entry entry;
971         FileObject file;
972
973         Pair(MultiDataObject.Entry e) {
974             entry = e;
975             file = e.getFile ();
976         }
977     }
978     
979     /** Represents one file in a {@link MultiDataObject group data object}. */
980     public abstract class Entry implements java.io.Serializable JavaDoc {
981         /** generated Serialized Version UID */
982         static final long serialVersionUID = 6024795908818133571L;
983
984         /** modified from MultiDataObject operations, that is why it is package
985         * private. Do not assign anything to this object, use changeFile method
986         */

987         private FileObject file;
988
989         /** This factory is used for creating new clones of the holding lock for internal
990         * use of this DataObject. It factory is null it means that the file entry is not
991         */

992         private transient WeakReference JavaDoc<FileLock> lock;
993
994         protected Entry (FileObject file) {
995             this.file = file;
996         if (!isImportant()) {
997                 file.setImportant(false);
998         }
999         }
1000
1001        /** A method to change the entry file to some else.
1002        * @param newFile
1003        */

1004        final void changeFile (FileObject newFile) {
1005            if (newFile.equals (file)) {
1006                return;
1007            }
1008            if (ERR.isLoggable(Level.FINE)) {
1009                ERR.fine("changeFile: " + newFile + " for " + this + " of " + getDataObject()); // NOI18N
1010
}
1011            newFile.setImportant (isImportant ());
1012            this.file = newFile;
1013            
1014            // release lock for old file
1015
FileLock l = lock == null ? null : (FileLock)lock.get ();
1016            if (l != null && l.isValid ()) {
1017                if (ERR.isLoggable(Level.FINE)) {
1018                    ERR.fine("releasing old lock: " + this + " was: " + l);
1019                }
1020                l.releaseLock ();
1021            }
1022            lock = null;
1023        }
1024
1025        /** Get the file this entry works with.
1026        */

1027        public final FileObject getFile () {
1028            return file;
1029        }
1030        
1031        /** Get the multi data object this entry is assigned to.
1032         * @return the data object
1033        */

1034        public final MultiDataObject getDataObject () {
1035            return MultiDataObject.this;
1036        }
1037        
1038        /** Method that allows to check whether an entry is important or is not.
1039        * Should be overriden by subclasses, the current implementation returns
1040        * true.
1041        *
1042        * @return true if this entry is important or false if not
1043        */

1044        public boolean isImportant () {
1045            return true;
1046        }
1047
1048        /** Called when the entry is to be copied.
1049        * Depending on the entry type, it should either copy the underlying <code>FileObject</code>,
1050        * or do nothing (if it cannot be copied).
1051        * @param f the folder to create this entry in
1052        * @param suffix the suffix to add to the name of original file
1053        * @return the copied <code>FileObject</code> or <code>null</code> if it cannot be copied
1054        * @exception IOException when the operation fails
1055        */

1056        public abstract FileObject copy (FileObject f, String JavaDoc suffix) throws IOException;
1057
1058        /** Called when the entry is to be renamed.
1059        * Depending on the entry type, it should either rename the underlying <code>FileObject</code>,
1060        * or delete it (if it cannot be renamed).
1061        * @param name the new name
1062        * @return the renamed <code>FileObject</code> or <code>null</code> if it has been deleted
1063        * @exception IOException when the operation fails
1064        */

1065        public abstract FileObject rename (String JavaDoc name) throws IOException;
1066
1067        /** Called when the entry is to be moved.
1068        * Depending on the entry type, it should either move the underlying <code>FileObject</code>,
1069        * or delete it (if it cannot be moved).
1070        * @param f the folder to move this entry to
1071        * @param suffix the suffix to use
1072        * @return the moved <code>FileObject</code> or <code>null</code> if it has been deleted
1073        * @exception IOException when the operation fails
1074        */

1075        public abstract FileObject move (FileObject f, String JavaDoc suffix) throws IOException;
1076
1077        /** Called when the entry is to be deleted.
1078        * @exception IOException when the operation fails
1079        */

1080        public abstract void delete () throws IOException;
1081
1082        /** Called when the entry is to be created from a template.
1083        * Depending on the entry type, it should either copy the underlying <code>FileObject</code>,
1084        * or do nothing (if it cannot be copied).
1085        * @param f the folder to create this entry in
1086        * @param name the new name to use
1087        * @return the copied <code>FileObject</code> or <code>null</code> if it cannot be copied
1088        * @exception IOException when the operation fails
1089        */

1090        public abstract FileObject createFromTemplate (FileObject f, String JavaDoc name) throws IOException;
1091
1092        /** Try to lock this file entry.
1093        * @return the lock if the operation was successful; otherwise <code>null</code>
1094        * @throws IOException if the lock could not be taken
1095        */

1096        public FileLock takeLock() throws IOException {
1097            FileLock l = lock == null ? null : lock.get ();
1098            if (l == null || !l.isValid ()){
1099                l = getFile ().lock ();
1100                lock = new WeakReference JavaDoc<FileLock> (l);
1101            }
1102            if (ERR.isLoggable(Level.FINE)) {
1103                ERR.fine("takeLock: " + this + " is: " + l);
1104            }
1105            return l;
1106        }
1107
1108        /** Tests whether the entry is locked.
1109         * @return <code>true</code> if so
1110         */

1111        public boolean isLocked() {
1112            FileLock l = lock == null ? null : lock.get ();
1113            return l != null && l.isValid ();
1114        }
1115
1116        public boolean equals(Object JavaDoc o) {
1117            if (! (o instanceof Entry)) return false;
1118            return getFile ().equals(((Entry) o).getFile ());
1119        }
1120
1121        public int hashCode() {
1122            return getFile ().hashCode();
1123        }
1124
1125        /** Make a Serialization replacement.
1126         * The entry is identified by the
1127        * file object is holds. When serialized, it stores the
1128        * file object and the data object. On deserialization
1129        * it finds the data object and creates the right entry
1130        * for it.
1131        */

1132        protected Object JavaDoc writeReplace () {
1133            return new EntryReplace (getFile ());
1134        }
1135    }
1136
1137    void notifyFileDeleted (FileEvent fe) {
1138        removeFile (fe.getFile ());
1139        if (fe.getFile ().equals (getPrimaryFile ())) {
1140            try {
1141                MultiDataObject.this.markInvalid0 ();
1142            } catch (PropertyVetoException JavaDoc ex) {
1143                // silently ignore?
1144
Logger.getLogger(MultiDataObject.class.getName()).log(Level.WARNING, null, ex);
1145            }
1146        }
1147    }
1148
1149    /** Fired when a file has been added to the same folder
1150     * @param fe the event describing context where action has taken place
1151     */

1152    void notifyFileDataCreated(FileEvent fe) {
1153        checked = false;
1154    }
1155
1156    final void updateFilesInCookieSet() {
1157        getCookieSet().assign(FileObject.class, files().toArray(new FileObject[0]));
1158    }
1159    
1160    /** Change listener and implementation of before.
1161     */

1162    private final class ChangeAndBefore implements ChangeListener, CookieSet.Before {
1163        public void stateChanged (ChangeEvent ev) {
1164            fireCookieChange ();
1165        }
1166
1167        public void beforeLookup(Class JavaDoc<?> clazz) {
1168            if (clazz.isAssignableFrom(FileObject.class)) {
1169                updateFilesInCookieSet();
1170            }
1171        }
1172    }
1173
1174    /** Entry replace.
1175    */

1176    private static final class EntryReplace extends Object JavaDoc implements java.io.Serializable JavaDoc {
1177        /** generated Serialized Version UID */
1178        static final long serialVersionUID = -1498798537289529182L;
1179
1180        /** file object of the entry */
1181        private FileObject file;
1182        /** entry to be used during read */
1183        private transient Entry entry;
1184
1185        public EntryReplace (FileObject fo) {
1186            file = fo;
1187        }
1188
1189        private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException JavaDoc {
1190            ois.defaultReadObject ();
1191            try {
1192                DataObject obj = DataObject.find (file);
1193                if (obj instanceof MultiDataObject) {
1194                    MultiDataObject m = (MultiDataObject)obj;
1195
1196                    if (file.equals (m.getPrimaryFile ())) {
1197                        // primary entry
1198
entry = m.getPrimaryEntry ();
1199                    } else {
1200                        // secondary entry
1201
Entry e = (Entry)m.findSecondaryEntry (file);
1202                        if (e == null) {
1203                            throw new InvalidObjectException (obj.toString ());
1204                        }
1205                        // remember the entry
1206
entry = e;
1207                    }
1208                }
1209            } catch (DataObjectNotFoundException ex) {
1210                throw new InvalidObjectException (ex.getMessage ());
1211            }
1212        }
1213
1214        public Object JavaDoc readResolve () {
1215            return entry;
1216        }
1217    }
1218}
1219
Popular Tags