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

What this is

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

Other links

The source code

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

package org.netbeans.modules.editor.java;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.SoftReference;
import java.util.*;
import org.netbeans.api.java.classpath.*;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.editor.ext.java.CompoundFinder;
import org.netbeans.editor.ext.java.JCBaseFinder;
import org.netbeans.editor.ext.java.JCClass;
import org.netbeans.editor.ext.java.JCFinder;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;
import org.openide.util.RequestProcessor;
import org.openide.util.WeakListeners;
    
/**
 * Factory producing misc JCFinders.
 *
 * 

This class uses three synchronization objects:

*
    *
  • JCFinderFactory.class instance is used to synchronize creation of * JCFinderFactory singleton in getDefault method
  • *
  • JCFinderFactory instance is used for synchronization of getFinder and * getGlobalFinder methods. The purpose is to synchronize situation when two * threads are asking for global finder or for finder for the same file. Both * methods are relatively fast: they #1) retrieve classpath(s) and ask for * parsed DBs and #2) if parser DB does not exist it will schedule its * parsing and continues. After scheduled parser DB was created the * parser thread will notify this class by resetCache() method.
  • *
  • CACHE_LOCK instance is used for synchronization of access to internal caches. * This lock is much finer compared to JCFinderFactory instance lock. * Only reading/modifying operations of the cache can happed under this lock. * Its purpose is to allow reseting of caches from listeners which was * found as deadlock prone when synchronized on JCFinderFactory instance lock.
  • *
*/ public final class JCFinderFactory { private static JCFinderFactory DEFAULT; /** Empty finder */ private static final JCBaseFinder EMPTY = new JCBaseFinder(JavaKit.class); /** Cache of >. * The FO is classpath root. Access to cache must be always synchronized * on CACHE_LOCK instance. */ private HashMap cache = new HashMap(); /** Weak map whose value is always null and only key is relevant. * The key is ClassPath on which we are already listening. The purpose * of this map is to not attach one listener on the classpath multiple times. */ private WeakHashMap cpListening = new WeakHashMap(); /** Weak map whose value is always null and only key is relevant. * The key is fakeJCClass used in web/jsp => XXX */ private WeakHashMap fakeClasses = new WeakHashMap(); /** This is property change listener listening on classpaths and * invalidating cache when cp has changed. It must be wrapped in weak * listener to allow cp to be garbage collected. */ private static PropertyChangeListener cpListener; /** Cached global finder. Access to this variable must be always * synchronized on CACHE_LOCK instance. */ private SoftReference globalFinder; private GlobalPathRegistryListener gpListener; /** Object used as lock for cache updating synchronization. */ private final Object CACHE_LOCK = new Object(); /** Was FakeFinder initialized? Use it in CompoundFinder? */ private boolean useFakeFinder = false; private JCFinderFactory() { cpListener = new ClassPathListener(); gpListener = new GlobalPathListener(); GlobalPathRegistry.getDefault().addGlobalPathRegistryListener(gpListener); } public static synchronized JCFinderFactory getDefault() { if (DEFAULT == null) { DEFAULT = new JCFinderFactory(); } return DEFAULT; } /** * Invalidate cache of finders. This method is expected to be called for example * when ParserThread finished parsing of a request or when parser DB was * deleted in parser DB manager by user. */ public void resetCache() { synchronized (CACHE_LOCK) { cache = new HashMap(); invalidateGlobalFinderCache(); } } /** Append fake JCClass. This support is needed from web/jsp module for evaluating of scriplets. * XXX - it is not recommended to use this method! */ public void appendClass(JCClass cls){ useFakeFinder = true; fakeClasses.put(cls, null); resetCache(); } /** Returns finder for the given file. * * @return finder; cannot be null; */ public synchronized JCFinder getFinder(FileObject fo) { // the file must be on a SOURCE classpath ClassPath sourceCP = ClassPath.getClassPath(fo, ClassPath.SOURCE); FileObject owner; if (sourceCP == null) { owner = null; }else{ owner = sourceCP.findOwnerRoot(fo); } FileObject cacheKey = (owner!=null) ? owner : fo; JCFinder finder = retrieveFromCache(cacheKey); if (finder != null) { return finder; } ArrayList finders = new ArrayList(); ArrayList fileObjects = new ArrayList(); if (owner!=null){ ClassPath cp = ClassPath.getClassPath(fo, ClassPath.SOURCE); addClasspathFinders(finders, fileObjects, cp, false); cp = ClassPath.getClassPath(fo, ClassPath.COMPILE); addClasspathFinders(finders, fileObjects, cp, true); cp = ClassPath.getClassPath(fo, ClassPath.BOOT); addClasspathFinders(finders, fileObjects, cp, true); }else{ finders.add(getGlobalFinder()); } // XXX - appending fake finder if (useFakeFinder){ JCBaseFinder fakeFinder = new FakeFinder(JavaKit.class); finders.add(fakeFinder); } finder = new CompoundFinder(finders, JavaKit.class); synchronized (CACHE_LOCK) { cache.put(cacheKey, new SoftReference(finder)); } return finder; } private class FakeFinder extends JCBaseFinder{ public FakeFinder(Class kitClass){ super(kitClass); Set keySet = fakeClasses.keySet(); Iterator iter = keySet.iterator(); while (iter.hasNext()){ JCClass cls = (JCClass) iter.next(); appendClass(cls); } } } /** Returns global finder which uses GlobalPathRegistry to learn * all ClassPaths in use and returns finder on top of all these classpaths. */ public synchronized JCFinder getGlobalFinder() { JCFinder finder; synchronized (CACHE_LOCK) { finder = globalFinder != null ? (JCFinder)globalFinder.get() : null; } if (finder != null) { return finder; } ArrayList finders = new ArrayList(); ArrayList fileObjects = new ArrayList(); Iterator it = GlobalPathRegistry.getDefault().getPaths(ClassPath.SOURCE).iterator(); while (it.hasNext()) { ClassPath cp = (ClassPath)it.next(); addClasspathFinders(finders, fileObjects, cp, false); } ArrayList allCPs = new ArrayList(); allCPs.addAll(GlobalPathRegistry.getDefault().getPaths(ClassPath.COMPILE)); allCPs.addAll(GlobalPathRegistry.getDefault().getPaths(ClassPath.BOOT)); it = allCPs.iterator(); while (it.hasNext()) { ClassPath cp = (ClassPath)it.next(); addClasspathFinders(finders, fileObjects, cp, true); } finder = new CompoundFinder(finders, JavaKit.class); synchronized (CACHE_LOCK) { globalFinder = new SoftReference(finder); } return finder; } /** * Invalidate global finder. This method is expected to be called for example * when list of globally registered ClassPaths has changed. */ private void invalidateGlobalFinderCache() { synchronized (CACHE_LOCK) { globalFinder = null; } } private void addClasspathFinders(List finders, List fileObjects, ClassPath cp, boolean findSources) { if (cp == null) { return; } Iterator it = cp.entries().iterator(); while (it.hasNext()) { ClassPath.Entry entry = (ClassPath.Entry)it.next(); if (findSources) { FileObject[] sroots = SourceForBinaryQuery.findSourceRoots(entry.getURL()).getRoots(); if (sroots.length > 0) { for (int i=0; i remove the key removeFromCache(fo); continue; } ClassPath c = ClassPath.getClassPath(fo, ClassPath.COMPILE); if (c != null && c.equals(cp)) { removeFromCache(fo); continue; } c = ClassPath.getClassPath(fo, ClassPath.SOURCE); if (c != null && c.equals(cp)) { removeFromCache(fo); continue; } c = ClassPath.getClassPath(fo, ClassPath.BOOT); if (c != null && c.equals(cp)) { removeFromCache(fo); continue; } } // the global finder is affected too: invalidate it invalidateGlobalFinderCache(); } private class ClassPathListener implements PropertyChangeListener { public void propertyChange(PropertyChangeEvent evt) { if (ClassPath.PROP_ENTRIES.equals(evt.getPropertyName())) { assert evt != null && evt.getSource() instanceof ClassPath; updateCache((ClassPath)evt.getSource()); } } } private class GlobalPathListener implements GlobalPathRegistryListener { public void pathsAdded(GlobalPathRegistryEvent event) { invalidateGlobalFinderCache(); // any change (e.g.: a project was open) will trigger parser DB creation // if it does not exist yet // Post it to RP and do not block event processing. The getGlobalFinder() // method is relatively fast and result is cached, but opening // multiple projects at once is visibly slower if global finder // is refreshed directly here. RequestProcessor.getDefault().postRequest(new Runnable() { public void run() { JCFinderFactory.getDefault().getGlobalFinder(); } }, 1000); // meaning of 1 sec is just to slightly delay this operation } public void pathsRemoved(GlobalPathRegistryEvent event) { invalidateGlobalFinderCache(); } } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

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

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