package cz.cuni.amis.utils;

import java.lang.ref.SoftReference;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class ClassUtils {
	
	/**
	 * Cache for results of 'getSubclasses' call.
	 */
	private static Map<Class, SoftReference<Collection<Class>>> subclassesCache = 
		new HashMap<Class, SoftReference<Collection<Class>>>();
	
	/**
	 * Adds 'interf' and all interfaces it extends into 'interfaces', called recursively.
	 * 
	 * @param interf
	 * @param interfaces
	 */
	@SuppressWarnings("unchecked")
	private static void probeInterface(Class interf, Collection<Class> interfaces) {
		interfaces.add(interf);
		for (int i = 0; i < interf.getInterfaces().length; ++i) {
			probeInterface(interf.getInterfaces()[i], interfaces);
		}
	}
	
	/**
	 * Returns all interfaces and super-classes the class 'cls' implements / inherit including
	 * 'cls' itself.
	 * <p><p>
	 * <b>EXCEPT:</b>Object
	 * <p><p>
	 * Don't fear of the performance implications - the results are cached so
	 * every class is probed only once. 
	 * 
	 * @param cls
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static synchronized Collection<Class> getSubclasses(Class cls) {
		
		// get cached result
		SoftReference<Collection<Class>> reference = subclassesCache.get(cls);
		
		Collection<Class> classes = null;
		
		// do we have cached result?
		if (reference != null) {
			// probe the soft reference...
			classes = reference.get();			
		}
		
		// if classes are not null we've got cached result, return it...
		if (classes != null) return classes;		
		
		// if not ... probe the class
		classes = new HashSet<Class>();
		classes.add(cls);
		if (cls.getInterfaces() != null) {
			for (int i = 0; i < cls.getInterfaces().length; ++i) {
				probeInterface(cls.getInterfaces()[i], classes);				
			}
		}
		for (Class superClass = cls.getSuperclass(); superClass != null; superClass = superClass.getSuperclass()) {
			classes.add(superClass);
			if (superClass.getInterfaces() != null) {
				for (int i = 0; i < superClass.getInterfaces().length; ++i) {
					probeInterface(superClass.getInterfaces()[i], classes);				
				}
			}
		}
		classes.remove(Object.class);
		
		// save the result
		subclassesCache.put(cls, new SoftReference(classes));
		
		return classes;
	}

}
