/***
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2007 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. 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.
 * 3. Neither the name of the copyright holders 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 alterrs.asm.tree.analysis;

import java.util.List;

import alterrs.asm.Type;

/**
 * An extended {@link BasicVerifier} that performs more precise verifications.
 * This verifier computes exact class types, instead of using a single "object
 * reference" type (as done in the {@link BasicVerifier}).
 * 
 * @author Eric Bruneton
 * @author Bing Ran
 */
public class SimpleVerifier extends BasicVerifier {

	/**
	 * The class that is verified.
	 */
	private final Type currentClass;

	/**
	 * The super class of the class that is verified.
	 */
	private final Type currentSuperClass;

	/**
	 * The interfaces implemented by the class that is verified.
	 */
	private final List currentClassInterfaces;

	/**
	 * If the class that is verified is an interface.
	 */
	private final boolean isInterface;

	/**
	 * The loader to use for referenced classes.
	 */
	private ClassLoader loader = getClass().getClassLoader();

	/**
	 * Constructs a new {@link SimpleVerifier}.
	 */
	public SimpleVerifier() {
		this(null, null, false);
	}

	/**
	 * Constructs a new {@link SimpleVerifier} to verify a specific class. This
	 * class will not be loaded into the JVM since it may be incorrect.
	 * 
	 * @param currentClass
	 *            the class that is verified.
	 * @param currentSuperClass
	 *            the super class of the class that is verified.
	 * @param isInterface
	 *            if the class that is verified is an interface.
	 */
	public SimpleVerifier(final Type currentClass,
			final Type currentSuperClass, final boolean isInterface) {
		this(currentClass, currentSuperClass, null, isInterface);
	}

	/**
	 * Constructs a new {@link SimpleVerifier} to verify a specific class. This
	 * class will not be loaded into the JVM since it may be incorrect.
	 * 
	 * @param currentClass
	 *            the class that is verified.
	 * @param currentSuperClass
	 *            the super class of the class that is verified.
	 * @param currentClassInterfaces
	 *            the interfaces implemented by the class that is verified.
	 * @param isInterface
	 *            if the class that is verified is an interface.
	 */
	public SimpleVerifier(final Type currentClass,
			final Type currentSuperClass, final List currentClassInterfaces,
			final boolean isInterface) {
		this.currentClass = currentClass;
		this.currentSuperClass = currentSuperClass;
		this.currentClassInterfaces = currentClassInterfaces;
		this.isInterface = isInterface;
	}

	/**
	 * Set the <code>ClassLoader</code> which will be used to load referenced
	 * classes. This is useful if you are verifying multiple interdependent
	 * classes.
	 * 
	 * @param loader
	 *            a <code>ClassLoader</code> to use
	 */
	public void setClassLoader(final ClassLoader loader) {
		this.loader = loader;
	}

	public Value newValue(final Type type) {
		if (type == null) {
			return BasicValue.UNINITIALIZED_VALUE;
		}

		boolean isArray = type.getSort() == Type.ARRAY;
		if (isArray) {
			switch (type.getElementType().getSort()) {
			case Type.BOOLEAN:
			case Type.CHAR:
			case Type.BYTE:
			case Type.SHORT:
				return new BasicValue(type);
			}
		}

		Value v = super.newValue(type);
		if (BasicValue.REFERENCE_VALUE.equals(v)) {
			if (isArray) {
				v = newValue(type.getElementType());
				String desc = ((BasicValue) v).getType().getDescriptor();
				for (int i = 0; i < type.getDimensions(); ++i) {
					desc = '[' + desc;
				}
				v = new BasicValue(Type.getType(desc));
			} else {
				v = new BasicValue(type);
			}
		}
		return v;
	}

	protected boolean isArrayValue(final Value value) {
		Type t = ((BasicValue) value).getType();
		return t != null
				&& ("Lnull;".equals(t.getDescriptor()) || t.getSort() == Type.ARRAY);
	}

	protected Value getElementValue(final Value objectArrayValue)
			throws AnalyzerException {
		Type arrayType = ((BasicValue) objectArrayValue).getType();
		if (arrayType != null) {
			if (arrayType.getSort() == Type.ARRAY) {
				return newValue(Type.getType(arrayType.getDescriptor()
						.substring(1)));
			} else if ("Lnull;".equals(arrayType.getDescriptor())) {
				return objectArrayValue;
			}
		}
		throw new Error("Internal error");
	}

	protected boolean isSubTypeOf(final Value value, final Value expected) {
		Type expectedType = ((BasicValue) expected).getType();
		Type type = ((BasicValue) value).getType();
		switch (expectedType.getSort()) {
		case Type.INT:
		case Type.FLOAT:
		case Type.LONG:
		case Type.DOUBLE:
			return type.equals(expectedType);
		case Type.ARRAY:
		case Type.OBJECT:
			if ("Lnull;".equals(type.getDescriptor())) {
				return true;
			} else if (type.getSort() == Type.OBJECT
					|| type.getSort() == Type.ARRAY) {
				return isAssignableFrom(expectedType, type);
			} else {
				return false;
			}
		default:
			throw new Error("Internal error");
		}
	}

	public Value merge(final Value v, final Value w) {
		if (!v.equals(w)) {
			Type t = ((BasicValue) v).getType();
			Type u = ((BasicValue) w).getType();
			if (t != null
					&& (t.getSort() == Type.OBJECT || t.getSort() == Type.ARRAY)) {
				if (u != null
						&& (u.getSort() == Type.OBJECT || u.getSort() == Type.ARRAY)) {
					if ("Lnull;".equals(t.getDescriptor())) {
						return w;
					}
					if ("Lnull;".equals(u.getDescriptor())) {
						return v;
					}
					if (isAssignableFrom(t, u)) {
						return v;
					}
					if (isAssignableFrom(u, t)) {
						return w;
					}
					// TODO case of array classes of the same dimension
					// TODO should we look also for a common super interface?
					// problem: there may be several possible common super
					// interfaces
					do {
						if (t == null || isInterface(t)) {
							return BasicValue.REFERENCE_VALUE;
						}
						t = getSuperClass(t);
						if (isAssignableFrom(t, u)) {
							return newValue(t);
						}
					} while (true);
				}
			}
			return BasicValue.UNINITIALIZED_VALUE;
		}
		return v;
	}

	protected boolean isInterface(final Type t) {
		if (currentClass != null && t.equals(currentClass)) {
			return isInterface;
		}
		return getClass(t).isInterface();
	}

	protected Type getSuperClass(final Type t) {
		if (currentClass != null && t.equals(currentClass)) {
			return currentSuperClass;
		}
		Class c = getClass(t).getSuperclass();
		return c == null ? null : Type.getType(c);
	}

	protected boolean isAssignableFrom(final Type t, final Type u) {
		if (t.equals(u)) {
			return true;
		}
		if (currentClass != null && t.equals(currentClass)) {
			if (getSuperClass(u) == null) {
				return false;
			} else {
				if (isInterface) {
					return u.getSort() == Type.OBJECT
							|| u.getSort() == Type.ARRAY;
				}
				return isAssignableFrom(t, getSuperClass(u));
			}
		}
		if (currentClass != null && u.equals(currentClass)) {
			if (isAssignableFrom(t, currentSuperClass)) {
				return true;
			}
			if (currentClassInterfaces != null) {
				for (int i = 0; i < currentClassInterfaces.size(); ++i) {
					Type v = (Type) currentClassInterfaces.get(i);
					if (isAssignableFrom(t, v)) {
						return true;
					}
				}
			}
			return false;
		}
		Class tc = getClass(t);
		if (tc.isInterface()) {
			tc = Object.class;
		}
		return tc.isAssignableFrom(getClass(u));
	}

	protected Class getClass(final Type t) {
		try {
			if (t.getSort() == Type.ARRAY) {
				return Class.forName(t.getDescriptor().replace('/', '.'),
						false, loader);
			}
			return Class.forName(t.getClassName(), false, loader);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e.toString());
		}
	}
}
