/*
 *  Copyright 2008 biaoping.yin
 *
 *  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.frameworkset.util.annotations;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.frameworkset.util.AntPathMatcher;
import org.frameworkset.util.Assert;
import org.frameworkset.util.BridgeMethodResolver;
import org.frameworkset.util.PathMatcher;

/**
 * <p>Title: AnnotationUtils.java</p> 
 * <p>Description: General utility methods for working with annotations, handling bridge methods
 * (which the compiler generates for generic declarations) as well as super
 * methods (for optional &quot;annotation inheritance&quot;). Note that none of
 * this is provided by the JDK's introspection facilities themselves.
 *
 * <p>As a general rule for runtime-retained annotations (e.g. for transaction
 * control, authorization or service exposure), always use the lookup methods on
 * this class (e.g., {@link #findAnnotation(Method, Class)},
 * {@link #getAnnotation(Method, Class)}, and {@link #getAnnotations(Method)})
 * instead of the plain annotation lookup methods in the JDK. You can still
 * explicitly choose between lookup on the given class level only
 * ({@link #getAnnotation(Method, Class)}) and lookup in the entire inheritance
 * hierarchy of the given method ({@link #findAnnotation(Method, Class)}).</p>
 * <p>bboss workgroup</p>
 * <p>Copyright (c) 2008</p>
 * @Date 2010-10-24
 * @author biaoping.yin
 * @version 1.0
 */
public abstract class AnnotationUtils {
	
	/** The attribute name for annotations with a single element */
	static final String	VALUE	= "value";
	static final PathMatcher pathmatcher = new AntPathMatcher();


	/**
	 * Get all {@link Annotation Annotations} from the supplied {@link Method}.
	 * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
	 * @param method the method to look for annotations on
	 * @return the annotations found
	 
	 */
	public static Annotation[] getAnnotations(Method method) {
		return BridgeMethodResolver.findBridgedMethod(method).getAnnotations();
	}

	/**
	 * Get a single {@link Annotation} of <code>annotationType</code> from the
	 * supplied {@link Method}.
	 * <p>Correctly handles bridge {@link Method Methods} generated by the compiler.
	 * @param method the method to look for annotations on
	 * @param annotationType the annotation class to look for
	 * @return the annotations found

	 */
	public static <A extends Annotation> A getAnnotation(Method method, Class<A> annotationType) {
		return BridgeMethodResolver.findBridgedMethod(method).getAnnotation(annotationType);
	}

	/**
	 * Get a single {@link Annotation} of <code>annotationType</code> from the
	 * supplied {@link Method}, traversing its super methods if no annotation
	 * can be found on the given method itself.
	 * <p>Annotations on methods are not inherited by default, so we need to handle
	 * this explicitly. Tge
	 * @param method the method to look for annotations on
	 * @param annotationType the annotation class to look for
	 * @return the annotation found, or <code>null</code> if none found
	 */
	public static <A extends Annotation> A findAnnotation(Method method, Class<A> annotationType) {
		A annotation = getAnnotation(method, annotationType);
		Class<?> cl = method.getDeclaringClass();
		while (annotation == null) {
			cl = cl.getSuperclass();
			if (cl == null || cl == Object.class) {
				break;
			}
			try {
				Method equivalentMethod = cl.getDeclaredMethod(method.getName(), method.getParameterTypes());
				annotation = getAnnotation(equivalentMethod, annotationType);
			}
			catch (NoSuchMethodException ex) {
				// We're done...
			}
		}
		return annotation;
	}

	/**
	 * Find a single {@link Annotation} of <code>annotationType</code> from the
	 * supplied {@link Class}, traversing its interfaces and super classes
	 * if no annotation can be found on the given class itself.
	 * <p>This method explicitly handles class-level annotations which are
	 * not declared as {@link java.lang.annotation.Inherited inherited}
	 * <i>as well as annotations on interfaces</i>.
	 * <p>The algorithm operates as follows: Searches for an annotation on the given
	 * class and returns it if found. Else searches all interfaces that the given
	 * class declares, returning the annotation from the first matching candidate,
	 * if any. Else proceeds with introspection of the superclass of the given class,
	 * checking the superclass itself; if no annotation found there, proceeds with
	 * the interfaces that the superclass declares. Recursing up through the entire
	 * superclass hierarchy if no match is found.
	 * @param clazz the class to look for annotations on
	 * @param annotationType the annotation class to look for
	 * @return the annotation found, or <code>null</code> if none found
	 */
	public static <A extends Annotation> A findAnnotation(Class<?> clazz, Class<A> annotationType) {
		Assert.notNull(clazz, "Class must not be null");
		A annotation = clazz.getAnnotation(annotationType);
		if (annotation != null) {
			return annotation;
		}
		for (Class<?> ifc : clazz.getInterfaces()) {
			annotation = findAnnotation(ifc, annotationType);
			if (annotation != null) {
				return annotation;
			}
		}
		Class superClass = clazz.getSuperclass();
		if (superClass == null || superClass == Object.class) {
			return null;
		}
		return findAnnotation(superClass, annotationType);
	}

	/**
	 * Find the first {@link Class} in the inheritance hierarchy of the
	 * specified <code>clazz</code> (including the specified
	 * <code>clazz</code> itself) which declares an annotation for the
	 * specified <code>annotationType</code>, or <code>null</code> if not
	 * found. If the supplied <code>clazz</code> is <code>null</code>,
	 * <code>null</code> will be returned.
	 * <p>If the supplied <code>clazz</code> is an interface, only the interface
	 * itself will be checked; the inheritance hierarchy for interfaces will not
	 * be traversed.
	 * <p>The standard {@link Class} API does not provide a mechanism for
	 * determining which class in an inheritance hierarchy actually declares an
	 * {@link Annotation}, so we need to handle this explicitly.
	 * @param annotationType the Class object corresponding to the annotation type
	 * @param clazz the Class object corresponding to the class on which to
	 * check for the annotation, or <code>null</code>.
	 * @return the first {@link Class} in the inheritance hierarchy of the
	 * specified <code>clazz</code> which declares an annotation for the specified
	 * <code>annotationType</code>, or <code>null</code> if not found.
	 * @see Class#isAnnotationPresent(Class)
	 * @see Class#getDeclaredAnnotations()
	 */
	public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation> annotationType, Class<?> clazz) {
		Assert.notNull(annotationType, "Annotation type must not be null");
		if (clazz == null || clazz.equals(Object.class)) {
			return null;
		}
		return (isAnnotationDeclaredLocally(annotationType, clazz)) ?
				clazz : findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
	}

	/**
	 * Determine whether an annotation for the specified <code>annotationType</code>
	 * is declared locally on the supplied <code>clazz</code>.
	 * The supplied {@link Class} object may represent any type.
	 * <p>Note: This method does <strong>not</strong> determine if the annotation
	 * is {@link java.lang.annotation.Inherited inherited}. For greater clarity
	 * regarding inherited annotations, consider using
	 * {@link #isAnnotationInherited(Class, Class)} instead.
	 * @param annotationType the Class object corresponding to the annotation type
	 * @param clazz the Class object corresponding to the class on which to
	 * check for the annotation
	 * @return <code>true</code> if an annotation for the specified
	 * <code>annotationType</code> is declared locally on the supplied <code>clazz</code>
	 * @see Class#getDeclaredAnnotations()
	 * @see #isAnnotationInherited(Class, Class)
	 */
	public static boolean isAnnotationDeclaredLocally(Class<? extends Annotation> annotationType, Class<?> clazz) {
		Assert.notNull(annotationType, "Annotation type must not be null");
		Assert.notNull(clazz, "Class must not be null");
		boolean declaredLocally = false;
		for (Annotation annotation : Arrays.asList(clazz.getDeclaredAnnotations())) {
			if (annotation.annotationType().equals(annotationType)) {
				declaredLocally = true;
				break;
			}
		}
		return declaredLocally;
	}

	/**
	 * Determine whether an annotation for the specified <code>annotationType</code>
	 * is present on the supplied <code>clazz</code> and is
	 * {@link java.lang.annotation.Inherited inherited}
	 * (i.e., not declared locally for the class).
	 * <p>If the supplied <code>clazz</code> is an interface, only the interface
	 * itself will be checked. In accord with standard meta-annotation
	 * semantics, the inheritance hierarchy for interfaces will not be
	 * traversed. See the {@link java.lang.annotation.Inherited JavaDoc} for the
	 * &#064;Inherited meta-annotation for further details regarding annotation
	 * inheritance.
	 * @param annotationType the Class object corresponding to the annotation type
	 * @param clazz the Class object corresponding to the class on which to
	 * check for the annotation
	 * @return <code>true</code> if an annotation for the specified
	 * <code>annotationType</code> is present on the supplied <code>clazz</code>
	 * and is {@link java.lang.annotation.Inherited inherited}
	 * @see Class#isAnnotationPresent(Class)
	 * @see #isAnnotationDeclaredLocally(Class, Class)
	 */
	public static boolean isAnnotationInherited(Class<? extends Annotation> annotationType, Class<?> clazz) {
		Assert.notNull(annotationType, "Annotation type must not be null");
		Assert.notNull(clazz, "Class must not be null");
		return (clazz.isAnnotationPresent(annotationType) && !isAnnotationDeclaredLocally(annotationType, clazz));
	}

	/**
	 * Retrieve the given annotation's attributes as a Map.
	 * @param annotation the annotation to retrieve the attributes for
	 * @return the Map of annotation attributes, with attribute names as keys
	 * and corresponding attribute values as values
	 */
	public static Map<String, Object> getAnnotationAttributes(Annotation annotation) {
		Map<String, Object> attrs = new HashMap<String, Object>();
		Method[] methods = annotation.annotationType().getDeclaredMethods();
		for (int j = 0; j < methods.length; j++) {
			Method method = methods[j];
			if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class) {
				try {
					attrs.put(method.getName(), method.invoke(annotation));
				}
				catch (Exception ex) {
					throw new IllegalStateException("Could not obtain annotation attribute values", ex);
				}
			}
		}
		return attrs;
	}

	/**
	 * Retrieve the <em>value</em> of the <code>&quot;value&quot;</code>
	 * attribute of a single-element Annotation, given an annotation instance.
	 * @param annotation the annotation instance from which to retrieve the value
	 * @return the attribute value, or <code>null</code> if not found
	 * @see #getValue(Annotation, String)
	 */
	public static Object getValue(Annotation annotation) {
		return getValue(annotation, VALUE);
	}

	/**
	 * Retrieve the <em>value</em> of a named Annotation attribute, given an
	 * annotation instance.
	 * @see #getValue(Annotation)
	 * @param annotation the annotation instance from which to retrieve the value
	 * @param attributeName the name of the attribute value to retrieve
	 * @return the attribute value, or <code>null</code> if not found
	 */
	public static Object getValue(Annotation annotation, String attributeName) {
		try {
			Method method = annotation.annotationType().getDeclaredMethod(attributeName, new Class[0]);
			return method.invoke(annotation);
		}
		catch (Exception ex) {
			return null;
		}
	}

	/**
	 * Retrieve the <em>default value</em> of the
	 * <code>&quot;value&quot;</code> attribute of a single-element
	 * Annotation, given an annotation instance.
	 * @param annotation the annotation instance from which to retrieve
	 * the default value
	 * @return the default value, or <code>null</code> if not found
	 * @see #getDefaultValue(Annotation, String)
	 */
	public static Object getDefaultValue(Annotation annotation) {
		return getDefaultValue(annotation, VALUE);
	}

	/**
	 * Retrieve the <em>default value</em> of a named Annotation attribute,
	 * given an annotation instance.
	 * @param annotation the annotation instance from which to retrieve
	 * the default value
	 * @param attributeName the name of the attribute value to retrieve
	 * @return the default value of the named attribute, or <code>null</code>
	 * if not found.
	 * @see #getDefaultValue(Class, String)
	 */
	public static Object getDefaultValue(Annotation annotation, String attributeName) {
		return getDefaultValue(annotation.annotationType(), attributeName);
	}

	/**
	 * Retrieve the <em>default value</em> of the
	 * <code>&quot;value&quot;</code> attribute of a single-element
	 * Annotation, given the {@link Class annotation type}.
	 * @param annotationType the <em>annotation type</em> for which the
	 * default value should be retrieved
	 * @return the default value, or <code>null</code> if not found
	 * @see #getDefaultValue(Class, String)
	 */
	public static Object getDefaultValue(Class<? extends Annotation> annotationType) {
		return getDefaultValue(annotationType, VALUE);
	}

	/**
	 * Retrieve the <em>default value</em> of a named Annotation attribute,
	 * given the {@link Class annotation type}.
	 * @param annotationType the <em>annotation type</em> for which the
	 * default value should be retrieved
	 * @param attributeName the name of the attribute value to retrieve.
	 * @return the default value of the named attribute, or <code>null</code>
	 * if not found
	 * @see #getDefaultValue(Annotation, String)
	 */
	public static Object getDefaultValue(Class<? extends Annotation> annotationType, String attributeName) {
		try {
			Method method = annotationType.getDeclaredMethod(attributeName, new Class[0]);
			return method.getDefaultValue();
		}
		catch (Exception ex) {
			return null;
		}
	}
	public static List<String> parserPathdata(String path)
	{
		if(path.startsWith("//"))
			path = path.substring(2);
		else if(path.startsWith("/"))
			path = path.substring(1);
		List<String> datas = new ArrayList<String>();
		int i = 0;
		char c = ' ';
		int end = path.length();
		StringBuilder bu = new StringBuilder();
		do
		{
			c = path.charAt(i);
			if(c == '/')
			{
				datas.add(bu.toString());
				bu.setLength(0);
				if(i == end -1)
				{
					datas.add("");
				}
			}
			else
			{
				bu.append(c);
			}
			i ++;
			
			
			
		}while(i < end);
		if(bu.length() > 0)
		{
			datas.add(bu.toString());
			bu = null;
		}
		return datas;
	}
	/**
	 * 获取restful路径中的变量的值
	 * @param method
	 * @param lookupPath
	 * @return
	 */
	public static Map resolvePathDatas(MethodInfo method,String lookupPath)
	{
		if(lookupPath == null || lookupPath.length() == 0)
			return null;
		PathVariableInfo[] variables = method.getPathVariables();
		if(variables == null || variables.length == 0)
			return null;
		List<String> datas = parserPathdata(lookupPath);
//		int j = 0;
//		if(lookupPath.startsWith("/") || lookupPath.startsWith("//"))
//			j = 1;
		Map retdatas = new HashMap();
//		Integer[] poses = method.getPathVariablePositions();
		for(int i = 0; i < variables.length; i ++)
		{
			PathVariableInfo variable = variables[i];
			int pos = variable.getPostion();
			String key = variable.getVariable();
			String value = datas.get(pos);
			if(variable.getConstantstr() == null)
			{
				retdatas.put(key, value);
			}
			else
			{
				if(value.endsWith(variable.getConstantstr()))
					retdatas.put(key, value.substring(0, value.length() - variable.getConstantstr().length()));
				else
				{
					retdatas.put(key, value);
				}
			}
		}
		return retdatas;
	}
	
	public static String converDefaultValue(String defaultValue)
	{
		if(ValueConstants.DEFAULT_NONE.equals(defaultValue))
			return null;
		return defaultValue;
				
	}

}
