0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.openide.loaders;
0043:
0044: import java.beans.PropertyChangeListener;
0045: import java.beans.PropertyChangeEvent;
0046: import java.io.IOException;
0047: import java.util.*;
0048: import java.util.logging.Level;
0049: import java.util.logging.Logger;
0050:
0051: import org.openide.*;
0052: import org.openide.filesystems.*;
0053: import org.openide.cookies.InstanceCookie;
0054: import org.openide.util.Task;
0055: import org.openide.util.TaskListener;
0056: import org.openide.util.RequestProcessor;
0057: import org.openide.util.WeakListeners;
0058:
0059: /** Support class for creation of an object from the content
0060: * of a {@link DataObject.Container}. It implements
0061: * {@link InstanceCookie}, so it
0062: * can be used as a cookie for a node or data object.
0063: * <P>
0064: * When created on a container and started by invoking run method,
0065: * it scans its content (in a separate
0066: * thread) and creates a list of instances from which the new
0067: * instance of this object should be composed. The object
0068: * automatically listens to changes of components
0069: * in the container, and if some change occurs, it allows the subclass to create
0070: * a new object.
0071: * </p>
0072: *
0073: * <p>Subclasses shall override the following methods:</p>
0074: *
0075: * <ol>
0076: * <li>{@link #createInstance(InstanceCookie[])} (required): this method is
0077: * called whenever the content has been changed. Its implementation
0078: * shall build up the data structures and perform the actions required
0079: * by this implementation.</li>
0080: * <li>The filter methods {@link #acceptDataObject(DataObject)}, {@link
0081: * #acceptCookie(InstanceCookie)},
0082: * {@link #acceptFolder(DataFolder)} and
0083: * {@link #acceptContainer(DataObject.Container)} (optional): the standard
0084: * way is to override one or several of the latter methods. Overriding
0085: * {@link #acceptDataObject(DataObject)} more deeply
0086: * modifies the default behavior, because the default implementation of
0087: * {@link #acceptDataObject(DataObject)} calls the
0088: * other 3 filter methods. See the method documentation for details.</li>
0089: * <li>The {@link InstanceCookie} methods
0090: * {@link #instanceClass()} (optional but recommended)
0091: * to inform about the class implemented by the return value of
0092: * {@link #instanceCreate()}.</li>
0093: * <li>Advanced subclasses may need to override {@link #postCreationTask}
0094: * and/or {@link #instanceForCookie}, but it is not common to need these.</li>
0095: * </ol>
0096: *
0097: * @author Jaroslav Tulach
0098: */
0099: public abstract class FolderInstance extends Task implements
0100: InstanceCookie { // XXX add generic type params?
0101:
0102: /* -------------------------------------------------------------------- */
0103: /* -- Constants ------------------------------------------------------- */
0104: /* -------------------------------------------------------------------- */
0105:
0106: /** a queue to run requests in */
0107: private static final RequestProcessor PROCESSOR = new RequestProcessor(
0108: "Folder Instance Processor" // NOI18N
0109: );
0110:
0111: /** static variable to hold current value for callbacks to tasks
0112: * Also used to synchronize access to map field on.
0113: */
0114: private static final ThreadLocal<Object> CURRENT = new ThreadLocal<Object>();
0115:
0116: /** The last finished folder instance in this thread. Works together
0117: * with CURRENT, because sometimes more than one FolderInstance.instanceCreate
0118: * can be called on the same thread.
0119: */
0120: private static final ThreadLocal<Object> LAST_CURRENT = new ThreadLocal<Object>();
0121:
0122: /* -------------------------------------------------------------------- */
0123: /* -- Instance attributes --------------------------------------------- */
0124: /* -------------------------------------------------------------------- */
0125:
0126: /** Folder to work with. Non null only if a constructor with DataFolder
0127: * is used to construct this object.
0128: */
0129: protected DataFolder folder;
0130:
0131: /** container to work with */
0132: private DataObject.Container container;
0133:
0134: /** map of primary file to their cookies (FileObject, HoldInstance) */
0135: private HashMap<FileObject, HoldInstance> map = new HashMap<FileObject, HoldInstance>(
0136: 17);
0137:
0138: /** Array of tasks that we have to check before we are ok. These are the tasks
0139: * associated with children of the current folder.
0140: */
0141: private Task[] waitFor;
0142:
0143: /** object for this cookie. Either the right instance of object or
0144: * an instance of IOException or ClassNotFoundException. By default
0145: * it is assigned to some private object in this class to signal that
0146: * it is uninitialized.
0147: */
0148: private Object object = CURRENT;
0149:
0150: /** Listener and runner for this object */
0151: private Listener listener;
0152:
0153: /** error manager for this instance */
0154: private Logger err;
0155:
0156: /** Task that computes the children list of the folder */
0157: private Task recognizingTask;
0158:
0159: /** A task that gets objects from InstanceCookie's and calls createInstance.
0160: * Started immediately after the <code>recognizingTask</code> is finished.
0161: */
0162: private Task creationTask;
0163:
0164: /* -------------------------------------------------------------------- */
0165: /* -- Constructor(s) -------------------------------------------------- */
0166: /* -------------------------------------------------------------------- */
0167:
0168: /** Create new folder instance.
0169: * @param df data folder to create instances from
0170: */
0171: public FolderInstance(DataFolder df) {
0172: this ((DataObject.Container) df);
0173: }
0174:
0175: /** A new object that listens on changes in a container.
0176: * @param container the object to associate with
0177: * @since 1.11
0178: */
0179: public FolderInstance(DataObject.Container container) {
0180: this (container, null);
0181: }
0182:
0183: /** Constructs everything.
0184: * @param container container
0185: * @param logName the name to use for logging purposes
0186: */
0187: private FolderInstance(DataObject.Container container,
0188: String logName) {
0189: if (container instanceof DataFolder) {
0190: folder = (DataFolder) container;
0191: if (logName == null) {
0192: logName = folder.getPrimaryFile().getPath().replace(
0193: '/', '.');
0194: }
0195: container = FolderList.find(folder.getPrimaryFile(), true);
0196: }
0197:
0198: listener = new Listener();
0199:
0200: if (logName == null) {
0201: logName = "org.openide.loaders.FolderInstance"; // NOI18N
0202: } else {
0203: logName = "org.openide.loaders.FolderInstance" + '.'
0204: + logName; // NOI18N
0205: }
0206:
0207: err = Logger.getLogger(logName);
0208:
0209: this .container = container;
0210: container
0211: .addPropertyChangeListener(org.openide.util.WeakListeners
0212: .propertyChange(listener, container));
0213:
0214: if (err.isLoggable(Level.FINE)) {
0215: err.fine("new " + this ); // NOI18N
0216: }
0217: }
0218:
0219: /* -------------------------------------------------------------------- */
0220: /* -- Implementation of org.openide.Cookies.InstanceCookie ------------ */
0221: /* -------------------------------------------------------------------- */
0222:
0223: /** The name of the class that we create.
0224: * @return the name
0225: */
0226: public String instanceName() {
0227: try {
0228: return instanceClass().getName();
0229: } catch (java.io.IOException ex) {
0230: return "java.lang.Object"; // NOI18N
0231: } catch (ClassNotFoundException ex) {
0232: return "java.lang.Object"; // NOI18N
0233: }
0234: }
0235:
0236: /** Returns the root class of all objects.
0237: * Supposed to be overriden in subclasses.
0238: *
0239: * @return Object.class
0240: * @exception IOException an I/O error occured
0241: * @exception ClassNotFoundException the class has not been found
0242: */
0243: public Class<?> instanceClass() throws java.io.IOException,
0244: ClassNotFoundException {
0245: Object object = this .object;
0246: if (object != null) {
0247: if (object instanceof java.io.IOException) {
0248: throw (java.io.IOException) object;
0249: }
0250: if (object instanceof ClassNotFoundException) {
0251: throw (ClassNotFoundException) object;
0252: }
0253: return object.getClass();
0254: }
0255:
0256: return Object.class;
0257: }
0258:
0259: /** Creates instance.
0260: * @return an object to work with
0261: * @exception IOException an I/O error occured
0262: * @exception ClassNotFoundException the class has not been found
0263: */
0264: public Object instanceCreate() throws java.io.IOException,
0265: ClassNotFoundException {
0266: Object object = CURRENT.get();
0267:
0268: if (object == null || LAST_CURRENT.get() != this ) {
0269: err.fine("do into waitFinished"); // NOI18N
0270: waitFinished();
0271:
0272: object = FolderInstance.this .object;
0273: }
0274:
0275: if (err.isLoggable(Level.FINE)) {
0276: err.fine("instanceCreate: " + object); // NOI18N
0277: }
0278:
0279: if (object instanceof java.io.IOException) {
0280: throw (java.io.IOException) object;
0281: }
0282: if (object instanceof ClassNotFoundException) {
0283: throw (ClassNotFoundException) object;
0284: }
0285:
0286: if (object == CURRENT) {
0287: // uninitialized
0288: throw new IOException(
0289: "Cyclic reference. Somebody is trying to get value from FolderInstance ("
0290: + getClass().getName()
0291: + ") from the same thread that is processing the instance"); // NOI18N
0292: }
0293:
0294: return object;
0295: }
0296:
0297: /* -------------------------------------------------------------------- */
0298: /* -- Wait ------------------------------------------------------------ */
0299: /* -------------------------------------------------------------------- */
0300:
0301: /** Wait for instance initialization to finish.
0302: */
0303: public final void instanceFinished() {
0304: waitFinished();
0305: }
0306:
0307: /* -------------------------------------------------------------------- */
0308: /* -- Extends org.openide.util.Task ----------------------------------- */
0309: /* -------------------------------------------------------------------- */
0310:
0311: /** Overrides the instance finished to deal with
0312: * internal state correctly.
0313: */
0314: public @Override
0315: void waitFinished() {
0316: boolean isLog = err.isLoggable(Level.FINE);
0317: for (;;) {
0318: err.fine("waitProcessingFinished on container"); // NOI18N
0319: waitProcessingFinished(container);
0320:
0321: Task originalRecognizing = checkRecognizingStarted();
0322: if (isLog) {
0323: err.fine("checkRecognizingStarted: "
0324: + originalRecognizing); // NOI18N
0325: }
0326: originalRecognizing.waitFinished();
0327:
0328: Task t = creationTask;
0329: if (isLog) {
0330: err.fine("creationTask: " + creationTask); // NOI18N
0331: }
0332: if (t != null) {
0333: t.waitFinished();
0334: }
0335:
0336: Task[] toWait = waitFor;
0337: if (isLog) {
0338: err.fine("toWait: " + toWait); // NOI18N
0339: }
0340: if (toWait != null) {
0341: for (int i = 0; i < toWait.length; i++) {
0342: if (isLog) {
0343: err.fine(" wait[" + i + "]: " + toWait[i]); // NOI18N
0344: }
0345: toWait[i].waitFinished();
0346: }
0347: }
0348:
0349: // loop if there was yet another task started to compute the
0350: // children list
0351: if (originalRecognizing == checkRecognizingStarted()) {
0352: if (isLog) {
0353: err.fine("breaking the wait loop"); // NOI18N
0354: }
0355: break;
0356: }
0357:
0358: //
0359: // otherwise go on an try it once more
0360: //
0361: }
0362: }
0363:
0364: /** Synchronously starts the creation of the instance. */
0365: public @Override
0366: void run() {
0367: recreate();
0368: instanceFinished();
0369: }
0370:
0371: /* -------------------------------------------------------------------- */
0372: /* -- Filter methods (protected, may be overridden by sub-classes) ---- */
0373: /* -------------------------------------------------------------------- */
0374:
0375: /** Allows subclasses to decide whether they want to work with the specified
0376: * <code>DataObject</code> or not.
0377: *
0378: * <p>The default implementation roughly performs the following steps:</p>
0379: *
0380: * <ol>
0381: * <li>if <code>dob</code> has an <code>InstanceCookie</code>
0382: * {@link #acceptCookie(InstanceCookie)} is called on that cookie</li>
0383: * <li>if <code>dob</code> has a <code>DataFolder</code> cookie,
0384: * {@link #acceptFolder(DataFolder)} is called on that folder</li>
0385: * <li>if <code>dob</code> has a <code>DataObject.Container</code> cookie,
0386: * {@link #acceptContainer(DataObject.Container)} is called on that
0387: * container</li>
0388: * </ol>
0389: *
0390: * <p>The first of the aforementioned steps which returns a non-<code>null</code>
0391: * cookie and does not throw an exception determines the return value. If
0392: * none of the steps succeeds, <code>null</code> is returned.</p>
0393: *
0394: * @param dob a <code>DataObject</code> to test
0395: * @return the cookie for the <code>DataObject</code> or <code>null</code>
0396: * if it should not be used
0397: */
0398: protected InstanceCookie acceptDataObject(DataObject dob) {
0399: int acceptType = -1;
0400:
0401: InstanceCookie cookie;
0402: //Order of checking reversed first check cookie and then folder
0403: // test if we accept the instance
0404: cookie = dob.getCookie(InstanceCookie.class);
0405: try {
0406: cookie = cookie == null ? null : acceptCookie(cookie);
0407: acceptType = 1;
0408: } catch (IOException ex) {
0409: // an error during a call to acceptCookie
0410: err.log(Level.WARNING, null, ex);
0411: cookie = null;
0412: } catch (ClassNotFoundException ex) {
0413: // an error during a call to acceptCookie
0414: err.log(Level.WARNING, null, ex);
0415: cookie = null;
0416: }
0417:
0418: if (cookie == null) {
0419: DataFolder folder = dob.getCookie(DataFolder.class);
0420: if (folder != null) {
0421: HoldInstance previous = map
0422: .get(folder.getPrimaryFile());
0423: if (previous != null && previous.cookie != null) {
0424: // the old cookie will be returned if the folder is already registered
0425: cookie = previous;
0426: acceptType = 2;
0427: } else {
0428: cookie = acceptFolder(folder);
0429: acceptType = 3;
0430: }
0431: }
0432: }
0433:
0434: if (cookie == null) {
0435: // try also the container
0436: DataObject.Container c = dob
0437: .getCookie(DataObject.Container.class);
0438: if (c != null) {
0439: cookie = acceptContainer(c);
0440: acceptType = 4;
0441: }
0442: }
0443:
0444: if (err.isLoggable(Level.FINE)) {
0445: err.fine("acceptDataObject: " + dob + " cookie: " + cookie
0446: + " acceptType: " + acceptType); // NOI18N
0447: }
0448:
0449: return cookie;
0450: }
0451:
0452: /** Allows subclasses to decide whether they want to work with
0453: * the specified <code>InstanceCookie</code> or not.
0454: * <p>The default implementation simply
0455: * returns the same cookie, but subclasses may
0456: * decide to return <code>null</code> or a different cookie.
0457: * </p>
0458: * <p>Compare {@link #acceptDataObject(DataObject)} to learn when this method
0459: * is called.</p>
0460: *
0461: * @param cookie the instance cookie to test
0462: * @return the cookie to use or <code>null</code> if this cookie should not
0463: * be used
0464: * @exception IOException if an I/O error occurred calling a cookie method
0465: * @exception ClassNotFoundException if a class is not found in a call to a cookie method
0466: */
0467: protected InstanceCookie acceptCookie(InstanceCookie cookie)
0468: throws java.io.IOException, ClassNotFoundException {
0469: return cookie;
0470: }
0471:
0472: /** Allows subclasses to decide how they want to work with a
0473: * provided folder.
0474: *
0475: * <p>The default implementation simply calls {@link #acceptContainer(DataObject.Container)}.</p>
0476: *
0477: * <p>A common override of this method is to return a new
0478: * <code>FolderInstance</code> based on the subfolder, permitting
0479: * recursion.</p>
0480: *
0481: * <p>Compare {@link #acceptDataObject(DataObject)} to learn when this method
0482: * is called.</p>
0483: *
0484: * @param df data folder to create cookie for
0485: * @return the cookie for this folder or <code>null</code> if this folder should not
0486: * be used
0487: */
0488: protected InstanceCookie acceptFolder(DataFolder df) {
0489: return acceptContainer(df);
0490: }
0491:
0492: /** Allows subclasses to decide how they want to work with an object
0493: * that implements a DataObject.Container.
0494: *
0495: * <p>By default this returns <code>null</code> to indicated that subfolders
0496: * (as well as {@link DataShadow}s, etc.) should be ignored.</p>
0497: *
0498: * <p>A common override of this method is to return a new
0499: * <code>FolderInstance</code> based on the subfolder, permitting
0500: * recursion.</p>
0501: *
0502: * <p>Compare {@link #acceptDataObject(DataObject)} to learn when this method
0503: * is called.</p>
0504: *
0505: * @param container the container to accept or not
0506: * @return cookie for this container or <code>null</code> if this object should
0507: * be ignored
0508: *
0509: * @since 1.11
0510: */
0511: protected InstanceCookie acceptContainer(
0512: DataObject.Container container) {
0513: return null;
0514: }
0515:
0516: /* ----------------------------------------------------------------------------- */
0517: /* -- Instances creation method (protected, must be overridden by sub-classes) - */
0518: /* ----------------------------------------------------------------------------- */
0519:
0520: /** Notifies subclasses that the set of cookies for this folder
0521: * has changed.
0522: * A new object representing the folder should
0523: * be created (or the old one updated).
0524: * Called both upon initialization of the class, and change of its cookies.
0525: *
0526: * <p>It may be poor style for this method to have side-effects. A
0527: * common way to use <code>FolderInstance</code> is to have this
0528: * method set some global state which is then used as the resulting
0529: * instance. Better is to treat the <code>FolderInstance</code> as
0530: * pure SPI and assign it to a variable of type
0531: * <code>InstanceCookie</code>. Then use the {@link
0532: * #instanceCreate} method to get the final result. However in some
0533: * cases there is a singleton live object which must be updated
0534: * in-place, and it only makes sense to do so here (in which case
0535: * the <code>InstanceCookie</code> methods are unused).</p>
0536: *
0537: * @param cookies updated array of instance cookies for the folder
0538: * @return object to represent these cookies
0539: *
0540: * @exception IOException an I/O error occured
0541: * @exception ClassNotFoundException a class has not been found
0542: */
0543: protected abstract Object createInstance(InstanceCookie[] cookies)
0544: throws java.io.IOException, ClassNotFoundException;
0545:
0546: /* ----------------------------------------------------------------------------- */
0547: /* -- Instances creation method (protected, may be overridden by sub-classes) - */
0548: /* ----------------------------------------------------------------------------- */
0549:
0550: /** Method that is called when a the folder instance really wants to
0551: * create an object from provided cookie.
0552: * It allows subclasses to overwrite the default behaviour (which is
0553: * to call {@link InstanceCookie#instanceCreate}).
0554: *
0555: * @param obj the data object that is the source of the cookie
0556: * @param cookie the instance cookie to read the instance from
0557: * @exception IOException when there I/O error
0558: * @exception ClassNotFoundException if the class cannot be found
0559: */
0560: protected Object instanceForCookie(DataObject obj,
0561: InstanceCookie cookie) throws IOException,
0562: ClassNotFoundException {
0563: return cookie.instanceCreate();
0564: }
0565:
0566: /* ----------------------------------------------------------------------------- */
0567: /* -- Recreation --------------------------------------------------------------- */
0568: /* ----------------------------------------------------------------------------- */
0569:
0570: /** Starts recreation of the instance in special thread.
0571: */
0572: public synchronized void recreate() {
0573: // this method should be synchronized so the recognizingTask is created
0574: // together with notification that we are running.
0575: // Fix of #16136 => sometimes it happened that the thread started in
0576: // the recognizer task was finished sooner then notifyRunning called.
0577: // In such case notifyFinished could be called before notifyRunning
0578: // and everything was completely broken
0579: err.fine("recreate");
0580: recognizingTask = computeChildrenList(container, listener);
0581: if (err.isLoggable(Level.FINE)) {
0582: err.fine(" recognizing task is now " + recognizingTask);
0583: }
0584: notifyRunning();
0585: }
0586:
0587: /** Checks whether recreation of this instance is running already
0588: * and in that case does nothing, otherwise calls
0589: * {@link #recreate() recreate} method.
0590: * This prevents from redundant recreation tasks of this instance caused by
0591: * first creation of underlying items which are also of {@link org.openide.util.Task Task}
0592: * type (e.g. sub-FolderInstances, sub-FolderLookups etc.). */
0593: final void checkRecreate() {
0594: if (isFinished()) {
0595: recreate();
0596: }
0597: }
0598:
0599: /** Checks whether recreation has already started and starts it if if was
0600: * was not yet started during the live of this <code>FolderInstance</code>.
0601: * @return the latest started task for children computation */
0602: private final synchronized Task checkRecognizingStarted() {
0603: if (recognizingTask == null) {
0604: recreate();
0605: }
0606:
0607: return recognizingTask;
0608: }
0609:
0610: /* ----------------------------------------------------------------------------- */
0611: /* -- Static helper methods (abstract away the differences between different --- */
0612: /* ------------------------- DataObject.Container types) ----------------------- */
0613: /* ----------------------------------------------------------------------------- */
0614:
0615: /** Waits until the task to compute the children of the currents folder is
0616: * finished. This methods provides a unified interface which allows to
0617: * treat <code>FolderList</code>s and general <code>DataObject.Container</code>s
0618: * is a uniform way.
0619: */
0620: private static void waitProcessingFinished(DataObject.Container c) {
0621: if (c instanceof FolderList) {
0622: ((FolderList) c).waitProcessingFinished();
0623: }
0624: }
0625:
0626: /** Starts and returns the task to compute the children of the current
0627: * folder. This methods provides a unified interface which allows to
0628: * treat <code>FolderList</code>s and general <code>DataObject.Container</code>s
0629: * is a uniform way.
0630: *
0631: * <p>The task returned uses the {@link #listener} to process the children.</p>
0632: */
0633: private static Task computeChildrenList(
0634: final DataObject.Container container,
0635: final FolderListListener listener) {
0636: if (container instanceof FolderList) {
0637: FolderList list = (FolderList) container;
0638: return list.computeChildrenList(listener);
0639: }
0640:
0641: // otherwise we have to simulate the listener by container methods
0642: // itself
0643: return PROCESSOR.post(new Runnable() {
0644: public void run() {
0645: DataObject[] arr = container.getChildren();
0646: ArrayList<DataObject> list = new ArrayList<DataObject>(
0647: arr.length);
0648: for (int i = 0; i < arr.length; i++) {
0649: listener.process(arr[i], list);
0650: }
0651: listener.finished(list);
0652: }
0653: });
0654: }
0655:
0656: /* ----------------------------------------------------------------------------- */
0657: /* -- Processing --------------------------------------------------------------- */
0658: /* ----------------------------------------------------------------------------- */
0659:
0660: /** A method that starts <code>creationTask</code>, the task which really
0661: * creates the instances from given objects. The task is started by a
0662: * call to {@link #postCreationTask(Runnable)}.
0663: *
0664: * @param arr collection of DataObjects
0665: */
0666: final void processObjects(final Collection<DataObject> arr) {
0667: creationTask = postCreationTask(new Runnable() {
0668: public void run() {
0669: defaultProcessObjects(arr);
0670: }
0671: });
0672: }
0673:
0674: /** Default processing of objects.
0675: * @param arr array of objects to process
0676: */
0677: private final void defaultProcessObjects(Collection<DataObject> arr) {
0678: err.fine("defaultProcessObjects");
0679: HashSet<FileObject> toRemove;
0680: ArrayList<HoldInstance> cookies = new ArrayList<HoldInstance>();
0681:
0682: // synchronized for safe access to map field
0683: synchronized (CURRENT) {
0684: toRemove = new HashSet<FileObject>(map.keySet());
0685: }
0686:
0687: for (DataObject obj : arr) {
0688: if (!obj.isValid()) {
0689: // #12960: skip over it, probably invalidated while we were
0690: // waiting for this task to be run...
0691: continue;
0692: }
0693:
0694: // testing
0695: InstanceCookie cookie = acceptDataObject(obj);
0696: if (cookie != null) {
0697: // cookie accepted
0698: FileObject fo = obj.getPrimaryFile();
0699:
0700: boolean attachListener = true;
0701: HoldInstance prevCookie = null;
0702: if (toRemove.remove(fo)) {
0703: // if the fo is in the map than try to find its cookie
0704: prevCookie = map.get(fo);
0705: if (prevCookie != null
0706: && (prevCookie.cookie == null || !prevCookie.cookie
0707: .equals(cookie))) {
0708: prevCookie = null;
0709: // #49199 - do not add second listener
0710: attachListener = false;
0711: }
0712: }
0713:
0714: if (prevCookie == null) {
0715: // such cookie is not there yet
0716: HoldInstance hold;
0717:
0718: if (cookie instanceof HoldInstance) {
0719: hold = (HoldInstance) cookie;
0720: } else {
0721: hold = new HoldInstance(obj, cookie);
0722: }
0723:
0724: // synchronized for safe access to map field
0725: synchronized (CURRENT) {
0726: map.put(fo, hold);
0727: }
0728:
0729: // register for changes of PROP_COOKIE property
0730: if (attachListener) {
0731: obj
0732: .addPropertyChangeListener(org.openide.util.WeakListeners
0733: .propertyChange(listener, obj));
0734: }
0735:
0736: cookies.add(hold);
0737: } else {
0738: // old cookie, already there => only add it to the list of cookies
0739: cookies.add(prevCookie);
0740: }
0741: } else {
0742: // empty instance placeholder
0743: synchronized (CURRENT) {
0744: FileObject fo = obj.getPrimaryFile();
0745: toRemove.remove(fo);
0746:
0747: HoldInstance hold = map.get(fo);
0748: if (hold != null && hold.cookie == null) {
0749: // already registered do not do any changes
0750: continue;
0751: }
0752:
0753: // not yet registered, add new
0754:
0755: hold = new HoldInstance(obj, null);
0756:
0757: map.put(fo, hold);
0758: }
0759:
0760: // register for changes of PROP_COOKIE property
0761: obj
0762: .addPropertyChangeListener(org.openide.util.WeakListeners
0763: .propertyChange(listener, obj));
0764:
0765: }
0766:
0767: }
0768:
0769: // synchronized for safe access to map field
0770: synchronized (CURRENT) {
0771: // now remove the cookies that are no longer in the folder
0772: map.keySet().removeAll(toRemove);
0773: }
0774:
0775: // create the list of cookies
0776: HoldInstance[] all = new HoldInstance[cookies.size()];
0777: cookies.toArray(all);
0778:
0779: updateWaitFor(all);
0780:
0781: Object result = null;
0782: try {
0783: result = createInstance(all);
0784: } catch (IOException ex) {
0785: result = ex;
0786: } catch (ClassNotFoundException ex) {
0787: result = ex;
0788: } finally {
0789: if (err.isLoggable(Level.FINE)) {
0790: err.fine("notifying finished"); // NOI18N
0791: for (int log = 0; log < all.length; log++) {
0792: err.fine(" #" + log + ": " + all[log]); // NOI18N
0793: }
0794: }
0795: object = result;
0796:
0797: Object prevResult = CURRENT.get();
0798: CURRENT.set(result);
0799: Object prevLast = LAST_CURRENT.get();
0800: LAST_CURRENT.set(this );
0801:
0802: try {
0803: notifyFinished();
0804: } finally {
0805: CURRENT.set(prevResult);
0806: LAST_CURRENT.set(prevLast);
0807: }
0808: }
0809: }
0810:
0811: /** Recomputes the list of tasks we should wait for (i.e. the tasks associated
0812: * with the children of the folder).
0813: */
0814: private void updateWaitFor(HoldInstance[] arr) {
0815: ArrayList<Task> out = new ArrayList<Task>(arr.length);
0816: for (int i = 0; i < arr.length; i++) {
0817: Task t = arr[i].getTask();
0818: if (t != null) {
0819: out.add(t);
0820: }
0821: }
0822: waitFor = out.toArray(new Task[out.size()]);
0823: }
0824:
0825: /* ----------------------------------------------------------------------------- */
0826: /* -- Processing: Start the creation task (protected, may be overridden) ------- */
0827: /* ----------------------------------------------------------------------------- */
0828:
0829: /** Invokes the creation of objects in a "safe" thread. This method is
0830: * for expert subclasses that want to control the thread that the
0831: * instance is created in.
0832: *
0833: * <p>The default implementation invokes the creation logic in the
0834: * request processor in non-blocking mode (no other tasks will
0835: * block on this).</p>
0836: *
0837: * @param run runnable to run
0838: * @return task to control the execution of the runnable or null if
0839: * the runnable is run immediatelly
0840: * @since 1.5
0841: */
0842: protected Task postCreationTask(Runnable run) {
0843: return PROCESSOR.post(run);
0844: }
0845:
0846: /* ----------------------------------------------------------------------------- */
0847: /* -- Getters ------------------------------------------------------------------ */
0848: /* ----------------------------------------------------------------------------- */
0849:
0850: /** Access to error manager for FolderLookup.
0851: */
0852: final Logger err() {
0853: return err;
0854: }
0855:
0856: public @Override
0857: String toString() {
0858: return getClass().getName() + "@"
0859: + Integer.toHexString(System.identityHashCode(this ))
0860: + "(" + this .container + ")"; // NOI18N
0861: }
0862:
0863: /* -------------------------------------------------------------------- */
0864: /* -- Inner class Listener -------------------------------------------- */
0865: /* -------------------------------------------------------------------- */
0866:
0867: /** Listener on change of folder's children and a starter for the task.
0868: *
0869: * <p>Each instance of {@link FolderInstance} has one instance of this class
0870: * associated with it. The latter serves for three purposes:</p>
0871: *
0872: * <ol>
0873: * <li>to listen for property changes of the {@link DataObject.Container}
0874: * this {@link FolderInstance} was created for (by implementing
0875: * {@link java.beans.PropertyChangeListener})</li>
0876: * <li>to listen for changes of the cookies of the children of this folder</li>
0877: * <li>to process the results of the computation of a child list
0878: * (by implementing {@link FolderListListener#finished(java.util.List)})
0879: * </li>
0880: * </ol>
0881: */
0882: private class Listener implements PropertyChangeListener,
0883: FolderListListener {
0884:
0885: Listener() {
0886: }
0887:
0888: /** Recreates the {@link FolderInstance} if the children list of
0889: * its container was changed.
0890: *
0891: * <p>Additionally ...</p>
0892: */
0893: public void propertyChange(PropertyChangeEvent ev) {
0894: Object s = ev.getSource();
0895: if (s == container) {
0896: if (DataObject.Container.PROP_CHILDREN.equals(ev
0897: .getPropertyName())) {
0898: err.fine("PROP_CHILDREN");
0899:
0900: recreate();
0901: }
0902: return;
0903: }
0904:
0905: if (DataObject.PROP_NAME.equals(ev.getPropertyName())) {
0906: if (s instanceof DataObject) {
0907: err.fine("PROP_NAME");
0908: recreate();
0909: }
0910: }
0911:
0912: // change of cookie in one of children of the container
0913:
0914: if (DataObject.PROP_COOKIE.equals(ev.getPropertyName())) {
0915: if (s instanceof DataObject) {
0916: DataObject source = (DataObject) s;
0917: err.fine("PROP_COOKIE: " + source); // NOI18N
0918:
0919: InstanceCookie ic = acceptDataObject(source);
0920:
0921: HoldInstance hi;
0922: FileObject fo = source.getPrimaryFile();
0923: synchronized (CURRENT) {
0924: hi = map.get(fo);
0925: }
0926:
0927: if (hi != null) {
0928: err.fine("previous instance: " + hi
0929: + " new instance " + ic); // NOI18N
0930: /* Recreate if the new instance cookie is null or differs
0931: * from the previous one.
0932: * When the default implementation of acceptDataObject is
0933: * used ic == hi is the case if source is a folder.
0934: * [XXX] Why not:
0935: * if ((ic == null && hi.cookie != null) || (ic != hi && !ic.equals (hi.cookie))) {
0936: */
0937: if (ic == null
0938: || (ic != hi && !ic.equals(hi.cookie))) {
0939: hi = new HoldInstance(source, ic);
0940:
0941: // synchronized for safe access to map field
0942: synchronized (CURRENT) {
0943: map.put(fo, hi);
0944: }
0945: recreate();
0946: }
0947: }
0948: }
0949: }
0950: }
0951:
0952: /** Callback for object processing after all children are computed.
0953: * This implementation starts a new task for the creation of the
0954: * child objects.
0955: * @param arr list of DataObjects
0956: */
0957: public void finished(java.util.List<DataObject> arr) {
0958: processObjects(arr);
0959: }
0960:
0961: /** Default implementation without filtering.
0962: * @param obj the object recognized
0963: * @param arr array where the implementation should add the
0964: * object
0965: */
0966: public void process(DataObject obj,
0967: java.util.List<DataObject> arr) {
0968: arr.add(obj);
0969: }
0970:
0971: }
0972:
0973: /* -------------------------------------------------------------------- */
0974: /* -- Inner class HoldInstance ---------------------------------------- */
0975: /* -------------------------------------------------------------------- */
0976:
0977: /** A instance cookie that holds the result of first
0978: * invocation of the provided cookie.
0979: *
0980: */
0981: private class HoldInstance extends Object implements
0982: InstanceCookie.Of, TaskListener {
0983: /** the data object -> source of this instance */
0984: private final DataObject source;
0985: /** the cookie to delegate to */
0986: protected final InstanceCookie cookie;
0987:
0988: public HoldInstance(DataObject source, InstanceCookie cookie) {
0989: this .cookie = cookie;
0990: this .source = source;
0991:
0992: if (cookie instanceof Task) {
0993: // for example FolderInstance ;-) attach itself for changes
0994: // in the cookie
0995: Task t = (Task) cookie;
0996: t.addTaskListener(WeakListeners.create(
0997: TaskListener.class, this , t));
0998: }
0999: }
1000:
1001: /** Full name of the data folder's primary file separated by dots.
1002: * @return the name
1003: */
1004: public String instanceName() {
1005: return cookie.instanceName();
1006: }
1007:
1008: /** Query to find out if the object created by this cookie is
1009: * instance of given type. The same code as:
1010: * <pre>
1011: * Class actualClass = instanceClass ();
1012: * result = type.isAsignableFrom (actualClass);
1013: * </pre>
1014: * But this can prevent the class <code>actualClass</code> to be
1015: * loaded into the <em>JavaVM</em>.
1016: *
1017: * @param type the class type we want to check
1018: * @return true if this cookie can produce object of given type
1019: */
1020: public boolean instanceOf(Class<?> type) {
1021: if (cookie instanceof InstanceCookie.Of) {
1022: InstanceCookie.Of of = (InstanceCookie.Of) cookie;
1023: return of.instanceOf(type);
1024: }
1025: // delegate
1026: try {
1027: Class<?> clazz = cookie.instanceClass();
1028: return type.isAssignableFrom(clazz);
1029: } catch (IOException ex) {
1030: return false;
1031: } catch (ClassNotFoundException ex) {
1032: return false;
1033: }
1034: }
1035:
1036: /** Returns the root class of all objects.
1037: * Supposed to be overriden in subclasses.
1038: *
1039: * @return Object.class
1040: * @exception IOException an I/O error occured
1041: * @exception ClassNotFoundException the class has not been found
1042: */
1043: public Class instanceClass() throws java.io.IOException,
1044: ClassNotFoundException {
1045: return cookie.instanceClass();
1046: }
1047:
1048: /**
1049: * @return an object to work with
1050: * @exception IOException an I/O error occured
1051: * @exception ClassNotFoundException the class has not been found
1052: */
1053: public Object instanceCreate() throws java.io.IOException,
1054: ClassNotFoundException {
1055: return instanceForCookie(source, cookie);
1056: }
1057:
1058: /** Called when a task finishes running.
1059: * @param task the finished task
1060: */
1061: public void taskFinished(Task task) {
1062: checkRecreate();
1063: }
1064:
1065: /** Waits till the instance is ready.
1066: */
1067: public Task getTask() {
1068: if (cookie instanceof Task) {
1069: // for example FolderInstance ;-) attach itself for changes
1070: // in the cookie
1071: return (Task) cookie;
1072: } else {
1073: return null;
1074: }
1075: }
1076: } // end of HoldInstance
1077:
1078: }
|