| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.openide.loaders; |
| |
| |
| import java.lang.ref.*; |
| import java.util.*; |
| import java.util.logging.*; |
| import javax.swing.event.ChangeListener; |
| import org.openide.filesystems.*; |
| import org.openide.util.*; |
| |
| /** Registration of all data objects in the system. |
| * Maps data objects to its handlers. |
| * |
| * @author Jaroslav Tulach |
| */ |
| final class DataObjectPool extends Object |
| implements ChangeListener { |
| /** set to null if the constructor is called from somewhere else than DataObject.find |
| * Otherwise contains items that have just been created in this thread and |
| * shall be notified. |
| */ |
| private static final ThreadLocal<Collection<Item>> FIND = new ThreadLocal<Collection<Item>>(); |
| |
| /** validator */ |
| private static final Validator VALIDATOR = new Validator (); |
| |
| private static final Collection<Item> TOKEN = Collections.unmodifiableList(new ArrayList<Item>()); |
| |
| /** assignes file objects a unique instance of Item, if it has been created |
| */ |
| private DoubleHashMap map = new DoubleHashMap(); |
| /** just for testing purposes |
| */ |
| static final void fastCache(boolean fast) { |
| if (fast) { |
| POOL.children = null; |
| } else { |
| POOL.children = new HashMap<FileObject, List<Item>>(); |
| } |
| } |
| /** map that assigns to each folder list of Items created for its children */ |
| private Map<FileObject,List<Item>> children = new HashMap<FileObject, List<Item>>(); |
| |
| /** covers all FileSystems we're listening on */ |
| private final Set<FileSystem> knownFileSystems = new WeakSet<FileSystem>(); |
| |
| /** error manager to log what is happening here */ |
| private static final Logger err = Logger.getLogger("org.openide.loaders.DataObject.find"); // NOI18N |
| |
| /** the pool for all objects. Use getPOOL method instead of direct referencing |
| * this field. |
| */ |
| private static DataObjectPool POOL; |
| |
| /** Lock for creating POOL instance */ |
| private static final Object lockPOOL = new Object(); |
| |
| /** check to know if someone is waiting in waitNotified, changed from |
| * inside synchronized block, but read without synchronization, that is |
| * why it is made volatile |
| */ |
| private volatile long inWaitNotified = -1; |
| |
| /** Get the instance of DataObjectPool - value of static field 'POOL'. |
| * Initialize the field if necessary. |
| * |
| * @return The DataObjectPool. |
| */ |
| static DataObjectPool getPOOL() { |
| synchronized (lockPOOL) { |
| if (POOL != null) |
| return POOL; |
| POOL = new DataObjectPool (); |
| } |
| |
| lp.addChangeListener(POOL); |
| |
| return POOL; |
| } |
| |
| /** Allows DataObject constructors to be called. |
| * @return a key to pass to exitAllowConstructor |
| */ |
| private static Collection<Item> enterAllowConstructor() { |
| Collection<Item> prev = FIND.get(); |
| FIND.set (TOKEN); |
| return prev; |
| } |
| |
| /** Disallows DataObject constructors to be called and notifies |
| * all created DataObjects. |
| */ |
| private static void exitAllowConstructor(Collection<Item> previous) { |
| Collection<Item> l = FIND.get (); |
| FIND.set (previous); |
| if (l != TOKEN) getPOOL ().notifyCreationAll(l); |
| } |
| |
| /** Method to check whether the constructor is allowed. |
| */ |
| final static boolean isConstructorAllowed() { |
| return FIND.get() != null; |
| } |
| |
| /** Calls into one loader. Setups security condition to allow DataObject ocnstructor |
| * to succeed. |
| */ |
| public static DataObject handleFindDataObject (DataLoader loader, FileObject fo, DataLoader.RecognizedFiles rec) |
| throws java.io.IOException { |
| DataObject ret; |
| |
| Collection<Item> prev = enterAllowConstructor(); |
| try { |
| // make sure this thread is allowed to recognize |
| getPOOL ().enterRecognition(fo); |
| |
| ret = loader.handleFindDataObject (fo, rec); |
| } finally { |
| exitAllowConstructor (prev); |
| } |
| |
| return ret; |
| } |
| |
| /** Calls into one loader. Setups security condition to allow DataObject ocnstructor |
| * to succeed. |
| */ |
| public static DataObject handleFindDataObject (DataObject.Factory factory, FileObject fo, Set<? super FileObject> rec) |
| throws java.io.IOException { |
| DataObject ret; |
| |
| Collection<Item> prev = enterAllowConstructor(); |
| try { |
| // make sure this thread is allowed to recognize |
| getPOOL ().enterRecognition(fo); |
| |
| ret = factory.findDataObject (fo, rec); |
| } finally { |
| exitAllowConstructor (prev); |
| } |
| |
| return ret; |
| } |
| |
| /** Creates and finishes registration of MultiDataObject. |
| */ |
| public static MultiDataObject createMultiObject (MultiFileLoader loader, FileObject fo) |
| throws java.io.IOException { |
| MultiDataObject ret; |
| |
| Collection<Item> prev = enterAllowConstructor(); |
| try { |
| ret = loader.createMultiObject (fo); |
| } finally { |
| exitAllowConstructor (prev); |
| } |
| |
| return ret; |
| } |
| |
| /** Calls into FolderLoader. Setups security condition to allow DataObject constructor |
| * to succeed. |
| */ |
| public static MultiDataObject createMultiObject(DataLoaderPool.FolderLoader loader, FileObject fo, DataFolder original) throws java.io.IOException { |
| MultiDataObject ret; |
| |
| Collection<Item> prev = enterAllowConstructor(); |
| try { |
| ret = loader.createMultiObject (fo, original); |
| } finally { |
| exitAllowConstructor (prev); |
| } |
| |
| return ret; |
| } |
| |
| |
| |
| /** Executes atomic action with privilege to create DataObjects. |
| */ |
| public void runAtomicActionSimple (FileObject fo, FileSystem.AtomicAction action) |
| throws java.io.IOException { |
| Collection<Item> prev = enterAllowConstructor(); |
| try { |
| fo.getFileSystem ().runAtomicAction(action); |
| } finally { |
| exitAllowConstructor (prev); |
| } |
| } |
| |
| // |
| // Support for running really atomic actions |
| // |
| private Thread atomic; |
| private RequestProcessor privileged; |
| /** the folder that is being modified */ |
| private FileObject blocked; |
| public void runAtomicAction (final FileObject target, final FileSystem.AtomicAction action) |
| throws java.io.IOException { |
| |
| class WrapAtomicAction implements FileSystem.AtomicAction { |
| public void run () throws java.io.IOException { |
| Thread prev; |
| FileObject prevBlocked; |
| synchronized (DataObjectPool.this) { |
| // make sure that we are the ones that own |
| // the recognition process |
| enterRecognition (null); |
| prev = atomic; |
| prevBlocked = blocked; |
| atomic = Thread.currentThread (); |
| blocked = target; |
| } |
| |
| Collection<Item> findPrev = enterAllowConstructor(); |
| try { |
| action.run (); |
| } finally { |
| synchronized (DataObjectPool.this) { |
| atomic = prev; |
| blocked = prevBlocked; |
| DataObjectPool.this.notifyAll (); |
| } |
| exitAllowConstructor (findPrev); |
| } |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) return false; |
| return action.equals(obj) || obj.equals(action); |
| } |
| |
| @Override |
| public int hashCode() { |
| return action.hashCode(); |
| } |
| |
| |
| } // end of WrapAtomicAction |
| |
| target.getFileSystem ().runAtomicAction(new WrapAtomicAction ()); |
| } |
| |
| /** The thread that runs in atomic action wants to delegate its privilege |
| * to somebody else. Used in DataFolder.getChildren that blocks on |
| * Folder Recognizer thread. |
| * |
| * @param delegate the privileged processor |
| */ |
| public synchronized void enterPrivilegedProcessor(RequestProcessor delegate) { |
| if (atomic == Thread.currentThread()) { |
| if (privileged != null) throw new IllegalStateException ("Previous privileged is not null: " + privileged + " now: " + delegate); // NOI18N |
| privileged = delegate; |
| } |
| // wakeup everyone in enterRecognition, as this changes the conditions there |
| notifyAll (); |
| } |
| |
| /** Exits the privileged processor. |
| */ |
| public synchronized void exitPrivilegedProcessor(RequestProcessor delegate) { |
| if (atomic == Thread.currentThread ()) { |
| if (privileged != delegate) throw new IllegalStateException ("Trying to unregister wrong privileged. Prev: " + privileged + " now: " + delegate); // NOI18N |
| privileged = null; |
| } |
| // wakeup everyone in enterRecognition, as this changes the conditions there |
| notifyAll (); |
| } |
| |
| /** Ensures it is safe to enter the recognition. |
| * @param fo file object we want to recognize or null if we do not know it |
| */ |
| private synchronized void enterRecognition (FileObject fo) { |
| // wait till nobody else stops the recognition |
| for (;;) { |
| if (atomic == null) { |
| // ok, I am the one who can enter |
| break; |
| } |
| if (atomic == Thread.currentThread()) { |
| // ok, reentering again |
| break; |
| } |
| |
| if (privileged != null && privileged.isRequestProcessorThread()) { |
| // ok, we have privileged request processor thread |
| break; |
| } |
| |
| if (fo != null && blocked != null && !blocked.equals (fo.getParent ())) { |
| // access to a file in different folder than it is blocked |
| // => go on |
| break; |
| } |
| |
| if (err.isLoggable(Level.FINE)) { |
| err.fine("Enter recognition block: " + Thread.currentThread()); // NOI18N |
| err.fine(" waiting for: " + fo); // NOI18N |
| err.fine(" blocking thread: " + atomic); // NOI18N |
| err.fine(" blocked on: " + blocked); // NOI18N |
| } |
| try { |
| if (FolderList.isFolderRecognizerThread()) { |
| inWaitNotified = System.currentTimeMillis(); |
| } |
| wait (); |
| } catch (InterruptedException ex) { |
| // means nothing, go on |
| } finally { |
| if (FolderList.isFolderRecognizerThread()) { |
| inWaitNotified = -1; |
| } |
| } |
| } |
| } |
| |
| /** Collection of all objects that has been created but their |
| * creation has not been yet notified to OperationListener.postCreate |
| * method. |
| */ |
| private Set<Item> toNotify = new HashSet<Item>(); |
| |
| /** Constructor. |
| */ |
| private DataObjectPool () { |
| } |
| |
| /** Checks whether there is a data object with primary file |
| * passed thru the parameter. |
| * |
| * @param fo the file to check |
| * @return data object with fo as primary file or null |
| */ |
| public DataObject find (FileObject fo) { |
| synchronized (this) { |
| Item doh = map.get(fo); |
| if (doh == null || !fo.isValid()) { |
| return null; |
| } |
| |
| // do not return DOs before their creation were notified to OperationListeners |
| if (toNotify.contains (doh)) { |
| // special test for data objects calling this method from |
| // their own constructor, those are ok to be returned if |
| // they exist |
| Collection<Item> l = FIND.get(); |
| if (l == null || !l.contains (doh)) { |
| return null; |
| } |
| } |
| |
| return doh.getDataObjectOrNull (); |
| } |
| } |
| |
| /** mapping of files to registration count */ |
| private final Map<FileObject,Integer> registrationCounts = new WeakHashMap<FileObject,Integer>(); |
| |
| void countRegistration(FileObject fo) { |
| Integer i = registrationCounts.get(fo); |
| Integer i2; |
| if (i == null) { |
| i2 = 0; |
| } else { |
| i2 = i + 1; |
| } |
| registrationCounts.put(fo, i2); |
| } |
| |
| /** For use from FolderChildren. @see "#20699" */ |
| int registrationCount(FileObject fo) { |
| Integer i = registrationCounts.get(fo); |
| if (i == null) { |
| return 0; |
| } else { |
| return i; |
| } |
| } |
| |
| /** Refresh of all folders. |
| */ |
| private void refreshAllFolders () { |
| Set<FileObject> files; |
| synchronized (this) { |
| files = new HashSet<FileObject>(map.keySet()); |
| } |
| |
| for (FileObject fo : files) { |
| if (fo.isFolder ()) { |
| DataObject obj = find (fo); |
| if (obj instanceof DataFolder) { |
| DataFolder df = (DataFolder)obj; |
| FileObject file = df.getPrimaryFile (); |
| synchronized (this) { |
| if (toNotify.isEmpty() || !toNotify.contains(map.get(file))) { |
| FolderList.changedDataSystem (file); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** Rescans all fileobjects in given set. |
| * @param s mutable set of FileObjects |
| * @return set of DataObjects that refused to be revalidated |
| */ |
| public Set<DataObject> revalidate (Set<FileObject> s) { |
| return VALIDATOR.revalidate (s); |
| } |
| |
| /** Rescan all primary files of currently existing data |
| * objects. |
| * |
| * @return set of DataObjects that refused to be revalidated |
| */ |
| public Set<DataObject> revalidate () { |
| Set<Item> set; |
| synchronized (this) { |
| // copy the values synchronously |
| set = new HashSet<Item>(map.values()); |
| } |
| return revalidate(createSetOfAllFiles(set)); |
| } |
| |
| /** Notifies that an object has been created. |
| * @param obj the object that was created |
| */ |
| public void notifyCreation (DataObject obj) { |
| notifyCreation (obj.item()); |
| } |
| |
| private static final DataLoaderPool lp = DataLoaderPool.getDefault(); |
| |
| /** Notifies the creation of an item*/ |
| private void notifyCreation (Item item) { |
| synchronized (this) { |
| if (err.isLoggable(Level.FINE)) { |
| err.fine("Notify created: " + item + " by " + Thread.currentThread()); // NOI18N |
| } |
| |
| if (toNotify.isEmpty()) { |
| if (err.isLoggable(Level.FINE)) { |
| err.fine(" but toNotify is empty"); // NOI18N |
| } |
| return; |
| } |
| |
| if (!toNotify.remove (item)) { |
| if (err.isLoggable(Level.FINE)) { |
| err.fine(" the item is not there: " + toNotify); // NOI18N |
| } |
| return; |
| } |
| |
| // if somebody is caught in waitNotified then wake him up |
| notifyAll (); |
| } |
| |
| DataObject obj = item.getDataObjectOrNull (); |
| if (obj != null) { |
| lp.fireOperationEvent ( |
| new OperationEvent (obj), OperationEvent.CREATE |
| ); |
| } |
| } |
| |
| /** Notifies all objects in the list */ |
| private void notifyCreationAll(Collection<Item> l) { |
| if (l.isEmpty()) return; |
| for (Item i : l) { |
| notifyCreation (i); |
| } |
| } |
| |
| /** Wait till the data object will be notified. But wait limited amount |
| * of time so we will not deadlock |
| * |
| * @param obj data object to check |
| */ |
| public void waitNotified (DataObject obj) { |
| for (;;) { |
| synchronized (this) { |
| try { |
| enterRecognition (obj.getPrimaryFile().getParent()); |
| |
| if (toNotify.isEmpty()) { |
| return; |
| } |
| |
| Collection<Item> l = FIND.get (); |
| final Item item = obj.item(); |
| if (l != null && l.contains (item)) { |
| return; |
| } |
| |
| if (!toNotify.contains (item)) { |
| return; |
| } |
| |
| if (err.isLoggable(Level.FINE)) { |
| err.fine("waitTillNotified: " + Thread.currentThread()); // NOI18N |
| err.fine(" waitingFor: " + obj.getPrimaryFile ().getPath ()); // NOI18N |
| } |
| |
| if (FolderList.isFolderRecognizerThread()) { |
| inWaitNotified = System.currentTimeMillis(); |
| } |
| wait (); |
| } catch (InterruptedException ex) { |
| // never mind |
| } finally { |
| if (FolderList.isFolderRecognizerThread()) { |
| inWaitNotified = -1; |
| } |
| } |
| } |
| } |
| } |
| |
| /** Allows to check whether folder recognizer is in waitNotified method in order |
| * to detect more precisly the condition needed for deadlock #65543 really |
| * happened. |
| * @return the time for how long the folder recognizer is waiting or -1 if it is not |
| */ |
| final long timeInWaitNotified() { |
| long l = inWaitNotified; |
| if (l == -1) { |
| return -1; |
| } else { |
| l = System.currentTimeMillis() - l; |
| if (l < 0) { |
| l = 0; |
| } |
| return l; |
| } |
| } |
| |
| |
| /** Add to list of created objects. |
| */ |
| private void notifyAdd (Item item) { |
| toNotify.add (item); |
| Collection<Item> l = FIND.get (); |
| if (l == TOKEN) FIND.set(l = new ArrayList<Item>()); |
| l.add (item); |
| } |
| |
| private static final Logger LISTENER = Logger.getLogger("org.openide.loaders.DataObjectPool.Listener"); // NOI18N |
| |
| |
| /** Listener used to distribute the File events to their DOs. |
| * [pnejedly] A little bit about its internals/motivation: |
| * Originally, every created DO have hooked its onw listener to the primary |
| * FO's parent folder for listening on primary FO changes. The listener |
| * was enhanced in MDO to also cover secondaries. |
| * Now there is one FSListener per FileSystem which have to distribute |
| * the events to the DOs using limited DOPool's knowledge about FO->DO |
| * mapping. Because the mapping knowledge is limited to primary FOs only, |
| * it have to resort to notifying all known DOs for given folder |
| * if the changed file is not known. Although it is not as good as direct |
| * notification used for known primaries, it is still no worse than |
| * all DOs listening on their folder themselves as it spares at least |
| * the zillions of WeakListener instances. |
| */ |
| private final class FSListener extends FileChangeAdapter { |
| FSListener() {} |
| |
| @Override |
| public void fileChanged(FileEvent fe) { |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine("fileChanged: " + fe); // NOI18N |
| } |
| for (Item item : getTargets(fe, false)) { |
| DataObject dobj = item.getDataObjectOrNull(); |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine(" to: " + dobj); // NOI18N |
| } |
| if (dobj != null) dobj.notifyFileChanged(fe); |
| } |
| } |
| |
| @Override |
| public void fileRenamed (FileRenameEvent fe) { |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine("fileRenamed: " + fe); // NOI18N |
| } |
| for (Item item : getTargets(fe, false)) { |
| DataObject dobj = item.getDataObjectOrNull(); |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine(" to: " + dobj); // NOI18N |
| } |
| if (dobj != null) dobj.notifyFileRenamed(fe); |
| } |
| } |
| |
| @Override |
| public void fileDeleted (FileEvent fe) { |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine("fileDeleted: " + fe); // NOI18N |
| } |
| for (Item item : getTargets(fe, true)) { |
| DataObject dobj = item.getDataObjectOrNull(); |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine(" to: " + dobj); // NOI18N |
| } |
| if (dobj != null) dobj.notifyFileDeleted(fe); |
| } |
| } |
| |
| @Override |
| public void fileDataCreated (FileEvent fe) { |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine("fileDataCreated: " + fe); // NOI18N |
| } |
| for (Item item : getTargets(fe, true)) { |
| DataObject dobj = item.getDataObjectOrNull(); |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine(" to: " + dobj); // NOI18N |
| } |
| if (dobj != null) dobj.notifyFileDataCreated(fe); |
| } |
| ShadowChangeAdapter.checkBrokenDataShadows(fe); |
| } |
| |
| @Override |
| public void fileAttributeChanged (FileAttributeEvent fe) { |
| checkAttributeChanged(fe); |
| } |
| |
| @Override |
| public void fileFolderCreated(FileEvent fe) { |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine("fileFolderCreated: " + fe); // NOI18N |
| } |
| ShadowChangeAdapter.checkBrokenDataShadows(fe); |
| } |
| } |
| |
| static private Collection<Item> getTargets(FileEvent fe, boolean checkSiblings) { |
| FileObject fo = fe.getFile(); |
| // The FileSystem notifying us about the changes should |
| // not hold any lock so we're safe here |
| FileObject[] siblings = null; |
| FileObject parent = null; |
| for (;;) { |
| OUTSIDE: synchronized (DataObjectPool.getPOOL()) { |
| Item itm = DataObjectPool.POOL.map.get(fo); |
| if (itm != null) { // the file was someones' primary |
| return Collections.singleton(itm); // so notify only owner |
| } else { // unknown file or someone secondary |
| List<Item> arr = DataObjectPool.POOL.children.get(fo.getParent()); |
| if (arr != null) { |
| return new ArrayList<Item>(arr); |
| } |
| if (!checkSiblings) { |
| return Collections.emptySet(); |
| } |
| List<Item> toNotify = new LinkedList<Item>(); |
| if (parent == null) { |
| parent = fo.getParent(); |
| } |
| if (parent != null) { // the fo is not root |
| if (siblings == null) { |
| break OUTSIDE; |
| } |
| // notify all in folder |
| for (int i = 0; i < siblings.length; i++) { |
| itm = (Item) DataObjectPool.POOL.map.get(siblings[i]); |
| if (itm == null) { |
| continue; |
| } |
| DataObject obj = itm.getDataObjectOrNull(); |
| if (obj == null) { |
| continue; |
| } |
| toNotify.add(itm); |
| } |
| } |
| return toNotify; |
| } |
| } |
| siblings = parent.getChildren(); |
| } |
| } |
| |
| /** Checks for attribute changes. |
| */ |
| public static void checkAttributeChanged(FileAttributeEvent fe) { |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine("fileAttributeChanged: " + fe); // NOI18N |
| } |
| for (Item item : getTargets(fe, false)) { |
| DataObject dobj = item.getDataObjectOrNull(); |
| if (LISTENER.isLoggable(Level.FINE)) { |
| LISTENER.fine(" to: " + dobj); // NOI18N |
| } |
| if (dobj != null) { |
| dobj.notifyAttributeChanged(fe); |
| } |
| } |
| } |
| |
| /** Registers new DataObject instance. |
| * @param fo primary file for obj |
| * @param loader the loader of the object to be created |
| * |
| * @return object with common information for this <CODE>DataObject</CODE> |
| * @exception DataObjectExistsException if the file object is already registered |
| */ |
| public Item register (FileObject fo, DataLoader loader) throws DataObjectExistsException { |
| if (FIND.get () == null) throw new IllegalStateException ("DataObject constructor can be called only thru DataObject.find - use that method"); // NOI18N |
| |
| // here we're registering a listener on fo's FileSystem so we can deliver |
| // fo changes to DO without lots of tiny listeners on folders |
| // The new DS bound to a repository can simply place a single listener |
| // on its repository instead of registering listeners on FileSystems. |
| try { // to register a listener of fo's FileSystem |
| FileSystem fs = fo.getFileSystem(); |
| synchronized (knownFileSystems) { |
| if (! knownFileSystems.contains(fs)) { |
| fs.addFileChangeListener (new FSListener()); |
| knownFileSystems.add(fs); |
| } |
| } |
| } catch (FileStateInvalidException e ) { |
| // no need to listen then |
| } |
| |
| Item doh; |
| DataObject obj; |
| FileObject parent = fo.getParent(); |
| synchronized (this) { |
| doh = map.get(fo); |
| // if Item for this file has not been created yet |
| if (doh == null) { |
| doh = new Item (fo); |
| map.putWithParent(fo, parent, doh); |
| countRegistration(fo); |
| notifyAdd (doh); |
| |
| VALIDATOR.notifyRegistered (fo); |
| |
| return doh; |
| } |
| |
| obj = doh.getDataObjectOrNull (); |
| |
| if (obj == null) { |
| // the item is to be finalize => create new |
| doh = new Item (fo); |
| map.putWithParent(fo, parent, doh); |
| countRegistration(fo); |
| notifyAdd (doh); |
| |
| return doh; |
| } |
| |
| if (!VALIDATOR.reregister (obj, loader)) { |
| throw new DataObjectExistsException (obj); |
| } |
| } |
| |
| try { |
| obj.setValid (false); |
| synchronized (this) { |
| // check if there isn't any new data object registered |
| // when this thread left synchronization block. |
| Item doh2 = map.get(fo); |
| if (doh2 == null) { |
| doh = new Item (fo); |
| map.putWithParent(fo, parent, doh); |
| countRegistration(fo); |
| notifyAdd (doh); |
| |
| return doh; |
| } |
| } |
| } catch (java.beans.PropertyVetoException ex) { |
| VALIDATOR.refusingObjects.add (obj); |
| } |
| throw new DataObjectExistsException (obj); |
| } |
| |
| /** Notifies all newly created objects to |
| |
| /** Deregister. |
| * @param item the item with common information to deregister |
| * @param refresh true if the parent folder should be refreshed |
| */ |
| private synchronized void deregister (Item item, FileObject fo, FileObject parent, boolean refresh) { |
| Item previous = map.remove(fo); |
| |
| if (previous != null && previous != item) { |
| // ops, mistake, |
| // return back the original |
| map.putWithParent(fo, parent, previous); |
| // Furthermore, item is probably in toNotify by mistake. |
| // Observed in DataFolderTest.testMove: after vetoing the move |
| // of a data folder, the bogus item for the temporary new folder |
| // (e.g. BB/AAA/A1) is left in the toNotify pool forever. This |
| // point is reached; remove it now. -jglick |
| if (toNotify.remove(item)) { |
| notifyAll(); |
| } |
| return; |
| } |
| |
| // refresh of parent folder |
| if (refresh) { |
| fo = fo.getParent (); |
| if (fo != null) { |
| Item item2 = map.get (fo); |
| if (item2 != null) { |
| DataFolder df = (DataFolder) item2.getDataObjectOrNull(); |
| if (df != null) { |
| VALIDATOR.refreshFolderOf (df); |
| } |
| } |
| } |
| } |
| } |
| |
| /** Changes the primary file to new one. |
| * @param item the item to change |
| * @param newFile new primary file to set |
| */ |
| private synchronized Item changePrimaryFile ( |
| Item item, FileObject newFile, FileObject newParent |
| ) { |
| if (item.primaryFile == newFile) { |
| return item; |
| } |
| Item prev = map.remove(item.primaryFile); |
| if (prev == null && item.getDataObjectOrNull() == null) { |
| return item; |
| } |
| assert prev == item : "Item: " + item; |
| final Item ni = new Item(item, newFile); |
| map.putWithParent(newFile, newParent, ni); |
| countRegistration(newFile); |
| return ni; |
| } |
| |
| /** When the loader pool is changed, then all objects are rescanned. |
| */ |
| @Override |
| public void stateChanged (javax.swing.event.ChangeEvent ev) { |
| revalidate(); |
| } |
| |
| /** Create list of all files for given collection of data objects. |
| * @param c collection of DataObjectPool.Item |
| * @return set of files |
| */ |
| private static Set<FileObject> createSetOfAllFiles(Collection<Item> c) { |
| Set<FileObject> set = new HashSet<FileObject>(c.size() * 7); |
| |
| for (Item item : c) { |
| DataObject obj = item.getDataObjectOrNull (); |
| if (obj != null) { |
| getPOOL ().waitNotified (obj); |
| set.addAll (obj.files ()); |
| } |
| } |
| return set; |
| } |
| |
| /** Returns all currently existing data |
| * objects. |
| */ |
| Iterator<Item> getActiveDataObjects () { |
| synchronized (this) { |
| return new ArrayList<Item>(map.values()).iterator(); |
| } |
| } |
| |
| /** One item in object pool. |
| */ |
| static final class Item extends Object { |
| /** initial value of obj field. */ |
| private static final Reference<DataObject> REFERENCE_NOT_SET = new WeakReference<DataObject>(null); |
| |
| /** weak reference data object with this primary file |
| * @GuardedBy("DataObjectPool.getPOOL()") |
| */ |
| private Reference<DataObject> obj; |
| |
| /** immutable primary file */ |
| final FileObject primaryFile; |
| |
| /** @param fo primary file |
| * @param pool object pool |
| */ |
| public Item (FileObject fo) { |
| assert Thread.holdsLock(DataObjectPool.getPOOL()); |
| this.primaryFile = fo; |
| this.obj = REFERENCE_NOT_SET; |
| } |
| |
| private Item(Item clone, FileObject newFo) { |
| assert Thread.holdsLock(DataObjectPool.getPOOL()); |
| this.primaryFile = newFo; |
| this.obj = clone.obj; |
| } |
| |
| /** Setter for the data object. Called immediately as possible. |
| * @param dobj the data object for this item |
| */ |
| public void setDataObject (DataObject dobj) { |
| synchronized (DataObjectPool.getPOOL()) { |
| this.obj = new ItemReference (dobj, this); |
| if (dobj != null && !dobj.getPrimaryFile ().isValid()) { |
| // if the primary file is already invalid => mark the object as invalid |
| DataObjectPool.getPOOL().countRegistration(dobj.getPrimaryFile()); |
| deregister (false); |
| } |
| DataObjectPool.getPOOL().notifyAll(); |
| } |
| } |
| |
| /** Getter for the data object. |
| * @return the data object or null |
| */ |
| DataObject getDataObjectOrNull () { |
| synchronized (DataObjectPool.getPOOL()) { |
| while (this.obj == REFERENCE_NOT_SET) { |
| try { |
| DataObjectPool.getPOOL().wait (); |
| } |
| catch (InterruptedException exc) { |
| } |
| } |
| return this.obj.get(); |
| } |
| } |
| |
| /** Getter for the data object. |
| * @return the data object |
| * @exception IllegalStateException if the data object has been lost |
| * due to weak references (should not happen) |
| */ |
| public DataObject getDataObject () { |
| DataObject o = getDataObjectOrNull (); |
| if (o == null) { |
| throw new IllegalStateException (); |
| } |
| return o; |
| } |
| |
| /** Deregister one reference. |
| * @param refresh true if the parent folder should be refreshed |
| */ |
| public void deregister (boolean refresh) { |
| getPOOL().deregister (this, primaryFile, primaryFile.getParent(), refresh); |
| } |
| |
| /** Changes the primary file to new one. |
| * @param newFile new primary file to set |
| */ |
| public Item changePrimaryFile (FileObject newFile) { |
| return getPOOL().changePrimaryFile (this, newFile, newFile.getParent()); |
| } |
| |
| /** Is the item valid? |
| */ |
| public boolean isValid () { |
| if (getPOOL().map.get (primaryFile) == this) { |
| return primaryFile.isValid(); |
| } else { |
| return false; |
| } |
| |
| } |
| |
| @Override |
| public String toString () { |
| synchronized (DataObjectPool.getPOOL()) { |
| DataObject o = this.obj.get (); |
| if (o == null) { |
| return "nothing[" + primaryFile + "]"; // NOI18N |
| } |
| return o.toString (); |
| } |
| } |
| } |
| |
| /** WeakReference - references a DataObject, strongly references an Item */ |
| static final class ItemReference extends WeakReference<DataObject> |
| implements Runnable { |
| /** Reference to an Item */ |
| private Item item; |
| |
| ItemReference(DataObject dobject, Item item) { |
| super(dobject, org.openide.util.Utilities.activeReferenceQueue()); |
| this.item = item; |
| } |
| |
| /** Does the cleanup of the reference */ |
| public void run () { |
| item.deregister(false); |
| item = null; |
| } |
| |
| } |
| |
| /** Validator to allow rescan of files. |
| */ |
| private static final class Validator extends Object |
| implements DataLoader.RecognizedFiles { |
| /** error manager to log what is happening here */ |
| private static final Logger err = Logger.getLogger("org.openide.loaders.DataObject.Validator"); // NOI18N |
| |
| /** set of all files that should be revalidated */ |
| private Set<FileObject> files; |
| /** current thread that is in the validator */ |
| private Thread current; |
| /** number of threads waiting to enter the validation */ |
| private int waiters; |
| /** Number of calls to enter by current thread minus 1 */ |
| private int reenterCount; |
| /** set of files that has been marked recognized */ |
| private Set<FileObject> recognizedFiles; |
| /** set with all objects that refused to be discarded */ |
| private Set<DataObject> refusingObjects; |
| /** set of files that has been registered during revalidation */ |
| private Set<FileObject> createdFiles; |
| |
| Validator() {} |
| |
| /** Enters the section. |
| * @param set mutable set of files that should be processed |
| * @return the set of files concatenated with any previous sets |
| */ |
| private synchronized Set<FileObject> enter(Set<FileObject> set) { |
| boolean log = err.isLoggable (Level.FINE); |
| if (log) { |
| err.fine("enter: " + set + " on thread: " + Thread.currentThread ()); // NOI18N |
| } |
| if (current == Thread.currentThread ()) { |
| reenterCount++; |
| if (log) { |
| err.fine("current thread, rentered: " + reenterCount); // NOI18N |
| } |
| } else { |
| waiters++; |
| if (log) { |
| err.fine("Waiting as waiter: " + waiters); // NOI18N |
| } |
| while (current != null) { |
| try { |
| wait (); |
| } catch (InterruptedException ex) { |
| } |
| } |
| current = Thread.currentThread (); |
| waiters--; |
| if (log) { |
| err.fine("Wait finished, waiters: " + waiters + " new current: " + current); // NOI18N |
| } |
| } |
| |
| if (files == null) { |
| if (log) { |
| err.fine("New files: " + set); // NOI18N |
| } |
| files = set; |
| } else { |
| files.addAll (set); |
| if (log) { |
| err.fine("Added files: " + set); // NOI18N |
| err.fine("So they are: " + files); // NOI18N |
| } |
| } |
| |
| return files; |
| } |
| |
| /** Leaves the critical section. |
| */ |
| private synchronized void exit () { |
| boolean log = err.isLoggable (Level.FINE); |
| if (reenterCount == 0) { |
| current = null; |
| if (waiters == 0) { |
| files = null; |
| } |
| notify (); |
| if (log) { |
| err.fine("Exit and notify from " + Thread.currentThread ()); // NOI18N |
| } |
| } else { |
| reenterCount--; |
| if (log) { |
| err.fine("Exit reentrant: " + reenterCount); // NOI18N |
| } |
| } |
| } |
| |
| /** If there is another waiting thread, then I can |
| * cancel my computation. |
| */ |
| private synchronized boolean goOn () { |
| return waiters == 0; |
| } |
| |
| /** Called to either refresh folder, or register the folder to be |
| * refreshed later is validation is in progress. |
| */ |
| public void refreshFolderOf (DataFolder df) { |
| if (createdFiles == null) { |
| // no validator in progress |
| FolderList.changedDataSystem (df.getPrimaryFile ()); |
| } |
| } |
| |
| /** Mark this file as being recognized. It will be excluded |
| * from further processing. |
| * |
| * @param fo file object to exclude |
| */ |
| public void markRecognized (FileObject fo) { |
| recognizedFiles.add (fo); |
| } |
| |
| public void notifyRegistered (FileObject fo) { |
| if (createdFiles != null) { |
| createdFiles.add (fo); |
| } |
| } |
| |
| /** Reregister new object for already existing file object. |
| * @param obj old object existing |
| * @param loader loader of new object to create |
| * @return true if the old object has been discarded and new one can |
| * be created |
| */ |
| public boolean reregister (DataObject obj, DataLoader loader) { |
| if (recognizedFiles == null) { |
| // revalidation not in progress |
| return false; |
| } |
| |
| if (obj.getLoader () == loader) { |
| // no change in loader => |
| return false; |
| } |
| |
| if (createdFiles.contains (obj.getPrimaryFile ())) { |
| // if the file already has been created |
| return false; |
| } |
| |
| if (refusingObjects.contains (obj)) { |
| // the object has been refused before |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** Rescans all fileobjects in given set. |
| * @param s mutable set of FileObjects |
| * @return set of objects that refused to be revalidated |
| */ |
| public Set<DataObject> revalidate (Set<FileObject> s) { |
| |
| // ----------------- fix of #30559 START |
| if ((s.size() == 1) && (current == Thread.currentThread ())) { |
| if (files != null && files.contains(s.iterator().next())) { |
| return new HashSet<DataObject>(); |
| } |
| } |
| // ----------------- fix of #30559 END |
| |
| // holds all created object, so they are not garbage |
| // collected till this method ends |
| List<DataObject> createObjects = new LinkedList<DataObject>(); |
| boolean log = err.isLoggable (Level.FINE); |
| try { |
| |
| s = enter (s); |
| |
| recognizedFiles = new HashSet<FileObject>(); |
| refusingObjects = new HashSet<DataObject>(); |
| createdFiles = new HashSet<FileObject>(); |
| |
| DataLoaderPool pool = lp; |
| Iterator<FileObject> it = s.iterator(); |
| while (it.hasNext () && goOn ()) { |
| try { |
| FileObject fo = it.next(); |
| if (log) { |
| err.fine("Iterate: " + fo); // NOI18N |
| } |
| |
| if (!recognizedFiles.contains (fo)) { |
| // first of all test if the file is on a valid filesystem |
| boolean invalidate = false; |
| |
| // the previous data object should be canceled |
| DataObject orig = getPOOL().find (fo); |
| if (log) { |
| err.fine("Original: " + orig); // NOI18N |
| } |
| if (orig == null) { |
| // go on |
| continue; |
| } |
| |
| // findDataObject |
| // is not using method DataObjectPool.find to locate data object |
| // directly for primary file, that is good |
| DataObject obj = pool.findDataObject (fo, this); |
| createObjects.add (obj); |
| |
| invalidate = obj != orig; |
| |
| if (invalidate) { |
| if (log) { |
| err.fine("Invalidate: " + obj); // NOI18N |
| } |
| it.remove(); |
| try { |
| orig.setValid (false); |
| } catch (java.beans.PropertyVetoException ex) { |
| refusingObjects.add (orig); |
| if (log) { |
| err.fine(" Refusing: " + orig); // NOI18N |
| } |
| } |
| } |
| } |
| } catch (DataObjectExistsException ex) { |
| // this should be no problem here |
| } catch (java.io.IOException ioe) { |
| Logger.getLogger(DataObjectPool.class.getName()).log(Level.WARNING, null, ioe); |
| } catch (ConcurrentModificationException cme) { |
| // not very nice but the only way I could come up to handle this: |
| // java.util.ConcurrentModificationException |
| // at java.util.HashMap$HashIterator.remove(HashMap.java:755) |
| // at org.openide.loaders.DataObjectPool$Validator.revalidate(DataObjectPool.java:916) |
| // at org.openide.loaders.DataObjectPool.revalidate(DataObjectPool.java:203) |
| // at org.openide.loaders.DataObjectPool.stateChanged(DataObjectPool.java:527) |
| // at org.openide.loaders.DataLoaderPool$1.run(DataLoaderPool.java:128) |
| // at org.openide.util.Task.run(Task.java:136) |
| //[catch] at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:635) |
| // is to ignore the exception and continue |
| it = s.iterator(); |
| if (log) { |
| err.log(Level.FINE, null, cme); |
| err.fine("New iterator over: " + s); // NOI18N |
| } |
| } |
| } |
| return refusingObjects; |
| } finally { |
| recognizedFiles = null; |
| refusingObjects = null; |
| createdFiles = null; |
| |
| exit (); |
| |
| if (log) { |
| err.fine("will do refreshAllFolders: "+ s.size ()); // NOI18N |
| } |
| |
| getPOOL().refreshAllFolders (); |
| |
| if (log) { |
| err.fine("refreshAllFolders done"); // NOI18N |
| } |
| } |
| } |
| |
| } // end of Validator |
| private final class DoubleHashMap extends HashMap<FileObject,Item> { |
| public DoubleHashMap() { |
| super(512); |
| } |
| |
| @Override |
| public Item put(FileObject obj, Item item) { |
| return putWithParent(obj, obj.getParent(), item); |
| } |
| |
| final Item putWithParent(FileObject obj, FileObject parent, Item item) { |
| Item prev = super.put(obj, item); |
| if (children == null) { |
| return prev; |
| } |
| |
| if (parent == null) { |
| return prev; |
| } |
| List<Item> arr = children.get(parent); |
| if (arr == null) { |
| arr = new ArrayList<Item>(); |
| } |
| arr.add(item); |
| return prev; |
| } |
| @Override |
| public Item remove(Object obj) { |
| Item prev = super.remove(obj); |
| if (! (obj instanceof FileObject)) { |
| return prev; |
| } |
| if (children == null) { |
| return prev; |
| } |
| |
| FileObject parent = ((FileObject)obj).getParent(); |
| if (parent == null) { |
| return prev; |
| } |
| List<Item> arr = children.get(parent); |
| if (arr != null) { |
| arr.remove(obj); // XXX this makes no sense; obj is a FileObject, arr is a List<Item> |
| if (arr.isEmpty()) { |
| children.remove(parent); |
| } |
| } |
| return prev; |
| } |
| } // end of DoubleHashMap |
| |
| } |