/* * 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.streams.plugins; import org.reflections.ReflectionUtils; import org.reflections.Reflections; import org.reflections.scanners.SubTypesScanner; import org.reflections.scanners.TypeAnnotationsScanner; import org.reflections.util.ConfigurationBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.Serializable; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.stream.Collectors; import java.util.stream.Stream; /** * Embed within your own java code * * <p/> * StreamsScalaGenerationConfig config = new StreamsScalaGenerationConfig(); * config.setTargetDirectory("target/generated-sources/scala"); * config.setTargetPackage("com.example"); * StreamsScalaSourceGenerator generator = new StreamsScalaSourceGenerator(config); * generator.run(); * */ public class StreamsScalaSourceGenerator implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(StreamsScalaSourceGenerator.class); private static final String LS = System.getProperty("line.separator"); private StreamsScalaGenerationConfig config; private Reflections reflections; private String outDir; /** * Run from CLI without Maven * * <p/> * java -jar streams-plugin-scala-jar-with-dependencies.jar StreamsScalaSourceGenerator target/generated-sources * * @param args [targetDirectory, targetPackage] * */ public static void main(String[] args) { StreamsScalaGenerationConfig config = new StreamsScalaGenerationConfig(); List<String> sourcePackages = new ArrayList<>(); String targetDirectory = "target/generated-sources/pojo"; String targetPackage = ""; if ( args.length > 0 ) { sourcePackages = Stream.of(args[0].split(",")).collect(Collectors.toList()); } if ( args.length > 1 ) { targetDirectory = args[1]; } if ( args.length > 2 ) { targetPackage = args[2]; } config.setSourcePackages(sourcePackages); config.setTargetPackage(targetPackage); config.setTargetDirectory(targetDirectory); StreamsScalaSourceGenerator streamsScalaSourceGenerator = new StreamsScalaSourceGenerator(config); streamsScalaSourceGenerator.run(); } /** * StreamsScalaSourceGenerator constructor. * @param config StreamsScalaGenerationConfig */ public StreamsScalaSourceGenerator(StreamsScalaGenerationConfig config) { this.config = config; this.outDir = config.getTargetDirectory().getAbsolutePath(); reflections = new Reflections( new ConfigurationBuilder() // TODO .forPackages( config.getSourcePackages() .toArray(new String[config.getSourcePackages().size()]) ) .setScanners( new SubTypesScanner(), new TypeAnnotationsScanner())); } @Override public void run() { List<Class<?>> serializableClasses = detectSerializableClasses(); LOGGER.info("Detected {} serialiables:", serializableClasses.size()); for ( Class clazz : serializableClasses ) { LOGGER.debug(clazz.toString()); } List<Class<?>> pojoClasses = detectPojoClasses(serializableClasses); LOGGER.info("Detected {} pojos:", pojoClasses.size()); for ( Class clazz : pojoClasses ) { LOGGER.debug(clazz.toString()); } List<Class<?>> traits = detectTraits(pojoClasses); LOGGER.info("Detected {} traits:", traits.size()); for ( Class clazz : traits ) { LOGGER.debug(clazz.toString()); } List<Class<?>> cases = detectCases(pojoClasses); LOGGER.info("Detected {} cases:", cases.size()); for ( Class clazz : cases ) { LOGGER.debug(clazz.toString()); } for ( Class clazz : traits ) { String pojoPath = clazz.getPackage().getName().replace(".pojo.json", ".scala").replace(".","/") + "/traits/"; String pojoName = clazz.getSimpleName() + ".scala"; String pojoScala = renderTrait(clazz); writeFile(outDir + "/" + pojoPath + pojoName, pojoScala); } for ( Class clazz : traits ) { String pojoPath = clazz.getPackage().getName().replace(".pojo.json", ".scala").replace(".","/") + "/"; String pojoName = clazz.getSimpleName() + ".scala"; String pojoScala = renderClass(clazz); writeFile(outDir + "/" + pojoPath + pojoName, pojoScala); } for ( Class clazz : cases ) { String pojoPath = clazz.getPackage().getName().replace(".pojo.json", ".scala").replace(".","/") + "/"; String pojoName = clazz.getSimpleName() + ".scala"; String pojoScala = renderCase(clazz); writeFile(outDir + "/" + pojoPath + pojoName, pojoScala); } } private void writeFile(String pojoFile, String pojoScala) { try { File path = new File(pojoFile); File dir = path.getParentFile(); if ( !dir.exists() ) { dir.mkdirs(); } Files.write(Paths.get(pojoFile), pojoScala.getBytes(), StandardOpenOption.CREATE_NEW); } catch (Exception ex) { LOGGER.error("Write Exception: {}", ex); } } /** * detectSerializableClasses. * @return List of Serializable Classes */ public List<Class<?>> detectSerializableClasses() { Set<Class<? extends Serializable>> classes = reflections.getSubTypesOf(java.io.Serializable.class); List<Class<?>> result = new ArrayList<>(); for ( Class clazz : classes ) { result.add(clazz); } return result; } /** * detect which Classes are Pojo Classes. * @param classes List of candidate Pojo Classes * @return List of actual Pojo Classes */ public List<Class<?>> detectPojoClasses(List<Class<?>> classes) { List<Class<?>> result = new ArrayList<>(); for ( Class clazz : classes ) { try { clazz.newInstance().toString(); } catch ( Exception ex) { // } // super-halfass way to know if this is a jsonschema2pojo if ( clazz.getAnnotations().length >= 1 ) { result.add(clazz); } } return result; } private List<Class<?>> detectTraits(List<Class<?>> classes) { List<Class<?>> traits = new ArrayList<>(); for ( Class clazz : classes ) { if (reflections.getSubTypesOf(clazz).size() > 0) { traits.add(clazz); } } return traits; } private List<Class<?>> detectCases(List<Class<?>> classes) { List<Class<?>> cases = new ArrayList<>(); for ( Class clazz : classes ) { if (reflections.getSubTypesOf(clazz).size() == 0) { cases.add(clazz); } } return cases; } private String renderTrait(Class<?> pojoClass) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("package "); stringBuffer.append(pojoClass.getPackage().getName().replace(".pojo.json", ".scala")); stringBuffer.append(".traits"); stringBuffer.append(LS); stringBuffer.append("trait ").append(pojoClass.getSimpleName()); stringBuffer.append(" extends Serializable"); stringBuffer.append(" {"); Set<Field> fields = ReflectionUtils.getAllFields(pojoClass); appendFields(stringBuffer, fields, "def", ";"); stringBuffer.append("}"); return stringBuffer.toString(); } private String renderClass(Class<?> pojoClass) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("package "); stringBuffer.append(pojoClass.getPackage().getName().replace(".pojo.json", ".scala")); stringBuffer.append(LS); stringBuffer.append("import org.apache.commons.lang.builder.{HashCodeBuilder, EqualsBuilder, ToStringBuilder}"); stringBuffer.append(LS); stringBuffer.append("class ").append(pojoClass.getSimpleName()); stringBuffer.append(" ("); Set<Field> fields = ReflectionUtils.getAllFields(pojoClass); appendFields(stringBuffer, fields, "var", ","); stringBuffer.append(")"); stringBuffer.append(" extends ").append(pojoClass.getPackage().getName().replace(".pojo.json", ".scala")).append(".traits.").append(pojoClass.getSimpleName()); stringBuffer.append(" with Serializable "); stringBuffer.append("{ "); stringBuffer.append(LS); stringBuffer.append("override def equals(obj: Any) = obj match { "); stringBuffer.append(LS); stringBuffer.append(" case other: "); stringBuffer.append(pojoClass.getSimpleName()); stringBuffer.append(" => other.getClass == getClass && EqualsBuilder.reflectionEquals(this,obj)"); stringBuffer.append(LS); stringBuffer.append(" case _ => false"); stringBuffer.append(LS); stringBuffer.append("}"); stringBuffer.append(LS); stringBuffer.append("override def hashCode = new HashCodeBuilder().hashCode"); stringBuffer.append(LS); stringBuffer.append("}"); return stringBuffer.toString(); } private String renderCase(Class<?> pojoClass) { StringBuffer stringBuffer = new StringBuffer(); stringBuffer.append("package "); stringBuffer.append(pojoClass.getPackage().getName().replace(".pojo.json", ".scala")); stringBuffer.append(LS); stringBuffer.append("case class " + pojoClass.getSimpleName()); stringBuffer.append("("); Set<Field> fields = ReflectionUtils.getAllFields(pojoClass); appendFields(stringBuffer, fields, "var", ","); stringBuffer.append(")"); if ( pojoClass.getSuperclass() != null && !pojoClass.getSuperclass().equals(java.lang.Object.class)) { stringBuffer.append(" extends " + pojoClass.getSuperclass().getPackage().getName().replace(".pojo.json", ".scala") + ".traits." + pojoClass.getSuperclass().getSimpleName()); } stringBuffer.append(LS); return stringBuffer.toString(); } private void appendFields(StringBuffer stringBuffer, Set<Field> fields, String varDef, String fieldDelimiter) { if ( fields.size() > 0 ) { stringBuffer.append(LS); Map<String,Field> fieldsToAppend = uniqueFields(fields); for ( Iterator<Field> iter = fieldsToAppend.values().iterator(); iter.hasNext(); ) { Field field = iter.next(); if ( override( field ) ) { stringBuffer.append("override "); } stringBuffer.append(varDef); stringBuffer.append(" "); stringBuffer.append(name(field)); stringBuffer.append(": "); if ( option(field) ) { stringBuffer.append("scala.Option["); stringBuffer.append(type(field)); stringBuffer.append("]"); } else { stringBuffer.append(type(field)); } if ( !fieldDelimiter.equals(";") && value(field) != null) { stringBuffer.append(" = "); if ( option(field) ) { stringBuffer.append("scala.Some("); stringBuffer.append(value(field)); stringBuffer.append(")"); } else { stringBuffer.append(value(field)); } } if ( iter.hasNext()) { stringBuffer.append(fieldDelimiter); } stringBuffer.append(LS); } } else { stringBuffer.append(LS); } } private boolean option(Field field) { return !field.getName().equals("verb") && !field.getType().equals(Map.class) && !field.getType().equals(List.class); } private String value(Field field) { switch (field.getName()) { case "verb": return "\"post\""; case "objectType": return "\"application\""; default: return null; } } private String type(Field field) { if ( field.getType().equals(java.lang.String.class)) { return "String"; } else if ( field.getType().equals(java.util.Map.class)) { return "scala.collection.mutable.Map[String,Any]"; } else if ( field.getType().equals(java.util.List.class)) { return "scala.collection.mutable.MutableList[Any]"; } return field.getType().getCanonicalName().replace(".pojo.json", ".scala"); } private Map<String,Field> uniqueFields(Set<Field> fieldset) { Map<String,Field> fields = new TreeMap<>(); Field item = null; for ( Iterator<Field> it = fieldset.iterator(); it.hasNext(); item = it.next() ) { if ( item != null && item.getName() != null ) { Field added = fields.put(item.getName(), item); } // ensure right class will get used } return fields; } private String name(Field field) { if ( field.getName().equals("object")) { return "obj"; } else { return field.getName(); } } private boolean override(Field field) { try { return field.getDeclaringClass().getSuperclass().getField(field.getName()) != null; } catch ( Exception ex ) { return false; } } }