com.baidu.bjf.remoting.protobuf.ProtobufProxy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jprotobuf Show documentation
Show all versions of jprotobuf Show documentation
A useful utility library for java programmer using google protobuf.
/*
* 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, 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 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;
}
}