org.openide.loaders.FolderLookup Maven / Gradle / Ivy
/*
* 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.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.openide.cookies.InstanceCookie;
import org.openide.ErrorManager;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup;
import org.openide.util.lookup.ProxyLookup;
import org.openide.util.Task;
/** Implements a lookup, that scans a content of a folder for its
* data objects and asks them for instance cookie, the created objects
* are then used to for the content of the lookup.
* Any instances which are in fact instances of Lookup
* will be proxied to, permitting one file to generate many instances
* in the lookup system easily.
* @author Jaroslav Tulach
* @since 1.11
*/
public class FolderLookup extends FolderInstance {
/** Lock for initiliazation of lookup. */
private static final Object LOCK = new Object ();
/** Lookup to delegate to. */
private ProxyLkp lookup;
/** The root name of the lookup. */
private String rootName;
/** Indicates whether this FolderLookup is at the root of (folder)tree which
* we are interested in and the one which collects all items from the tree. */
private final boolean isRoot;
/** Constructs the FolderLookup for given container. A default ID prefix is
* used for identification of located items.
*
* @param df container (or folder) to work on
*/
public FolderLookup (DataObject.Container df) {
this (df, "FL["); // NOI18N
}
/** Constructs the FolderLookup for given container.
* @param df container (or folder) to work on
* @param prefix the prefix to use
*/
public FolderLookup (DataObject.Container df, String prefix) {
this(df, prefix, true);
}
/** Constructs the FolderLookup for given container.
* @param df container (or folder) to work on
* @param prefix the prefix to use
* @param isRoot indicates whether this instance is the at the root of tree
* in which we perform the lookup -> only this instance has lookup
* which collects all items from the tree */
private FolderLookup(DataObject.Container df, String prefix, boolean isRoot) {
super(df);
this.rootName = prefix;
this.isRoot = isRoot;
}
/** The correct class that this folder recognizes.
* @return Proxy.Lkp class. */
public final Class instanceClass () {
return ProxyLkp.class;
}
/**
* Getter for the lookup that should be used.
*
Serializable since 3.27.
* @return a lookup
*/
public final Lookup getLookup () {
boolean inited = false;
synchronized(LOCK) {
if(lookup == null) {
lookup = new ProxyLkp(this);
inited = true;
}
}
if(inited) {
checkRecreate();
}
return lookup;
}
/** Updates the content of the lookup.
* @param cookies updated array of instance cookies for the folder
* @return object to represent these cookies
*
* @exception IOException an I/O error occured
* @exception ClassNotFoundException a class has not been found
*/
protected final Object createInstance(InstanceCookie[] cookies)
throws IOException, ClassNotFoundException {
FolderLookupData flData = new FolderLookupData();
// If we are root, preserve place for abstract lookup which collects items.
// see ProxyLkp.update method.
if(isRoot) {
flData.lookups.add(null);
}
for (int i = 0; i < cookies.length; i++) {
try {
// It's either result from underlying lookup or some another lookup or it is ICItem.
Object obj = cookies[i].instanceCreate ();
if(obj instanceof FolderLookupData) {
// It's from underlying 'sub'-lookup.
flData.items.addAll(((FolderLookupData)obj).items);
flData.lookups.addAll(((FolderLookupData)obj).lookups);
} else if(obj instanceof Lookup) {
flData.lookups.add(obj);
} else {
// Has to be ICItem.
flData.items.add(obj);
}
} catch(IOException ex) {
exception(ex);
} catch(ClassNotFoundException ex) {
exception(ex);
}
}
// If this is not the root lookup just return items+lookups
// which will be collected by root lookup.
if(!isRoot) {
return flData;
}
// We are root FolderLookup. Now collect all items from underlying world.
// Initializes lookup.
getLookup();
lookup.update(flData.items, flData.lookups);
return lookup;
}
/** Overrides superclass method. It returns instance
* for DataObject
&InstanceCookie
'pair'.
* If the instance is of FolderLookup.Lkp
class it is created otherwise
* new Lkp.ICItem
created and returned.
*
* @param dobj the data object that is the source of the cookie
* @param cookie the instance cookie to read the instance from
* @exception IOException when there I/O error
* @exception ClassNotFoundException if the class cannot be found */
protected Object instanceForCookie(DataObject dobj, InstanceCookie cookie)
throws IOException, ClassNotFoundException {
boolean isLookup;
if(cookie instanceof InstanceCookie.Of) {
isLookup = ((InstanceCookie.Of)cookie).instanceOf(Lookup.class);
} else {
isLookup = Lookup.class.isAssignableFrom(cookie.instanceClass ());
}
if(isLookup) {
// Is underlying FolderLookup create it.
return cookie.instanceCreate();
} else {
return new ICItem(dobj, rootName, cookie);
}
}
/** Folder is recognized as underlying FolderLookup
which passes
* its items to parent FolderLookup
.
* @param df the folder found
* @return new FolderLookup
*/
protected InstanceCookie acceptFolder (DataFolder df) {
return new FolderLookup(df, objectName(rootName, df), false);
}
/** Container is recognized as underlying FolderLookup
which passes
* its items to parent FolderLookup
.
* @param df the container found
* @return new FolderLookup
*/
protected InstanceCookie acceptContainer (DataObject.Container df) {
return new FolderLookup(
df,
rootName == null ? "" : rootName + "", // NOI18N
false
);
}
/** Starts the creation of the object in the Folder recognizer thread.
* Doing all the lookup stuff in one thread should prevent deadlocks,
* but because we call unknown data loaders, they obviously must be
* implemented in correct way.
*
* @param run runable to start
* @return null
, because the runnable is started immediatelly
*/
protected final Task postCreationTask (Runnable run) {
run.run ();
return null;
}
/** Concatenates name of folder with name of object. Helper method.
* @param folderName name of folder or null
* @param obj object to concatenate
* @return new name
*/
private static String objectName (String name, DataObject obj) {
if (name == null) {
return obj.getName ();
} else {
return name + '/' + obj.getName ();
}
}
/** Notifies the exception. Helper method. */
private static void exception (Exception e) {
ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, e);
}
private static void exception(Exception e, FileObject fo) {
ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN, "Bad file: " + fo, null, null, null); // NOI18N
exception(e);
}
/** ProxyLookup
delegate so we can change the lookups on fly. */
static final class ProxyLkp extends ProxyLookup implements Serializable {
private static final long serialVersionUID = 1L;
/** FolderLookup
we are associated with. */
private transient FolderLookup fl;
/** Content to control the abstract lookup. */
private transient AbstractLookup.Content content;
private transient boolean readFromStream;
/** Constructs lookup which holds all items+lookups from underlying world.
* @param folder FolderLookup
to associate to */
public ProxyLkp(FolderLookup folder) {
this(folder, new AbstractLookup.Content());
}
/** Constructs lookup. */
private ProxyLkp(FolderLookup folder, AbstractLookup.Content content) {
super(new Lookup[] {new AbstractLookup(content)});
this.fl = folder;
this.content = content;
}
public String toString() {
return "FolderLookup.lookup[\"" + fl.rootName + "\"]";
}
private void writeObject (ObjectOutputStream oos) throws IOException {
Lookup[] ls = getLookups();
for (int i = 0; i < ls.length; i++) {
oos.writeObject(ls[i]);
}
oos.writeObject(null);
oos.writeObject (fl.folder);
oos.writeObject (fl.rootName);
oos.writeObject (content);
}
private void readObject (ObjectInputStream ois) throws IOException, ClassNotFoundException {
List ls = new ArrayList(); // List
Lookup l;
while ((l = (Lookup)ois.readObject()) != null) {
ls.add(l);
}
Lookup[] arr = (Lookup[])ls.toArray(new Lookup[ls.size()]);
DataFolder df = (DataFolder)ois.readObject ();
String root = (String)ois.readObject ();
fl = new FolderLookup (df, root, true);
fl.lookup = this;
content = (AbstractLookup.Content)ois.readObject ();
setLookups (arr);
readFromStream = true;
org.openide.util.RequestProcessor.getDefault ().post (fl, 0, Thread.MIN_PRIORITY);
}
/** Updates internal data.
* @param items Items to assign to all pairs
* @param lookups delegates to delegate to (first item is null)
*/
public void update(Collection items, List lookups) {
readFromStream = false;
// remember the instance lookup
Lookup pairs = getLookups ()[0];
// changes the its content
content.setPairs (items);
if (fl.err().isLoggable(ErrorManager.INFORMATIONAL)) fl.err ().log ("Changed pairs: " + items); // NOI18N
lookups.set(0, pairs);
Lookup[] arr = (Lookup[])lookups.toArray (new Lookup[lookups.size ()]);
setLookups (arr);
if (fl.err().isLoggable(ErrorManager.INFORMATIONAL)) fl.err ().log ("Changed lookups: " + lookups); // NOI18N
}
/** Waits before the processing of changes is finished. */
protected void beforeLookup (Template template) {
if (readFromStream) {
// ok
return;
}
// do not wait in folder recognizer, but in all other cases
if (!FolderList.isFolderRecognizerThread () && ICItem.DANGEROUS.get () == null) {
fl.instanceFinished ();
}
}
/** Mostly for testing purposes, to allow the tests to wait
* for the scan to finished after deserialization.
*/
public void waitFinished () {
fl.waitFinished ();
}
} // End of ProxyLkp class.
/** Item that delegates to InstanceCookie
. Item which
* the internal lookup data structure is made from. */
private static final class ICItem extends AbstractLookup.Pair implements Serializable {
static final long serialVersionUID = 10L;
static final ThreadLocal DANGEROUS = new ThreadLocal ();
/** when deserialized only primary file is stored */
private FileObject fo;
private transient InstanceCookie ic;
/** source data object */
private transient DataObject obj;
/** reference to created object */
private transient WeakReference ref;
/** root folder */
private String rootName;
/** Constructs new item. */
public ICItem (DataObject obj, String rootName, InstanceCookie ic) {
this.ic = ic;
this.obj = obj;
this.rootName = rootName;
this.fo = obj.getPrimaryFile();
}
/** Initializes the item
*/
public void init () {
if (ic != null) return;
Object prev = DANGEROUS.get ();
try {
DANGEROUS.set (this);
if (obj == null) {
try {
obj = DataObject.find(fo);
} catch (DataObjectNotFoundException donfe) {
ic = new BrokenInstance("File: " + fo.getPath(), donfe); // NOI18N
return;
}
}
ic = (InstanceCookie)obj.getCookie (InstanceCookie.class);
if (ic == null) {
ic = new BrokenInstance("File: " + fo.getPath(), null); // NOI18N
}
} finally {
DANGEROUS.set (prev);
}
}
/**
* Fake instance cookie.
* Used in case a file had an instance in a previous session but now does not
* (or the data object could not even be created correctly).
*/
private static final class BrokenInstance implements InstanceCookie.Of {
private final String message;
private final Exception ex;
public BrokenInstance(String message, Exception ex) {
this.message = message;
this.ex = ex;
}
public String instanceName() {
return "java.lang.Object"; // NOI18N
}
private ClassNotFoundException die() {
if (ex != null) {
return new ClassNotFoundException(message, ex);
} else {
return new ClassNotFoundException(message);
}
}
public Class instanceClass() throws IOException, ClassNotFoundException {
throw die();
}
public Object instanceCreate() throws IOException, ClassNotFoundException {
throw die();
}
public boolean instanceOf(Class type) {
return false;
}
}
/** The class of the result item.
* @return the class of the item
*/
protected boolean instanceOf (Class clazz) {
init ();
if (ic instanceof InstanceCookie.Of) {
// special handling for special cookies
InstanceCookie.Of of = (InstanceCookie.Of)ic;
return of.instanceOf (clazz);
}
// handling of normal instance cookies
try {
return clazz.isAssignableFrom (ic.instanceClass ());
} catch (ClassNotFoundException ex) {
exception(ex, fo);
} catch (IOException ex) {
exception(ex, fo);
}
return false;
}
/** The class of the result item.
* @return the instance of the object or null if it cannot be created
*/
public Object getInstance() {
init ();
try {
Object obj = ic.instanceCreate();
ref = new WeakReference (obj);
return obj;
} catch (ClassNotFoundException ex) {
exception(ex, fo);
} catch (IOException ex) {
exception(ex, fo);
}
return null;
}
/** Hash code is the InstanceCookie
's code. */
public int hashCode () {
init ();
return System.identityHashCode (ic);
}
/** Two items are equal if they point to the same cookie. */
public boolean equals (Object obj) {
if (obj instanceof ICItem) {
ICItem i = (ICItem)obj;
i.init ();
init ();
return ic == i.ic;
}
return false;
}
/** An identity of the item.
* @return string representing the item, that can be used for
* persistance purposes to locate the same item next time */
public String getId() {
init ();
if (obj == null) {
// Deser problems.
return ""; // NOI18N
}
return objectName(rootName, obj);
}
/** Display name is extracted from name of the objects node. */
public String getDisplayName () {
init ();
if (obj == null) {
// Deser problems.
return ""; // NOI18N
}
return obj.getNodeDelegate ().getDisplayName ();
}
/** Method that can test whether an instance of a class has been created
* by this item.
*
* @param obj the instance
* @return if the item has already create an instance and it is the same
* as obj.
*/
protected boolean creatorOf(Object obj) {
WeakReference w = ref;
if (w != null && w.get () == obj) {
return true;
}
if (this.obj instanceof InstanceDataObject) {
return ((InstanceDataObject)this.obj).creatorOf (obj);
}
return false;
}
/** The class of this item.
* @return the correct class
*/
public Class getType() {
init ();
try {
return ic.instanceClass ();
} catch (IOException ex) {
// ok, no class available
} catch (ClassNotFoundException ex) {
// ok, no class available
}
return Object.class;
}
} // End of ICItem class.
/** Data structure which holds ICItem
's and Lookup
's got
* from current folder and underlying sub-folders making it possible to
* pass to parent folder together. */
private static class FolderLookupData {
/** Collection of ICItem
's found in current
* folder and its sub-folders. */
private Collection items;
/** List of Lookup
's found in current folder
* and its sub-folders. */
private List lookups;
/** Constructs data structure with inited fields. */
public FolderLookupData() {
items = new ArrayList(30);
lookups = new ArrayList(5);
}
} // End of FolderLookupData class.
}