Java tutorial
package com.ning.killbill.generators.ruby; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.io.Resources; import com.ning.killbill.KillbillListener; import com.ning.killbill.com.ning.killbill.args.KillbillParserArgs.GENERATOR_MODE; import com.ning.killbill.generators.GeneratorException; import com.ning.killbill.objects.Annotation; import com.ning.killbill.objects.ClassEnumOrInterface; import com.ning.killbill.objects.Field; import com.ning.killbill.objects.MethodOrDecl; import com.ning.killbill.objects.Type; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.Writer; import java.net.URL; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.TreeSet; import javax.annotation.Nullable; public class JRubyPluginGenerator extends RubyBaseGenerator { private final static String LICENSE_NAME = "RubyLicense.txt"; private final static int INDENT_LEVEL = 2; private static final String REQUIRE_API_PREFIX = "killbill/gen/api"; private static final String REQUIRE_PLUGIN_API_PREFIX = "killbill/gen/plugin-api"; private final String[] POJO_MODULES = { "Killbill", "Plugin", "Model" }; private final String[] API_MODULES = { "Killbill", "Plugin", "Api" }; private static final List<ClassEnumOrInterface> STATICALLY_API_GENERATED_CLASSES = ImmutableList .<ClassEnumOrInterface>builder().add(new ClassEnumOrInterface("EnumeratorIterator", ClassEnumOrInterface.ClassEnumOrInterfaceType.CLASS, null, null, false)) .build(); private final List<ClassEnumOrInterface> staticallyGeneratedClasses; public JRubyPluginGenerator() { super(); this.staticallyGeneratedClasses = new ArrayList<ClassEnumOrInterface>(); } private void generateStaticClass(final String className, final File outputDir) throws GeneratorException { OutputStream out = null; try { final String resourceName = createFileName(className, true); final URL classUrl = Resources.getResource(resourceName); final File output = new File(outputDir, resourceName); out = new FileOutputStream(output); Resources.copy(classUrl, out); } catch (IOException e) { throw new GeneratorException("Failed to generate file " + className, e); } finally { if (out != null) { try { out.close(); } catch (IOException ignore) { } } } } @Override protected void generateClass(final ClassEnumOrInterface obj, final List<ClassEnumOrInterface> allClasses, final File outputDir, final GENERATOR_MODE mode) throws GeneratorException { resetIndentation(); final File output = new File(outputDir, createFileName(obj.getName(), true)); writeLicense(output); Writer w = null; try { w = new FileWriter(output, true); writeHeader(w); final boolean isInterface = obj.isInterface(); final boolean isApi = isInterface && isApiFile(obj.getName()); generateStartModules(w, isApi); final List<MethodOrDecl> flattenedMethods = new ArrayList<MethodOrDecl>(); flattenedMethods.addAll(getTopMethods(obj, allClasses)); flattenedMethods.addAll(obj.getMethodOrDecls()); dedupPreserveOrder(flattenedMethods); writeNewLine(w); int curIndent = INDENT_LEVEL; if (isInterface) { writeWithIndentationAndNewLine("java_package '" + obj.getPackageName() + "'", w, curIndent); curIndent = 0; } if (mode == GENERATOR_MODE.JRUBY_PLUGIN_API && isApi) { writeWithIndentationAndNewLine("class " + obj.getName() + " < JPlugin", w, curIndent); } else { writeWithIndentationAndNewLine("class " + obj.getName(), w, curIndent); } writeNewLine(w); if (isInterface) { writeWithIndentationAndNewLine("include " + obj.getPackageName() + "." + obj.getName(), w, INDENT_LEVEL); } writeNewLine(w); if (isApi) { generateForKillbillApi(obj, w, flattenedMethods, mode); } else { generateForPojo(obj, w, flattenedMethods); } writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); generateEndModules(w, isApi); w.flush(); w.close(); } catch (FileNotFoundException e) { throw new GeneratorException("Failed to generate file " + obj.getName(), e); } catch (IOException e) { throw new GeneratorException("Failed to generate file " + obj.getName(), e); } } private void generateEndModules(final Writer w, boolean api) throws IOException { final String[] modules = api ? API_MODULES : POJO_MODULES; for (int i = 0; i < modules.length; i++) { writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } } private void generateStartModules(final Writer w, boolean api) throws IOException { final String[] modules = api ? API_MODULES : POJO_MODULES; boolean first = true; for (int i = 0; i < modules.length; i++) { if (first) { writeWithIndentationAndNewLine("module " + modules[i], w, 0); first = false; } else { writeWithIndentationAndNewLine("module " + modules[i], w, INDENT_LEVEL); } } } private void generateForKillbillApi(final ClassEnumOrInterface obj, final Writer w, final List<MethodOrDecl> flattenedMethods, GENERATOR_MODE mode) throws IOException, GeneratorException { if (mode == GENERATOR_MODE.JRUBY_API) { generateApiInitializeMethod(w); } else if (mode == GENERATOR_MODE.JRUBY_PLUGIN_API) { generatePluginApiInitializeMethod(w); } for (final MethodOrDecl m : flattenedMethods) { final String methodName = generateMethodSignature(w, m); generateMethodArgumentConversion(w, mode, m); if (mode == GENERATOR_MODE.JRUBY_API) { generateApiMethodReturnConversion(w, m, methodName); } else if (mode == GENERATOR_MODE.JRUBY_PLUGIN_API) { generatePluginApiMethodReturnConversion(w, m, methodName); } writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } } private void generatePluginApiInitializeMethod(final Writer w) throws IOException { writeWithIndentationAndNewLine("def initialize(real_class_name, services = {})", w, 0); writeWithIndentationAndNewLine("super(real_class_name, services)", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeNewLine(w); } private void generateApiInitializeMethod(final Writer w) throws IOException { writeWithIndentationAndNewLine("def initialize(real_java_api)", w, 0); writeWithIndentationAndNewLine("@real_java_api = real_java_api", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeNewLine(w); } private void generatePluginApiMethodReturnConversion(final Writer w, final MethodOrDecl m, final String methodName) throws IOException, GeneratorException { final boolean isVoidReturn = "void".equals(m.getReturnValueType().getBaseType()); writeWithIndentationAndNewLine("begin", w, 0); if (isVoidReturn) { writeWithIndentation("@delegate_plugin." + methodName + "(", w, INDENT_LEVEL); } else { writeWithIndentation("res = @delegate_plugin." + methodName + "(", w, INDENT_LEVEL); } boolean first = true; for (Field f : m.getOrderedArguments()) { if (!first) { writeAppend(", ", w); } writeAppend(f.getName(), w); first = false; } writeAppend(")", w); writeNewLine(w); // conversion if (!isVoidReturn) { writeConversionToJava("res", m.getReturnValueType(), allClasses, w, 0, ""); writeWithIndentationAndNewLine("return res", w, 0); } writeWithIndentationAndNewLine("rescue Exception => e", w, -INDENT_LEVEL); writeWithIndentationAndNewLine("message = \"Failure in " + methodName + ": #{e}\"", w, +INDENT_LEVEL); writeWithIndentationAndNewLine("unless e.backtrace.nil?", w, +0); writeWithIndentationAndNewLine("message = \"#{message}\\n#{e.backtrace.join(\"\\n\")}\"", w, +INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, +-INDENT_LEVEL); writeWithIndentationAndNewLine("logger.warn message", w, +0); writeWithIndentationAndNewLine( "raise Java::org.killbill.billing.payment.plugin.api.PaymentPluginApiException.new(\"" + methodName + " failure\", e.message)", w, 0); writeWithIndentationAndNewLine("ensure", w, -INDENT_LEVEL); writeWithIndentationAndNewLine("@delegate_plugin.after_request", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } private void generateApiMethodReturnConversion(final Writer w, final MethodOrDecl m, final String methodName) throws IOException, GeneratorException { final boolean isVoidReturn = "void".equals(m.getReturnValueType().getBaseType()); final boolean gotExceptions = (m.getExceptions().size() > 0); if (gotExceptions) { writeWithIndentationAndNewLine("begin", w, 0); } if (isVoidReturn) { writeWithIndentation("@real_java_api." + methodName + "(", w, gotExceptions ? INDENT_LEVEL : 0); } else { writeWithIndentation("res = @real_java_api." + methodName + "(", w, gotExceptions ? INDENT_LEVEL : 0); } boolean first = true; for (Field f : m.getOrderedArguments()) { if (!first) { writeAppend(", ", w); } writeAppend(f.getName(), w); first = false; } writeAppend(")", w); writeNewLine(w); // conversion if (!isVoidReturn) { writeConversionToRuby(null, "res", m.getReturnValueType(), allClasses, w, 0, false); writeWithIndentationAndNewLine("return res", w, 0); } if (gotExceptions) { for (String curException : m.getExceptions()) { writeWithIndentationAndNewLine("rescue Java::" + curException + " => e", w, -INDENT_LEVEL); try { findClassEnumOrInterface(curException, allClasses); final String jrubyPoJo = getJrubyPoJo(curException); writeWithIndentationAndNewLine("raise " + jrubyPoJo + ".new.to_ruby(e)", w, INDENT_LEVEL); } catch (GeneratorException e) { writeWithIndentationAndNewLine( "raise ApiException.new(\"" + curException + ": #{e.msg unless e.msg.nil?}\")", w, INDENT_LEVEL); } } writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } } private void generateMethodArgumentConversion(final Writer w, final GENERATOR_MODE mode, final MethodOrDecl m) throws GeneratorException, IOException { boolean firstArg = true; for (Field f : m.getOrderedArguments()) { writeNewLine(w); int curIndentLevel = firstArg ? INDENT_LEVEL : 0; if (mode == GENERATOR_MODE.JRUBY_API) { writeConversionToJava(f.getName(), f.getType(), allClasses, w, curIndentLevel, ""); } else if (mode == GENERATOR_MODE.JRUBY_PLUGIN_API) { writeConversionToRuby(null, f.getName(), f.getType(), allClasses, w, curIndentLevel, false); } firstArg = false; } } private String generateMethodSignature(final Writer w, final MethodOrDecl m) throws IOException { final String returnValue = m.getReturnValueType().getBaseType(); writeNewLine(w); writeWithIndentation("java_signature 'Java::" + returnValue + " " + m.getName() + "(", w, 0); boolean first = true; for (Field f : m.getOrderedArguments()) { if (!first) { writeAppend(", ", w); } writeAppend("Java::" + f.getType().getBaseType(), w); first = false; } writeAppend(")'", w); writeNewLine(w); final String methodName = camelToUnderscore(m.getName()); writeWithIndentation("def " + methodName + "(", w, 0); first = true; for (Field f : m.getOrderedArguments()) { if (!first) { writeAppend(", ", w); } writeAppend(f.getName(), w); first = false; } writeAppend(")", w); writeNewLine(w); return methodName; } private boolean isApiFile(final String fileName) { return fileName.endsWith("Api"); } private void generateForPojo(final ClassEnumOrInterface obj, final Writer w, final List<MethodOrDecl> flattenedMethods) throws IOException, GeneratorException { generateAttributeAccessorFromGetterMethods(obj, flattenedMethods, w); writeWithIndentationAndNewLine("def initialize()", w, 0); writeWithIndentationAndNewLine("end", w, 0); writeNewLine(w); generateToJava(obj, flattenedMethods, w); generateToRuby(obj, flattenedMethods, w); } private void generateToRuby(final ClassEnumOrInterface obj, final List<MethodOrDecl> flattenedMethods, final Writer w) throws IOException, GeneratorException { boolean first = true; writeWithIndentationAndNewLine("def to_ruby(j_obj)", w, 0); for (final MethodOrDecl m : flattenedMethods) { if (!m.isGetter()) { continue; } if (first) { writeConversionToRuby(obj, m, allClasses, w, INDENT_LEVEL); first = false; } else { writeNewLine(w); writeConversionToRuby(obj, m, allClasses, w, 0); } } writeWithIndentationAndNewLine("self", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeNewLine(w); } private void writeConversionToRuby(@Nullable final ClassEnumOrInterface obj, final MethodOrDecl m, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset) throws GeneratorException, IOException { final String member = camelToUnderscore(convertGetterMethodToFieldName(m.getName())); writeConversionToRuby(obj, member, m.getReturnValueType(), allClasses, w, indentOffset, true); } private void writeConversionToRuby(@Nullable final ClassEnumOrInterface obj, final String member, final String returnValueType, final String returnValueGeneric, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset, boolean fromJobj) throws GeneratorException, IOException { writeConversionToRuby(obj, member, new Type(returnValueType, returnValueGeneric), allClasses, w, indentOffset, fromJobj); } private void writeConversionToRuby(@Nullable final ClassEnumOrInterface obj, final String member, final Type type, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset, boolean fromJobj) throws GeneratorException, IOException { writeConversionToRuby(obj, member, type, allClasses, w, indentOffset, fromJobj, 0); } private void writeConversionToRuby(@Nullable final ClassEnumOrInterface obj, final String member, final Type type, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset, boolean fromJobj, int depth) throws GeneratorException, IOException { final String returnValueType = type.getBaseType(); final String returnValueGeneric = type.getGenericType(); writeWithIndentationAndNewLine("# conversion for " + member + " [type = " + type.toString() + "]", w, indentOffset); // See https://github.com/killbill/killbill-java-parser/issues/6 and https://github.com/killbill/killbill-java-parser/issues/7 final boolean shouldIgnore = obj != null && (("plan".equals(member) && "PlanPhase".equals(obj.getName())) || ("min_top_up_credit".equals(member) && "TieredBlock".equals(obj.getName()))); final String linePrefix = shouldIgnore ? "#" : ""; final String memberPrefix = fromJobj ? "@" : ""; if (fromJobj) { // See https://github.com/killbill/killbill-java-parser/issues/5 final String hackedMember = member.equals("included") && "org.killbill.billing.catalog.api.Product[]".equals(type.toString()) ? "get_" + member : member; writeWithIndentationAndNewLine(linePrefix + memberPrefix + member + " = j_obj." + hackedMember, w, 0); } if (returnValueType.equals("byte")) { } else if (returnValueType.equals("short") || returnValueType.equals("java.lang.Short")) { } else if (returnValueType.equals("int") || returnValueType.equals("java.lang.Integer")) { } else if (returnValueType.equals("long") || returnValueType.equals("java.lang.Long")) { } else if (returnValueType.equals("float") || returnValueType.equals("java.lang.Float")) { } else if (returnValueType.equals("double") || returnValueType.equals("java.lang.Double")) { } else if (returnValueType.equals("char") || returnValueType.equals("java.lang.Char")) { } else { final String tmp = depth == 0 ? "tmp" : "tmp" + depth; if (returnValueType.equals("boolean") || returnValueType.equals("java.lang.Boolean")) { writeWithIndentationAndNewLine("if " + memberPrefix + member + ".nil?", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = false", w, INDENT_LEVEL); writeWithIndentationAndNewLine("else", w, -INDENT_LEVEL); writeWithIndentationAndNewLine( tmp + "_bool = (" + memberPrefix + member + ".java_kind_of? java.lang.Boolean) ? " + memberPrefix + member + ".boolean_value : " + memberPrefix + member, w, INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp + "_bool ? true : false", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else if (returnValueType.equals("java.lang.Throwable")) { writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".to_s unless " + memberPrefix + member + ".nil?", w, 0); } else { if ("java.lang.String".equals(returnValueType) || // TTO same thing as for to_java "java.lang.Object".equals(returnValueType)) { } else if ("java.util.UUID".equals(returnValueType)) { writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".nil? ? nil : " + memberPrefix + member + ".to_s", w, 0); } else if ("java.math.BigDecimal".equals(returnValueType)) { writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".nil? ? 0 : BigDecimal.new(" + memberPrefix + member + ".to_s)", w, 0); } else if ("org.joda.time.DateTime".equals(returnValueType) || "java.util.Date".equals(returnValueType)) { writeWithIndentationAndNewLine("if !" + memberPrefix + member + ".nil?", w, 0); if ("java.util.Date".equals(returnValueType)) { // First convert to DateTime writeWithIndentationAndNewLine(memberPrefix + member + " = " + "Java::org.joda.time.DateTime.new(" + memberPrefix + member + ")", w, INDENT_LEVEL); } writeWithIndentationAndNewLine( "fmt = Java::org.joda.time.format.ISODateTimeFormat.date_time_no_millis # See https://github.com/killbill/killbill-java-parser/issues/3", w, ("java.util.Date".equals(returnValueType) ? 0 : INDENT_LEVEL)); writeWithIndentationAndNewLine("str = fmt.print(" + memberPrefix + member + ")", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = " + "DateTime.iso8601(str)", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else if ("org.joda.time.LocalDate".equals(returnValueType)) { writeWithIndentationAndNewLine("if !" + memberPrefix + member + ".nil?", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".to_s", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else if ("org.joda.time.DateTimeZone".equals(returnValueType)) { writeWithIndentationAndNewLine("if !" + memberPrefix + member + ".nil?", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = " + "TZInfo::Timezone.get(" + memberPrefix + member + ".get_id)", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else if ("java.util.List".equals(returnValueType) || "java.util.Collection".equals(returnValueType) || "java.util.Set".equals(returnValueType) || Type.ARRAY.equals(returnValueType)) { writeWithIndentationAndNewLine(tmp + " = []", w, 0); writeWithIndentationAndNewLine("(" + memberPrefix + member + " || []).each do |m|", w, 0); writeConversionToRuby(obj, "m", returnValueGeneric, null, allClasses, w, INDENT_LEVEL, false); writeWithIndentationAndNewLine(tmp + " << m", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp, w, 0); } else if ("java.lang.Iterable".equals(returnValueType)) { writeWithIndentationAndNewLine(tmp + " = []", w, 0); writeWithIndentationAndNewLine("(" + memberPrefix + member + ".nil? ? [] : " + memberPrefix + member + ".iterator).each do |m|", w, 0); writeConversionToRuby(obj, "m", returnValueGeneric, null, allClasses, w, INDENT_LEVEL, false); writeWithIndentationAndNewLine(tmp + " << m", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp, w, 0); } else if ("java.util.Iterator".equals(returnValueType)) { // TODO // Leave default where ruby can call next, not ideal because that would break pure ruby plugin code } else if ("java.util.Map".equals(returnValueType)) { writeWithIndentationAndNewLine(tmp + " = {}", w, 0); final String jtmp = "j" + tmp + depth; writeWithIndentationAndNewLine( jtmp + " = " + memberPrefix + member + " || java.util.HashMap.new", w, 0); writeWithIndentationAndNewLine(jtmp + ".key_set.each do |k|", w, 0); if (type.getGenericSubTypes() != null && type.getGenericSubTypes().size() == 2) { writeConversionToRuby(obj, "k", type.getGenericSubTypes().get(0), allClasses, w, INDENT_LEVEL, false, depth + 1); writeWithIndentationAndNewLine("v = " + jtmp + ".get(k)", w, 0); writeConversionToRuby(obj, "v", type.getGenericSubTypes().get(1), allClasses, w, INDENT_LEVEL, false, depth + 1); } else { // No generic information? Should we bail or trust JRuby to do the right thing? writeWithIndentationAndNewLine("v = " + "j" + tmp + depth + ".get(k)", w, 0); } writeWithIndentationAndNewLine(tmp + "[k] = v", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp, w, -INDENT_LEVEL); } else { final ClassEnumOrInterface classEnumOrInterface = findClassEnumOrInterface(returnValueType, allClasses); if (classEnumOrInterface.isEnum()) { writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".to_s.to_sym unless " + memberPrefix + member + ".nil?", w, 0); } else { writeWithIndentationAndNewLine(linePrefix + memberPrefix + member + " = " + getJrubyPoJo(returnValueType) + ".new.to_ruby(" + memberPrefix + member + ") unless " + memberPrefix + member + ".nil?", w, 0); } } } } } private String getJrubyPoJo(final String pojoBaseName) throws GeneratorException { String[] parts = pojoBaseName.split("\\."); final String jrubyObject = Joiner.on("::").join(POJO_MODULES) + "::" + parts[parts.length - 1]; return jrubyObject; } private void generateToJava(final ClassEnumOrInterface obj, final List<MethodOrDecl> flattenedMethods, final Writer w) throws IOException, GeneratorException { boolean first = true; writeWithIndentationAndNewLine("def to_java()", w, 0); for (final MethodOrDecl m : flattenedMethods) { if (!m.isGetter()) { continue; } if (first) { writeConversionToJava(m, allClasses, w, INDENT_LEVEL); first = false; } else { writeNewLine(w); writeConversionToJava(m, allClasses, w, 0); } } // // If this is a class, that sucks because we can't rely on the jruby layer to map the Jruby object to a valid java object based on the include mechanism // so we new to explicitely call the CTOR of that class. // TODO There is a hack here we assumes that CTOR takes all the fields as arguments is the correct order. We should realy fix our API to not have those cases // or be smarter in the conversion process and loo at the CTOR arguments into more details if (obj.isClass()) { writeWithIndentation("Java::" + obj.getPackageName() + "." + obj.getName() + ".new(", w, 0); first = true; for (final MethodOrDecl m : flattenedMethods) { if (!m.isGetter()) { continue; } if (!first) { writeAppend(", ", w); } final String member = camelToUnderscore(convertGetterMethodToFieldName(m.getName())); writeAppend("@" + member, w); first = false; } writeAppend(")", w); writeNewLine(w); } else { writeWithIndentationAndNewLine("self", w, 0); } writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeNewLine(w); } private void writeConversionToJava(final MethodOrDecl m, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset) throws GeneratorException, IOException { final String member = camelToUnderscore(convertGetterMethodToFieldName(m.getName())); writeConversionToJava(member, m.getReturnValueType(), allClasses, w, indentOffset, "@"); } private void writeConversionToJava(final String member, final String returnValueType, final String returnValueGeneric, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset, String memberPrefix) throws GeneratorException, IOException { writeConversionToJava(member, new Type(returnValueType, returnValueGeneric), allClasses, w, indentOffset, memberPrefix); } private void writeConversionToJava(final String member, final Type type, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset, String memberPrefix) throws GeneratorException, IOException { writeConversionToJava(member, type, allClasses, w, indentOffset, memberPrefix, 0); } private void writeConversionToJava(final String member, final Type type, final List<ClassEnumOrInterface> allClasses, final Writer w, int indentOffset, String memberPrefix, int depth) throws GeneratorException, IOException { final String returnValueType = type.getBaseType(); final String returnValueGeneric = type.getGenericType(); writeWithIndentationAndNewLine("# conversion for " + member + " [type = " + type.toString() + "]", w, indentOffset); if (returnValueType.equals("byte")) { // default jruby conversion should be fine } else if (returnValueType.equals("short") || returnValueType.equals("java.lang.Short")) { // default jruby conversion should be fine } else if (returnValueType.equals("int") || returnValueType.equals("java.lang.Integer")) { // default jruby conversion should be fine writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member, w, 0); } else if (returnValueType.equals("long") || returnValueType.equals("java.lang.Long")) { // default jruby conversion should be fine writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member, w, 0); } else if (returnValueType.equals("float") || returnValueType.equals("java.lang.Float")) { // default jruby conversion should be fine writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member, w, 0); } else if (returnValueType.equals("double") || returnValueType.equals("java.lang.Double")) { // default jruby conversion should be fine writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member, w, 0); } else if (returnValueType.equals("char") || returnValueType.equals("java.lang.Char")) { // default jruby conversion should be fine writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member, w, 0); } else if (returnValueType.equals("boolean") || returnValueType.equals("java.lang.Boolean")) { writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".nil? ? java.lang.Boolean.new(false) : java.lang.Boolean.new(" + memberPrefix + member + ")", w, 0); } else if (returnValueType.equals("java.lang.Throwable")) { writeWithIndentationAndNewLine( memberPrefix + member + " = " + memberPrefix + member + ".to_s unless " + member + ".nil?", w, 0); } else { if ("java.lang.String".equals(returnValueType) || // TODO fix KB API really! // We assume Object is a string in that case "java.lang.Object".equals(returnValueType)) { // default jruby conversion should be fine writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".to_s unless " + memberPrefix + member + ".nil?", w, 0); } else if ("java.util.UUID".equals(returnValueType)) { writeWithIndentationAndNewLine(memberPrefix + member + " = java.util.UUID.fromString(" + memberPrefix + member + ".to_s) unless " + memberPrefix + member + ".nil?", w, 0); } else if ("java.math.BigDecimal".equals(returnValueType)) { writeWithIndentationAndNewLine("if " + memberPrefix + member + ".nil?", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = java.math.BigDecimal::ZERO", w, INDENT_LEVEL); writeWithIndentationAndNewLine("else", w, -INDENT_LEVEL); //writeWithIndentationAndNewLine(member + " = java.math.BigDecimal.new(" + member + ".respond_to?(:cents) ? " + member + ".cents : " + member + ".to_i)", w, INDENT_LEVEL); writeWithIndentationAndNewLine( memberPrefix + member + " = java.math.BigDecimal.new(" + memberPrefix + member + ".to_s)", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else if ("org.joda.time.DateTime".equals(returnValueType) || "java.util.Date".equals(returnValueType)) { writeWithIndentationAndNewLine("if !" + memberPrefix + member + ".nil?", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = (" + memberPrefix + member + ".kind_of? Time) ? DateTime.parse(" + memberPrefix + member + ".to_s) : " + memberPrefix + member, w, INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = Java::org.joda.time.DateTime.new(" + memberPrefix + member + ".to_s, Java::org.joda.time.DateTimeZone::UTC)", w, 0); if ("java.util.Date".equals(returnValueType)) { writeWithIndentationAndNewLine( memberPrefix + member + " = " + memberPrefix + member + ".to_date", w, 0); } writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else if ("org.joda.time.LocalDate".equals(returnValueType)) { writeWithIndentationAndNewLine("if !" + memberPrefix + member + ".nil?", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = Java::org.joda.time.LocalDate.parse(" + memberPrefix + member + ".to_s)", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else if ("org.joda.time.DateTimeZone".equals(returnValueType)) { writeWithIndentationAndNewLine("if !" + memberPrefix + member + ".nil?", w, 0); writeWithIndentationAndNewLine(memberPrefix + member + " = Java::org.joda.time.DateTimeZone.forID((" + memberPrefix + member + ".respond_to?(:identifier) ? " + memberPrefix + member + ".identifier : " + memberPrefix + member + ".to_s))", w, INDENT_LEVEL); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); } else { final String tmp = depth == 0 ? "tmp" : "tmp" + depth; if (Type.ARRAY.equals(returnValueType)) { writeWithIndentationAndNewLine(tmp + " = java.util.ArrayList.new", w, 0); writeWithIndentationAndNewLine("(" + memberPrefix + member + " || []).each do |m|", w, 0); writeConversionToJava("m", returnValueGeneric, null, allClasses, w, INDENT_LEVEL, ""); writeWithIndentationAndNewLine(tmp + ".add(m)", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp + ".toArray", w, 0); } else if ("java.util.List".equals(returnValueType) || "java.util.Collection".equals(returnValueType) || "java.lang.Iterable".equals(returnValueType)) { writeWithIndentationAndNewLine(tmp + " = java.util.ArrayList.new", w, 0); writeWithIndentationAndNewLine("(" + memberPrefix + member + " || []).each do |m|", w, 0); writeConversionToJava("m", returnValueGeneric, null, allClasses, w, INDENT_LEVEL, ""); writeWithIndentationAndNewLine(tmp + ".add(m)", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp, w, 0); } else if ("java.util.Set".equals(returnValueType) || ("java.util.SortedSet".equals(returnValueType))) { writeWithIndentationAndNewLine(tmp + " = java.util.TreeSet.new", w, 0); writeWithIndentationAndNewLine("(" + memberPrefix + member + " || []).each do |m|", w, 0); writeConversionToJava("m", returnValueGeneric, null, allClasses, w, INDENT_LEVEL, ""); writeWithIndentationAndNewLine(tmp + ".add(m)", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp, w, 0); } else if ("java.util.Iterator".equals(returnValueType)) { writeWithIndentationAndNewLine(memberPrefix + member + " = Killbill::Plugin::Model::EnumeratorIterator.new(" + memberPrefix + member + ")", w, 0); } else if ("java.util.Map".equals(returnValueType)) { writeWithIndentationAndNewLine(tmp + " = java.util.HashMap.new", w, 0); writeWithIndentationAndNewLine("(" + memberPrefix + member + " || {}).each do |k,v|", w, 0); if (type.getGenericSubTypes() != null && type.getGenericSubTypes().size() == 2) { writeConversionToJava("k", type.getGenericSubTypes().get(0), allClasses, w, INDENT_LEVEL, "", depth + 1); writeConversionToJava("v", type.getGenericSubTypes().get(1), allClasses, w, INDENT_LEVEL, "", depth + 1); } else { // No generic information? Should we bail or trust JRuby to do the right thing? } writeWithIndentationAndNewLine(tmp + ".put(k, v)", w, 0); writeWithIndentationAndNewLine("end", w, -INDENT_LEVEL); writeWithIndentationAndNewLine(memberPrefix + member + " = " + tmp, w, -INDENT_LEVEL); } else { final ClassEnumOrInterface classEnumOrIfce = findClassEnumOrInterface(returnValueType, allClasses); if (classEnumOrIfce.isEnum()) { writeWithIndentationAndNewLine(memberPrefix + member + " = Java::" + classEnumOrIfce.getFullName() + ".value_of(\"#{" + memberPrefix + member + ".to_s}\") unless " + memberPrefix + member + ".nil?", w, 0); } else { writeWithIndentationAndNewLine(memberPrefix + member + " = " + memberPrefix + member + ".to_java unless " + memberPrefix + member + ".nil?", w, 0); } } } } } private void dedupPreserveOrder(final List<MethodOrDecl> flattenedMethods) { final TreeSet<String> currentMethodNames = new TreeSet<String>(); final Iterator<MethodOrDecl> it = flattenedMethods.iterator(); while (it.hasNext()) { final String methodName = it.next().getName(); if (currentMethodNames.contains(methodName)) { it.remove(); continue; } currentMethodNames.add(methodName); } } private void generateAttributeAccessorFromGetterMethods(final ClassEnumOrInterface obj, final List<MethodOrDecl> flattenedMethods, final Writer w) throws IOException, GeneratorException { boolean first = true; writeWithIndentation("attr_accessor ", w, obj.isInterface() ? 0 : INDENT_LEVEL); for (final MethodOrDecl m : flattenedMethods) { if (!m.isGetter()) { continue; } final String member = camelToUnderscore(convertGetterMethodToFieldName(m.getName())); final String baseString = ":" + member; if (!first) { writeAppend(", ", w); } writeAppend(baseString, w); first = false; } writeNewLine(w); writeNewLine(w); } private String convertGetterMethodToFieldName(final String input) throws GeneratorException { if (input.startsWith("get")) { return input.substring(3); } else if (input.startsWith("is")) { return input; } else { // Some method ended there but don't seem to really be getters (e.g iterator), we just return them 'as is' return input; } } @Override protected String createFileName(final String name, final boolean withExtension) { final String extension = withExtension ? ".rb" : ""; return camelToUnderscore(name + extension); } @Override protected String getRequirePrefix(final GENERATOR_MODE mode) throws GeneratorException { if (mode == GENERATOR_MODE.JRUBY_API) { return REQUIRE_API_PREFIX; } else if (mode == GENERATOR_MODE.JRUBY_PLUGIN_API) { return REQUIRE_PLUGIN_API_PREFIX; } else { throw new GeneratorException("Unexpected mode :" + mode); } } @Override protected String getRequireFileName() { return REQUIRE_FILE_NAME; } @Override protected void startGeneration(final List<ClassEnumOrInterface> classes, final File outputDir, final GENERATOR_MODE mode) throws GeneratorException { switch (mode) { case JRUBY_API: staticallyGeneratedClasses.addAll(STATICALLY_API_GENERATED_CLASSES); break; case JRUBY_PLUGIN_API: case NON_APPLICABLE: default: break; } for (ClassEnumOrInterface cur : staticallyGeneratedClasses) { generateStaticClass(cur.getName(), outputDir); } } @Override protected void completeGeneration(final List<ClassEnumOrInterface> classes, final File outputDir, final GENERATOR_MODE mode) throws GeneratorException { classes.addAll(staticallyGeneratedClasses); generateRubyRequireFile(classes, outputDir, mode); } @Override protected String getLicense() { return LICENSE_NAME; } private List<MethodOrDecl> getTopMethods(final ClassEnumOrInterface obj, final List<ClassEnumOrInterface> allClasses) throws GeneratorException { if (obj.isEnum()) { return Collections.emptyList(); } final List<MethodOrDecl> result = new ArrayList<MethodOrDecl>(); if (obj.isClass()) { getMethodsFromExtendedClasses(obj, allClasses, result); } else if (obj.isInterface()) { getMethodsFromExtendedInterfaces(obj, allClasses, result); } else { throw new GeneratorException("Unexpected obj with no class/enum/interface:" + obj.getName()); } return result; } private void getMethodsFromExtendedInterfaces(final ClassEnumOrInterface obj, final List<ClassEnumOrInterface> allClasses, final List<MethodOrDecl> result) throws GeneratorException { // Reverse list to match original algorithm from ruby parser final List<String> superInterfaces = Lists.reverse(obj.getSuperInterfaces()); for (final String cur : superInterfaces) { // Don't expect to find those in our packages if (cur.startsWith("java.lang")) { addMissingMethodsFromJavaLang(cur, allClasses, result); continue; } final ClassEnumOrInterface ifce = findClassEnumOrInterface(cur, allClasses); result.addAll(ifce.getMethodOrDecls()); getMethodsFromExtendedInterfaces(ifce, allClasses, result); } } private void getMethodsFromExtendedClasses(final ClassEnumOrInterface obj, final List<ClassEnumOrInterface> allClasses, final List<MethodOrDecl> result) throws GeneratorException { final String superBaseClass = obj.getSuperBaseClass(); if (superBaseClass == null) { return; } // Don't expect to find those in our packages if (superBaseClass.startsWith("java.lang")) { return; } final ClassEnumOrInterface superClass = findClassEnumOrInterface(superBaseClass, allClasses); result.addAll(superClass.getMethodOrDecls()); getMethodsFromExtendedClasses(superClass, allClasses, result); } // // Since we don't parse all java classes, we need to add the methods from the java.lang classes we care about. private void addMissingMethodsFromJavaLang(final String javaLangBaseClass, final List<ClassEnumOrInterface> allClasses, final List<MethodOrDecl> result) throws GeneratorException { if (javaLangBaseClass.equals("java.lang.Iterable")) { final MethodOrDecl iteratorMethod = new JavaLanMethodOrDecl("iterator", new Type("java.util.Iterator", KillbillListener.UNDEFINED_GENERIC), true, null); result.add(iteratorMethod); } } private static class JavaLanMethodOrDecl extends MethodOrDecl { public JavaLanMethodOrDecl(final String name, final Type returnValueType, final boolean isAbstract, final List<Annotation> annotations) { super(name, returnValueType, isAbstract, annotations); } public boolean isGetter() { return true; } } }