blob: e50af5913189d6286767b24245e065d75709d14f [file] [log] [blame]
/*
* 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
*
* https://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.avro.tool;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import org.apache.avro.Protocol;
import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData.StringType;
import org.apache.avro.compiler.specific.SpecificCompiler;
import org.apache.avro.compiler.specific.SpecificCompiler.FieldVisibility;
/**
* A Tool for compiling avro protocols or schemas to Java classes using the Avro
* SpecificCompiler.
*/
public class SpecificCompilerTool implements Tool {
@Override
public int run(InputStream in, PrintStream out, PrintStream err, List<String> args) throws Exception {
if (args.size() < 3) {
System.err.println(
"Usage: [-encoding <outputencoding>] [-string] [-bigDecimal] [-fieldVisibility <visibilityType>] [-templateDir <templateDir>] (schema|protocol) input... outputdir");
System.err.println(" input - input files or directories");
System.err.println(" outputdir - directory to write generated java");
System.err.println(" -encoding <outputencoding> - set the encoding of " + "output file(s)");
System.err.println(" -string - use java.lang.String instead of Utf8");
System.err.println(" -fieldVisibility [private|public|public_deprecated]- use either and default private");
System.err
.println(" -bigDecimal - use java.math.BigDecimal for " + "decimal type instead of java.nio.ByteBuffer");
System.err.println(" -templateDir - directory with custom Velocity templates");
return 1;
}
StringType stringType = StringType.CharSequence;
boolean useLogicalDecimal = false;
Optional<String> encoding = Optional.empty();
Optional<String> templateDir = Optional.empty();
Optional<FieldVisibility> fieldVisibility = Optional.empty();
int arg = 0;
int counter = 0;
if (args.contains("-encoding")) {
arg = args.indexOf("-encoding") + 1;
encoding = Optional.of(args.get(arg));
counter = counter + 2;
}
if (args.contains("-string")) {
stringType = StringType.String;
counter = counter + 1;
}
if ("-fieldVisibility".equals(args.get(arg))) {
arg++;
try {
fieldVisibility = Optional.of(FieldVisibility.valueOf(args.get(arg).toUpperCase(Locale.ENGLISH)));
} catch (IllegalArgumentException | IndexOutOfBoundsException e) {
System.err.println("Expected one of" + Arrays.toString(FieldVisibility.values()));
return 1;
}
arg++;
}
if ("-bigDecimal".equalsIgnoreCase(args.get(arg))) {
useLogicalDecimal = true;
counter = counter + 1;
}
if (args.contains("-templateDir")) {
arg = args.indexOf("-templateDir") + 1;
templateDir = Optional.of(args.get(arg));
counter = counter + 2;
}
arg = counter;
String method = args.get(arg);
List<File> inputs = new ArrayList<>();
File output = new File(args.get(args.size() - 1));
for (int i = arg + 1; i < args.size() - 1; i++) {
inputs.add(new File(args.get(i)));
}
if ("schema".equals(method)) {
Schema.Parser parser = new Schema.Parser();
for (File src : determineInputs(inputs, SCHEMA_FILTER)) {
Schema schema = parser.parse(src);
final SpecificCompiler compiler = new SpecificCompiler(schema);
executeCompiler(compiler, encoding, stringType, fieldVisibility, useLogicalDecimal, templateDir, src, output);
}
} else if ("protocol".equals(method)) {
for (File src : determineInputs(inputs, PROTOCOL_FILTER)) {
Protocol protocol = Protocol.parse(src);
final SpecificCompiler compiler = new SpecificCompiler(protocol);
executeCompiler(compiler, encoding, stringType, fieldVisibility, useLogicalDecimal, templateDir, src, output);
}
} else {
System.err.println("Expected \"schema\" or \"protocol\".");
return 1;
}
return 0;
}
private void executeCompiler(SpecificCompiler compiler, Optional<String> encoding, StringType stringType,
Optional<FieldVisibility> fieldVisibility, boolean enableDecimalLogicalType, Optional<String> templateDir,
File src, File output) throws IOException {
compiler.setStringType(stringType);
templateDir.ifPresent(compiler::setTemplateDir);
compiler.setEnableDecimalLogicalType(enableDecimalLogicalType);
encoding.ifPresent(compiler::setOutputCharacterEncoding);
fieldVisibility.ifPresent(compiler::setFieldVisibility);
compiler.compileToDestination(src, output);
}
@Override
public String getName() {
return "compile";
}
@Override
public String getShortDescription() {
return "Generates Java code for the given schema.";
}
/**
* For an Array of files, sort using {@link String#compareTo(String)} for each
* filename.
*
* @param files Array of File objects to sort
* @return the sorted File array
*/
private static File[] sortFiles(File[] files) {
Objects.requireNonNull(files, "files cannot be null");
Arrays.sort(files, Comparator.comparing(File::getName));
return files;
}
/**
* For a List of files or directories, returns a File[] containing each file
* passed as well as each file with a matching extension found in the directory.
* Each directory is sorted using {@link String#compareTo(String)} for each
* filename.
*
* @param inputs List of File objects that are files or directories
* @param filter File extension filter to match on when fetching files from a
* directory
* @return Unique array of files
*/
private static File[] determineInputs(List<File> inputs, FilenameFilter filter) {
Set<File> fileSet = new LinkedHashSet<>(); // preserve order and uniqueness
for (File file : inputs) {
// if directory, look at contents to see what files match extension
if (file.isDirectory()) {
File[] files = file.listFiles(filter);
// sort files in directory to compile deterministically
// independent of system/ locale
Collections.addAll(fileSet, files != null ? sortFiles(files) : new File[0]);
}
// otherwise, just add the file.
else {
fileSet.add(file);
}
}
if (fileSet.size() > 0) {
System.err.println("Input files to compile:");
for (File file : fileSet) {
System.err.println(" " + file);
}
} else {
System.err.println("No input files found.");
}
return fileSet.toArray(new File[0]);
}
private static final FileExtensionFilter SCHEMA_FILTER = new FileExtensionFilter("avsc");
private static final FileExtensionFilter PROTOCOL_FILTER = new FileExtensionFilter("avpr");
private static class FileExtensionFilter implements FilenameFilter {
private String extension;
private FileExtensionFilter(String extension) {
this.extension = extension;
}
@Override
public boolean accept(File dir, String name) {
return name.endsWith(this.extension);
}
}
}