/*
 * Copyright (c) 2005-2007 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.jvnet.lafwidget.utils;

import java.awt.Component;
import java.io.*;
import java.net.URL;
import java.util.*;

import javax.swing.JToggleButton;
import javax.swing.ListCellRenderer;

/**
 * This class is used to decide whether the automatic animations in the
 * {@link FadeStateListener#trackModelChange(org.jvnet.lafwidget.utils.FadeTracker.FadeKind, boolean, boolean)}
 * should be applied on a specific component. The specific LAF implementation
 * may decide to ignore animations on cell renderers and editors.
 * 
 * <p>
 * At runtime, the manager is populated from
 * <code>META-INF/lafwidget.animations.properties</code> resources found in
 * classpath. This allows providing additional application-specific components
 * to be registered as animation-"free". Each such resource should contain a
 * list of fully qualified class names, one class name per line. The class name
 * may be a "leaf" class (such as {@link JToggleButton}), or can be a parent
 * class / interface (such as {@link ListCellRenderer}). In the later case, the
 * animations will be ignored on all components that extend such class /
 * implement such interface.
 * </p>
 * 
 * <p>
 * In addition, the {@link #addToIgnoreAnimations(Class)} and
 * {@link #addToIgnoreAnimations(String)} API can be used to register additional
 * application-specific components.
 * </p>
 * 
 * @author Kirill Grouchnikov
 */
public class FadeIgnoreManager {
	/**
	 * Contains {@link Class} instances. If a class instance is in this set, all
	 * components of this class (directly, via extension or via inheritance)
	 * will not have animations on model changes.
	 */
	protected Set<Class<?>> ignoreAnimationsOn;

	/**
	 * Contains {@link Class} instances. This serves as a cache to speed up the
	 * subsequent processing.
	 * 
	 * @see #toIgnoreAnimations(Component)
	 */
	protected Set<Class<?>> cache;

	/**
	 * Creates a new empty manager.
	 */
	public FadeIgnoreManager() {
		this.ignoreAnimationsOn = new HashSet<Class<?>>();
		this.cache = new HashSet<Class<?>>();
	}

	/**
	 * Populates the {@link #ignoreAnimationsOn} set. The classpath is scanned
	 * for all resources that match the name
	 * <code>META-INF/lafwidget.animations.properties</code>.
	 * 
	 * @see #populateFrom(URL)
	 */
	public void populate() {
		// the following is fix by Dag Joar and Christian Schlichtherle
		// for application running with -Xbootclasspath VM flag. In this case,
		// the using MyClass.class.getClassLoader() would return null,
		// but the context class loader will function properly
		// that classes will be properly loaded regardless of whether the lib is
		// added to the system class path, the extension class path and
		// regardless of the class loader architecture set up by some
		// frameworks.
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		try {
			Enumeration<?> rs = cl
					.getResources("META-INF/lafwidget.animations.properties");
			while (rs.hasMoreElements()) {
				URL rUrl = (URL) rs.nextElement();
				this.populateFrom(rUrl);
			}
		} catch (IOException ioe) {
		}
	}

	/**
	 * Populates the {@link #ignoreAnimationsOn} set from the specific URL
	 * resource. The resource should contain a list of fully qualified class
	 * names, one class name per line. The class name may be a "leaf" class
	 * (such as {@link JToggleButton}), or can be a parent class / interface
	 * (such as {@link ListCellRenderer}). In the later case, the animations
	 * will be ignored on all components that extend such class / implement such
	 * interface.
	 * 
	 * @param url
	 *            Resource URL.
	 */
	protected void populateFrom(URL url) {
		InputStream is = null;
		BufferedReader br = null;
		try {
			is = url.openStream();
			br = new BufferedReader(new InputStreamReader(is));
			while (true) {
				String line = br.readLine();
				if (line == null)
					break;
				line = line.trim();
				if (line.length() == 0)
					continue;
				this.addToIgnoreAnimations(line);
			}
		} catch (IOException ioe) {
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (IOException ioe) {
				}
			}
			if (br != null) {
				try {
					br.close();
				} catch (IOException ioe) {
				}
			}
		}
	}

	/**
	 * Adds the specified class to the set of components that should ignore
	 * animations.
	 * 
	 * @param clazz
	 *            Component class.
	 */
	public void addToIgnoreAnimations(Class<?> clazz) {
		this.ignoreAnimationsOn.add(clazz);
	}

	/**
	 * Adds the specified class name to the set of components that should ignore
	 * animations.
	 * 
	 * @param className
	 *            Component class name.
	 */
	public void addToIgnoreAnimations(String className) {
		try {
			this.ignoreAnimationsOn.add(Class.forName(className));
		} catch (Exception exc) {
		}
	}

	/**
	 * Returns indication whether the specified component should ignore
	 * animations. The component hierarchy is scanned from the component up. At
	 * each stage, the current ascendant is checked against all entries in the
	 * {@link #ignoreAnimationsOn} set. If the class of the current ascendant
	 * matches one of the entries, <code>true</code> is returned. Otherwise,
	 * <code>false</code> is returned (no ascendant found matching any of the
	 * entries in the {@link #ignoreAnimationsOn} set).
	 * 
	 * <p>
	 * The implementation uses the {@link #cache} tp speed up the processing.
	 * When a component class is determined to ignore animations, its class is
	 * put in this cache. The implementation first consults this cache - if the
	 * component class is found, <code>true</code> is returned. Otherwise, the
	 * above algorithm is performed.
	 * 
	 * @param comp
	 *            Component.
	 * @return <code>true</code> if the specified component should ignore
	 *         animations, <code>false</code> otherwise.
	 */
	public synchronized boolean toIgnoreAnimations(Component comp) {
		Component currComp = comp;
		while (currComp != null) {
			Class<?> currClazz = currComp.getClass();
			if (cache.contains(currClazz))
				return true;
			for (Iterator<Class<?>> it = this.ignoreAnimationsOn.iterator(); it.hasNext();) {
				Class<?> ignoreAnimationClazz = it.next();
				if (ignoreAnimationClazz.isAssignableFrom(currClazz)) {
					cache.add(currClazz);
					return true;
				}
			}
			currComp = currComp.getParent();
		}
		return false;
	}
}
