/**
 * Copyright 2010-2013 Jingjing Li.
 *
 * This file is part of jplot2d.
 *
 * jplot2d is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or any later version.
 *
 * jplot2d is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with jplot2d. If not, see .
 */
package org.jplot2d.env;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantLock;

import org.jplot2d.element.Element;
import org.jplot2d.element.PComponent;
import org.jplot2d.element.impl.ComponentEx;
import org.jplot2d.element.impl.ElementEx;
import org.jplot2d.notice.NoticeType;
import org.jplot2d.notice.Notifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An environment that a plot can realization. Once a plot is put an environment, changes on the plot can cause
 * re-layout or redraw, according to the changed property.
 * 

* Renderers can be added to an environment to get the rendered result synchronously or asynchronously. * * @author Jingjing Li * */ public abstract class Environment { private static final Logger logger = LoggerFactory.getLogger("org.jplot2d.env"); /** * The global locking object for get/set environment. */ private static final Object LOCK = new Object(); private static final int MAX_BATCH_DEPTH = 64; private final ReentrantLock lock; /** * batch SN in every depth. batchSND[n], n is batch depth -1. the value is the SN for the batch depth */ private int[] batchSND = new int[MAX_BATCH_DEPTH]; private int batchDepth; /** * To log a command */ protected CommandLogger cmdLogger; /** * To notify something when ending a command. */ protected Notifier notifier; /** * A impl to proxy map that contains all element in this environment. It's a LinkedHashMap to keep the order of * elements added. In case of the same z-order, the adding order take into account. */ protected final Map proxyMap = new LinkedHashMap(); private final List plotStructureListenerList = Collections .synchronizedList(new ArrayList()); protected static Object getGlobalLock() { return LOCK; } /** * Returns true if the given element a is ancestor of element c, or they are the same element. * * @param a * @param c * @return */ protected static boolean isAncestor(Element a, Element c) { if (c == null) { return false; } else if (c == a) { return true; } else { return isAncestor(a, c.getParent()); } } /** * Returns the first cacheable parent. If all its ancestor are not cacheable, the top ancestor will be returned. * * @param comp * @return */ protected static final ComponentEx getCacheableAncestor(ComponentEx comp) { if (comp.isCacheable()) { return comp; } else { ComponentEx parent = comp.getParent(); if (parent == null) { return comp; } else { return getCacheableAncestor(parent); } } } protected Environment(boolean threadSafe) { if (threadSafe) { lock = new ReentrantLock(); } else { lock = null; } } /** * Returns the proxy wrapper of the given element. * * @param impl * @return */ public final Element getProxy(ElementEx impl) { return proxyMap.get(impl); } /** * Called after a component has been added to this environment, and its parent has been set. * * @param element * the added element * @param env * the environment of the added element */ void elementAdded(ElementEx element, Environment env) { proxyMap.putAll(env.proxyMap); env.proxyMap.clear(); } /** * Called after a component has been added to this environment, and its parent has been set. Or called when adding a * top plot to a plot environment. The added component either is cacheable, or has a cacheable parent. * * @param comp * the added ComponentEx * @param env * the environment of the added component */ void componentAdded(ComponentEx comp, Environment env) { proxyMap.putAll(env.proxyMap); env.proxyMap.clear(); fireComponentAdded((PComponent) getProxy(comp)); } /** * A component is going to be removed from this environment * * @param comp * @param elements * elements to be removed. */ void componentRemoving(ComponentEx comp) { fireComponentRemoving((PComponent) getProxy(comp)); } /** * Called when a element has been removed from its parent * * @param comp * the removed component * @return a DummyEnvironment hosts the removed component and descendants. */ DummyEnvironment componentRemoved(ElementEx comp) { DummyEnvironment result = createDummyEnvironment(); // remove all elements whoes parent has been removed Iterator> ite = proxyMap.entrySet().iterator(); while (ite.hasNext()) { Entry e = ite.next(); if (isAncestor(comp, e.getKey())) { result.proxyMap.put(e.getKey(), e.getValue()); ite.remove(); } } fireComponentRemoved((PComponent) getProxy(comp)); return result; } void elementPropertyChanged(ElementEx comp) { fireElementPropertyChanged(getProxy(comp)); } /* --- JPlot2DChangeListener --- */ private void fireComponentAdded(PComponent element) { ElementChangeListener[] ls = getElementChangeListeners(); if (ls.length > 0) { ElementChangeEvent evt = new ElementChangeEvent(this, element); for (ElementChangeListener lsnr : ls) { lsnr.componentAdded(evt); } } } private void fireComponentRemoving(PComponent element) { ElementChangeListener[] ls = getElementChangeListeners(); if (ls.length > 0) { ElementChangeEvent evt = new ElementChangeEvent(this, element); for (ElementChangeListener lsnr : ls) { lsnr.componentRemoving(evt); } } } private void fireComponentRemoved(PComponent element) { ElementChangeListener[] ls = getElementChangeListeners(); if (ls.length > 0) { ElementChangeEvent evt = new ElementChangeEvent(this, element); for (ElementChangeListener lsnr : ls) { lsnr.componentRemoved(evt); } } } /** * Notify all registered JPlot2DChangeListener that the properties of the given element has been changed. * * @param element */ private void fireElementPropertyChanged(Element element) { ElementChangeListener[] ls = getElementChangeListeners(); if (ls.length > 0) { ElementChangeEvent evt = new ElementChangeEvent(this, element); for (ElementChangeListener lsnr : ls) { lsnr.propertiesChanged(evt); } } } /** * Returns all ElementChangeListeners. * * @return ElementChangeListener in an array */ public ElementChangeListener[] getElementChangeListeners() { return plotStructureListenerList.toArray(new ElementChangeListener[0]); } /** * Add an ElementChangeListener * * @param listener */ public void addElementChangeListener(ElementChangeListener listener) { plotStructureListenerList.add(listener); } /** * Remove an ElementChangeListener * * @param listener */ public void removeElementChangeListener(ElementChangeListener listener) { plotStructureListenerList.remove(listener); } /** * Return the command logger who receive commands executed in this environment. If there is no command logger, * returns null. * * @return the command logger */ public CommandLogger getCommandLogger() { return cmdLogger; } /** * Sets a command logger to receive commands executed in this environment. * * @param cmdLogger * a CommandLogger */ public void setCommandLogger(CommandLogger cmdLogger) { this.cmdLogger = cmdLogger; } /** * Create a dummy environment. * * @return a dummy environment */ protected DummyEnvironment createDummyEnvironment() { return new DummyEnvironment((lock != null)); } private String getBatchString() { if (batchDepth == 0) { throw new Error("Not in batch block."); } String result = "(" + batchSND[0]; for (int i = 1; i < batchDepth; i++) { result += "-" + batchSND[i]; } return result + ")"; } private BatchToken createBatchToken() { return new BatchToken(batchDepth, batchSND); } private boolean verifyBatchToken(BatchToken token) { return token.match(batchDepth, batchSND); } /** * Begin a batch block. In this mode, all update on plot will not be processed, until endBatch is called. If this * environment is thread-safe, this method also lock the ReentrantLock, will block other thread call this method. * * @param msg * the short description about the batch * @return a batch token */ public final BatchToken beginBatch(String msg) { beginCommand(msg); return createBatchToken(); } /** * End the batch. All pending update will be committed at once. * * @param token * the token gotten from beginBatch */ public final void endBatch(BatchToken token) { endBatch(token, null); } /** * End the batch. All pending update will be committed at once. * * @param token * the token gotten from beginBatch * @param type * the type for notice processor */ public final void endBatch(BatchToken token, NoticeType type) { begin(); if (!verifyBatchToken(token)) { throw new IllegalArgumentException("Batch token not match. Require " + getBatchString() + " but is " + token); } endCommand(type); end(); } /** * The calling to method must be surrounded by {@link #begin()} and {@link #end()}. * * @return if this environment is in batch mode. */ protected final boolean isBatch() { return batchDepth > 0; } /** * A light weight version of beginBatch, without creating a batch token. *

* This method is called from element proxy invocation handler before a setter method of the proxy is called. * * @param msg * the message for logging */ protected final void beginCommand(String msg) { begin(); ++batchSND[batchDepth]; batchDepth++; batchSND[batchDepth] = 0; logger.trace("[>] {}{} {}", Integer.toHexString(hashCode()), getBatchString(), msg); } /** * A light weight version of endBatch, without verifying a batch token. It will never throw an exception in any * case. *

* This method is called from element proxy invocation handler after a setter method of the proxy is called. */ protected final void endCommand() { endCommand(null); } /** * A light weight version of endBatch, without verifying a batch token. It will never throw an exception in any * case. * * @param type * the type for notice processor */ private void endCommand(NoticeType type) { logger.trace("[<] {}{}", Integer.toHexString(hashCode()), getBatchString()); if (batchDepth == 1) { try { commit(); } catch (Exception e) { logger.warn("", e); } if (notifier != null) { try { notifier.processNotices(type); } catch (Exception e) { logger.warn("", e); } } } batchDepth--; end(); } /** * The begin of a block wrapper, to synchronize calling. */ protected final void begin() { if (lock != null) { lock.lock(); } } /** * The end of a block wrapper, to synchronize calling. */ protected final void end() { if (lock != null) { lock.unlock(); } } /** * Commit all pending processes in a batch. The isBatch() should remain true while this method is * called. This method can be called many times. */ protected abstract void commit(); }

Ads help maintain this website.