Package org.jdesktop.wonderland.client.cell

Source Code of org.jdesktop.wonderland.client.cell.CellCacheBasicImpl$CellStatusChanger

/**
* Open Wonderland
*
* Copyright (c) 2010 - 2012, Open Wonderland Foundation, All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* The Open Wonderland Foundation designates this particular file as
* subject to the "Classpath" exception as provided by the Open Wonderland
* Foundation in the License file that accompanied this code.
*/

/**
* Project Wonderland
*
* Copyright (c) 2004-2009, Sun Microsystems, Inc., All Rights Reserved
*
* Redistributions in source code form must reproduce the above
* copyright and this condition.
*
* The contents of this file are subject to the GNU General Public
* License, Version 2 (the "License"); you may not use this file
* except in compliance with the License. A copy of the License is
* available at http://www.opensource.org/licenses/gpl-license.php.
*
* Sun designates this particular file as subject to the "Classpath"
* exception as provided by Sun in the License file that accompanied
* this code.
*/
package org.jdesktop.wonderland.client.cell;

import com.jme.bounding.BoundingVolume;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdesktop.wonderland.client.ClientContext;
import org.jdesktop.wonderland.client.cell.CellStatistics.TimeCellStat;
import org.jdesktop.wonderland.client.cell.TransformChangeListener.ChangeSource;
import org.jdesktop.wonderland.client.cell.view.ViewCell;
import org.jdesktop.wonderland.client.comms.WonderlandSession;
import org.jdesktop.wonderland.common.cell.*;
import org.jdesktop.wonderland.common.cell.messages.CellReparentMessage;
import org.jdesktop.wonderland.common.cell.state.CellClientState;

/**
* A basic implementation of core cell cache features. This is a convenience class
* designed to be called from more complete cache implementations.
*
* @author paulby
*/
public class CellCacheBasicImpl implements CellCache, CellCacheConnection.CellCacheMessageListener {
    private static final String STATUS_MANAGER_PROP =
            CellCacheBasicImpl.class.getSimpleName() + ".StatusManager";
    private static final String STATUS_MANAGER_DEFAULT =
            ParallelCellStatusManager.class.getName();
   
    private static final String ASSET_PRELOAD_PROP =
            CellCacheBasicImpl.class.getSimpleName() + ".PreloadAssets";
   
    private Map<CellID, Cell> cells = Collections.synchronizedMap(new HashMap());
    private Set<Cell> rootCells = Collections.synchronizedSet(new HashSet());
   
    protected static Logger logger = Logger.getLogger(CellCacheBasicImpl.class.getName());
   
    private ViewCell viewCell = null;

    /** the classloader to use when instantiating classes */
    private ClassLoader classLoader;
   
    /** the session this cache is associated with */
    private WonderlandSession session;
   
    /** the connection for sending cell cache information */
    private CellCacheConnection cellCacheConnection;
   
    /** the connection for sending cell information */
    private CellChannelConnection cellChannelConnection;

    /** listeners */
    private final Set<CellCacheListener> listeners =
            new CopyOnWriteArraySet<CellCacheListener>();

    /** statistics */
    private final CellStatistics stats = new CellStatistics();

    /** status manager */
    private final CellStatusManager statusManager;
   
    /** cell being processed on the current thread */
    private static final ThreadLocal<Cell> currentCell = new ThreadLocal<Cell>();
           
    /**
     * Create a new cache implementation
     * @param session the WonderlandSession the cache is associated with
     * @param cellCacheConnection the connection for sending cell cache
     * information
     * @param cellChannelConnection the connectiong for sending cell channel
     * messages
     */
    public CellCacheBasicImpl(WonderlandSession session,
                              ClassLoader classLoader,
                              CellCacheConnection cellCacheConnection,
                              CellChannelConnection cellChannelConnection) {
        this.session = session;
        this.classLoader = classLoader;
        this.cellCacheConnection = cellCacheConnection;

        this.cellChannelConnection = cellChannelConnection;
        this.cellChannelConnection.setCellCache(this);

        this.statusManager = createStatusManager();
       
        ClientContext.registerCellCache(this, session);
        if (this.classLoader == null) {
            this.classLoader = getClass().getClassLoader();
        }
       
        logger.warning("Create cell cache");
    }
   
    /**
     * {@inheritDoc}
     */
    public Cell getCell(CellID cellId) {
        return cells.get(cellId);
    }

    /**
     * {@inheritDoc}
     */
    public EnvironmentCell getEnvironmentCell() {
        return (EnvironmentCell) getCell(CellID.getEnvironmentCellID());
    }

    /**
     * Return the set of cells in this cache
     * @return
     */
    public Cell[] getCells() {
        return (Cell[]) cells.values().toArray(new Cell[cells.size()]);
    }

    /**
     * {@inheritDoc}
     */
    public Cell loadCell(CellID cellId,
                         String className,
                         BoundingVolume localBounds,
                         CellID parentCellID,
                         CellTransform cellTransform,
                         CellClientState setup,
                         String cellName) {

        long startTime = System.currentTimeMillis();

        try {
            Cell cell;

            synchronized (cells) {
                if (cells.containsKey(cellId)) {
                    logger.severe("Attempt to create cell that already exists "+cellId);
                    return null;
                }

                logger.info("creating cell "+className+" "+cellId);
                cell = instantiateCell(className, cellId);
                if (cell==null)
                    return null;     // Instantiation failed, error has already been logged

                cells.put(cellId, cell);
            }
          
            // cell we are currently working on. Be sure to reset in the
            // finally block below
            currentCell.set(cell);
                
            cell.setName(cellName);
            Cell parent = cells.get(parentCellID);
            if (parent != null) {
                try {
                    parent.addChild(cell);
                } catch (MultipleParentException ex) {
                    logger.log(Level.SEVERE, "Failed to load cell", ex);
                }
            } else if (parentCellID != null) {
                logger.warning("Failed to find parent " + parentCellID +" for child " + cellId);
                /*
                 * parent is not found so stop loading cell and
                 * resend the reparent message to server
                */
                CellEditChannelConnection connection = (CellEditChannelConnection) session
                        .getConnection(CellEditConnectionType.CLIENT_TYPE);
                connection.send(new CellReparentMessage(cell.getCellID(), parentCellID, cell.getLocalTransform()));
                return null;
            }

            cell.setLocalBounds(localBounds);
            cell.setLocalTransform(cellTransform, TransformChangeListener.ChangeSource.SERVER_ADJUST);
    //        System.out.println("Loading Cell "+className+" "+cellTransform.getTranslation(null));

            // cells.put(cellId, cell);

            // record the set of root cells
            logger.fine("LOADING CELL " + cell.getName());
            if (parent == null && !cellId.equals(CellID.getEnvironmentCellID())) {
                logger.fine("LOADING ROOT CELL " + cell.getName());
                rootCells.add(cell);
            }

            // TODO this will change, the state will applied when the cell
            // becomes ACTIVE
            if (setup!=null)
                cell.setClientState(setup);
            else
                logger.warning("Cell has null setup "+className+"  "+cell);

            // Force the cell to create the JME renderer entity
            // Current assumption is that the cell is about to be VISIBLE, so we want the renderer asap
//            createCellRenderer(cell);

            // notify listeners
            fireCellLoaded(cell);

            if (viewCell!=null) {
                // No point in makeing cells active if we don't have a view
                // The changeCellStatus actually changes the status on another thread
                // so we don't perform geometry load operations on the DS listener thread
                changeCellStatus(cell, CellStatus.VISIBLE);
            } else if (cell instanceof ViewCell ||
                       cellId.equals(CellID.getEnvironmentCellID()))
            {
                changeCellStatus(cell, CellStatus.ACTIVE);
            }

            // record loading time statistic
            long time = System.currentTimeMillis() - startTime;
            TimeCellStat loadStat =
                    new TimeCellStat("LoadTime", "Cell Load Time");
            loadStat.setValue(time);
            getStatistics().add(cell, loadStat);

            return cell;
        } catch(Exception e) {
            // notify listeners
            fireCellLoadFailed(cellId, className, parentCellID, e);
            logger.log(Level.SEVERE, "Failed to loadCell", e);
            return null;
        } finally {
            currentCell.remove();
        }
    }

    /**
     * {@inheritDoc}
     */
    public void configureCell(CellID cellID, CellClientState clientState, String cellName) {
        // First fetch the cell from the cache given the unique ID. If there
        // is none, post a message to the log
        Cell cell = cells.get(cellID);
        if (cell == null) {
            logger.warning("Received a CONFIGURE_CELL message to a non-" +
                    "existent cell with id " + cellID);
            return;
        }

        try {
            // set the cell we are currently operating on
            currentCell.set(cell);
       
            // If the name of the cell has changed, then change it
            if (cellName != null && cellName.equals(cell.getName()) == false) {
                cell.setName(cellName);
            }

            // If there is a non-null client state object, then set it
            if (clientState != null) {
                cell.setClientState(clientState);
            }
        } finally {
            currentCell.remove();
        }
    }


    /**
     * Create a the cell renderer for this cache.
     * @param cell the cell to create a renderer for
     */
    protected CellRenderer createCellRenderer(Cell cell) {
        try {
            return cell.getCellRenderer(ClientContext.getRendererType());
        } catch(Exception e) {
            logger.log(Level.SEVERE, "Failed to get Cell Renderer for cell "+cell.getClass().getName(), e);
        }
        return null;
    }

    /**
     * Unload the cell from memory, sets the Cells status to DISK
     * @param cellId
     */
    public void unloadCell(CellID cellId) {
        Cell cell = cells.remove(cellId);
        if (cell != null) {
            logger.fine("UNLOADING CELL " + cell.getName());
            try {
                currentCell.set(cell);
           
                // notify listeners
                fireCellUnloaded(cell);

                // OWL issue #37: make sure to update the status on a separate
                // thread to prevent deadlocks waiting for update messages and
                // races with loading the cell
                changeCellStatus(cell, CellStatus.DISK);

                if (cell.getParent() == null) {
                    logger.fine("UNLOADING ROOT CELL " + cell.getName());
                    rootCells.remove(cell);
                } else {
                    cell.getParent().removeChild(cell);
                }
            } finally {
                currentCell.remove();
            }
        } else {
            logger.log(Level.WARNING, "Unloading unknown cell " + cellId);
        }
    }

    /**
     * {@inheritDoc}
     */
    public void deleteCell(CellID cellId) {
        // TODO - remove local resources from client asset cache as long
        // as they are not shared
        unloadCell(cellId);
    }

    /**
     * TODO - this is not used yet, we are reparenting by removing and adding
     * nodes as a test to ensure there are no threading issues
     *
     * {@inheritDoc}
     */
    public void changeParent(CellID cellID, CellID parentCellID, CellTransform cellTransform) {
        // Find the current parent of the Cell and remove the Cell. If the Cell
        // does not exist in the cache, then do nothing. It could perhaps be
        // created on the client at a later date.
        logger.warning("Changing the parent of Cell with ID " + cellID);
        logger.warning("The ID of the new parent " + parentCellID);
        logger.warning("The new transform is " + cellTransform.toString());

        Cell cell = cells.get(cellID);
        if (cell == null) {
            logger.warning("Unable to find Cell in Cache with ID " + cellID);
            return;
        }
       
        try {
            currentCell.set(cell);
       
            // First, remove the Cell from its parent, if the parent is not null. If
            // the parent is null, this means it is a "root" Cell and we need to
            // remove it from the list of root Cells.
            Cell parentCell = cell.getParent();
            if (parentCell != null) {
                // The removeChild() method will remove from the parent but also
                // null the parent reference in the child Cell.
                logger.warning("Removing the Cell from old parent " + parentCell.getCellID());
                parentCell.removeChild(cell);
            }
            else {
                logger.warning("Removing the Cell from the root");
                rootCells.remove(cell);
            }

            // Find the new parent Cell. If the parent Cell ID is -1 (which will
            // result in no parent Cell being found, it means we wish to add this
            // to the root.
            Cell newParentCell = cells.get(parentCellID);
            if (newParentCell != null) {
                try {
                    logger.warning("Adding the Cell to new parent " + newParentCell.getCellID());
                    newParentCell.addChild(cell);
                } catch (MultipleParentException excp) {
                    logger.log(Level.WARNING, "Multiple parents are set for Cell" +
                               " with ID " + cellID, excp);
                }
            }
            else if (!cellID.equals(CellID.getEnvironmentCellID())) {
                logger.warning("Adding the Cell to the root");
                rootCells.add(cell);
            }

            // Update the Cell transform with the new local transform
            cell.setLocalTransform(cellTransform, ChangeSource.REMOTE);
        } finally {
            currentCell.remove();
        }
    }

   
    /**
     * Set the cell status, ensuring that the cell passes through any intermediate
     * status.
     *
     * @param cell
     * @param status
     */
    private void setCellStatus(Cell cell, CellStatus status) {
        logger.fine("Set status of cell " + cell.getCellID() +
                       " to " + status);

        synchronized(cell) {
            int currentStatus = cell.getStatus().ordinal();
            int requiredStatus = status.ordinal();

            if (currentStatus==requiredStatus)
                return;

            int dir = (requiredStatus>currentStatus ? 1 : -1);
            boolean increasing = (dir==1);

            while(currentStatus!=requiredStatus) {
                currentStatus += dir;

                CellStatus nextStatus = CellStatus.values()[currentStatus];
                long startTime = System.currentTimeMillis();
                TimeCellStat loadStat = getLoadStat(cell, nextStatus);
                try {
                    cell.setStatus(nextStatus, increasing);
                } finally {
                    long time = System.currentTimeMillis() - startTime;
                    loadStat.changeValue(time);
                }

                cell.fireCellStatusChanged(nextStatus);
            }
        }
    }

    private TimeCellStat getLoadStat(Cell cell, CellStatus status) {
        String statId = status.name() + "-time";
        TimeCellStat loadStat;

        synchronized (getStatistics()) {
            loadStat = (TimeCellStat) getStatistics().get(cell, statId);
            if (loadStat == null) {
                loadStat = new TimeCellStat(statId);
                getStatistics().add(cell, loadStat);
            }
        }

        return loadStat;
    }

    /**
     * {@inheritDoc}
     */
    public Collection<Cell> getRootCells() {
        return new LinkedList(rootCells);
    }
   
    private Cell instantiateCell(String className, CellID cellId) {
        Cell cell;
       
        try {
            Class clazz = Class.forName(className, true, classLoader);
            Constructor constructor = clazz.getConstructor(CellID.class, CellCache.class);
            cell = (Cell) constructor.newInstance(cellId, this);
        } catch(Exception e) {
            logger.log(Level.SEVERE, "Problem instantiating cell "+className, e);
            return null;
        }
       
        return cell;
    }

    /**
     * {@inheritDoc}
     */
    public void setViewCell(ViewCell viewCell) {
        this.viewCell = viewCell;
       
        // Activate all current cells
        synchronized(cells) {
            // issue #850 -- make sure to notify parents before their
            // children
            for(Cell cell : cells.values()) {
                if (cell.getParent() != null) {
                    // ignore child cells
                    continue;
                }

                // change the status of this parent and all its children
                changeCellTreeStatus(cell, CellStatus.VISIBLE);
            }
        }
    }

    /**
     * Notify a tree of cells to change their status
     * @param cell the root cell to notify
     * @param status the status to set
     */
    private void changeCellTreeStatus(Cell root, CellStatus status) {
        if (root.getStatus().ordinal() < status.ordinal()) {
            changeCellStatus(root, status);
        }

        for (Cell child : root.getChildren()) {
            changeCellTreeStatus(child, status);
        }
    }

    /**
     * {@inheritDoc}
     */
    public ViewCell getViewCell() {
        return viewCell;
    }
   
    /**
     * {@inheritDoc}
     */
    public WonderlandSession getSession() {
        return session;
    }

   
    public CellChannelConnection getCellChannelConnection() {
        return cellChannelConnection;
    }

    /**
     * {@inheritDoc}
     */
    public void addCellCacheListener(CellCacheListener listener) {
        listeners.add(listener);
    }

    /**
     * {@inheritDoc}
     */
    public void removeCellCacheListener(CellCacheListener listener) {
        listeners.remove(listener);
    }

    /**
     * {@inheritDoc}
     */
    public CellStatistics getStatistics() {
        return stats;
    }

    /**
     * Notify listeners that a cell is loaded
     * @param cell the cell that was loaded
     */
    protected void fireCellLoaded(Cell cell) {
        for (CellCacheListener listener : listeners) {
            listener.cellLoaded(cell.getCellID(), cell);
        }
    }

    /**
     * Notify listeners that a cell laod failed
     * @param cellID the id of the cell that failed to load
     * @param className the class of the cell that failed to load
     * @param parentCellID the id of the cell's parent
     * @param cause the reason for failure
     */
    protected void fireCellLoadFailed(CellID cellID, String className,
                                      CellID parentCellID, Throwable cause)
    {
        for (CellCacheListener listener : listeners) {
            listener.cellLoadFailed(cellID, className, parentCellID, cause);
        }
    }

    /**
     * Notify listeners that a cell is unloaded
     * @param cell the cell that was unloaded
     */
    protected void fireCellUnloaded(Cell cell) {
        for (CellCacheListener listener : listeners) {
            listener.cellUnloaded(cell.getCellID(), cell);
        }
    }

    /**
     * Cell status changes can take a while so should not be performed on the
     * Darkstar listener thread that calls the cache methods. Therefore
     * schedule a task actually apply the status change to the cell.
     * @param cell
     * @param status
     */
    private void changeCellStatus(Cell cell, CellStatus status) {
        statusManager.setCellStatus(cell, status);
    }
   
    /**
     * Get the currently active cell for this thread. If no cell is active,
     * this method return null.
     * @return the currently active cell for this thread, if any
     */
    public static Cell getCurrentActiveCell() {
        return currentCell.get();
    }

    /**
     * Create a cell status manager
     */
    private CellStatusManager createStatusManager() {
        String className = System.getProperty(STATUS_MANAGER_PROP,
                                              STATUS_MANAGER_DEFAULT);
        try {
            Class<CellStatusManager> clazz = (Class<CellStatusManager>)
                    Class.forName(className);
           
            Constructor<CellStatusManager> ctor = clazz.getConstructor(CellCacheBasicImpl.class);
            return ctor.newInstance(this);
        } catch (ClassNotFoundException cnfe) {
            throw new RuntimeException(cnfe);
        } catch (NoSuchMethodException nsme) {
            throw new RuntimeException(nsme);
        } catch (InstantiationException ie) {
            throw new RuntimeException(ie);
        } catch (IllegalAccessException iae) {
            throw new RuntimeException(iae);
        } catch (InvocationTargetException te) {
            throw new RuntimeException(te);
        }
    }
   
    interface CellStatusManager {
        public void setCellStatus(Cell cell, CellStatus status);
    }
   
    private class SynchronousCellStatusManager implements CellStatusManager {
        /** executor for modifying values in separate threads */
        private final ExecutorService cacheExecutor =
                Executors.newSingleThreadExecutor(new StatusCellFactory());

       
        public SynchronousCellStatusManager() {
        }
       
        public void setCellStatus(Cell cell, CellStatus status) {
            cacheExecutor.submit(new CellStatusChanger(cell, status));
        }
    }
   
    private class ParallelCellStatusManager
        implements CellStatusChangeListener, CellChildrenChangeListener,
                   CellStatusManager
    {
        private final Map<CellID, CellStatusEntry> waiting =
                new LinkedHashMap<CellID, CellStatusEntry>();
       
        private final ExecutorService downloader =
                Executors.newCachedThreadPool(new URLDownloadCellFactory());
       
        private final ExecutorService executor =
                Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(),
                                             new StatusCellFactory());
       
        private final boolean preloadAssets;
       
        public ParallelCellStatusManager() {
            CellManager.getCellManager().addCellStatusChangeListener(this);
       
            preloadAssets = Boolean.parseBoolean(
                    System.getProperty(ASSET_PRELOAD_PROP, "true"));
        }
       
        public synchronized void setCellStatus(Cell cell, CellStatus status) {
            // create a status entry for the cell
            CellStatusEntry entry = new CellStatusEntry(cell, status);
           
            // does the cell have resources to download?
            if (preloadAssets &&
                cell instanceof AssetPreloader &&
                cell.getStatus() == CellStatus.DISK)
            {
                entry.setDownloaded(false);
            }
           
            // is the cell ready now?
            if (eligible(entry)) {
                executor.submit(new CellStatusChanger(entry.getCell(), entry.getStatus()));
                return;
            }
           
            // if the cell is not ready, add it to our map of
            // waiting cells
            waiting.put(cell.getCellID(), entry);
           
            // listen for child changes
            cell.addChildrenChangeListener(this);
           
            // if the cell needs downloading, start now
            if (!entry.isDownloaded()) {
                startDownloading(entry);
            }
        }
       
        /**
         * Start downloading files associated with the given entry
         * @param entry the entry to download files for
         */
        private void startDownloading(CellStatusEntry entry) {
            downloader.submit(new URLDownloader(this, entry));
        }
       
        /**
         * Notification that a cell has finished downloading
         * @param cellID the id of the cell that finished
         */
        private synchronized void finishedDownloading(CellID cellID) {
            CellStatusEntry entry = waiting.get(cellID);
            if (entry != null) {
                entry.setDownloaded(true);
            }
           
            // see if the cell is now eligible to load
            checkCell(cellID);
        }
       
        /**
         * When a cell's status changes, check this cell and any parents
         * or children that are in the waiting list
         * @param cell the cell that changed
         * @param status the updated status
         */
        public void cellStatusChanged(Cell cell, CellStatus status) {
            // check this cell
            checkCell(cell.getCellID());
           
            // check the cell's parent (if any)
            if (cell.getParent() != null) {
                checkCell(cell.getParent().getCellID());
            }
           
            // check the cell's children (if any)
            if (cell.getChildren() != null) {
                for (Cell child : cell.getChildren()) {
                    checkCell(child.getCellID());
                }
            }
           
        }
       
        /**
         * When a cell's children change, it may become eligible for a status
         * change. Check.
         */
        public void childAdded(Cell cell, Cell child) {
            checkCell(cell.getCellID());
            checkCell(child.getCellID());
        }

        /**
         * When a cell's children change, it may become eligible for a status
         * change. Check.
         */
        public void childRemoved(Cell cell, Cell child) {
            checkCell(cell.getCellID());
            checkCell(child.getCellID());
        }
       
        /**
         * Check a particular entry in the map, and update it as needed
         * @param cellID this id of the cell to check
         */
        private synchronized void checkCell(CellID cellID) {
            CellStatusEntry entry = waiting.get(cellID);
            if (entry != null && eligible(entry)) {
                waiting.remove(cellID);
               
                // stop listening for child changes
                entry.getCell().removeChildChangeListener(this);
               
                executor.submit(new CellStatusChanger(entry.getCell(), entry.getStatus()));
            }
        }
       
        /**
         * Return true if a cell is eligible to move to the status
         * change queue, or false if not
         * @param entry the status entry describing the cell
         */
        private boolean eligible(CellStatusEntry entry) {
            // does the content of the cell need to be downloaded
            if (!entry.isDownloaded()) {
                return false;
            }
           
            // is the cell's target state an increase or decrease from
            // the current state?
            boolean increasing = (entry.getCell().getStatus().ordinal() <=
                                  entry.getStatus().ordinal());
            if (increasing) {
                // a cell is eligible for an increase if the parent's status
                // is at least the target
                Cell parent = entry.getCell().getParent();
                if (parent != null) {
                    return parent.getStatus().ordinal() >=
                           entry.getStatus().ordinal();
                }
            } else {
                // a cell is eligible for a decrease if all of the children's
                // statuses are lower than the target
                List<Cell> children = entry.getCell().getChildren();
                if (children != null) {
                    for (Cell child : children) {
                        if (child.getStatus().ordinal() > entry.getStatus().ordinal()) {
                            return false;
                        }                       
                    }
                }
            }
           
            // if we passed all the checks above, we are eligible
            return true;
        }
       
        private ExecutorService getDownloader() {
            return downloader;
        }
    }
   
    private static class CellStatusEntry {
        private final Cell cell;
        private final CellStatus status;
        private boolean downloaded = true;
       
        public CellStatusEntry(Cell cell, CellStatus status) {
            this.cell = cell;
            this.status = status;
        }
       
        public Cell getCell() {
            return cell;
        }
       
        public CellStatus getStatus() {
            return status;
        }
       
        public synchronized boolean isDownloaded() {
            return downloaded;
        }
       
        public synchronized void setDownloaded(boolean downloaded) {
            this.downloaded = downloaded;
        }
    }
   
    private static class URLDownloader implements Runnable {
        private final ParallelCellStatusManager pcsm;
        private final CellStatusEntry entry;
       
        public URLDownloader(ParallelCellStatusManager pcsm,
                             CellStatusEntry entry)
        {
            this.pcsm = pcsm;
            this.entry = entry;
        }
       
        public void run() {
            Queue<Future<InputStream>> queue = new LinkedList<Future<InputStream>>();
           
            try {
                currentCell.set(entry.getCell());
               
                for (URL u : ((AssetPreloader) entry.getCell()).getAssets()) {
                    enqueue(entry.getCell(), u, queue);
                }
           
                Future<InputStream> f = null;
                while ((f = queue.poll()) != null) {
                    // wait for the object to be done loading
                    try {
                        f.get();
                    } catch (InterruptedException ie) {
                        // ignore
                    } catch (ExecutionException ee) {
                        logger.log(Level.WARNING, "Error in execution", ee);
                    }
                }
           
                // all done
                pcsm.finishedDownloading(entry.getCell().getCellID());
            } finally {
                currentCell.set(null);
            }
        }
       
        private void enqueue(final Cell cell, final URL u,
                             final Queue<Future<InputStream>> queue)
        {
            Future<InputStream> f = pcsm.getDownloader().submit(new Callable<InputStream>() {
                public InputStream call() throws Exception {
                    try {
                        currentCell.set(cell);
                   
                        // open the stream -- this will cause the item to be
                        // downloaded from the server using the asset manager
                        InputStream is = u.openStream();
                   
                        // once the stream is ready, see if it leads to any
                        // additional URLs (enqueue them if it does)
                        for (URL n : ((AssetPreloader) cell).assetLoaded(u, is)) {
                            enqueue(cell, n, queue);
                        }
                   
                        // return the stream
                        return is;
                    } finally {
                        currentCell.set(null);
                    }
                }
            });
           
            queue.add(f);
        }
    }
   
    private class CellStatusChanger implements Runnable {

        private final Cell cell;
        private final CellStatus cellStatus;

        public CellStatusChanger(Cell cell, CellStatus status) {
            this.cell = cell;
            this.cellStatus = status;
        }

        public void run() {
            try {
                // set the currently active cell
                currentCell.set(cell);
               
                setCellStatus(cell, cellStatus);
            } catch(Throwable t) {
                // Report the exception, otherwise it will get swallowed
                logger.log(Level.WARNING, "Exception thrown in Cell.setStatus "+t.getLocalizedMessage(), t);
            } finally {
                currentCell.remove();
            }
        }
    }
   
    private static class StatusCellFactory implements ThreadFactory {
        private static int count = 0;
       
        public synchronized static int nextCount() {
            return count++;
        }
       
        public Thread newThread(Runnable r) {
            return new Thread(r, "Cell Status Changer " + nextCount());
        }       
    }
   
    private static class URLDownloadCellFactory implements ThreadFactory {
        private static int count = 0;
       
        public synchronized static int nextCount() {
            return count++;
        }
       
        public Thread newThread(Runnable r) {
            return new Thread(r, "URL Download " + nextCount());
        }       
    }
}
TOP

Related Classes of org.jdesktop.wonderland.client.cell.CellCacheBasicImpl$CellStatusChanger

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.