org.coursera.courier.JavaGenerator Maven / Gradle / Ivy
/*
* Copyright 2015 Coursera Inc.
*
* Licensed 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.coursera.courier;
import com.linkedin.data.schema.DataSchema;
import com.linkedin.data.schema.DataSchemaConstants;
import com.linkedin.pegasus.generator.JavaCodeGeneratorBase;
import com.linkedin.pegasus.generator.JavaDataTemplateGenerator;
import com.linkedin.pegasus.generator.spec.ClassTemplateSpec;
import com.sun.codemodel.CodeWriter;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JPackage;
import org.apache.commons.io.output.NullOutputStream;
import org.coursera.courier.api.DefaultGeneratorRunner;
import org.coursera.courier.api.GeneratedCode;
import org.coursera.courier.api.GeneratedCodeTargetFile;
import org.coursera.courier.api.GeneratorRunnerOptions;
import org.coursera.courier.api.PegasusCodeGenerator;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
/**
* Generator for Pegasus style Java bindings.
*/
public class JavaGenerator implements PegasusCodeGenerator {
public static void main(String[] args) throws Throwable {
if (args.length != 3) {
throw new IllegalArgumentException(
"Usage: " + JavaGenerator.class.getName() +
" targetPath resolverPath sourcePath1[:sourcePath2]+");
}
String targetPath = args[0];
String resolverPath = args[1];
String sourcePathString = args[2];
String[] sourcePaths = sourcePathString.split(File.pathSeparator);
GeneratorRunnerOptions options =
new GeneratorRunnerOptions(targetPath, sourcePaths, resolverPath);
new DefaultGeneratorRunner().run(new JavaGenerator(), options);
}
public JavaGenerator() {
}
public static class JavaCompilationUnit extends GeneratedCodeTargetFile {
public JavaCompilationUnit(String name, String namespace) {
super(name, namespace, "java");
}
}
@Override
public GeneratedCode generate(ClassTemplateSpec templateSpec) {
if (predef.contains(templateSpec.getSchema())) {
return null;
}
JavaDataTemplateGenerator.Config config = new JavaDataTemplateGenerator.Config();
JavaDataTemplateGenerator generator = new JavaDataTemplateGenerator(config);
JClass result = generator.generate(templateSpec);
ByteArrayOutputStream out = new ByteArrayOutputStream();
String code;
try {
generator.getCodeModel().build(
new CapturingCodeWriter(templateSpec.getNamespace(), templateSpec.getClassName(), out));
out.flush();
out.close();
code = out.toString("UTF-8");
} catch (IOException e) {
throw new RuntimeException("Error generating code for " + templateSpec.getFullName(), e);
}
if (code.trim().equals("")) {
throw new RuntimeException("Failed to generate code for " + templateSpec.getFullName());
}
return new GeneratedCode(
new JavaCompilationUnit(
result.name(), result._package().name()), code);
}
@Override
public Collection generatePredef() {
return Collections.emptySet();
}
private static final Collection predef = new HashSet<>();
static {
predef.add(DataSchemaConstants.INTEGER_DATA_SCHEMA);
predef.add(DataSchemaConstants.LONG_DATA_SCHEMA);
predef.add(DataSchemaConstants.FLOAT_DATA_SCHEMA);
predef.add(DataSchemaConstants.DOUBLE_DATA_SCHEMA);
predef.add(DataSchemaConstants.BOOLEAN_DATA_SCHEMA);
predef.add(DataSchemaConstants.STRING_DATA_SCHEMA);
predef.add(DataSchemaConstants.BYTES_DATA_SCHEMA);
predef.add(DataSchemaConstants.NULL_DATA_SCHEMA);
predef.addAll(JavaDataTemplateGenerator.PredefinedJavaClasses.keySet());
}
/**
* A code writer for CodeModel that captures the generated code for a single
* class as a string.
*
* CodeModel may generate multiple Java classes in a single run. This writer
* captures the generated code for only the desired class and ignores the rest.
*/
private static class CapturingCodeWriter extends CodeWriter {
private final PrintStream out;
private final String namespace;
private final String className;
// Get access to the escaper that Pegasus uses for Java.
private static final class Escaper extends JavaCodeGeneratorBase {
public Escaper() {
super("");
}
public static String escape(String name) {
return escapeReserved(name);
}
}
/**
* @param namespace provides the namespace of the generated class to capture.
* @param className provides the class name of the generated class to capture. The class
* name must be unescaped.
* @param os
* This stream will be closed at the end of the code generation.
*/
public CapturingCodeWriter(String namespace, String className, OutputStream os) {
this.out = new PrintStream(os);
this.namespace = namespace;
this.className = className;
}
private static final int SUFFIX_LENGTH = ".java".length();
public OutputStream openBinary(JPackage pkg, String fileName) throws IOException {
boolean namespaceMatches =
(pkg.isUnnamed() && (namespace == null || namespace.isEmpty())) ||
pkg.name().equals(namespace);
String name = fileName.substring(0, fileName.length() - SUFFIX_LENGTH);
boolean classNameMatches = name.equals(Escaper.escape(className));
// Ignore all but the class we're intentionally generating code for.
if (namespaceMatches && classNameMatches) {
return out;
} else {
return new NullOutputStream();
}
}
public void close() throws IOException {
out.close();
}
}
@Override
public Collection definedSchemas() {
return predef;
}
@Override
public String buildLanguage() {
return "java";
}
@Override
public String customTypeLanguage() {
return "java";
}
}