
org.apache.thrift.compiler.ThriftCompiler Maven / Gradle / Ivy
Show all versions of thrift-compiler Show documentation
/*
* 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 org.apache.thrift.compiler;
import static java.util.Objects.requireNonNull;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Provides a Java API for invoking the Apache Thrift compiler.
* This class is capable of invoking the Thrift compiler in two ways. First,
* a pure Java implementation of the compiler can be used, requiring no
* external dependencies. However, if a native build of the Thrift compiler
* is available on the host system, it can be used instead of the pure Java
* version. This may be advantageous if you need to use a specific version of
* Thrift, or also because the Java version executes more slowly and has
* significant startup overhead on the first invocation (1-2 seconds, YMMV).
* Instances of this class encapsulate the type of invocation to be
* performed (native vs. pure Java, path to the executable, etc), and are
* immutable and thread safe so it is fine to share across threads without
* synchronization. Instantiation is performed through the static
* newCompiler
factory methods defined by this class.
*
* @author Benjamin Gould (bcg)
*/
public abstract class ThriftCompiler {
public static void main(String... args) throws Throwable {
if ((args.length > 0) &&
(args[0].equals(OPT_EXPORT_SRC) || args[0].equals(OPT_UNZIP_SRC))) {
if (args.length == 2) {
final File destDir = new File(args[1]);
try {
if (OPT_EXPORT_SRC.equals(args[0])) {
ThriftCompiler.exportSource(destDir);
} else if (OPT_UNZIP_SRC.equals(args[0])) {
ThriftCompiler.unzipSource(destDir);
} else {
throw new IllegalStateException();
}
System.exit(0);
} catch (Exception e) {
System.err.println("An error occurred: " + e.getMessage());
System.exit(2);
}
} else {
System.err.println("thrift " + args[0] + " ");
System.exit(1);
}
}
final ThriftCompiler compiler = ThriftCompiler.newCompiler();
final ExecutionResult result = compiler.execute(args);
if (result.throwable != null) {
throw result.throwable;
}
if (result.outString != null) {
System.out.print(result.outString);
}
if (result.errString != null) {
System.err.print(result.errString);
}
System.exit(result.exitCode);
}
public static File exportSource(File destDir) throws IOException {
final File destFile = new File(requireNonNull(destDir), THRIFT_SRC_RSRC);
if (!destDir.isDirectory()) {
throw new IOException(
"Destination for " + THRIFT_SRC_RSRC + " must be a directory.");
}
if (destFile.exists()) {
throw new IOException(
"Destination file for " + THRIFT_SRC_RSRC + " already exists.");
}
try (final InputStream libsIn = thriftLibsResource().openStream()) {
try (final FileOutputStream out = new FileOutputStream(destFile)) {
final byte[] buffer = new byte[2048];
for (int n = -1; (n = libsIn.read(buffer)) > -1; ) {
out.write(buffer, 0, n);
}
}
}
return destFile;
}
public static File unzipSource(File destDir) throws IOException {
final File resultFile = new File(destDir, "lib");
if (resultFile.exists()) {
throw new IOException(
"Destination " + resultFile.getAbsolutePath() + " already exists.");
}
try (final InputStream libsIn = thriftLibsResource().openStream()) {
try (final ZipInputStream zipIn = new ZipInputStream(libsIn)) {
final byte[] buffer = new byte[2048];
for (ZipEntry entry; (entry = zipIn.getNextEntry()) != null; ) {
final File file = new File(destDir, entry.getName());
if (!entry.getName().startsWith("thrift-src/")) {
throw new IllegalStateException(
"entry should start with thrift-src/: " + entry.getName());
}
if (entry.isDirectory()) {
file.mkdirs();
} else {
try (final FileOutputStream out = new FileOutputStream(file)) {
for (int n = -1; (n = zipIn.read(buffer)) > -1; ) {
out.write(buffer, 0, n);
}
}
}
}
}
}
return resultFile;
}
public static final String OPT_UNZIP_SRC = "--unzip-source";
public static final String OPT_EXPORT_SRC = "--export-source";
public static final String PROPERTY_DEBUG = "thrift.compiler.debug";
public static final String PROPERTY_NATIVE = "thrift.compiler.native";
public static final String PROPERTY_EXECUTABLE = "thrift.compiler.executable";
public static final String WINDOWS_EXECUTABLE = "thrift.exe";
public static final String DEFAULT_EXECUTABLE = "thrift";
public static final String THRIFT_SRC_RSRC = "thrift-src.zip";
/**
*
* Instantiates an instance of {@link ThriftCompiler} based on a
* {@link java.util.Properties} object that it takes as its argument to allow
* the caller fine-grained control over the implementation of the compiler
* that will be used. By default, an instance of the pure Java implementation
* of the compiler will be created. To control or alter this behavior, the
* following properties can be specified:
*
*
* thrift.compiler.native
: set this property to true
* in order to use a native executable (by default thrift.exe
on
* Windows, thrift
on other systems)
*
*
* thrift.compiler.executable
: if
* thrift.compiler.native
is set to true, this property can be
* used to provide the path to the specific Thrift executable to be used.
*
*
* The above properties can be set on the {@link java.util.Properties} object
* passed as an argument, or alternatively can be specified as system
* properties. Values passed in via the argument take precedence over
* values specified as system properties.
*
*
* Returns a new {@link ThriftCompiler}. The exact implementation of the
* compiler will be dictated first by the supplied
* {@link java.util.Properties} object, and then the system properties,
* as a fallback if properties are not specified on the argument object.
* Passing null as the argument to this method is same as passing an empty
* {@link java.util.Properties} object.
*
* @param properties Properties to consult when constructing the compiler.
* @return A new instance of the Thrift compiler.
*/
public static final ThriftCompiler newCompiler(Properties properties) {
debug("newCompiler(Properties) called");
if (properties == null) {
properties = new Properties();
}
final String nativeProp = properties.getProperty(PROPERTY_NATIVE,
System.getProperty(PROPERTY_NATIVE, ""));
final boolean useNative = Boolean.valueOf(nativeProp);
if (useNative) {
String executable = properties.getProperty(PROPERTY_EXECUTABLE,
System.getProperty(PROPERTY_EXECUTABLE, ""));
if ("".equals(executable.trim())) {
executable = getDefaultExecutableName();
}
return new NativeThriftCompiler(executable);
} else {
return new NestedVmThriftCompiler();
}
}
/**
*
* "Automagically" chooses an implementation of the Thrift compiler based on
* the host environment. If the thrift.compiler.native
system
* property is set to a non-null value, this method is the equivalent of
* instantiating a compiler with a null
* {@link java.util.Properties} object. Otherwise, the environment's
* PATH
will be searched for an executable named 'thrift' (or
* 'thrift.exe' on Windows). If the output of 'thrift -v
'
* matches the version string of the embedded pure Java implementation, the
* native executable is used as an optimization. If the 'thrift' command
* cannot be executed or if the version does not match, the embedded pure
* Java version is used as a fallback.
*
*
* Returns a new {@link ThriftCompiler}. The exact implementation of the
* compiler will be dictated by system properties, or by default will be the
* {@link NestedVmThriftCompiler} if system properties are not specified.
*
* @return A new instance of the Thrift compiler.
*/
public static final ThriftCompiler newCompiler() {
debug("newCompiler() called");
final boolean nativeProp = System.getProperty(PROPERTY_NATIVE) != null;
if (!nativeProp) {
debug("native property not set; loading default properties");
return newCompiler(loadDefaultProperties());
} else {
debug("native property is set; skipping default properties");
return newCompiler(null);
}
}
/**
* Finds the default executable name for the host platform.
* (thrift.exe
on Windows, thrift
on all others).
* @return The default executable name for the host platform.
*/
public static final String getDefaultExecutableName() {
return System.getProperty("os.name").startsWith("Windows")
? WINDOWS_EXECUTABLE
: DEFAULT_EXECUTABLE;
}
/**
* Executes the compiler with the supplied arguments.
* @param args The arguments to pass in to the Thrift compiler.
* @return {@link ExecutionResult} encapsulating the details of the execution.
*/
public abstract ExecutionResult execute(String... args);
/**
* Determine if a native Thrift compiler is being used.
* @return True if the compiler is using a native executable.
*/
public abstract boolean isNativeExecutable();
/**
* Executes the Thrift compiler to get the version string.
* @return The output of the compiler with the -version
flag
*/
public String version() {
final ExecutionResult result = execute("-version");
return result.outString.trim();
}
/**
* Executes the Thrift compiler to get the help string.
* @return The output of the compiler with the -help
flag
*/
public String help() {
final ExecutionResult result = execute("-help");
return result.errString.trim();
}
private static URL thriftLibsResource() throws IOException {
final URL rsrc = ThriftCompiler.class.getResource(THRIFT_SRC_RSRC);
if (rsrc == null) {
throw new IOException(
"Embedded " + THRIFT_SRC_RSRC + " resource not found.");
}
return rsrc;
}
/**
* Retrieves the version string of embedded Java Thrift compiler
* @return The version string
*/
private static final String embeddedThriftCompilerVersion() {
final URL rsc = ThriftCompiler.class.getResource("thrift.compiler.version");
if (rsc == null) {
throw new IllegalStateException("thift.compiler.version not found");
}
try (final InputStream in = rsc.openStream()) {
try (final ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
final byte[] buffer = new byte[64];
for (int n = -1; (n = in.read(buffer)) > -1; ) {
baos.write(buffer, 0, n);
}
return baos.toString("UTF-8").trim();
}
} catch (IOException e) {
throw new ThriftCompilerException(e);
}
}
/**
* Lazy loads and returns the default properties object for use when no
* properties are specified for initializing the Thrift compiler
* @return The default properties
*/
private static final Properties loadDefaultProperties() {
return ThriftDefaultProperties.INSTANCE;
}
private static final boolean isDebug() {
return System.getProperty(PROPERTY_DEBUG, "").equals("true");
}
private static final void debug(String fmt, Object... args) {
if (isDebug()) {
System.err.format("[ThriftCompiler] " + fmt + "%n", args);
}
}
private static final class ThriftDefaultProperties {
private static final Properties INSTANCE = defaultProperties();
/**
*
* This method is only intended to run once during the JVM lifecycle...
* its purpose is lazy load an instance of the default properties to use
* for {@link ThriftCompiler} when properties are not otherwise specified.
*
*
* First it tries to run "-version" using the default executable name
* (either "thrift" or "thrift.exe" depending on platform). If that leads
* to any error, it is assumed that the pure Java version of the compiler
* should be used as a fallback. In that case, a Thread is launch to load
* the class in the background (it is large and can take 1-2 secs to load).
*
* @return
*/
private static final Properties defaultProperties() {
debug("defaultProperties() called");
final String dflt = getDefaultExecutableName();
final ThriftCompiler nativeCompiler = new NativeThriftCompiler(dflt);
debug("attempting to run native compiler: %s", nativeCompiler);
String nativeVersion;
try {
nativeVersion = nativeCompiler.version();
} catch (ThriftCompilerException e) {
nativeVersion = null;
}
debug("native version string: %s", nativeVersion);
final String embeddedVer = embeddedThriftCompilerVersion();
debug("embedded Java version: %s", embeddedVer);
if (nativeVersion != null && nativeVersion.equals(embeddedVer)) {
final Properties result = new Properties();
result.setProperty(ThriftCompiler.PROPERTY_NATIVE, "true");
result.setProperty(ThriftCompiler.PROPERTY_EXECUTABLE, dflt);
debug("using native version for default: %s", result);
return result;
}
final Thread loaderThread = new Thread(new Runnable() {
@Override
public void run() {
debug("loading embedded Java Thrift compiler");
final String pkg = ThriftCompiler.class.getPackage().getName();
final String cls = pkg + ".internal.Runtime";
try {
Class.forName(cls);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
debug("embedded Java Thrift compiler class loaded");
}
});
loaderThread.start();
return new Properties();
}
}
}