content: /*
   *  Copyright Beijing 58 Information Technology Co.,Ltd.
   *
   *  Licensed to the Apache Software Foundation (ASF) under one
   *  or more contributor license agreements.  See the NOTICE file
   *  distributed with this work for additional information
   *  regarding copyright ownership.  The ASF licenses this file
   *  to you 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 com.bj58.spat.gaea.server.deploy.bytecode;
  
  import java.lang.reflect.Method;
  import java.lang.reflect.Modifier;
  import java.lang.reflect.Type;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import com.bj58.spat.gaea.server.contract.annotation.AnnotationUtil;
  import com.bj58.spat.gaea.server.contract.annotation.HttpPathParameter;
  import com.bj58.spat.gaea.server.contract.annotation.HttpRequestMapping;
  import com.bj58.spat.gaea.server.contract.annotation.OperationContract;
  import com.bj58.spat.gaea.server.contract.annotation.ServiceBehavior;
  import com.bj58.spat.gaea.server.contract.annotation.ServiceContract;
  import com.bj58.spat.gaea.server.contract.log.ILog;
  import com.bj58.spat.gaea.server.contract.log.LogFactory;
  import com.bj58.spat.gaea.server.deploy.bytecode.ContractInfo.SessionBean;
  import com.bj58.spat.gaea.server.deploy.hotdeploy.DynamicClassLoader;
  import com.bj58.spat.gaea.server.util.ClassHelper;
  import com.bj58.spat.gaea.server.util.FileHelper;
  
  /**
   * GaeaBinaryConvert
   * 
   * @author Service Platform Architecture Team (spat@58.com)
   */
  public class ScanClass {
  	
  	/**
  	 * logger
  	 */
  	private static ILog logger = LogFactory.getLogger(ScanClass.class);
  	
  	
  	/**
  	 * ContractInfo for create proxy
  	 */
  	private static ContractInfo contractInfo = null;
  	
  	/**
  	 * contract ClassInfo
  	 */
  	private static List<ClassInfo> contractClassInfos = null;
  	
  	/**
  	 * behavior ClassInfo
  	 */
  	private static List<ClassInfo> behaviorClassInfos = null;
  	
  	private static Object lockHelper = new Object();
  	
  	
  	/**
  	 * 
  	 * @param path
  	 * @param classLoader
  	 * @return
  	 * @throws Exception
  	 */
  	public static ContractInfo getContractInfo(String path, DynamicClassLoader classLoader) throws Exception {
  		if(contractInfo == null) {
  			synchronized (lockHelper) {
  				if(contractInfo == null) {
  					scan(path, classLoader);
  				}
  			}
  		}
  		
  		return contractInfo;
  	}
  	
  	/**
  	 * 
  	 * @param path
  	 * @param classLoader
  	 * @return
  	 * @throws Exception
  	 */
  	public static List<ClassInfo> getContractClassInfos(String path, DynamicClassLoader classLoader) throws Exception {
  		if(contractInfo == null) {
  			synchronized (lockHelper) {
  				if(contractInfo == null) {
  					scan(path, classLoader);
  				}
  			}
  		}
  		
  		return contractClassInfos;
  	}
  	
  	/**
  	 * 
  	 * @param path
  	 * @param classLoader
  	 * @return
  	 * @throws Exception
  	 */
  	public static List<ClassInfo> getBehaviorClassInfos(String path, DynamicClassLoader classLoader) throws Exception {
  		if(contractInfo == null) {
  			synchronized (lockHelper) {
  				if(contractInfo == null) {
  					scan(path, classLoader);
  				}
  			}
  		}
  		
  		return behaviorClassInfos;
  	}
  	
  	
  	/**
  	 * scan jars create ContractInfo
  	 * @param path
  	 * @param classLoader
  	 * @return
  	 * @throws Exception
  	 */
  	private static void scan(String path, DynamicClassLoader classLoader) throws Exception {
  		logger.info("begin scan jar from path:" + path);
  
  		List<String> jarPathList = FileHelper.getUniqueLibPath(path);
  
  		if(jarPathList == null) {
  		    throw new Exception("no jar fonded from path: " + path);
  		}
  
  		contractClassInfos = new ArrayList<ClassInfo>();
  		behaviorClassInfos = new ArrayList<ClassInfo>();
  
  		for (String jpath : jarPathList) {
  		    Set<Class<?>> clsSet = null;
  		    try {
  		   	 	clsSet = ClassHelper.getClassFromJar(jpath, classLoader);
  		    } catch (Exception ex) {
  		   	 	throw ex;
  		    }
  		    
  		    if (clsSet == null) {
  		        continue;
  		    }
  		    
  		    for (Class<?> cls : clsSet) {
  		        try {
  		       	 ServiceBehavior behavior = cls.getAnnotation(ServiceBehavior.class);
  		       	 ServiceContract contract = cls.getAnnotation(ServiceContract.class);
  		       	 if(behavior == null && contract == null) {
  		       		 continue;
  		       	 }
  		       	 
  		       	 if(contract != null) {
  		       		 ClassInfo ci = contract(cls);
  		       		 if(ci != null) {
  		       			 contractClassInfos.add(ci);
  		       		 }
  		       	 } else if(behavior != null) {
  		       		 ClassInfo ci = behavior(cls);
  		       		 if(ci != null) {
  		       			 behaviorClassInfos.add(ci);
  		       		 }
  		       	 }
  		        } catch (Exception ex) {
  		       	 throw ex;
  		        }
  		    }
  		}
  
  		contractInfo = createContractInfo(contractClassInfos, behaviorClassInfos);
  		
  		logger.info("finish scan jar");
  	}
  	
  	
  	/**
  	 * 
  	 * @param cls
  	 * @param ignoreAnnotation
  	 * @return
  	 */
  	protected static ClassInfo contract(Class<?> cls, boolean ignoreAnnotation) {
  		if(ignoreAnnotation) {
  			ClassInfo ci = new ClassInfo();
  			ci.setCls(cls);
  			ci.setClassType(ClassInfo.ClassType.INTERFACE);
  
  			Method[] methods = cls.getDeclaredMethods();
  			List<ClassInfo.MethodInfo> methodInfos = new ArrayList<ClassInfo.MethodInfo>();
  			
  			 for(Method m : methods) {
  				 if(Modifier.isPublic(m.getModifiers()) || Modifier.isProtected(m.getModifiers())) {
  					 ClassInfo.MethodInfo mi = new ClassInfo.MethodInfo();
  					 mi.setMethod(m);
  					 methodInfos.add(mi);
  				 }
  			 }
  			 ci.setMethodList(methodInfos);
  			 
  			 return ci;
  		} else {
  			return contract(cls);
  		}
  	}
  	
  	/**
  	 * 
  	 * @param cls
  	 * @return
  	 */
  	protected static ClassInfo contract(Class<?> cls) {
  		ServiceContract contractAnn = cls.getAnnotation(ServiceContract.class);
  		
  		ClassInfo ci = new ClassInfo();
  		ci.setCls(cls);
  		ci.setClassType(ClassInfo.ClassType.INTERFACE);
  
  		List<Class<?>> interfaceList = getInterfaces(cls);
  		List<ClassInfo.MethodInfo> methodInfos = new ArrayList<ClassInfo.MethodInfo>();
  
  		for(Class<?> interfaceCls : interfaceList) {
  			Method[] methods = interfaceCls.getDeclaredMethods();
  			if(contractAnn != null && contractAnn.defaultAll()) {
  				 for(Method m : methods) {
  					 if(Modifier.isPublic(m.getModifiers()) || Modifier.isProtected(m.getModifiers())) {
  						 ClassInfo.MethodInfo mi = new ClassInfo.MethodInfo();
  						 mi.setMethod(m);
  						 methodInfos.add(mi);
  					 }
  				 }
  			} else {
  				 for(Method m : methods) {
  					 if(Modifier.isPublic(m.getModifiers()) || Modifier.isProtected(m.getModifiers())) {
  						 OperationContract oc = m.getAnnotation(OperationContract.class);
  						 if(oc != null) {
  							 ClassInfo.MethodInfo mi = new ClassInfo.MethodInfo();
  							 mi.setMethod(m);
  							 methodInfos.add(mi);
  						 }
  					 }
  				 }
  			}
  		}
  		
  		ci.setMethodList(methodInfos);
  
  		return ci;
  		
  	}
  	
  	
  	/**
  	 * 
  	 * @param cls
  	 * @return
  	 * @throws Exception
  	 */
  	protected static ClassInfo behavior(Class<?> cls) throws Exception {
  		ServiceBehavior behaviorAnn = cls.getAnnotation(ServiceBehavior.class);
  
  		ClassInfo ci = new ClassInfo();
  		ci.setCls(cls);
  		ci.setClassType(ClassInfo.ClassType.CLASS);
  
  		if(behaviorAnn != null && !behaviorAnn.lookUP().equalsIgnoreCase(AnnotationUtil.DEFAULT_VALUE)) {
  			ci.setLookUP(behaviorAnn.lookUP());
  		} else {
  			ci.setLookUP(cls.getSimpleName());
  		}
  		Method[] methods = cls.getDeclaredMethods();
  		List<ClassInfo.MethodInfo> methodInfos = new ArrayList<ClassInfo.MethodInfo>();
  
  		for(Method m : methods) {
  			//only load public or protected method
  			if(Modifier.isPublic(m.getModifiers()) || Modifier.isProtected(m.getModifiers())) {
  				ClassInfo.MethodInfo mi = new ClassInfo.MethodInfo();
  				mi.setMethod(m);
  				
  				HttpRequestMapping requestMappingAnn = m.getAnnotation(HttpRequestMapping.class);
  				mi.setHttpRequestMapping(requestMappingAnn);
  				
  				Class<?>[] paramAry = m.getParameterTypes();
  				Type[] types = m.getGenericParameterTypes();
  				
  				String[] paramNames = ClassHelper.getParamNames(cls, m);
  				String[] mapping = new String[paramAry.length]; 
  				HttpPathParameter[] paramAnnAry = new HttpPathParameter[paramAry.length];
  
  				//load RequestMapping
  				if(requestMappingAnn != null) {
  					Object[][] annotations = ClassHelper.getParamAnnotations(cls, m);
  					for(int i=0; i<annotations.length; i++) {
  						for(int j=0; j<annotations[i].length; j++) {
  							HttpPathParameter paramAnn = null;
  							try {
  								paramAnn = (HttpPathParameter)annotations[i][j];
  							} catch(Exception ex) {
  								
  							}
  							
  							paramAnnAry[i] = paramAnn;
  							if(paramAnn != null) {
  								mapping[i] = paramAnn.mapping();
  								break;
  							} else {
  								mapping[i] = paramNames[i];
  							}
  						}
  					}
  					
  					for(int i=0; i<paramAry.length; i++) {
  						if(mapping[i] == null) {
  							mapping[i] = paramNames[i];
  						}
  					}
  				}
  				
  				ClassInfo.ParamInfo[] paramInfoAry = new ClassInfo.ParamInfo[paramAry.length];
  				for(int i=0; i<paramAry.length; i++) {
  					paramInfoAry[i] = new ClassInfo.ParamInfo(i, 
  							paramAry[i], 
  							types[i],
  							paramNames[i], 
  							mapping[i], 
  							paramAnnAry[i]);
  				}
  				mi.setParamInfoAry(paramInfoAry);
  				
  				methodInfos.add(mi);
  			}
  		}
  		ci.setMethodList(methodInfos);
  
  		return ci;
  	}
  	
  	/**
  	 * create ContractInfo from contracts, behaviors
  	 * @param contracts
  	 * @param behaviors
  	 * @return
  	 */
  	private static ContractInfo createContractInfo(List<ClassInfo> contracts,
  												   List<ClassInfo> behaviors) {
  		
  		ContractInfo contractInfo = new ContractInfo();
  		List<SessionBean> sessionBeanList = new ArrayList<SessionBean>();
  		for(ClassInfo c : contracts) {
  			SessionBean bean = new SessionBean();
  			bean.setInterfaceClass(c);
  			bean.setInterfaceName(c.getCls().getName());
  			Map<String, String> implMap = new HashMap<String, String>();
  			
  			for(ClassInfo b : behaviors) {
  				Class<?>[] interfaceAry = b.getCls().getInterfaces();
  				for(Class<?> item : interfaceAry) {
  					if(item == c.getCls()) {
  						implMap.put(b.getLookUP(), b.getCls().getName());
  						break;
  					}
  				}
  			}
  			bean.setInstanceMap(implMap);
  			sessionBeanList.add(bean);
  		}
  		
  		contractInfo.setSessionBeanList(sessionBeanList);
  		return contractInfo;
  	}
  	
  	/**
  	 * get all interfaces
  	 * @param cls
  	 * @return
  	 */
  	private static List<Class<?>> getInterfaces(Class<?> cls) {
  		List<Class<?>> clsList = new ArrayList<Class<?>>();
  		getInterfaces(cls, clsList);
  		return clsList;
  	}
  	
  	/**
  	 * get all interfaces
  	 * @param cls
  	 * @param clsList
  	 */
  	private static void getInterfaces(Class<?> cls, List<Class<?>> clsList) {
  		clsList.add(cls);
  		Class<?>[] aryCls = cls.getInterfaces();
  		
  		if(aryCls != null && aryCls.length > 0) {
  			for(Class<?> c : aryCls) {
  				getInterfaces(c, clsList);
  			}
  		}
  	}
  }