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.vcscore.cache;

import java.io.*;
import java.util.*;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem;


/** Abstract parent of caches. Includes the common behaviour for all caches. (Just guessed.)
 *  The filesystem should add itself as a listener of the cache to be noted of changes in the cache.
 *
 * @author  mkleint
 */

public abstract class FileSystemCache extends java.lang.Object {
    protected boolean isSaved;
    
    protected String identifier;

    private ArrayList listeners = new ArrayList ();
    private Object listenersLock = new Object();
    
    /**
     * Constants that identifies a type of change in the cache, 
     * calls the cacheAdded() method in the listener.
     * Used in the fireCacheHandlerEvent().
     */
    public static final int EVENT_ADD = 0;

    /**
     * Constants that identifies a type of change in the cache, 
     * calls the cacheRemoved() method in the listener.
     * Used in the fireCacheHandlerEvent().
     */
    
    public static final int EVENT_REMOVE = 1;

    /**
     * Constants that identifies a type of change in the cache, 
     * calls the statusChanged() method in the listener.
     * Used in the fireCacheHandlerEvent().
     */
    public static final int EVENT_CHANGED = 2;    

    /**
     * Constants that identifies a type of change in the cache. 
     * calls the statusChanged() method in the listener.
     * Used in the fireCacheHandlerEvent().
     */
    public static final int EVENT_CHANGED_RECURSIVELY = 3;
    
    /**
     * Constants that identifies a type of change in the cache. 
     * calls the statusChanged() method in the listener.
     * Used in the fireCacheHandlerEvent().
     */
    public static final int EVENT_CHANGED_IGNORE_LIST = 4;
    
    /** A hashmap that stores file objects that are stored during recursive refresh.
     * Later they are released and the cache shrinks automatically.
     * Key: topNode dir's absolutepath()
     * Value: Linked list with all the FileObjects in the dir substructure.
     */
    private HashMap lockedFileObjects;
    
    /**
     * the same key as lockedFileObject, however the value is an int, indicating
     * how many locks are on this directory. 
     */
    private HashMap locks;    
    
    
    /** Creates new FileSystemCache */
    public FileSystemCache(String id) {
       isSaved = false;
       identifier = id;
       lockedFileObjects = new HashMap();
       locks = new HashMap();
       
    }
    
    /**
     * Returns the unique identifier or the cache object.
     */
    public String getId() {
        return identifier;
    }
    
    /** returns true, if the cache was already written to disk. 
     Important when more filesystem share one cache instance and have to decide what to do when the IDE is being closed.
     */
    public boolean isWrittenToDisk() {
        return isSaved;
    }
    
    /** 
     * Writes the cache to the  disk. Needs to be iplemented by the subclass.
     * Make sure to set the isSaved variable when more fs share this cache.
     */
    public abstract void writeAllToDisk();
    

    /** this method gets the cached file from the cache.
     *  The file is identified by full path.
     *  @param strategy - for strategy descriptions see CacheHandler.getCacheFile()
     *  @param locker The directory content locker. An object, that is used to
     *                prevent the obtained directory content from being
     *                garbage-collected.
     *                The cache directories are kept with the complete content
     *                in memory until the locker object is kept in the memory.
     *                The locker object can be null, then the
     *                content of the directory can be garbage-collected
     *                and the retrieval of it's children object can be much more
     *                expensive, because they would have to be reloaded from the
     *                disk cache.
     */
    public abstract CacheFile getCacheFile(File toFind, int strategy, Object locker);
    
    /** Adds a directory into the "fast-lookup" table. Only directories are stored there.
     *  Add a directory into that table everytime you add a irectory into the tree structure.
     *  Note: needed to avoid 2 paralel trees when having just 1 cache for all filesystems.
     *  Keep only weak references to cache objects!
     */
    public abstract void registerDir(CacheDir dir);
    /** opposite to registerCache. To be called when removing the dir from the tree
     */
    public abstract void unregisterDir(CacheDir dir);
    
    /** returns the cached Dir object that was registered with the registerDir() method
     * @param fullName - absolute path of the directory
     */
    public abstract CacheDir getDir(String fullName);
    
    /** 
     * decides wheather the current loading strategy for the specifid directory 
     * is higher than the level already applied to the dir sometimes before now
     * previously in CacheHandler.
     * @param dir - the directory's current applied strategy level
     * @param goalLevel - the strategy level that the user (filesystem) requests 
     * @param toPass - both dir and goalLevel need to be higher than this in order 
     *                 to fire the action associated with this level.
     */
    protected boolean doesStrategyApply(CacheDir dir, int goalLevel, int toPass) {
//        doesStrategyApply:: reqLevel=5  dir is on=0  need to pass=0
//        D.deb("doesStrategyApply:  goalLevel=" + goalLevel + "  dir is on=" + dir.getAppliedLevel() + "  need to pass=" +toPass);
        if (dir.getAppliedLevel() >= toPass) return false;
        if (goalLevel < toPass) return false; // does not apply.. requested level is lower then the one needed to pass
        if (goalLevel <= dir.getAppliedLevel()) return false; // dir is already on higher level then requested
        if (goalLevel >= CacheHandler.STRAT_REFRESHING && toPass > CacheHandler.STRAT_LOCAL) {
            // for  levels REFRESHING and up do exact match. thus for REFRESH do only REFRESH etc.
            if (goalLevel != toPass) return false;
        }
        return true;
    }    

    /** Includes the generic algorithm of applying strategies to directories.
     *  In case this principle doens't suit you, you need to rewrite it :)
     *  Basically it first tries to load the local files in the directory, 
     *  then loads the disk cache and finally checks the server
     *  Decision what is being called, is based on the doesStrategyApply() result.
     */
    protected void loadDir(CacheDir dir, int strategy, Object locker) {
        //System.out.println("loadDir("+dir+", "+strategy+")");
        if (dir == null) return;
        if (strategy == CacheHandler.STRAT_REFRESH || 
            strategy == CacheHandler.STRAT_REFRESH_RECURS) {
          // we're doing something that needs to re-create the cache
            dir.setAppliedLevel(CacheHandler.STRAT_NONE);
            boolean recursiv = (strategy == CacheHandler.STRAT_REFRESH_RECURS);
            if (recursiv) {
                dir.removeAll(recursiv);
            } else {
                dir.removeFiles();
            }
        }
        /*
        if (dir.isComplete() != true && strategy >= CacheHandler.STRAT_LOCAL) {
            // we're doing something that needs to re-create the cache
            dir.setAppliedLevel(CacheHandler.STRAT_NONE);
        }
         */
        if (doesStrategyApply(dir, strategy, CacheHandler.STRAT_LOCAL)) {
            //System.out.println("  populating dir with local files.");
            dir.populateWithLocal(locker);
            //dir.setComplete(true);
        }
        //every strategy above STRAT_LOCAL applies only to CVS directories
        if (dir.isLocal()) return;
        
        if (strategy == CacheHandler.STRAT_DISK_OR_REFRESH_RECURS) {
            loadFromDiskOrRefreshRecursively(dir, locker);
            return ;
        }
        
        if (doesStrategyApply(dir, strategy, CacheHandler.STRAT_DISK)) {
            //System.out.println("  reading dir from disk.");
            if (!dir.readFromDisk(locker)) { 
                // do server check when local disk cache is not present? I Don't think so!
                if (strategy == CacheHandler.STRAT_DISK_OR_REFRESH) {
                    dir.checkServer(locker);
                    //dir.setComplete(true);
                }
            }
            //dir.setComplete(true);
        } 
        if (doesStrategyApply(dir, strategy, CacheHandler.STRAT_REFRESH)) {
           //System.out.println("  checking the server.");
           dir.checkServer(locker);
           //dir.setComplete(true);
        }

        if (doesStrategyApply(dir, strategy, CacheHandler.STRAT_REFRESHING)) {
           //dir.setComplete(true);
           dir.setAppliedLevel(CacheHandler.STRAT_REFRESHING);
        }
        
        if (doesStrategyApply(dir, strategy, CacheHandler.STRAT_REFRESH_RECURS)) {
           dir.checkServerRecursive(locker);
           //dir.setComplete(true);
        }
    }
    
    /**
     * Load the cache directory recursively either from disk or by recursive
     * refresh if disk cache does not exist.
     */
    protected void loadFromDiskOrRefreshRecursively(CacheDir dir, Object locker) {
        if (!dir.readFromDisk(locker)) { 
            dir.checkServerRecursive(locker);
        } else {
            CacheDir[] subDirs = dir.getSubDirs();
            for (int i = 0; i < subDirs.length; i++) {
                if (!subDirs[i].isLocal()) loadFromDiskOrRefreshRecursively(subDirs[i], locker);
            }
        }
    }
    
    /** 
     * adds a listener that listens to changes in the cache.
     */

    public void addCacheHandlerListener(CacheHandlerListener toAdd) {
        synchronized (listenersLock) {
            if (listeners == null) {
                listeners = new ArrayList ();
            }
            listeners.add (toAdd);
        }
    }
    
    /**
     * Removes a listener from the list of CacheHandlerEvent listeners.
     */
    
    public void removeCacheHandlerListener(CacheHandlerListener toRemove) {
        synchronized (listenersLock) {
            if (listeners != null) {
                listeners.remove (toRemove);
            }    
        }
    } 
    
    /**
     * removes all listeners from the cache objects.. to be used when uninstalling the module..
     */
    void removeAllListeners() {
        synchronized (listenersLock) {
            if (listeners != null) {
                listeners.clear();
                listeners = null;
            }
        }
    }

    /** fires an CacheHandlerEvent.
     *  
     * @param type - 4 possible types of events are available: 
     *  EVENT_ADD, EVENT_REMOVE, EVENT_CHANGED, EVENT_CHANGED_RECURSIVELY
     * @param firedFile the added/removed/changed cache file
     */
    // friendly (public), in order to be able to fire it from CvsCachDir..
    public void fireCacheHandlerEvent(int type, CacheFile firedFile) {
        CacheHandlerEvent event = new CacheHandlerEvent (firedFile, type == EVENT_CHANGED_RECURSIVELY);
        fireCacheHandlerEvent(type, event);
    }
    
    /** fires an CacheHandlerEvent.
     *  
     * @param type - 4 possible types of events are available: 
     *  EVENT_ADD, EVENT_REMOVE, EVENT_CHANGED, EVENT_CHANGED_RECURSIVELY
     * @param event the event to be fired
     */
    // friendly (public), in order to be able to fire it from CvsCachDir..
    public void fireCacheHandlerEvent(int type, CacheHandlerEvent event) {
        ArrayList list;
        synchronized (listenersLock) {
            if (listeners == null) return ;
            list = (ArrayList) this.listeners.clone();
        }
        for (int i = 0; i < list.size(); i++) {
            if (type == EVENT_CHANGED || type == EVENT_CHANGED_IGNORE_LIST ||
                type == EVENT_CHANGED_RECURSIVELY) {
                ((CacheHandlerListener)list.get (i)).statusChanged (event);
            }
            if (type == EVENT_ADD) {
                ((CacheHandlerListener)list.get (i)).cacheAdded (event);
            }
            if (type == EVENT_REMOVE) {
                ((CacheHandlerListener)list.get (i)).cacheRemoved (event);
            }
        }
    }
    
    /** 
     * See removeLockedFileObjects(String topNode) for details.
     *
    
    public void removeLockedFileObjects(CacheDir topNode) {
        removeLockedFileObjects(topNode.getAbsolutePath());
    }
    
    /**
     * When called the fileobjects locked by the doLockFileObjects() method, are released.
     * See doLockFileObjects() for details.
     *
    
    public void removeLockedFileObjects(String topNode) {
        // it reduces the number of locks on the directory by one.. 
        // if the numbr reaches 1, then the  fileobjects are released
        LinkedList list = (LinkedList)lockedFileObjects.get(topNode);
        if (list != null) {
            Integer numList = (Integer)locks.get(topNode);
            if (numList.intValue() == 1) {
                list.clear();
                lockedFileObjects.remove(topNode);
                locks.remove(topNode);
            } else {
                Integer newNumList = new Integer(numList.intValue() - 1);
                locks.put(topNode, newNumList);
            }
        }
    }   
    
    /** For the specified fileObject collects all it's subfiles/dirs into an array and holds the reference until the 
     *  removeLockedFileObject() method is called. That way we can ensure that 
     * during the time we operate with the cache nothing gets discarded by the ReferenceQueue
     * If called multiple times, the lock multiplies as well and multiple calls 
     * of removeLockedFileObjects are needed. 

* Please note that usually the prepareCache method should be called right after this one, * to ensure that the cache is in a state we want it to be. (required strategy level) * protected boolean doLockFileObjects(FileObject topFO, String topNode, boolean recursively) { LinkedList oldList = null; LinkedList list = (LinkedList)lockedFileObjects.get(topNode); if (list != null) { oldList = list; } list = new LinkedList(); if (topFO != null) { recursiveAddParentFO(topFO, list); recursiveFOCollect(topFO, list, recursively); // if an older list exist and is bigger, lets assume, // that was recursive and more complete list if (oldList != null && (list.size() < oldList.size())) { list = oldList; } lockedFileObjects.put(topNode, list); synchronized (locks) { Integer count = (Integer)locks.get(topNode); if (count != null) { count = new Integer(count.intValue() + 1); } else { count = new Integer(1); } locks.put(topNode, count); } return true; } return false; } private void recursiveFOCollect(FileObject fo, LinkedList list, boolean recurs) { FileObject[] children = fo.getChildren(); for (int index = 0; index < children.length; index++) { // System.out.println("added FO for=" + children[index]); list.add(children[index]); if (children[index].isFolder() && recurs) { recursiveFOCollect(children[index], list, recurs); } } } private void recursiveAddParentFO(FileObject fo, LinkedList list) { list.add(fo); if (fo.getParent() != null) { recursiveAddParentFO(fo.getParent(), list); } } */ /** This method creates a (recursive) cache structure with requested strategy. * It should be called after lockFileObjects to ensure he whole structure * has the wanted strategy level. * For example for Recursive refresh use, where it's not guaranteed that the cache will be present * @param rootDir - the absolute path to the directory that should act as root for the cache loading * @param isRecursive - loads recursively if set to true * @param strategyToUse - will ensure the whole structure has the same requested strategy level loaded. * @param cacheID - id string of the cache. * @returns the * public void prepareCache(File rootDir, boolean isRecursive, int strategyToUse) { // first check if the rootDir is in the File[] subfiles = rootDir.listFiles(); CacheDir cvsFile = null; if (subfiles == null) return ; for (int index = 0; index < subfiles.length; index++) { if (!subfiles[index].isDirectory()) { // sets all files to local before applying the refresh results. CacheFile cvsFl = (CacheFile)CacheHandler.getInstance().getCacheFile(subfiles[index], strategyToUse, getId()); continue; } cvsFile = (CacheDir)CacheHandler.getInstance().getCacheFile(subfiles[index], strategyToUse, getId()); if (cvsFile != null && isRecursive) { // just sanity check cvsFile.setAppliedLevel(strategyToUse); prepareCache(new File(cvsFile.getAbsolutePath()), isRecursive, strategyToUse); } } } */ }

... 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.