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.Type;
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  
  import javassist.ClassPool;
  import javassist.CtClass;
  import javassist.CtField;
  import javassist.CtMethod;
  
  import com.bj58.spat.gaea.server.contract.annotation.AnnotationUtil;
  import com.bj58.spat.gaea.server.contract.annotation.OperationContract;
  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.ClassInfo.MethodInfo;
  import com.bj58.spat.gaea.server.deploy.hotdeploy.DynamicClassLoader;
  
  /**
   * GaeaBinaryConvert
   * 
   * @author Service Platform Architecture Team (spat@58.com)
   */
  public class ProxyClassCreaterV2 {
  
  	private static ILog logger = LogFactory.getLogger(ProxyClassCreater.class);
  	
  	
  	@SuppressWarnings("rawtypes")
  	public List<ClassFile> createProxy(DynamicClassLoader classLoader, 
  									   ContractInfo serviceContract, 
  									   long time) throws Exception {
  		
  		logger.info("begin create dynamic proxy v2...");
  		List<ClassFile> clsList = new ArrayList<ClassFile>();
  		
  		for (ContractInfo.SessionBean sessionBean : serviceContract.getSessionBeanList()) {
  			if(sessionBean.getInterfaceClass() != null) {
  				Iterator it = sessionBean.getInstanceMap().entrySet().iterator();
  				while (it.hasNext()) {
  					Map.Entry entry = (Map.Entry) it.next();
  					String lookup = entry.getKey().toString();
  					String implClassName = entry.getValue().toString();
  					String proxyClassName = lookup + "ProxyStub" + time;
  					logger.info("create => " + proxyClassName);
  					logger.info("implClassName:"+implClassName);
  					
  					ClassPool pool = ClassPool.getDefault();
  					
  					List<String> jarList = classLoader.getJarList();
  					for(String jar : jarList) {
  						pool.appendClassPath(jar);
  					}
  	
  					CtClass ctProxyClass = pool.makeClass(proxyClassName, null);
  					
  					CtClass localProxy = pool.getCtClass(Constant.IPROXYSTUB_CLASS_NAME);
  					ctProxyClass.addInterface(localProxy);
  					
  					
  					CtField proxyField = CtField.make("private static " +
  														sessionBean.getInterfaceName() +
  														" serviceProxy = new " +
  														implClassName +
  														"();", ctProxyClass);
  					ctProxyClass.addField(proxyField);
  					
  					
  					List<MethodInfo> methodList = sessionBean.getInterfaceClass().getMethodList();
  
  	
  					//method
  					for(MethodInfo mi : methodList) {
  						Method m = mi.getMethod();
  						logger.debug("create method:" + m.getName());
  						String methodStr = createMethods(proxyClassName, mi);
  						logger.debug("method("+m.getName()+") source code:"+methodStr);
  						CtMethod methodItem = CtMethod.make(methodStr, ctProxyClass);
  						ctProxyClass.addMethod(methodItem);
  					}					
  					
  					//invoke
  					String invokeMethod = createInvoke(proxyClassName, methodList);
  					logger.debug("create invoke method:" + invokeMethod);
  					CtMethod invoke = CtMethod.make(invokeMethod, ctProxyClass);
  					ctProxyClass.addMethod(invoke);
  	
  					clsList.add(new ClassFile(proxyClassName, ctProxyClass.toBytecode()));
  				}
  			}
  		}
  		logger.info("create dynamic proxy success!!!");
  		return clsList;
  	}
  	
  
  	/**
  	 * ceate invoke method
  	 * @param uniqueNameList
  	 * @return
  	 */
  	private String createInvoke(String className, List<MethodInfo> miList) {
  		StringBuilder sb = new StringBuilder();
  		sb.append("public " + Constant.GAEARESPONSE_CLASS_NAME + " invoke(" + Constant.GAEACONTEXT_CLASS_NAME + " context) throws " + Constant.SERVICEFRAMEEXCEPTION_CLASS_NAME + " {");
  		sb.append("String methodName = ((" + Constant.REQUEST_PROTOCOL_CLASS_NAME + ")context.getGaeaRequest().getProtocol().getSdpEntity()).getMethodName();");
  		for (MethodInfo mi : miList) {
  			String methodName = getMethodName(mi);
  			
  			sb.append("if(methodName.equalsIgnoreCase(\"");
  			sb.append(methodName);
  			sb.append("\")){return ");
  			sb.append(methodName);
  			sb.append("(context);}");
  		}
  		sb.append("throw new " + Constant.SERVICEFRAMEEXCEPTION_CLASS_NAME + "(\"method:" + className + ".invoke--msg:not found method (\"+methodName+\")\", context.getChannel().getRemoteIP(), context.getChannel().getLocalIP(), context.getGaeaRequest().getProtocol().getSdpEntity(), " + Constant.ERRORSTATE_CLASS_NAME + ".NotFoundMethodException, null);");
  		sb.append("}");
  		return sb.toString();
  	}
  	
  	
  	/**
  	 * create service method
  	 * @param methodName
  	 * @param methodList
  	 * @param uniqueNameList
  	 * @return
  	 */
  	public String createMethods(String className, MethodInfo mi) {
  		String methodName = getMethodName(mi);
  		
  		StringBuilder sb = new StringBuilder();
  		sb.append("public " + Constant.GAEARESPONSE_CLASS_NAME + " ");
  		sb.append(methodName);
  		sb.append("(" + Constant.GAEACONTEXT_CLASS_NAME + " context) throws " + Constant.SERVICEFRAMEEXCEPTION_CLASS_NAME + " {"); // begin method
  
  		// define local parameter: convert and request
  		defineConvertRequest(sb);
  
  		// define parameter
  		definaMethodParam(sb, className, mi);
  		
  		// set value to parameter
  		setValueToParam(sb, className, mi);
  		
  		// invoke real method
  		invokeRealMethod(sb, className, mi);
  		
  
  		sb.append("}"); // method end
  		return sb.toString();
  	}
  	
  	
  
  	
  	
  	private void defineConvertRequest(StringBuilder sb){
  		sb.append(Constant.ICONVERT_CLASS_NAME + " convert = " + Constant.CONVERT_FACTORY_CLASS_NAME + ".getConvert(context.getGaeaRequest().getProtocol());");
  		sb.append(Constant.REQUEST_PROTOCOL_CLASS_NAME + " request = (" + Constant.REQUEST_PROTOCOL_CLASS_NAME + ")context.getGaeaRequest().getProtocol().getSdpEntity();");
  		sb.append("java.util.List listKV = request.getParaKVList();");
  	}
  	
  	
  	private void definaMethodParam(StringBuilder sb, String className, MethodInfo mi) {
  		Class<?>[] mType = mi.getMethod().getParameterTypes();
  		Type[] mGenericType = mi.getMethod().getGenericParameterTypes();
  		
  		sb.append("java.util.List listOutPara = new java.util.ArrayList();");
  		
  		for(int i=0; i<mGenericType.length;i++) {
  			String paraName = mGenericType[i].toString().replaceFirst("class ", "");
  			boolean isOutPara = false;
  			if(paraName.startsWith(Constant.OUT_PARAM)) {
  				isOutPara = true;
  			}
  			
  			if(paraName.startsWith("[")) {
  				paraName = mType[i].getCanonicalName();
  			}
  			if(isOutPara){
  				sb.append(Constant.OUT_PARAM + " arg" + i);
  			} else {
  				sb.append(paraName.replaceAll("\\<.*\\>", ""));
  				sb.append(" arg" + i);
  			}
  			
  			if(!isOutPara) {
  				paraName = paraName.replaceAll("java.util.", "").replaceAll("java.lang.", "");
  				
  				if(paraName.equals("long")){
  					sb.append(" = 0L;");
  				} else if(paraName.equals("float")){
  					sb.append(" = 0F;");
  				} else if(paraName.equals("double")){
  					sb.append(" = 0D;");
  				} else if(paraName.equals("int")){
  					sb.append(" = 0;");
  				} else if(paraName.equals("short")){
  					sb.append(" = (short)0;");
  				} else if(paraName.equals("byte")){
  					sb.append(" = (byte)0;");
  				} else if(paraName.equals("boolean")){
  					sb.append(" = false;");
  				} else if(paraName.equals("char")){
  					sb.append(" = (char)'\\0';");
  				}
  				
  				else if(paraName.equals("Long")){
  					sb.append(" = new Long(\"0\");");
  				} else if(paraName.equals("Float")){
  					sb.append(" = new Float(\"0\");");
  				} else if(paraName.equals("Double")){
  					sb.append(" = new Double(\"0\");");
  				} else if(paraName.equals("Integer")){
  					sb.append(" = new Integer(\"0\");");
  				} else if(paraName.equals("Short")){
  					sb.append(" = new Short(\"0\");");
  				} else if(paraName.equals("Byte")){
  					sb.append(" = new Byte(\"0\");");
  				} else if(paraName.equals("Boolean")){
  					sb.append(" = new Boolean(\"false\");");
  				} else if(paraName.equals("Character")){
  					sb.append(" = new Character((char)'\\0');");
  				} else{
  					sb.append(" = null;");
  				}
  			} else {
  				sb.append(" = new " + Constant.OUT_PARAM);
  				sb.append("();");
  			}
  			
  			if(isOutPara){
  				sb.append("listOutPara.add(arg"+i+");");
  			}
  		}
  	}
  	
  	
  	private void setValueToParam(StringBuilder sb, String className, MethodInfo mi) {
  		Class<?>[] mType = mi.getMethod().getParameterTypes();
  		Type[] mGenericType = mi.getMethod().getGenericParameterTypes();
  		String methodName = getMethodName(mi);
  		
  		if(mGenericType.length > 0) {
  			sb.append("try {"); //begin try(set value to param)
  		}
  		for(int i=0; i<mGenericType.length;i++){
  			String paraName = mGenericType[i].toString().replaceFirst("class ", "");
  			boolean isOutPara = false;
  			if(paraName.startsWith(Constant.OUT_PARAM)){
  				isOutPara = true;
  			}
  			
  			if(paraName.startsWith("[")){
  				paraName = mType[i].getCanonicalName();
  			}
  			
  			String pn = paraName.replaceAll("java.util.", "").replaceAll("java.lang.", "");
  			if(!isOutPara){
  				if (pn.equalsIgnoreCase("String")
  						|| pn.equalsIgnoreCase("int") || pn.equalsIgnoreCase("Integer")
  						|| pn.equalsIgnoreCase("long")
  						|| pn.equalsIgnoreCase("short")
  						|| pn.equalsIgnoreCase("float")
  						|| pn.equalsIgnoreCase("boolean")
  						|| pn.equalsIgnoreCase("double")
  						|| pn.equalsIgnoreCase("char") || pn.equalsIgnoreCase("Character")
  						|| pn.equalsIgnoreCase("byte")){
  					
  					sb.append("arg"+i);
  					sb.append(" = convert.convertTo"+pn+"(((" + Constant.KEYVALUEPAIR_PROTOCOL_CLASS_NAME + ")listKV.get("+i+")).getValue());");
  				} else {
  					sb.append("arg"+i);
  					sb.append(" = ("+paraName.replaceAll("\\<.*\\>", "")+")convert.convertToT(((" + Constant.KEYVALUEPAIR_PROTOCOL_CLASS_NAME + ")listKV.get("+i+")).getValue(), ");
  					sb.append(paraName.replaceAll("<.*?>", "") + ".class");
  					
  					if(paraName.indexOf("java.util.List") >=0 ||
  							paraName.indexOf("java.util.ArrayList") >=0 ||
  							paraName.indexOf("java.util.Vector") >=0 ||
  							paraName.indexOf("java.util.Set") >=0 ||
  							paraName.indexOf("java.util.HashSet") >=0 ) {
  						
  						sb.append(", ");
  						sb.append(paraName.replaceAll("java.util.List<", "")
  										  .replaceAll("java.util.ArrayList<", "")
  										  .replaceAll("java.util.Vector<", "")
  										  .replaceAll("java.util.Set<", "")
  										  .replaceAll("java.util.HashSet<", "")
  										  .replaceAll(">", "")
  						);
  						sb.append(".class");
  					}
  					sb.append(");");
  				}
  			} else {
  				String outType = paraName.replaceAll(Constant.OUT_PARAM + "<", "");
  				outType = outType.substring(0, outType.length() - 1);
  				
  				String outpn = outType.replaceAll("java.util.", "")
  				   					  .replaceAll("java.lang.", "");
  				
  				if (outpn.equalsIgnoreCase("String")
  						|| outpn.equalsIgnoreCase("Integer")
  						|| outpn.equalsIgnoreCase("Long")
  						|| outpn.equalsIgnoreCase("Short")
  						|| outpn.equalsIgnoreCase("Float")
  						|| outpn.equalsIgnoreCase("Boolean")
  						|| outpn.equalsIgnoreCase("Double")
  						|| outpn.equalsIgnoreCase("Character")
  						|| outpn.equalsIgnoreCase("Byte")){
  					
  					sb.append("arg"+i);
  					sb.append(".setOutPara(convert.convertTo"+outpn+"(((" + Constant.KEYVALUEPAIR_PROTOCOL_CLASS_NAME + ")listKV.get("+i+")).getValue()));");
  				} else {
  					sb.append("arg"+i);
  					sb.append(".setOutPara(("+outType+")convert.convertToT(((" + Constant.KEYVALUEPAIR_PROTOCOL_CLASS_NAME + ")listKV.get("+i+")).getValue(), "+outType+".class");
  					
  					if(outType.indexOf("java.util.List") >=0 ||
  							outType.indexOf("java.util.ArrayList") >=0 ||
  							outType.indexOf("java.util.Vector") >=0 ||
  							outType.indexOf("java.util.Set") >=0 ||
  							outType.indexOf("java.util.HashSet") >=0 ) {
  						
  						sb.append(", ");
  						sb.append(outType.replaceAll("java.util.List<", "")
  										 .replaceAll("java.util.ArrayList<", "")
  										 .replaceAll("java.util.Vector<", "")
  										 .replaceAll("java.util.Set<", "")
  										 .replaceAll("java.util.HashSet<", "")
  										 .replaceAll(">", "")
  						);
  						sb.append(".class");
  					}
  					sb.append("));");
  				}
  			}
  		}
  		
  		if(mGenericType.length > 0) {
  			sb.append("} catch (Exception e) {"); // end try(set value to param)   begin catch(set value to param)
  			sb.append("throw new " + Constant.SERVICEFRAMEEXCEPTION_CLASS_NAME + "(\"method:" + className + "." + methodName + "--msg:parse gaeaRequest error\", context.getChannel().getRemoteIP(), context.getChannel().getLocalIP(), context.getGaeaRequest().getProtocol().getSdpEntity(), " + Constant.ERRORSTATE_CLASS_NAME + ".ParaException, e);");
  			sb.append("}"); // end catch(set value to param)
  		}
  	}
  	
  	private void invokeRealMethod(StringBuilder sb, String className, MethodInfo mi) {
  		Type[] mGenericType = mi.getMethod().getGenericParameterTypes();
  		String methodName = getMethodName(mi);
  		
  		Class<?> classReturn = mi.getMethod().getReturnType();
  		Type typeReturn = mi.getMethod().getGenericReturnType();
  		String returnValueType = typeReturn.toString().replaceFirst("class ", "");
  		if(returnValueType.startsWith("[")){
  			returnValueType = classReturn.getCanonicalName();
  		}
  		 
  		
  		sb.append("try {");	// begin try(invoke real method)
  		if(!returnValueType.equalsIgnoreCase("void")) {
  			sb.append(returnValueType.replaceAll("\\<.*\\>", "") + " returnValue = ");
  		} 
  
  		sb.append("serviceProxy.");
  		sb.append(mi.getMethod().getName());
  		sb.append("(");
  		//method para
  		for(int i=0; i<mGenericType.length;i++) {
  			sb.append("arg");
  			sb.append(i);
  			if(i != mGenericType.length-1) {
  				sb.append(", ");
  			}
  		}
  		sb.append(");");
  		
  		if(!returnValueType.equalsIgnoreCase("void")) {
  			sb.append("return new " + Constant.GAEARESPONSE_CLASS_NAME + "(returnValue");
  		} else {
  			sb.append("return new " + Constant.GAEARESPONSE_CLASS_NAME + "(null");
  		}
  		sb.append(", listOutPara);");
  		sb.append("} catch (Exception e) {"); // end try(invoke real method) begin catch(invoke real method)
  		sb.append("throw new " + Constant.SERVICEFRAMEEXCEPTION_CLASS_NAME + "(\"method:" + className + "." + methodName + "--msg:invoke real service error\", context.getChannel().getRemoteIP(), context.getChannel().getLocalIP(), context.getGaeaRequest().getProtocol().getSdpEntity(), " + Constant.ERRORSTATE_CLASS_NAME + ".ServiceException, e);");
  		sb.append("}"); // end catch(invoke real method)
  	}
  	
  	/**
  	 * 
  	 * @param mi
  	 * @return
  	 */
  	private String getMethodName(MethodInfo mi) {
  		String methodName = "";
  		OperationContract oc = mi.getMethod().getAnnotation(OperationContract.class);
  		if(oc == null) {
  			methodName = mi.getMethod().getName();
  		} else {
  			if(!oc.methodName().equalsIgnoreCase(AnnotationUtil.DEFAULT_VALUE)) {
  				methodName = oc.methodName();
  			} else {
  				methodName = mi.getMethod().getName();
  			}
  		}
  		return methodName;
  	}
  	
  	/**
  	 * get simple para name
  	 * @param paraName
  	 * @return
  	 */
  	public String getSimpleParaName(String paraName) {
  		if(paraName.indexOf(".") > 0) {
  			paraName = paraName.replaceAll(" ",	"");
  			String[] pnAry = paraName.split("");
  			
  			List<String> originalityList = new ArrayList<String>();
  			List<String> replaceList = new ArrayList<String>();
  			
  			String tempP = "";
  			for(int i=0; i<pnAry.length; i++) {
  				if(pnAry[i].equalsIgnoreCase("<")) {
  					originalityList.add(tempP);
  					replaceList.add(tempP.substring(tempP.lastIndexOf(".") + 1));
  					tempP = "";
  				} else if(pnAry[i].equalsIgnoreCase(">")) {
  					originalityList.add(tempP);
  					replaceList.add(tempP.substring(tempP.lastIndexOf(".") + 1));
  					tempP = "";
  				} else if(pnAry[i].equalsIgnoreCase(",")){
  					originalityList.add(tempP);
  					replaceList.add(tempP.substring(tempP.lastIndexOf(".") + 1));
  					tempP = "";
  				} else if(i == pnAry.length - 1){
  					originalityList.add(tempP);
  					replaceList.add(tempP.substring(tempP.lastIndexOf(".") + 1));
  					tempP = "";
  				} else {
  					if(!pnAry[i].equalsIgnoreCase("[") && !pnAry[i].equalsIgnoreCase("]")) {
  						tempP += pnAry[i];
  					}
  				}
  			}
  			
  			for(int i=0; i<replaceList.size(); i++) {
  				paraName = paraName.replaceAll(originalityList.get(i), replaceList.get(i));
  			}
  			return paraName;
  		} else {
  			return paraName;
  		}
  	}
  	
  }