org.openide.loaders.FolderChildren Maven / Gradle / Ivy
Go to download
The NetBeans Platform is a generic base for desktop applications. It provides the services common
to almost all large desktop applications: window management, menus, settings and storage, an update
manager, and file access. Get a head start by reusing these standard components, allowing you to
concentrate fully on your application's business logic.
The newest version!
/*
* 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-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.loaders;
import java.beans.*;
import java.util.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.openide.ErrorManager;
import org.openide.filesystems.FileObject;
import org.openide.util.RequestProcessor;
import org.openide.util.Task;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
/** Watches over a folder and represents its
* child data objects by nodes.
*
* @author Jaroslav Tulach
*/
final class FolderChildren extends Children.Keys
implements PropertyChangeListener, ChangeListener {
/** the folder */
private DataFolder folder;
/** filter of objects */
private final DataFilter filter;
/** listener on changes in nodes */
private PropertyChangeListener listener;
/** logging, if needed */
private ErrorManager err;
/** this is true between addNotify and removeNotify */
private boolean active;
/** true if the refrersh is done after DataFilter change */
private boolean refresh;
/** we wait for this task finished in getNodes(true) */
private RequestProcessor.Task refreshTask;
/** Runnable scheduled to refRP */
private ChildrenRefreshRunnable refreshRunnable;
/** Private req processor for the refresh tasks */
private static RequestProcessor refRP =
new RequestProcessor("FolderChildren_Refresh"); // NOI18N
/**
* @param f folder to display content of
* @param map map to use for holding of children
*/
public FolderChildren (DataFolder f) {
this (f, DataFilter.ALL);
}
/**
* @param f folder to display content of
* @param filter filter of objects
*/
public FolderChildren (DataFolder f, DataFilter filter) {
this.folder = f;
this.filter = filter;
this.listener = org.openide.util.WeakListeners.propertyChange (this, folder);
err = ErrorManager.getDefault().getInstance("org.openide.loaders.FolderChildren." + f.getPrimaryFile().getPath().replace('/','.')); // NOI18N
if (!err.isLoggable(ErrorManager.INFORMATIONAL)) {
err = null;
}
}
/** used from DataFolder */
DataFilter getFilter () {
return filter;
}
/** If the folder changed its children we change our nodes.
*/
public void propertyChange (final PropertyChangeEvent ev) {
if (DataFolder.PROP_CHILDREN.equals (ev.getPropertyName ())) {
if (err != null) err.log("Got PROP_CHILDREN");
refreshChildren().schedule (0);
postClearTask();
return;
}
if (
DataFolder.PROP_SORT_MODE.equals (ev.getPropertyName ()) ||
DataFolder.PROP_ORDER.equals (ev.getPropertyName ())
) {
if (err != null) err.log("Got PROP_SORT_MODE or PROP_ORDER");
refreshChildren().schedule (0);
postClearTask();
return;
}
}
public void stateChanged( ChangeEvent e ) {
// Filtering changed need to recompute children
refresh = true;
refreshChildren().schedule(0);
postClearTask();
return;
}
/**
* refreshRunnable holds references to the data object
* to prevent GC. This method post a task to the same request processor
* (refRP) to clear this references after they are no longer needed.
*/
private void postClearTask() {
refRP.post(new Runnable() {
public void run() {
refreshRunnable.clear();
}
});
}
/** Refreshes the children.
*/
synchronized RequestProcessor.Task refreshChildren() {
if (refreshTask == null) {
refreshTask = refRP.post(refreshRunnable = new ChildrenRefreshRunnable());
}
return refreshTask;
}
/** Create a node for one data object.
* @param key DataObject
*/
protected Node[] createNodes (Object key) {
if (err != null) err.log("createNodes: " + key);
FileObject fo = ((Pair)key).primaryFile;
DataObject obj;
try {
obj = DataObject.find (fo);
if (filter == null || filter.acceptDataObject (obj)) {
return new Node[] { obj.getClonedNodeDelegate (filter) };
} else {
return new Node[0];
}
} catch (DataObjectNotFoundException e) {
ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, e);
return new Node[0];
}
}
public Node[] getNodes(boolean optimalResult) {
if (optimalResult) {
if (checkChildrenMutex()) {
active = true;
FolderList.find(folder.getPrimaryFile(), true).waitProcessingFinished();
Task task = refreshChildren();
task.waitFinished();
} else {
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL,
new IllegalStateException("getNodes(true) called while holding the Children.MUTEX") // NOI18N
);
}
}
Node[] res = getNodes();
refreshRunnable.clear(); // we can clean the references to data objects now
// they are no longer needed
return res;
}
public Node findChild(String name) {
if (checkChildrenMutex()) {
getNodes(true);
}
return super.findChild(name);
}
/**
* @return true if it is safe to wait (our thread is
* not in Children.MUTEX.readAccess
*/
private static boolean checkChildrenMutex() {
return !Children.MUTEX.isReadAccess() && !Children.MUTEX.isWriteAccess ();
}
/** Initializes the children.
*/
protected void addNotify () {
// add as a listener for changes on nodes
folder.addPropertyChangeListener (listener);
// add listener to the filter
if ( filter instanceof ChangeableDataFilter ) {
((ChangeableDataFilter)filter).addChangeListener( this );
}
//
active = true;
// start the refresh task to compute the children
refreshChildren();
}
/** Deinitializes the children.
*/
protected void removeNotify () {
// removes the listener
folder.removePropertyChangeListener (listener);
// remove listener from filter
if ( filter instanceof ChangeableDataFilter ) {
((ChangeableDataFilter)filter).removeChangeListener( this );
}
//
active = false;
// we don't call the setKeys directly here because
// there can be a task spawned by refreshChildren - so
// we want to clear the children after that task is finished
refreshChildren();
}
/** Display name */
public String toString () {
return folder.getPrimaryFile ().toString ();
}
/**
* Instances of this class are posted to the request processor refRP
* (FolderChildren_refresher). We do this because we do not want
* to call setKeys synchronously.
*/
private final class ChildrenRefreshRunnable implements Runnable {
/** store the referneces to the data objects to
* prevent GC.
*/
private DataObject[] ch;
/** calls setKeys with the folder children
* or with empty collection if active is false
*/
public void run() {
if (! active) {
setKeys (java.util.Collections.EMPTY_SET);
return;
}
ch = folder.getChildren();
Object []keys = new Object[ch.length];
for (int i = 0; i < keys.length; i++) {
keys[i] = new Pair(ch[i].getPrimaryFile());
}
setKeys(Arrays.asList(keys));
if ( refresh ) {
refresh = false;
for (int i = 0; i < keys.length; i++) {
refreshKey( keys[i] );
}
}
}
/** stop holding the references to the data objects. After
* calling this they can be GCed again.
*/
public void clear() {
ch = null;
}
}
/** Pair of dataobject invalidation sequence # and primary file.
* It serves as a key for the given data object.
* It is here to create something different then data object,
* because the data object should be finalized when not needed and
* that is why it should not be used as a key.
*/
private static final class Pair extends Object {
public FileObject primaryFile;
public int seq;
public Pair (FileObject primaryFile) {
this.primaryFile = primaryFile;
this.seq = DataObjectPool.getPOOL().registrationCount(primaryFile);
}
public int hashCode () {
return primaryFile.hashCode () ^ seq;
}
public boolean equals (Object o) {
if (o instanceof Pair) {
Pair p = (Pair)o;
return primaryFile.equals (p.primaryFile) && seq == p.seq;
}
return false;
}
public String toString() {
return "FolderChildren.Pair[" + primaryFile + "," + seq + "]"; // NOI18N
}
}
}