/*
 * Copyright 2010-2012 VMware and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springsource.loaded;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * Representation of a Method. Some of the bitflags and state are only set for 'incremental' methods - those found in a secondary
 * type descriptor representing a type reload.
 * 
 * @author Andy Clement
 * @since 0.5.0
 */
public class MethodMember extends AbstractMember {

	final static MethodMember[] NONE = new MethodMember[0];

	protected final String[] exceptions;

	public int bits;

	// computed up front:
	public final static int BIT_CATCHER = 0x001;
	public final static int BIT_CLASH = 0x0002;
	// identifies a catcher method placed into an abstract class (where a method from a super interface hasn't been implemented)
	public final static int BIT_CATCHER_INTERFACE = 0x004;
	public final static int BIT_SUPERDISPATCHER = 0x0008;

	// computed on incremental members to indicate what changed:
	public final static int MADE_STATIC = 0x0010;
	public final static int MADE_NON_STATIC = 0x0020;
	public final static int VISIBILITY_CHANGE = 0x0040;
	public final static int IS_NEW = 0x0080;
	public final static int WAS_DELETED = 0x0100;
	public final static int EXCEPTIONS_CHANGE = 0x0200;

	// For MethodMembers in an incremental type descriptor, this tracks the method in the original type descriptor (if there was one)
	public MethodMember original;

	public final String nameAndDescriptor;

	public Method cachedMethod;

	protected MethodMember(int modifiers, String name, String descriptor, String signature, String[] exceptions) {
		super(modifiers, name, descriptor, signature);
		this.exceptions = perhapsSortIfNecessary(exceptions);
		this.nameAndDescriptor = new StringBuilder(name).append(descriptor).toString();
	}

	private String[] perhapsSortIfNecessary(String[] exceptions) {
		if (exceptions == null) {
			return Constants.NO_STRINGS;
		}
		//		Arrays.sort(exceptions);
		return exceptions;
	}

	public String[] getExceptions() {
		return exceptions;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("0x").append(Integer.toHexString(modifiers));
		sb.append(" ").append(name).append(descriptor);
		if (exceptions.length != 0) {
			sb.append(" throws ");
			for (String ex : exceptions) {
				sb.append(ex).append(" ");
			}
		}
		return sb.toString().trim();
	}

	public String getParamDescriptor() {
		// more likely to be at the end, lets go back from there
		for (int pos = descriptor.length() - 1; pos > 0; pos--) {
			if (descriptor.charAt(pos) == ')') {
				return descriptor.substring(0, pos + 1);
			}
		}
		throw new IllegalStateException("Method has invalid descriptor: " + descriptor);
	}

	public boolean hasReturnValue() {
		return descriptor.charAt(descriptor.length() - 1) != 'V';
	}

	public boolean equals(Object other) {
		if (!(other instanceof MethodMember)) {
			return false;
		}
		MethodMember o = (MethodMember) other;
		if (!name.equals(o.name)) {
			return false;
		}
		if (modifiers != o.modifiers) {
			return false;
		}
		if (!descriptor.equals(o.descriptor)) {
			return false;
		}
		if (exceptions.length != o.exceptions.length) {
			return false;
		}
		if (signature == null && o.signature != null) {
			return false;
		}
		if (signature != null && o.signature == null) {
			return false;
		}
		if (signature != null) {
			if (!signature.equals(o.signature)) {
				return false;
			}
		}
		for (int i = 0; i < exceptions.length; i++) {
			if (!exceptions[i].equals(o.exceptions[i])) {
				return false;
			}
		}
		return true;
	}

	public int hashCode() {
		int result = modifiers;
		result = result * 37 + name.hashCode();
		result = result * 37 + descriptor.hashCode();
		if (signature != null) {
			result = result * 37 + signature.hashCode();
		}
		if (exceptions != null) {
			for (String ex : exceptions) {
				result = result * 37 + ex.hashCode();
			}
		}
		return result;
	}

	public MethodMember catcherCopyOf() {
		int newModifiers = modifiers & ~Modifier.NATIVE;
		if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
			newModifiers = Modifier.PUBLIC;
		} else if ((modifiers & Modifier.PROTECTED) != 0) {
			// promote to public
			// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
			// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
			newModifiers = Modifier.PUBLIC;
		} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
			// promote to public from default
			// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
			// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
			newModifiers = Modifier.PUBLIC;
		}
		MethodMember copy = new MethodMember(newModifiers, name, descriptor, signature, exceptions);
		copy.bits |= MethodMember.BIT_CATCHER;
		return copy;
	}
	
	public MethodMember superDispatcherFor() {
		int newModifiers = modifiers & ~Modifier.NATIVE;
		if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
			newModifiers = Modifier.PUBLIC;
		} else if ((modifiers & Modifier.PROTECTED) != 0) {
			// promote to public
			// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
			// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
			newModifiers = Modifier.PUBLIC;
		} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
			// promote to public from default
			// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
			// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
			newModifiers = Modifier.PUBLIC;
		}
		MethodMember copy = new MethodMember(newModifiers, name+"_$superdispatcher$", descriptor, signature, exceptions);
		copy.bits |= MethodMember.BIT_SUPERDISPATCHER;
		return copy;
	}

	public MethodMember catcherCopyOfWithAbstractRemoved() {
		int newModifiers = modifiers & ~(Modifier.NATIVE | Modifier.ABSTRACT);
		if (name.equals("clone") && (modifiers & Modifier.NATIVE) != 0) {
			newModifiers = Modifier.PUBLIC;
		} else if ((modifiers & Modifier.PROTECTED) != 0) {
			// promote to public
			// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
			// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
			newModifiers = Modifier.PUBLIC;
		} else if ((modifiers & Constants.ACC_PUBLIC_PRIVATE_PROTECTED) == 0) {
			// promote to public from default
			// The reason for this is that the executor may try and call these things and as it is not in the hierarchy
			// it cannot. The necessary knock on effect is that subtypes get their methods promoted to public too...
			newModifiers = Modifier.PUBLIC;
		}
		MethodMember copy = new MethodMember(newModifiers, name, descriptor, signature, exceptions);
		copy.bits |= MethodMember.BIT_CATCHER;
		copy.bits |= MethodMember.BIT_CATCHER_INTERFACE;
		return copy;
	}

	public boolean equalsApartFromModifiers(MethodMember other) {
		if (!(other instanceof MethodMember)) {
			return false;
		}
		MethodMember o = other;
		if (!name.equals(o.name)) {
			return false;
		}
		if (!descriptor.equals(o.descriptor)) {
			return false;
		}
		//		if (exceptions.length != o.exceptions.length) {
		//			return false;
		//		}
		//		for (int i = 0; i < exceptions.length; i++) {
		//			if (!exceptions[i].equals(o.exceptions[i])) {
		//				return false;
		//			}
		//		}
		return true;
	}

	public String getNameAndDescriptor() {
		return nameAndDescriptor;
	}

	public static boolean isClash(MethodMember method) {
		return (method.bits & MethodMember.BIT_CLASH) != 0;
	}
	
	public static boolean isSuperDispatcher(MethodMember method) {
		return (method.bits & BIT_SUPERDISPATCHER) != 0;
	}

	public static boolean isCatcher(MethodMember method) {
		return (method.bits & BIT_CATCHER) != 0;
	}

	public static boolean isCatcherForInterfaceMethod(MethodMember method) {
		return (method.bits & BIT_CATCHER_INTERFACE) != 0;
	}

	public static boolean isDeleted(MethodMember method) {
		return (method.bits & WAS_DELETED) != 0;
	}

	public Object bitsToString() {
		StringBuilder s = new StringBuilder();
		if ((bits & BIT_CATCHER) != 0) {
			s.append("catcher ");
		}
		if ((bits & BIT_CLASH) != 0) {
			s.append("clash ");
		}
		if ((bits & BIT_SUPERDISPATCHER) != 0) {
			s.append("superdispatcher ");
		}
		if ((bits & MADE_STATIC) != 0) {
			s.append("made_static ");
		}
		if ((bits & MADE_NON_STATIC) != 0) {
			s.append("made_non_static ");
		}
		if ((bits & VISIBILITY_CHANGE) != 0) {
			s.append("vis_change ");
		}
		if ((bits & IS_NEW) != 0) {
			s.append("is_new ");
		}
		if ((bits & WAS_DELETED) != 0) {
			s.append("is_new ");
		}
		return "[" + s.toString().trim() + "]";
	}

	/*
	 * Determine whether this method should replace the other method on reload. In accordance to how JVM works at class load time,
	 * this will be the case if this and other have the same Class, name, parameter types and return type. I.e. formally, in JVM
	 * bytecode (unlike source code) a method doesn't override a method with a different return type. When such a situation occurs
	 * in source code, the compiler will introduce a bridge method in bytecode.
	 */
	public boolean shouldReplace(MethodMember other) {
		if (!name.equals(other.name)) {
			return false;
		}
		if (!descriptor.equals(other.descriptor)) {
			return false;
		}
		return true;
	}

	public boolean isConstructor() {
		return name.equals("<init>");
	}

}
