net.dongliu.prettypb.maven.Protoc Maven / Gradle / Ivy
package net.dongliu.prettypb.maven;
import org.codehaus.plexus.util.cli.CommandLineException;
import org.codehaus.plexus.util.cli.CommandLineUtils;
import org.codehaus.plexus.util.cli.Commandline;
import java.io.File;
import java.util.*;
import static com.google.inject.internal.util.Preconditions.checkArgument;
import static com.google.inject.internal.util.Preconditions.checkNotNull;
import static com.google.inject.internal.util.Preconditions.checkState;
/**
* This class represents an invokable configuration of the {@code protoc}
* compiler. The actual executable is invoked using the plexus
* {@link org.codehaus.plexus.util.cli.Commandline}.
*
* This class currently only supports generating java source files.
*
* @author [email protected] (Gregory Kick)
*/
final class Protoc {
private final String executable;
private final Set protoPathElements;
private final Set protoFiles;
private final File javaOutputDirectory;
private final CommandLineUtils.StringStreamConsumer output;
private final CommandLineUtils.StringStreamConsumer error;
// protoc build type, java or prettypb
private final String type;
/**
* Constructs a new instance. This should only be used by the {@link Builder}.
*
* @param executable The path to the {@code protoc} executable.
* @param protoPath The directories in which to search for imports.
* @param protoFiles The proto source files to compile.
* @param javaOutputDirectory The directory into which the java source files
* will be generated.
*/
private Protoc(String executable, Set protoPath,
Set protoFiles, File javaOutputDirectory, String type) {
this.executable = checkNotNull(executable, "executable");
this.protoPathElements = checkNotNull(protoPath, "protoPath");
this.protoFiles = checkNotNull(protoFiles, "protoFiles");
this.javaOutputDirectory = checkNotNull(javaOutputDirectory, "javaOutputDirectory");
this.error = new CommandLineUtils.StringStreamConsumer();
this.output = new CommandLineUtils.StringStreamConsumer();
this.type = type;
}
/**
* Invokes the {@code protoc} compiler using the configuration specified at
* construction.
*
* @return The exit status of {@code protoc}.
* @throws org.codehaus.plexus.util.cli.CommandLineException
*/
public int compile() throws CommandLineException {
Commandline cl = new Commandline();
cl.setExecutable(executable);
List args = getProtocArgs();
cl.addArguments(args.toArray(new String[args.size()]));
return CommandLineUtils.executeCommandLine(cl, null, output, error);
}
/**
* Creates the command line arguments.
*
* This method has been made visible for testing only.
*
* @return A list consisting of the executable followed by any arguments.
*/
List getProtocArgs() {
final List args = new ArrayList<>();
// add the executable
for (File protoPathElement : protoPathElements) {
args.add("--proto_path=" + protoPathElement);
}
args.add("--" + type + "_out=" + javaOutputDirectory);
for (File protoFile : protoFiles) {
args.add(protoFile.toString());
}
return Collections.unmodifiableList(args);
}
/**
* @return the output
*/
public String getOutput() {
return output.getOutput();
}
/**
* @return the error
*/
public String getError() {
return error.getOutput();
}
/**
* This class builds {@link Protoc} instances.
*
* @author [email protected] (Gregory Kick)
*/
static final class Builder {
private final String executable;
private final File javaOutputDirectory;
private Set protopathElements;
private Set protoFiles;
private String type;
/**
* Constructs a new builder. The two parameters are present as they are
* required for all {@link Protoc} instances.
*
* @param executable The path to the {@code protoc} executable.
* @param javaOutputDirectory The directory into which the java source files
* will be generated.
* @throws NullPointerException If either of the arguments are {@code null}.
* @throws IllegalArgumentException If the {@code javaOutputDirectory} is
* not a directory.
*/
public Builder(String executable, File javaOutputDirectory) {
this.executable = checkNotNull(executable, "executable");
this.javaOutputDirectory = checkNotNull(javaOutputDirectory);
checkArgument(javaOutputDirectory.isDirectory());
this.protoFiles = new HashSet<>();
this.protopathElements = new HashSet<>();
}
/**
* Adds a proto file to be compiled. Proto files must be on the protopath
* and this method will fail if a proto file is added without first adding a
* parent directory to the protopath.
*
* @param protoFile
* @return The builder.
* @throws IllegalStateException If a proto file is added without first
* adding a parent directory to the protopath.
* @throws NullPointerException If {@code protoFile} is {@code null}.
*/
public Builder addProtoFile(File protoFile) {
checkNotNull(protoFile);
checkArgument(protoFile.isFile());
checkArgument(protoFile.getName().endsWith(".proto"));
checkProtoFileIsInProtoPath(protoFile);
protoFiles.add(protoFile);
return this;
}
private void checkProtoFileIsInProtoPath(File protoFile) {
assert protoFile.isFile();
checkState(protoFileIsInProtoPath(protoFile.getParentFile()));
}
private boolean protoFileIsInProtoPath(File directory) {
assert directory.isDirectory();
if (protopathElements.contains(directory)) {
return true;
} else {
final File parentDirectory = directory.getParentFile();
return (parentDirectory != null) && protoFileIsInProtoPath(parentDirectory);
}
}
/**
* @see #addProtoFile(java.io.File)
*/
public Builder addProtoFiles(Iterable protoFiles) {
for (File protoFile : protoFiles) {
addProtoFile(protoFile);
}
return this;
}
public Builder addProtoPathElements(Iterable protoPathElements) {
for (File protoPathElement : protoPathElements) {
addProtoPathElement(protoPathElement);
}
return this;
}
/**
* Adds the {@code protoPathElement} to the protopath.
*
* @param protoPathElement A directory to be searched for imported protocol
* buffer definitions.
* @return The builder.
* @throws NullPointerException If {@code protopathElement} is {@code null}.
* @throws IllegalArgumentException If {@code protpathElement} is not a
* directory.
*/
public Builder addProtoPathElement(File protoPathElement) {
checkNotNull(protoPathElement);
checkArgument(protoPathElement.isDirectory());
protopathElements.add(protoPathElement);
return this;
}
public Builder setType(String type) {
this.type = type;
return this;
}
/**
* @return A configured {@link Protoc} instance.
* @throws IllegalStateException If no proto files have been added.
*/
public Protoc build() {
checkState(!protoFiles.isEmpty());
return new Protoc(executable, Collections.unmodifiableSet(protopathElements),
Collections.unmodifiableSet(protoFiles), javaOutputDirectory, type);
}
}
}