| /** |
| * 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.avro.compiler.specific; |
| |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.Writer; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.avro.specific.SpecificData; |
| import org.codehaus.jackson.JsonNode; |
| |
| import org.apache.avro.Protocol; |
| import org.apache.avro.Protocol.Message; |
| import org.apache.avro.Schema; |
| import org.apache.avro.Schema.Field; |
| import org.apache.avro.SchemaNormalization; |
| import org.apache.avro.JsonProperties; |
| import org.apache.avro.generic.GenericData; |
| import org.apache.avro.generic.GenericData.StringType; |
| import org.apache.velocity.Template; |
| import org.apache.velocity.VelocityContext; |
| import org.apache.velocity.app.VelocityEngine; |
| import org.apache.velocity.runtime.RuntimeServices; |
| import org.apache.velocity.runtime.log.LogChute; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import static org.apache.avro.specific.SpecificData.RESERVED_WORDS; |
| |
| /** |
| * Generate specific Java interfaces and classes for protocols and schemas. |
| * |
| * Java reserved keywords are mangled to preserve compilation. |
| */ |
| public class SpecificCompiler { |
| |
| /* |
| * From Section 4.10 of the Java VM Specification: |
| * A method descriptor is valid only if it represents method parameters with a total length of 255 or less, |
| * where that length includes the contribution for this in the case of instance or interface method invocations. |
| * The total length is calculated by summing the contributions of the individual parameters, where a parameter |
| * of type long or double contributes two units to the length and a parameter of any other type contributes one unit. |
| * |
| * Arguments of type Double/Float contribute 2 "parameter units" to this limit, all other types contribute 1 |
| * "parameter unit". All instance methods for a class are passed a reference to the instance (`this), and hence, |
| * they are permitted at most `JVM_METHOD_ARG_LIMIT-1` "parameter units" for their arguments. |
| * |
| * @see <a href="http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.10">JVM Spec: Section 4.10</a> |
| */ |
| private static final int JVM_METHOD_ARG_LIMIT = 255; |
| |
| /* |
| * Note: This is protected instead of private only so it's visible for testing. |
| */ |
| protected static final int MAX_FIELD_PARAMETER_UNIT_COUNT = JVM_METHOD_ARG_LIMIT - 1; |
| |
| public static enum FieldVisibility { |
| PUBLIC, PUBLIC_DEPRECATED, PRIVATE |
| } |
| |
| private final Set<Schema> queue = new HashSet<Schema>(); |
| private Protocol protocol; |
| private VelocityEngine velocityEngine; |
| private String templateDir; |
| private FieldVisibility fieldVisibility = FieldVisibility.PUBLIC_DEPRECATED; |
| private boolean createSetters = true; |
| private boolean createAllArgsConstructor = true; |
| private String outputCharacterEncoding; |
| |
| /* |
| * Used in the record.vm template. |
| */ |
| public boolean isCreateAllArgsConstructor() { |
| return createAllArgsConstructor; |
| } |
| |
| /* Reserved words for accessor/mutator methods */ |
| private static final Set<String> ACCESSOR_MUTATOR_RESERVED_WORDS = |
| new HashSet<String>(Arrays.asList(new String[] { |
| "class", "schema", "classSchema" |
| })); |
| static { |
| // Add reserved words to accessor/mutator reserved words |
| ACCESSOR_MUTATOR_RESERVED_WORDS.addAll(RESERVED_WORDS); |
| } |
| |
| /* Reserved words for error types */ |
| private static final Set<String> ERROR_RESERVED_WORDS = new HashSet<String>( |
| Arrays.asList(new String[] { "message", "cause" })); |
| static { |
| // Add accessor/mutator reserved words to error reserved words |
| ERROR_RESERVED_WORDS.addAll(ACCESSOR_MUTATOR_RESERVED_WORDS); |
| } |
| |
| private static final String FILE_HEADER = |
| "/**\n" + |
| " * Autogenerated by Avro\n" + |
| " * \n" + |
| " * DO NOT EDIT DIRECTLY\n" + |
| " */\n"; |
| |
| public SpecificCompiler(Protocol protocol) { |
| this(); |
| // enqueue all types |
| for (Schema s : protocol.getTypes()) { |
| enqueue(s); |
| } |
| this.protocol = protocol; |
| } |
| |
| public SpecificCompiler(Schema schema) { |
| this(); |
| enqueue(schema); |
| this.protocol = null; |
| } |
| |
| SpecificCompiler() { |
| this.templateDir = |
| System.getProperty("org.apache.avro.specific.templates", |
| "/org/apache/avro/compiler/specific/templates/java/classic/"); |
| initializeVelocity(); |
| } |
| |
| /** Set the resource directory where templates reside. First, the compiler checks |
| * the system path for the specified file, if not it is assumed that it is |
| * present on the classpath.*/ |
| public void setTemplateDir(String templateDir) { |
| this.templateDir = templateDir; |
| } |
| |
| /** |
| * @return true if the record fields should be marked as deprecated |
| */ |
| public boolean deprecatedFields() { |
| return (this.fieldVisibility == FieldVisibility.PUBLIC_DEPRECATED); |
| } |
| |
| /** |
| * @return true if the record fields should be public |
| */ |
| public boolean publicFields() { |
| return (this.fieldVisibility == FieldVisibility.PUBLIC || |
| this.fieldVisibility == FieldVisibility.PUBLIC_DEPRECATED); |
| } |
| |
| /** |
| * @return true if the record fields should be private |
| */ |
| public boolean privateFields() { |
| return (this.fieldVisibility == FieldVisibility.PRIVATE); |
| } |
| |
| /** |
| * Sets the field visibility option. |
| */ |
| public void setFieldVisibility(FieldVisibility fieldVisibility) { |
| this.fieldVisibility = fieldVisibility; |
| } |
| |
| public boolean isCreateSetters() { |
| return this.createSetters; |
| } |
| |
| /** |
| * Set to false to not create setter methods for the fields of the record. |
| */ |
| public void setCreateSetters(boolean createSetters) { |
| this.createSetters = createSetters; |
| } |
| |
| private static String logChuteName = null; |
| |
| private void initializeVelocity() { |
| this.velocityEngine = new VelocityEngine(); |
| |
| // These properties tell Velocity to use its own classpath-based |
| // loader, then drop down to check the root and the current folder |
| velocityEngine.addProperty("resource.loader", "class, file"); |
| velocityEngine.addProperty("class.resource.loader.class", |
| "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); |
| velocityEngine.addProperty("file.resource.loader.class", |
| "org.apache.velocity.runtime.resource.loader.FileResourceLoader"); |
| velocityEngine.addProperty("file.resource.loader.path", "/, ."); |
| velocityEngine.setProperty("runtime.references.strict", true); |
| |
| // try to use Slf4jLogChute, but if we can't use the null one. |
| if (null == logChuteName) { |
| // multiple threads can get here concurrently, but that's ok. |
| try { |
| new Slf4jLogChute(); |
| logChuteName = Slf4jLogChute.class.getName(); |
| } catch (Exception e) { |
| logChuteName = "org.apache.velocity.runtime.log.NullLogChute"; |
| } |
| } |
| velocityEngine.setProperty("runtime.log.logsystem.class", logChuteName); |
| } |
| |
| /** |
| * Captures output file path and contents. |
| */ |
| static class OutputFile { |
| String path; |
| String contents; |
| String outputCharacterEncoding; |
| |
| /** |
| * Writes output to path destination directory when it is newer than src, |
| * creating directories as necessary. Returns the created file. |
| */ |
| File writeToDestination(File src, File destDir) throws IOException { |
| File f = new File(destDir, path); |
| if (src != null && f.exists() && f.lastModified() >= src.lastModified()) |
| return f; // already up to date: ignore |
| f.getParentFile().mkdirs(); |
| Writer fw; |
| if (outputCharacterEncoding != null) { |
| fw = new OutputStreamWriter(new FileOutputStream(f), outputCharacterEncoding); |
| } else { |
| fw = new FileWriter(f); |
| } |
| try { |
| fw.write(FILE_HEADER); |
| fw.write(contents); |
| } finally { |
| fw.close(); |
| } |
| return f; |
| } |
| } |
| |
| /** |
| * Generates Java interface and classes for a protocol. |
| * @param src the source Avro protocol file |
| * @param dest the directory to place generated files in |
| */ |
| public static void compileProtocol(File src, File dest) throws IOException { |
| compileProtocol(new File[] {src}, dest); |
| } |
| |
| /** |
| * Generates Java interface and classes for a number of protocol files. |
| * @param srcFiles the source Avro protocol files |
| * @param dest the directory to place generated files in |
| */ |
| public static void compileProtocol(File[] srcFiles, File dest) throws IOException { |
| for (File src : srcFiles) { |
| Protocol protocol = Protocol.parse(src); |
| SpecificCompiler compiler = new SpecificCompiler(protocol); |
| compiler.compileToDestination(src, dest); |
| } |
| } |
| |
| /** Generates Java classes for a schema. */ |
| public static void compileSchema(File src, File dest) throws IOException { |
| compileSchema(new File[] {src}, dest); |
| } |
| |
| /** Generates Java classes for a number of schema files. */ |
| public static void compileSchema(File[] srcFiles, File dest) throws IOException { |
| Schema.Parser parser = new Schema.Parser(); |
| |
| for (File src : srcFiles) { |
| Schema schema = parser.parse(src); |
| SpecificCompiler compiler = new SpecificCompiler(schema); |
| compiler.compileToDestination(src, dest); |
| } |
| } |
| |
| /** Recursively enqueue schemas that need a class generated. */ |
| private void enqueue(Schema schema) { |
| if (queue.contains(schema)) return; |
| switch (schema.getType()) { |
| case RECORD: |
| queue.add(schema); |
| for (Schema.Field field : schema.getFields()) |
| enqueue(field.schema()); |
| break; |
| case MAP: |
| enqueue(schema.getValueType()); |
| break; |
| case ARRAY: |
| enqueue(schema.getElementType()); |
| break; |
| case UNION: |
| for (Schema s : schema.getTypes()) |
| enqueue(s); |
| break; |
| case ENUM: |
| case FIXED: |
| queue.add(schema); |
| break; |
| case STRING: case BYTES: |
| case INT: case LONG: |
| case FLOAT: case DOUBLE: |
| case BOOLEAN: case NULL: |
| break; |
| default: throw new RuntimeException("Unknown type: "+schema); |
| } |
| } |
| |
| /** Generate java classes for enqueued schemas. */ |
| Collection<OutputFile> compile() { |
| List<OutputFile> out = new ArrayList<OutputFile>(); |
| for (Schema schema : queue) { |
| out.add(compile(schema)); |
| } |
| if (protocol != null) { |
| out.add(compileInterface(protocol)); |
| } |
| return out; |
| } |
| |
| /** Generate output under dst, unless existing file is newer than src. */ |
| public void compileToDestination(File src, File dst) throws IOException { |
| for (Schema schema : queue) { |
| OutputFile o = compile(schema); |
| o.writeToDestination(src, dst); |
| } |
| if (protocol != null) { |
| compileInterface(protocol).writeToDestination(src, dst); |
| } |
| } |
| |
| private String renderTemplate(String templateName, VelocityContext context) { |
| Template template; |
| try { |
| template = this.velocityEngine.getTemplate(templateName); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| StringWriter writer = new StringWriter(); |
| template.merge(context, writer); |
| return writer.toString(); |
| } |
| |
| OutputFile compileInterface(Protocol protocol) { |
| protocol = addStringType(protocol); // annotate protocol as needed |
| VelocityContext context = new VelocityContext(); |
| context.put("protocol", protocol); |
| context.put("this", this); |
| String out = renderTemplate(templateDir+"protocol.vm", context); |
| |
| OutputFile outputFile = new OutputFile(); |
| String mangledName = mangle(protocol.getName()); |
| outputFile.path = makePath(mangledName, protocol.getNamespace()); |
| outputFile.contents = out; |
| outputFile.outputCharacterEncoding = outputCharacterEncoding; |
| return outputFile; |
| } |
| |
| static String makePath(String name, String space) { |
| if (space == null || space.isEmpty()) { |
| return name + ".java"; |
| } else { |
| return space.replace('.', File.separatorChar) + File.separatorChar + name |
| + ".java"; |
| } |
| } |
| |
| /** |
| * Returns the number of parameter units required by fields for the |
| * AllArgsConstructor. |
| * |
| * @param record a Record schema |
| */ |
| protected int calcAllArgConstructorParameterUnits(Schema record) { |
| |
| if (record.getType() != Schema.Type.RECORD) |
| throw new RuntimeException("This method must only be called for record schemas."); |
| |
| return record.getFields().size(); |
| } |
| |
| protected void validateRecordForCompilation(Schema record) { |
| this.createAllArgsConstructor = |
| calcAllArgConstructorParameterUnits(record) <= MAX_FIELD_PARAMETER_UNIT_COUNT; |
| |
| if (!this.createAllArgsConstructor) |
| new Slf4jLogChute().log(LogChute.WARN_ID, "Record '" + record.getFullName() + |
| "' contains more than " + MAX_FIELD_PARAMETER_UNIT_COUNT + |
| " parameters which exceeds the JVM " + |
| "spec for the number of permitted constructor arguments. Clients must " + |
| "rely on the builder pattern to create objects instead. For more info " + |
| "see JIRA ticket AVRO-1642."); |
| } |
| |
| OutputFile compile(Schema schema) { |
| schema = addStringType(schema); // annotate schema as needed |
| String output = ""; |
| VelocityContext context = new VelocityContext(); |
| context.put("this", this); |
| context.put("schema", schema); |
| |
| switch (schema.getType()) { |
| case RECORD: |
| validateRecordForCompilation(schema); |
| output = renderTemplate(templateDir+"record.vm", context); |
| break; |
| case ENUM: |
| output = renderTemplate(templateDir+"enum.vm", context); |
| break; |
| case FIXED: |
| output = renderTemplate(templateDir+"fixed.vm", context); |
| break; |
| case BOOLEAN: |
| case NULL: |
| break; |
| default: throw new RuntimeException("Unknown type: "+schema); |
| } |
| |
| OutputFile outputFile = new OutputFile(); |
| String name = mangle(schema.getName()); |
| outputFile.path = makePath(name, schema.getNamespace()); |
| outputFile.contents = output; |
| outputFile.outputCharacterEncoding = outputCharacterEncoding; |
| return outputFile; |
| } |
| |
| private StringType stringType = StringType.CharSequence; |
| |
| /** Set the Java type to be emitted for string schemas. */ |
| public void setStringType(StringType t) { this.stringType = t; } |
| |
| // annotate map and string schemas with string type |
| private Protocol addStringType(Protocol p) { |
| if (stringType != StringType.String) |
| return p; |
| |
| Protocol newP = new Protocol(p.getName(), p.getDoc(), p.getNamespace()); |
| Map<Schema,Schema> types = new LinkedHashMap<Schema,Schema>(); |
| |
| // Copy properties |
| for (Map.Entry<String,JsonNode> prop : p.getJsonProps().entrySet()) |
| newP.addProp(prop.getKey(), prop.getValue()); // copy props |
| |
| // annotate types |
| Collection<Schema> namedTypes = new LinkedHashSet<Schema>(); |
| for (Schema s : p.getTypes()) |
| namedTypes.add(addStringType(s, types)); |
| newP.setTypes(namedTypes); |
| |
| // annotate messages |
| Map<String,Message> newM = newP.getMessages(); |
| for (Message m : p.getMessages().values()) |
| newM.put(m.getName(), m.isOneWay() |
| ? newP.createMessage(m.getName(), m.getDoc(), m.getJsonProps(), |
| addStringType(m.getRequest(), types)) |
| : newP.createMessage(m.getName(), m.getDoc(), m.getJsonProps(), |
| addStringType(m.getRequest(), types), |
| addStringType(m.getResponse(), types), |
| addStringType(m.getErrors(), types))); |
| return newP; |
| } |
| |
| private Schema addStringType(Schema s) { |
| if (stringType != StringType.String) |
| return s; |
| return addStringType(s, new LinkedHashMap<Schema,Schema>()); |
| } |
| |
| // annotate map and string schemas with string type |
| private Schema addStringType(Schema s, Map<Schema,Schema> seen) { |
| if (seen.containsKey(s)) return seen.get(s); // break loops |
| Schema result = s; |
| switch (s.getType()) { |
| case STRING: |
| result = Schema.create(Schema.Type.STRING); |
| GenericData.setStringType(result, stringType); |
| break; |
| case RECORD: |
| result = |
| Schema.createRecord(s.getFullName(), s.getDoc(), null, s.isError()); |
| for (String alias : s.getAliases()) |
| result.addAlias(alias, null); // copy aliases |
| seen.put(s, result); |
| List<Field> newFields = new ArrayList<Field>(); |
| for (Field f : s.getFields()) { |
| Schema fSchema = addStringType(f.schema(), seen); |
| Field newF = |
| new Field(f.name(), fSchema, f.doc(), f.defaultValue(), f.order()); |
| for (Map.Entry<String,JsonNode> p : f.getJsonProps().entrySet()) |
| newF.addProp(p.getKey(), p.getValue()); // copy props |
| for (String a : f.aliases()) |
| newF.addAlias(a); // copy aliases |
| newFields.add(newF); |
| } |
| result.setFields(newFields); |
| break; |
| case ARRAY: |
| Schema e = addStringType(s.getElementType(), seen); |
| result = Schema.createArray(e); |
| break; |
| case MAP: |
| Schema v = addStringType(s.getValueType(), seen); |
| result = Schema.createMap(v); |
| GenericData.setStringType(result, stringType); |
| break; |
| case UNION: |
| List<Schema> types = new ArrayList<Schema>(); |
| for (Schema branch : s.getTypes()) |
| types.add(addStringType(branch, seen)); |
| result = Schema.createUnion(types); |
| break; |
| } |
| for (Map.Entry<String,JsonNode> p : s.getJsonProps().entrySet()) |
| result.addProp(p.getKey(), p.getValue()); // copy props |
| seen.put(s, result); |
| return result; |
| } |
| |
| private String getStringType(JsonNode overrideClassProperty) { |
| if (overrideClassProperty != null) |
| return overrideClassProperty.getTextValue(); |
| switch (stringType) { |
| case String: return "java.lang.String"; |
| case Utf8: return "org.apache.avro.util.Utf8"; |
| case CharSequence: return "java.lang.CharSequence"; |
| default: throw new RuntimeException("Unknown string type: "+stringType); |
| } |
| } |
| |
| private static final Schema NULL_SCHEMA = Schema.create(Schema.Type.NULL); |
| |
| /** Utility for template use. Returns the java type for a Schema. */ |
| public String javaType(Schema schema) { |
| switch (schema.getType()) { |
| case RECORD: |
| case ENUM: |
| case FIXED: |
| return mangle(schema.getFullName()); |
| case ARRAY: |
| return "java.util.List<" + javaType(schema.getElementType()) + ">"; |
| case MAP: |
| return "java.util.Map<" |
| + getStringType(schema.getJsonProp(SpecificData.KEY_CLASS_PROP))+"," |
| + javaType(schema.getValueType()) + ">"; |
| case UNION: |
| List<Schema> types = schema.getTypes(); // elide unions with null |
| if ((types.size() == 2) && types.contains(NULL_SCHEMA)) |
| return javaType(types.get(types.get(0).equals(NULL_SCHEMA) ? 1 : 0)); |
| return "java.lang.Object"; |
| case STRING: |
| return getStringType(schema.getJsonProp(SpecificData.CLASS_PROP)); |
| case BYTES: return "java.nio.ByteBuffer"; |
| case INT: return "java.lang.Integer"; |
| case LONG: return "java.lang.Long"; |
| case FLOAT: return "java.lang.Float"; |
| case DOUBLE: return "java.lang.Double"; |
| case BOOLEAN: return "java.lang.Boolean"; |
| case NULL: return "java.lang.Void"; |
| default: throw new RuntimeException("Unknown type: "+schema); |
| } |
| } |
| |
| /** Utility for template use. Returns the unboxed java type for a Schema. */ |
| public String javaUnbox(Schema schema) { |
| switch (schema.getType()) { |
| case INT: return "int"; |
| case LONG: return "long"; |
| case FLOAT: return "float"; |
| case DOUBLE: return "double"; |
| case BOOLEAN: return "boolean"; |
| default: return javaType(schema); |
| } |
| } |
| |
| /** Utility for template use. Returns the java annotations for a schema. */ |
| public String[] javaAnnotations(JsonProperties props) { |
| JsonNode value = props.getJsonProp("javaAnnotation"); |
| if (value == null) |
| return new String[0]; |
| if (value.isTextual()) |
| return new String[] { value.getTextValue() }; |
| if (value.isArray()) { |
| int i = 0; |
| String[] result = new String[value.size()]; |
| for (JsonNode v : value) |
| result[i++] = v.getTextValue(); |
| return result; |
| } |
| return new String[0]; |
| } |
| |
| // maximum size for string constants, to avoid javac limits |
| int maxStringChars = 8192; |
| |
| /** Utility for template use. Takes a (potentially overly long) string and |
| * splits it into a quoted, comma-separted sequence of escaped strings. |
| * @param s The string to split |
| * @return A sequence of quoted, comma-separated, escaped strings |
| */ |
| public String javaSplit(String s) throws IOException { |
| StringBuilder b = new StringBuilder("\""); // initial quote |
| for (int i = 0; i < s.length(); i += maxStringChars) { |
| if (i != 0) b.append("\",\""); // insert quote-comma-quote |
| String chunk = s.substring(i, Math.min(s.length(), i + maxStringChars)); |
| b.append(javaEscape(chunk)); // escape chunks |
| } |
| b.append("\""); // final quote |
| return b.toString(); |
| } |
| |
| /** Utility for template use. Escapes quotes and backslashes. */ |
| public static String javaEscape(Object o) { |
| return o.toString().replace("\\","\\\\").replace("\"", "\\\""); |
| } |
| |
| /** Utility for template use. Escapes comment end with HTML entities. */ |
| public static String escapeForJavadoc(String s) { |
| return s.replace("*/", "*/"); |
| } |
| |
| /** Utility for template use. Returns empty string for null. */ |
| public static String nullToEmpty(String x) { |
| return x == null ? "" : x; |
| } |
| |
| /** Utility for template use. Adds a dollar sign to reserved words. */ |
| public static String mangle(String word) { |
| return mangle(word, false); |
| } |
| |
| /** Utility for template use. Adds a dollar sign to reserved words. */ |
| public static String mangle(String word, boolean isError) { |
| return mangle(word, isError ? ERROR_RESERVED_WORDS : RESERVED_WORDS); |
| } |
| |
| /** Utility for template use. Adds a dollar sign to reserved words. */ |
| public static String mangle(String word, Set<String> reservedWords) { |
| return mangle(word, reservedWords, false); |
| } |
| |
| /** Utility for template use. Adds a dollar sign to reserved words. */ |
| public static String mangle(String word, Set<String> reservedWords, |
| boolean isMethod) { |
| if (word.contains(".")) { |
| // If the 'word' is really a full path of a class we must mangle just the classname |
| int lastDot = word.lastIndexOf("."); |
| String packageName = word.substring(0, lastDot + 1); |
| String className = word.substring(lastDot + 1); |
| return packageName + mangle(className, reservedWords, isMethod); |
| } |
| if (reservedWords.contains(word) || |
| (isMethod && reservedWords.contains( |
| Character.toLowerCase(word.charAt(0)) + |
| ((word.length() > 1) ? word.substring(1) : "")))) { |
| return word + "$"; |
| } |
| return word; |
| } |
| |
| /** Utility for use by templates. Return schema fingerprint as a long. */ |
| public static long fingerprint64(Schema schema) { |
| return SchemaNormalization.parsingFingerprint64(schema); |
| } |
| |
| /** |
| * Generates the name of a field accessor method. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the accessor name. |
| * @return the name of the accessor method for the given field. |
| */ |
| public static String generateGetMethod(Schema schema, Field field) { |
| return generateMethodName(schema, field, "get", ""); |
| } |
| |
| /** |
| * Generates the name of a field mutator method. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the mutator name. |
| * @return the name of the mutator method for the given field. |
| */ |
| public static String generateSetMethod(Schema schema, Field field) { |
| return generateMethodName(schema, field, "set", ""); |
| } |
| |
| /** |
| * Generates the name of a field "has" method. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the "has" method name. |
| * @return the name of the has method for the given field. |
| */ |
| public static String generateHasMethod(Schema schema, Field field) { |
| return generateMethodName(schema, field, "has", ""); |
| } |
| |
| /** |
| * Generates the name of a field "clear" method. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the accessor name. |
| * @return the name of the has method for the given field. |
| */ |
| public static String generateClearMethod(Schema schema, Field field) { |
| return generateMethodName(schema, field, "clear", ""); |
| } |
| |
| /** Utility for use by templates. Does this schema have a Builder method? */ |
| public static boolean hasBuilder(Schema schema) { |
| switch (schema.getType()) { |
| case RECORD: |
| return true; |
| |
| case UNION: |
| List<Schema> types = schema.getTypes(); // elide unions with null |
| if ((types.size() == 2) && types.contains(NULL_SCHEMA)) { |
| return hasBuilder(types.get(types.get(0).equals(NULL_SCHEMA) ? 1 : 0)); |
| } |
| return false; |
| |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Generates the name of a field Builder accessor method. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the Builder accessor name. |
| * @return the name of the Builder accessor method for the given field. |
| */ |
| public static String generateGetBuilderMethod(Schema schema, Field field) { |
| return generateMethodName(schema, field, "get", "Builder"); |
| } |
| |
| /** |
| * Generates the name of a field Builder mutator method. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the Builder mutator name. |
| * @return the name of the Builder mutator method for the given field. |
| */ |
| public static String generateSetBuilderMethod(Schema schema, Field field) { |
| return generateMethodName(schema, field, "set", "Builder"); |
| } |
| |
| /** |
| * Generates the name of a field Builder "has" method. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the "has" Builder method name. |
| * @return the name of the "has" Builder method for the given field. |
| */ |
| public static String generateHasBuilderMethod(Schema schema, Field field) { |
| return generateMethodName(schema, field, "has", "Builder"); |
| } |
| |
| /** |
| * Generates a method name from a field name. |
| * @param schema the schema in which the field is defined. |
| * @param field the field for which to generate the accessor name. |
| * @param prefix method name prefix, e.g. "get" or "set". |
| * @param postfix method name postfix, e.g. "" or "Builder". |
| * @return the generated method name. |
| */ |
| private static String generateMethodName(Schema schema, Field field, |
| String prefix, String postfix) { |
| |
| // Check for the special case in which the schema defines two fields whose |
| // names are identical except for the case of the first character: |
| char firstChar = field.name().charAt(0); |
| String conflictingFieldName = (Character.isLowerCase(firstChar) ? |
| Character.toUpperCase(firstChar) : Character.toLowerCase(firstChar)) + |
| (field.name().length() > 1 ? field.name().substring(1) : ""); |
| boolean fieldNameConflict = schema.getField(conflictingFieldName) != null; |
| |
| StringBuilder methodBuilder = new StringBuilder(prefix); |
| String fieldName = mangle(field.name(), |
| schema.isError() ? ERROR_RESERVED_WORDS : |
| ACCESSOR_MUTATOR_RESERVED_WORDS, true); |
| |
| boolean nextCharToUpper = true; |
| for (int ii = 0; ii < fieldName.length(); ii++) { |
| if (fieldName.charAt(ii) == '_') { |
| nextCharToUpper = true; |
| } |
| else if (nextCharToUpper) { |
| methodBuilder.append(Character.toUpperCase(fieldName.charAt(ii))); |
| nextCharToUpper = false; |
| } |
| else { |
| methodBuilder.append(fieldName.charAt(ii)); |
| } |
| } |
| methodBuilder.append(postfix); |
| |
| // If there is a field name conflict append $0 or $1 |
| if (fieldNameConflict) { |
| if (methodBuilder.charAt(methodBuilder.length() - 1) != '$') { |
| methodBuilder.append('$'); |
| } |
| methodBuilder.append(Character.isLowerCase(firstChar) ? '0' : '1'); |
| } |
| |
| return methodBuilder.toString(); |
| } |
| |
| /** Tests whether an unboxed Java type can be set to null */ |
| public static boolean isUnboxedJavaTypeNullable(Schema schema) { |
| switch (schema.getType()) { |
| // Primitives can't be null; assume anything else can |
| case INT: |
| case LONG: |
| case FLOAT: |
| case DOUBLE: |
| case BOOLEAN: return false; |
| default: return true; |
| } |
| } |
| |
| public static void main(String[] args) throws Exception { |
| //compileSchema(new File(args[0]), new File(args[1])); |
| compileProtocol(new File(args[0]), new File(args[1])); |
| } |
| |
| public static final class Slf4jLogChute implements LogChute { |
| private Logger logger = LoggerFactory.getLogger("AvroVelocityLogChute"); |
| @Override |
| public void init(RuntimeServices rs) throws Exception { |
| // nothing to do |
| } |
| |
| @Override |
| public void log(int level, String message) { |
| switch (level) { |
| case LogChute.DEBUG_ID: |
| logger.debug(message); |
| break; |
| case LogChute.TRACE_ID: |
| logger.trace(message); |
| break; |
| case LogChute.WARN_ID: |
| logger.warn(message); |
| break; |
| case LogChute.ERROR_ID: |
| logger.error(message); |
| break; |
| default: |
| case LogChute.INFO_ID: |
| logger.info(message); |
| break; |
| } |
| } |
| |
| @Override |
| public void log(int level, String message, Throwable t) { |
| switch (level) { |
| case LogChute.DEBUG_ID: |
| logger.debug(message, t); |
| break; |
| case LogChute.TRACE_ID: |
| logger.trace(message, t); |
| break; |
| case LogChute.WARN_ID: |
| logger.warn(message, t); |
| break; |
| case LogChute.ERROR_ID: |
| logger.error(message, t); |
| break; |
| default: |
| case LogChute.INFO_ID: |
| logger.info(message, t); |
| break; |
| } |
| } |
| |
| @Override |
| public boolean isLevelEnabled(int level) { |
| switch (level) { |
| case LogChute.DEBUG_ID: |
| return logger.isDebugEnabled(); |
| case LogChute.TRACE_ID: |
| return logger.isTraceEnabled(); |
| case LogChute.WARN_ID: |
| return logger.isWarnEnabled(); |
| case LogChute.ERROR_ID: |
| return logger.isErrorEnabled(); |
| default: |
| case LogChute.INFO_ID: |
| return logger.isInfoEnabled(); |
| } |
| } |
| } |
| |
| /** Sets character encoding for generated java file |
| * @param outputCharacterEncoding Character encoding for output files (defaults to system encoding) |
| */ |
| public void setOutputCharacterEncoding(String outputCharacterEncoding) { |
| this.outputCharacterEncoding = outputCharacterEncoding; |
| } |
| } |
| |