All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.baidu.bjf.remoting.protobuf.ProtobufProxy Maven / Gradle / Ivy

/*
 * Copyright (c) Baidu Inc. All rights reserved.
 * 
 * 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.baidu.bjf.remoting.protobuf;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baidu.bjf.remoting.protobuf.annotation.Ignore;
import com.baidu.bjf.remoting.protobuf.code.ICodeGenerator;
import com.baidu.bjf.remoting.protobuf.code.TemplateCodeGenerator;
import com.baidu.bjf.remoting.protobuf.utils.ClassHelper;
import com.baidu.bjf.remoting.protobuf.utils.CodePrinter;
import com.baidu.bjf.remoting.protobuf.utils.JDKCompilerHelper;
import com.baidu.bjf.remoting.protobuf.utils.StringUtils;
import com.baidu.bjf.remoting.protobuf.utils.compiler.Compiler;

/**
 * A simple protocol buffer encode and decode utility tool.
 * 
 * 
 *   example code as follow:
 *   
 *   User user = new User();
 *   ...
 *   Codec codec = ProtobufProxy.create(User.class);
 *   
 *   // do encode
 *   byte[] result = codec.encode(user);
 *   // do decode
 *   User user2 = codec.decode(result);
 * 
 * 
* * @author xiemalin * @since 1.0.0 */ public final class ProtobufProxy { /** The Constant DEBUG_CONTROLLER. */ public static final ThreadLocal DEBUG_CONTROLLER = new ThreadLocal(); /** Logger for this class. */ private static final Logger LOGGER = LoggerFactory.getLogger(ProtobufProxy.class.getName()); /** * cached {@link Codec} instance by class name. */ private static final Map CACHED = new ConcurrentHashMap(); /** The Constant OUTPUT_PATH for target directory to create generated source code out. */ public static final ThreadLocal OUTPUT_PATH = new ThreadLocal(); /** The Constant OUTPUT_PATH for target directory to create generated source code out. */ public static final ThreadLocal CACHE_ENABLED = new ThreadLocal(); /** The Constant for debug control from environment. */ private static final String DEBUG_CONTROL = "X_DEBUG_ENABLE"; /** * Clear cache. */ public static void clearCache() { CACHED.clear(); } /** * Compile. * * @param cls target class to be compiled * @param outputPath compile byte files output stream */ public static void compile(Class cls, File outputPath) { if (outputPath == null) { throw new NullPointerException("Param 'outputPath' is null."); } if (!outputPath.isDirectory()) { throw new RuntimeException("Param 'outputPath' value should be a path directory. path=" + outputPath); } } /** * To create a protobuf proxy class for target class. * * @param generic type * @param cls target class to parse @Protobuf annotation * @return {@link Codec} instance proxy */ public static Codec create(Class cls) { return create(cls, isDebugEnabled()); } /** * Creates the. * * @param the generic type * @param cls the cls * @param debug the debug * @return the codec */ public static Codec create(Class cls, boolean debug) { return create(cls, debug, null); } /** * To create a protobuf proxy class for target class. * * @param target object type to be proxied. * @param cls target object class * @param debug true will print generate java source code * @param path the path * @return proxy instance object. */ public static Codec create(Class cls, boolean debug, File path) { String uniClsName = cls.getName(); // to check cache early if (isCacheEnabled()) { Codec codec = CACHED.get(uniClsName); if (codec != null) { return codec; } } String className = getFullClassName(cls); Codec codec = loadCompiledClass(uniClsName, className); if (codec != null) { return codec; } return create(cls, debug, path, null, getCodeGenerator(cls)); } /** * To create a protobuf proxy class for target class. * * @param target object type to be proxied. * @param cls target object class * @param debug true will print generate java source code * @param path the path * @param compiler the compiler * @param codeGenerator the code generator * @return proxy instance object. */ public static Codec create(Class cls, boolean debug, File path, Compiler compiler, ICodeGenerator codeGenerator) { DEBUG_CONTROLLER.set(debug); OUTPUT_PATH.set(path); try { return doCreate(cls, isDebugEnabled(), compiler, codeGenerator); } finally { DEBUG_CONTROLLER.remove(); OUTPUT_PATH.remove(); } } /** * To create a protobuf proxy class for target class. * * @param generic type * @param cls target class to parse @Protobuf annotation * @param compiler the compiler * @param codeGenerator the code generator * @return {@link Codec} instance proxy */ public static Codec create(Class cls, Compiler compiler, ICodeGenerator codeGenerator) { return create(cls, isDebugEnabled(), null, compiler, codeGenerator); } /** * To create a protobuf proxy class for target class. * * @param target object type to be proxied. * @param cls target object class * @param debug true will print generate java source code * @param compiler the compiler * @param cg the cg * @return proxy instance object. */ protected static Codec doCreate(Class cls, boolean debug, Compiler compiler, ICodeGenerator cg) { if (cls == null) { throw new NullPointerException("Parameter cls is null"); } Ignore ignore = cls.getAnnotation(Ignore.class); if (ignore != null) { LOGGER.info("class '{}' marked as @Ignore annotation, proxy ignored.", cls.getName()); return null; } String uniClsName = cls.getName(); if (isCacheEnabled()) { Codec codec = CACHED.get(uniClsName); if (codec != null) { return codec; } } // crate code generator cg.setDebug(debug); File path = OUTPUT_PATH.get(); cg.setOutputPath(path); // try to load first String className = cg.getFullClassName(); Codec codec = loadCompiledClass(uniClsName, className); if (codec != null) { return codec; } String code = cg.getCode(); if (debug) { String printCode = code; CodePrinter.printCode(printCode, "generate protobuf proxy code"); } FileOutputStream fos = null; if (path != null && path.isDirectory()) { String pkg = ""; if (className.indexOf('.') != -1) { pkg = StringUtils.substringBeforeLast(className, "."); } // mkdirs String dir = path + File.separator + pkg.replace('.', File.separatorChar); File f = new File(dir); f.mkdirs(); try { fos = new FileOutputStream(new File(f, cg.getClassName() + ".class")); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } if (compiler == null) { compiler = JDKCompilerHelper.getJdkCompiler(); } Class newClass; // get last modify time long lastModify = ClassHelper.getLastModifyTime(cls); try { newClass = compiler.compile(className, code, cls.getClassLoader(), fos, lastModify); } catch (Exception e) { compiler = JDKCompilerHelper.getJdkCompiler(cls.getClassLoader()); newClass = compiler.compile(className, code, cls.getClassLoader(), fos, lastModify); } if (fos != null) { try { fos.close(); } catch (IOException e) { throw new RuntimeException(e.getMessage(), e); } } try { Codec newInstance = (Codec)newClass.getDeclaredConstructor(new Class[0]).newInstance(new Object[0]); if (!CACHED.containsKey(uniClsName)) { CACHED.put(uniClsName, newInstance); } try { // try to eagle load Set> relativeProxyClasses = cg.getRelativeProxyClasses(); for (Class relativeClass : relativeProxyClasses) { ProtobufProxy.create(relativeClass, debug, path, compiler, cg); } } catch (Exception e) { if (LOGGER.isDebugEnabled()) { LOGGER.debug(e.getMessage(), e); } } return newInstance; } catch (IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { throw new RuntimeException(e.getMessage(), e); } catch (InstantiationException e) { throw new RuntimeException(e.getMessage(), e); } catch (IllegalAccessException e) { throw new RuntimeException(e.getMessage(), e); } } /** * To generate a protobuf proxy java source code for target class. * * @param os to generate java source code * @param cls target class * @param charset charset type * @throws IOException in case of any io relative exception. */ public static void dynamicCodeGenerate(OutputStream os, Class cls, Charset charset) throws IOException { dynamicCodeGenerate(os, cls, charset, getCodeGenerator(cls)); } /** * To generate a protobuf proxy java source code for target class. * * @param os to generate java source code * @param cls target class * @param charset charset type * @param codeGenerator the code generator * @throws IOException in case of any io relative exception. */ public static void dynamicCodeGenerate(OutputStream os, Class cls, Charset charset, ICodeGenerator codeGenerator) throws IOException { if (cls == null) { throw new NullPointerException("Parameter 'cls' is null"); } if (os == null) { throw new NullPointerException("Parameter 'os' is null"); } if (charset == null) { charset = Charset.defaultCharset(); } String code = codeGenerator.getCode(); os.write(code.getBytes(charset)); } /** * Enable cache. * * @param enabled the enabled */ public static void enableCache(boolean enabled) { CACHE_ENABLED.set(enabled); } /** * Gets the class loader. * * @return the class loader */ private static ClassLoader getClassLoader() { ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); return contextClassLoader; } /** * Gets the code generator. * * @param cls the cls * @return the code generator */ private static ICodeGenerator getCodeGenerator(Class cls) { // check if has default constructor if (!cls.isMemberClass()) { try { cls.getConstructor(new Class[0]); } catch (NoSuchMethodException e2) { throw new IllegalArgumentException( "Class '" + cls.getName() + "' must has default constructor method with no parameters.", e2); } catch (SecurityException e2) { throw new IllegalArgumentException(e2.getMessage(), e2); } } ICodeGenerator cg = new TemplateCodeGenerator(cls); return cg; } /** * Gets the full class name. * * @param cls the cls * @return the full class name */ public static String getFullClassName(Class cls) { String pkg = ClassHelper.getPackage(cls); String className = ClassHelper.getClassName(cls) + ICodeGenerator.DEFAULT_SUFFIX_CLASSNAME; if (StringUtils.isEmpty(pkg)) { return className; } return pkg + ClassHelper.PACKAGE_SEPARATOR + className; } /** * Checks if is cache enabled. * * @return true, if is cache enabled */ public static boolean isCacheEnabled() { Boolean b = CACHE_ENABLED.get(); if (b == null) { return true; } return b; } /** * Checks if is debug enabled. * * @return true, if is debug enabled */ public static boolean isDebugEnabled() { String debugEnv = System.getenv(DEBUG_CONTROL); if (debugEnv != null && Boolean.parseBoolean(debugEnv)) { return true; } Boolean debug = DEBUG_CONTROLLER.get(); if (debug == null) { debug = false; // set default to close debug info } return debug; } /** * Load compiled class. * * @param the generic type * @param uniClsName the uni cls name * @param className the class name * @return the codec */ private static Codec loadCompiledClass(String uniClsName, String className) { Class c = null; try { c = Class.forName(className, true, getClassLoader()); } catch (ClassNotFoundException e1) { try { c = Class.forName(className, true, ProtobufProxy.class.getClassLoader()); } catch (ClassNotFoundException e2) { // if class not found so should generate a new java source class. c = null; } } if (c != null) { try { Codec newInstance = (Codec) c.newInstance(); if (!CACHED.containsKey(uniClsName)) { CACHED.put(uniClsName, newInstance); } return newInstance; } catch (InstantiationException e) { throw new RuntimeException(e.getMessage(), e); } catch (IllegalAccessException e) { throw new RuntimeException(e.getMessage(), e); } } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy