alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.loaders;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.ref.Reference;
import java.io.IOException;
import java.util.*;

import org.openide.ErrorManager;
import org.openide.util.datatransfer.*;
import org.openide.filesystems.*;
import org.openide.util.*;

/** Watches a folder and its children.
 *
 * 

{@link java.beans.PropertyChangeListener}s * may be registered which will be informed about changes in the ordered * children list. The {@link java.beans.PropertyChangeEvent}s fired by instances * of this class do neither contain information about the old value nor about * the new value of the children list.

* *

The list of children can be retrieved by calls to * the methods {@link #getChildren()} resp. {@link #getChildrenList()}. If you * want to filter the children which shall be included into the folder list, * call {@link #computeChildrenList(FolderListListener)}. The same is true * if you want to trigger children computation asynchronously. In this case * the implementation of {@link FolderListListener#finished(List)} shall be * used to get informed about the result of the computation.

* *

To retrieve the appropriate instance of this class for a given folder * call {@link #find(FileObject, boolean)}.

* * @author Jaroslav Tulach */ final class FolderList extends Object implements FileChangeListener, DataObject.Container { /* -------------------------------------------------------------------- */ /* -- Constants ------------------------------------------------------- */ /* -------------------------------------------------------------------- */ /** serial version UID */ static final long serialVersionUID = -592616022226761148L; /** priority for tasks that can be run later */ private static final int LATER_PRIORITY = Thread.NORM_PRIORITY; /** request processor for recognizing of folders */ private static final RequestProcessor PROCESSOR = new RequestProcessor ( "Folder recognizer" // NOI18N ); /** map of (FileObject, Reference (FolderList)) */ private static final Map map = new WeakHashMap (101); /** refresh time in milliseconds */ private static int REFRESH_TIME = -1; // will be updated in getRefreshTime /* -------------------------------------------------------------------- */ /* -- Instance attributes --------------------------------------------- */ /* -------------------------------------------------------------------- */ /** data folder to work with */ protected FileObject folder; /** The task that computes the content of FolderList. There is also * only one computation task in the PROCESSOR for each FolderList. * Whenever a new change notification arrives (thru file listener) * the previous task is canceled (if not running) and new is created. */ transient private RequestProcessor.Task refreshTask; /** task that is non-null if a setOrder has been called */ transient private volatile RequestProcessor.Task comparatorTask; /** Primary files in this folder. Maps (FileObject, Reference (DataObject)) */ transient private HashMap primaryFiles = null; /** order of primary files (FileObject) */ transient private List order; private static final ErrorManager err = ErrorManager.getDefault().getInstance("org.openide.loaders.FolderList"); // NOI18N /** property change support */ transient private PropertyChangeSupport pcs; /** * If true, this folder has been fully created (though it might * still be refreshing etc.). Used to avoid e.g. MDO.PROP_FILES * firing before the folder is ready. */ transient private boolean folderCreated = false; /* -------------------------------------------------------------------- */ /* -- Constructor (private) ------------------------------------------- */ /* -------------------------------------------------------------------- */ /** * @param df data folder to show */ private FolderList (FileObject folder, boolean attach) { this.folder = folder; if (attach) { // creates object that handles all elements in array and // assignes it to the folder.addFileChangeListener (org.openide.filesystems.FileUtil.weakFileChangeListener (this, folder)); } } /* final void reassign(DataFolder df, FileObject fo) { folder = df; // reassign is called from DataFolder.handleMove() // in this time the folder - df - does not have // setup the right primary file // so the fo is the new primary file for df fo.addFileChangeListener (WeakListener.fileChange (this, fo)); } */ public String toString () { return "FolderList{" + folder + "}"; // NOI18N } /* -------------------------------------------------------------------- */ /* -- Factory method (static) ----------------------------------------- */ /* -------------------------------------------------------------------- */ /** A public method to get the correct list for given file object. * * @param folder the folder to find FolderList for * @param create if true than new FolderList should be created if it does not exists * @return the FolderList or null if create was false */ public static FolderList find (FileObject folder, boolean create) { FolderList list = null; synchronized (FolderList.class) { Reference ref = (Reference)map.get (folder); list = ref == null ? null : (FolderList)ref.get (); if (list == null && create) { list = new FolderList (folder, true); map.put (folder, new SoftReference (list)); } } return list; } /** * Has this FolderList finished creation of this list (at least once)? * @return true if it has been created (may still be refreshing), false if still in progress */ public boolean isCreated() { return folderCreated; } /* -------------------------------------------------------------------- */ /* -- Static methods -------------------------------------------------- */ /* -------------------------------------------------------------------- */ /** Checks whether the calling thread is the FolderRecognizer. */ public static boolean isFolderRecognizerThread () { return PROCESSOR.isRequestProcessorThread (); } /* -------------------------------------------------------------------- */ /* -- Static methods to inform FolderList for a given folder ---------- */ /* -------------------------------------------------------------------- */ /** A method used to notify the FolderList system that order has changed * for a given file object. * * @param folder the affected file object */ public static void changedFolderOrder (FileObject folder) { FolderList list = find (folder, false); if (list != null) { list.changeComparator (); } } /** Called when a data system changed so much that there is a need for refresh * of a content of a folder. * * @param folder file object that can be affected */ public static void changedDataSystem (FileObject folder) { FolderList list = find (folder, false); if (list != null) { list.refresh (); } } /* -------------------------------------------------------------------- */ /* -- Folder content and content processing --------------------------- */ /* -------------------------------------------------------------------- */ /** Computes array of children associated * with this folder. */ public DataObject[] getChildren () { List res = getChildrenList (); DataObject[] arr = new DataObject[res.size ()]; res.toArray (arr); return arr; } /** List all children. * @return array with children */ public List getChildrenList () { ListTask lt; try { DataObjectPool.getPOOL().enterPriviledgedProcessor (PROCESSOR); lt = getChildrenList (null); lt.task.waitFinished(); } finally { DataObjectPool.getPOOL().exitPriviledgedProcessor (PROCESSOR); } return lt.result; } /** Blocks if the processing of content of folder is in progress. */ public void waitProcessingFinished () { Task t = comparatorTask; if (t != null) { t.waitFinished (); } t = refreshTask; if (t != null) { t.waitFinished (); } } /** Starts computation of children list asynchronously. */ public RequestProcessor.Task computeChildrenList (FolderListListener filter) { return getChildrenList (filter).task; } private ListTask getChildrenList (FolderListListener filter) { ListTask lt = new ListTask (filter); int priority = Thread.currentThread().getPriority(); // and then post your read task and wait lt.task = PROCESSOR.post (lt, 0, priority); return lt; } /** Setter for sort mode. */ private synchronized void changeComparator () { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("changeComparator on " + folder); final RequestProcessor.Task previous = comparatorTask; final RequestProcessor.Task[] COMP = new RequestProcessor.Task[1]; synchronized (COMP) { comparatorTask = PROCESSOR.post (new Runnable () { public void run () { synchronized (COMP) { if (previous != null) { previous.waitFinished (); } // if has been notified // change mode and regenerated children if (primaryFiles != null) { // the old children if (LOG) err.log ("changeComparator on " + folder + ": get old"); List v = getObjects (null); if (v.size () != 0) { // the new children - also are stored to be returned next time from getChildrenList () order = null; if (LOG) err.log ("changeComparator: get new"); List r = getObjects (null); if (LOG) err.log ("changeComparator: fire change"); fireChildrenChange (r, v); } } synchronized (FolderList.this) { // clean the task if is my own not assigned by somebody else if (comparatorTask == COMP[0]) { comparatorTask = null; } } } } }, 0, Thread.MIN_PRIORITY); COMP[0] = comparatorTask; } } /* -------------------------------------------------------------------- */ /* -- Refresh --------------------------------------------------------- */ /* -------------------------------------------------------------------- */ /** Refreshes the list of children. */ public void refresh () { final long now = System.currentTimeMillis(); final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("refresh on " + folder + " @" + now); synchronized (this) { if (refreshTask == null) { refreshTask = PROCESSOR.post (new Runnable () { public void run () { RequestProcessor.Task t = comparatorTask; if (t != null) { // first of all finish setting up comparator t.waitFinished (); } if (LOG) err.log ("-- refresh on " + folder + ": now=" + now); if (primaryFiles != null) { // list of children is created, recreate it for new files createBoth (null, true); } } }, getRefreshTime(), LATER_PRIORITY); } else { refreshTask.schedule(getRefreshTime()); } } } /** Tries to read the value of the refresh time from a system property. * If the system property is not present a default value (currently 10) * is used. */ private static int getRefreshTime() { if (REFRESH_TIME >= 0) { return REFRESH_TIME; } String sysProp = System.getProperty("org.openide.loaders.FolderList.refresh.interval"); // NOI18N if (sysProp != null) { try { REFRESH_TIME = Integer.parseInt(sysProp); } catch (NumberFormatException nfe) { ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, nfe); } } if (REFRESH_TIME < 0) { REFRESH_TIME = 10; } return REFRESH_TIME; } /* -------------------------------------------------------------------- */ /* -- Implementation of FileChangeListener ---------------------------- */ /* -------------------------------------------------------------------- */ /** Fired when a file has been changed. Refreshes the list when a * has be changed which up to now was not a member of the list but * becomes a member as a consequence of the change. * * @param fe the event describing context where action has taken place */ public void fileChanged (FileEvent fe) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("fileChanged: " + fe); FileObject fo = fe.getFile (); /** condition fo.isValid () is hot fix for solving problem (similar to #17328) * inside filesystems and should be reviewed. */ if (fo.isData () && fo.isValid ()) { // when a data on the disk has been changed, look whether we // should reparse children if (primaryFiles != null) { // a file has been changed and the list of files is created try { DataObject obj = DataObject.find (fo); if (!primaryFiles.containsKey (obj.getPrimaryFile ())) { // BUGFIX: someone who recognized the file and who isn't registered // yet => // may be still not O.K. // this primary file is not registered yet // so recreate list of children refresh(); } } catch (DataObjectNotFoundException ex) { ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, ex); // file without data object => no changes } } // Resort if sorting by last modification or size: DataFolder.SortMode sortMode = getComparator().getSortMode(); if (sortMode == DataFolder.SortMode.LAST_MODIFIED || sortMode == DataFolder.SortMode.SIZE) { changeComparator(); } } } /** Fired when a file has been deleted. * @param fe the event describing context where action has taken place */ public void fileDeleted (FileEvent fe) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("fileDeleted: " + fe); // boolean debug = fe.getFile().toString().equals("P"); // NOI18N //if (debug) System.out.println ("fileDeleted: " + fe.getFile ()); // NOI18N //if (debug) System.out.println ("fileList: " + fileList + " file: " + fileList.get (fe.getFile ())); // NOI18N if (primaryFiles == null || primaryFiles.containsKey (fe.getFile ())) { // one of main files has been deleted => reparse //if (debug) System.out.println ("RecreateChildenList"); // NOI18N refresh(); //if (debug) System.out.println ("Done"); // NOI18N } } /** Fired when a new file has been created. This action can only be * listened in folders containing the created file up to the root of * file system. * * @param fe the event describing context where action has taken place */ public void fileDataCreated (FileEvent fe) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("fileDataCreated: " + fe); refresh(); } /** Fired when a new file has been created. This action can only be * listened in folders containing the created file up to the root of * file system. * * @param fe the event describing context where action has taken place */ public void fileFolderCreated (FileEvent fe) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("fileFolderCreated: " + fe); refresh(); } /** Fired when a new file has been renamed. * * @param fe the event describing context where action has taken place */ public void fileRenamed (FileRenameEvent fe) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("fileRenamed: " + fe); refresh(); // Typically order may change as a result (#13820): changeComparator(); } /** Fired when a file attribute has been changed. * * @param fe the event describing context where action has taken place */ public void fileAttributeChanged(FileAttributeEvent fe) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log("fileAttributeChanged: " + fe); // update list when attrs defining order were changed if (fe.getFile() == folder) { /** Means one of attributes were changed*/ if (fe.getName() == null) { changeComparator(); return; } if (DataFolder.EA_ORDER.equals(fe.getName()) || DataFolder.EA_SORT_MODE.equals(fe.getName()) || -1 != fe.getName().indexOf("/")) { changeComparator(); } } } /* -------------------------------------------------------------------- */ /* -- Processing methods (only called in PROCESSOR) ------------------- */ /* -------------------------------------------------------------------- */ /** The comparator for this file objects. * @return the comparator to use */ private FolderOrder getComparator () { return FolderOrder.findFor (folder); } /** Getter for list of children. * @param f filter to be notified about additions * @return List with DataObject types */ private List getObjects (FolderListListener f) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("getObjects on " + folder); List res; if (primaryFiles == null) { res = createBoth (f, false); } else { if (order != null) { res = createObjects (order, primaryFiles, f); } else { res = createObjects (primaryFiles.keySet (), primaryFiles, f); res = carefullySort (res, getComparator ()); order = createOrder (res); } } return res; /* createChildrenAndFiles ();/* ArrayList v = (Collection)childrenList.get (); //if (debug) System.out.println ("Children list xxxxxxxxxxxxxx"); if (v == null) { //if (debug) System.out.println ("Create them xxxxxxxxxxxx"); v = createChildrenList (f); //if (debug) System.out.println ("result: " + v); } return v;*/ } /** Sort a list of DataObject's carefully. * The supplied comparator should supply a basic ordering, * and may also have an associated overriding partial ordering. * If the partial ordering is given and is self-contradictory, * it will be ignored and a warning issued. * @param l the list to sort * @param c a comparator and maybe partial comparator to use * @return the sorted list (may or may not be the same) */ private /*static*/ List carefullySort (List l, FolderOrder c) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("carefullySort on " + folder); // Not quite right: topologicalSort will not guarantee that these are left alone, // even if the constraints do not mention files in the existing folder order. // Adding constraints between adjacent pairs in the existing folder order is // not good either, since that could produce an inconsistency relative to // the explicitly specified constraints. E.g. you have: // {a, b, c, d, e, x, y} Folder-Order=[a, b, c, d, e] c/x c/y x/d y/d // This will currently produce the order: [a, b, c, e, x, y, d] // If you had the existing folder order [a, b, d, c, e], then trying to make // the sort stabler would just cause it to fail. XXX could try to add in the // stabilizing constraints first, and if that fails, try again without them... Collections.sort (l, c); Map constraints = c.getOrderingConstraints(l); if (constraints == null) { return l; } else { if (LOG) err.log ("carefullySort: partial orders"); try { return Utilities.topologicalSort(l, constraints); } catch (TopologicalSortException ex) { List corrected = ex.partialSort(); if (err.isLoggable(ErrorManager.WARNING)) { err.log (ErrorManager.WARNING, "Note: folder " + folder + " cannot be consistently sorted due to ordering conflicts."); // NOI18N err.notify (ErrorManager.INFORMATIONAL, ex); err.log (ErrorManager.WARNING, "Using partial sort: " + corrected); // NOI18N } return corrected; } } } /** Creates list of primary files from the list of data objects. * @param list list of DataObject * @return list of FileObject */ private static List createOrder (List list) { int size = list.size (); List res = new ArrayList (size); for (int i = 0; i < size; i++) { res.add (((DataObject)list.get (i)).getPrimaryFile ()); } return res; } /** Creates array of data objects from given order * and mapping between files and data objects. * * @param order list of FileObjects that define the order to use * @param map mapping (FileObject, Reference (DataObject)) to create data objects from * @param f filter that is notified about additions - only items * which are accepted by the filter will be added. Null means no filtering. * @return array of data objects */ private /*static*/ List createObjects ( Collection order, Map map, FolderListListener f ) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("createObjects on " + folder); int size = order.size (); Iterator it = order.iterator (); List res = new ArrayList (size); for (int i = 0; i < size; i++) { FileObject fo = (FileObject)it.next (); if (!fo.isValid ()) continue; Reference ref = (Reference)map.get (fo); DataObject obj = ref != null ? (DataObject)ref.get() : null; if (obj == null) { // try to find new data object try { obj = DataObject.find (fo); ref = new SoftReference (obj); } catch (DataObjectNotFoundException ex) { ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, ex); } } // add if accepted if (obj != null) { // JST: Cannot be avoided otherwise DataObject.files () can be unconsistent // avoid to checkFiles(this) //obj.recognizedByFolder(); if (f == null) { // accept all objects res.add (obj); } else { // allow the listener f to filter // objects in the array res f.process (obj, res); } } } if (f != null) { f.finished (res); } return res; } /** Scans for files in the folder and creates representation for * children. Fires info about changes in the nodes. * * @param filter listener to addition of nodes or null * @param notify true if changes in the children should be fired * @return vector of children */ private List createBoth (FolderListListener filter, boolean notify) { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("createBoth on " + folder); // map for (FileObject, DataObject) final HashMap file = new HashMap (); // list of all processed objects List all = new ArrayList (); // result list to return from the method List res = new ArrayList (); // map of current objects (FileObject, DataObject) final HashMap remove = primaryFiles == null ? new HashMap () : (HashMap)primaryFiles.clone (); // list of new objects to add final List add = new ArrayList (); DataLoaderPool pool = DataLoaderPool.getDefault(); // hashtable with FileObjects that are marked to be recognized // and that is why being out of enumeration final HashSet marked = new HashSet (); DataLoader.RecognizedFiles recog = new DataLoader.RecognizedFiles () { /** Adds the file object to the marked hashtable. * @param fo file object (can be null) */ public void markRecognized (FileObject fo) { if (fo != null) { marked.add (fo); } } }; // enumeration of all files in the folder Enumeration en = folder.getChildren (false); while (en.hasMoreElements ()) { FileObject fo = (FileObject)en.nextElement (); if (!marked.contains (fo)) { // the object fo has not been yet marked as recognized // => continue in computation DataObject obj; try { obj = pool.findDataObject (fo, recog); } catch (DataObjectExistsException ex) { // use existing data object obj = ex.getDataObject (); } catch (IOException ex) { // data object not recognized or not found obj = null; ErrorManager.getDefault ().notify(ex); } if (obj != null) { // adds object to data if it is not already there // avoid to checkFiles(this) obj.recognizedByFolder(); // primary file FileObject primary = obj.getPrimaryFile (); boolean doNotRemovePrimaryFile = false; if (!file.containsKey (primary)) { // realy added object, test if it is new // if we have not created primaryFiles before, then it is new boolean goIn = primaryFiles == null; if (!goIn) { Reference r = (Reference)primaryFiles.get (primary); // if its primary file is not between original primary files // then data object is new goIn = r == null; if (!goIn) { // if the primary file is there, but the previous data object // exists and is different, then this one is new DataObject obj2 = (DataObject)r.get (); goIn = obj2 == null || obj2 != obj; if (goIn) { doNotRemovePrimaryFile = true; } } } if (goIn) { // realy new add.add (obj); /* JST: In my opinion it should not be here * so I moved this out of this if. Is it ok? if (filter != null) { // fire info about addition filter.acceptDataObject (obj); } */ } // adds the object all.add (obj); if (filter == null) { res.add (obj); } else { filter.process (obj, res); } } if (!doNotRemovePrimaryFile) { // this object exists it should not be removed remove.remove (primary); } // add it to the list of primary files file.put (primary, new WeakReference (obj)); } else { // 1. nothing to add to data object list // 2. remove this object if it was in list of previous ones // 3. do not put the file into list of know primary files // => do nothing at all } } } // !!! section that fires info about changes should be here !!! // now file contains newly inserted files // data contains data objects // remove contains data objects that should be removed // add contains data object that were added primaryFiles = file; all = carefullySort (all, getComparator ()); order = createOrder (all); if (all.size () == res.size ()) { // assume no filtering has been done res = all; } else { // sort also content of res res = carefullySort (res, getComparator ()); } ////if (debug) System.out.println ("Notified: " + notified + " added: " + add.size () + " removed: " + remove.size ()); // NOI18N if (notify) { fireChildrenChange (add, remove.keySet ()); } // notify the filter if (filter != null) { filter.finished (res); } return res; } /* -------------------------------------------------------------------- */ /* -- PropertyChangeListener management ------------------------------- */ /* -------------------------------------------------------------------- */ /** Fires info about change of children to the folder. * @param add added data objects * @param removed removed data objects */ private void fireChildrenChange (Collection add, Collection removed) { if (pcs != null) { if (!add.isEmpty() || !removed.isEmpty()) { pcs.firePropertyChange (PROP_CHILDREN, null, null); } } } /** Removes property change listener. * @param l the listener */ public void removePropertyChangeListener(PropertyChangeListener l) { if (pcs != null) { pcs.removePropertyChangeListener (l); } } /** Adds a listener. * @param l the listener */ public void addPropertyChangeListener(PropertyChangeListener l) { if (pcs == null) { synchronized (this) { if (pcs == null) { pcs = new PropertyChangeSupport (this); } } } pcs.addPropertyChangeListener (l); } /* -------------------------------------------------------------------- */ /* -- Inner class ListTask -------------------------------------------- */ /* -------------------------------------------------------------------- */ /** Task that holds result and also task. Moreover * can do the computation. */ private final class ListTask implements Runnable { private FolderListListener filter; public ListTask (FolderListListener filter) { this.filter = filter; } public List result; public RequestProcessor.Task task; public void run () { final boolean LOG = err.isLoggable(ErrorManager.INFORMATIONAL); if (LOG) err.log ("ListTask.run 1 on " + folder); // invokes the refresh task before we do anything else if (comparatorTask != null) { comparatorTask.waitFinished (); } if (refreshTask != null) { refreshTask.waitFinished (); } err.log ("ListTask.run 2"); result = getObjects (filter); err.log ("ListTask.run 3"); folderCreated = true; } } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.