From 3cdde0ff364ef9738d815bf9ea4abcfe3a767766 Mon Sep 17 00:00:00 2001 From: lucalas Date: Thu, 31 May 2018 23:24:35 +0200 Subject: [PATCH] Add automation of Properties creator in build phase with a maven plugin Feature to automate the properties file creation; Created a plugin that can be add in the pom to automatic create properties based on passed args --- owner-creator-maven-plugin/pom.xml | 67 +++ .../plugin/PropertiesFileCreatorMojo.java | 99 +++++ .../main/java/org/aeonbits/owner/Config.java | 50 +++ .../org/aeonbits/owner/creator/Creator.java | 18 + .../owner/creator/PropertiesFileCreator.java | 393 ++++++++++++++++++ .../creator/PropertiesFileCreatorTest.java | 60 +++ pom.xml | 1 + 7 files changed, 688 insertions(+) create mode 100644 owner-creator-maven-plugin/pom.xml create mode 100644 owner-creator-maven-plugin/src/main/java/org/aeonbits/owner/plugin/PropertiesFileCreatorMojo.java create mode 100644 owner/src/main/java/org/aeonbits/owner/creator/Creator.java create mode 100644 owner/src/main/java/org/aeonbits/owner/creator/PropertiesFileCreator.java create mode 100644 owner/src/test/java/org/aeonbits/owner/creator/PropertiesFileCreatorTest.java diff --git a/owner-creator-maven-plugin/pom.xml b/owner-creator-maven-plugin/pom.xml new file mode 100644 index 00000000..0e692b96 --- /dev/null +++ b/owner-creator-maven-plugin/pom.xml @@ -0,0 +1,67 @@ + + 4.0.0 + + org.aeonbits.owner + maven-plugin-owner-creator + maven-plugin + 1.0.11-SNAPSHOT + OWNER :: Plugin + http://maven.apache.org + + + org.apache.maven + maven-plugin-api + 3.5.3 + + + org.apache.maven.plugin-tools + maven-plugin-annotations + 3.5.1 + provided + + + org.apache.maven + maven-project + 2.0.8 + + + junit + junit + 3.8.1 + test + + + org.aeonbits.owner + owner + 1.0.11-SNAPSHOT + + + org.apache.maven + maven-artifact + 2.0.8 + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.2 + + + true + + + + + mojo-descriptor + + descriptor + + + + + + + diff --git a/owner-creator-maven-plugin/src/main/java/org/aeonbits/owner/plugin/PropertiesFileCreatorMojo.java b/owner-creator-maven-plugin/src/main/java/org/aeonbits/owner/plugin/PropertiesFileCreatorMojo.java new file mode 100644 index 00000000..b3b9a625 --- /dev/null +++ b/owner-creator-maven-plugin/src/main/java/org/aeonbits/owner/plugin/PropertiesFileCreatorMojo.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012-2015, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package org.aeonbits.owner.plugin; + +import java.io.File; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.List; +import org.aeonbits.owner.creator.PropertiesFileCreator; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.Component; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.apache.maven.project.MavenProject; + +/** + * + * @author Luca Taddeo + */ + +@Mojo(name = "create", requiresDependencyResolution = ResolutionScope.RUNTIME) +public class PropertiesFileCreatorMojo + extends AbstractMojo { + + /** + * Location of the output properties file. + */ + @Parameter(required=true) + private String outputDirectory; + + /** + * Config class to parse. + */ + @Parameter(required=true) + private String packageClass; + + /** + * Jar to include. + */ + @Parameter + private String jarPath; + + /** + * Project Name. + */ + @Parameter + private String projectName; + + /** + * Project description. + */ + @Parameter + private String projectDesription; + + @Component + private MavenProject project; + + @Override + public void execute() throws MojoExecutionException, MojoFailureException { + try { + + List urls = new ArrayList(); + + if(jarPath != null && !jarPath.isEmpty()) { + System.out.println("Use passed jar [" + jarPath + "]"); + File jar = new File(jarPath); + urls.add(jar.toURI().toURL()); + } else { + System.out.println("Use classpath"); + // Loading project classpath to retrieve class + for (Object ele : project.getRuntimeClasspathElements()) { + File jar = new File(ele.toString()); + urls.add(jar.toURI().toURL()); + } + } + + URLClassLoader jarPack = new URLClassLoader(urls.toArray(new URL[urls.size()]), this.getClass().getClassLoader()); + Class classToLoad = jarPack.loadClass(packageClass); + + // Parse class and create property file + PropertiesFileCreator creator = new PropertiesFileCreator(); + creator.parse(classToLoad, outputDirectory, projectName, projectDesription); + System.out.println("Conversion succeded, properties saved [" + outputDirectory + "]"); + } catch (Throwable ex) { + throw new MojoExecutionException(ex.getMessage()); + } + } + +} diff --git a/owner/src/main/java/org/aeonbits/owner/Config.java b/owner/src/main/java/org/aeonbits/owner/Config.java index 56c5c60d..bcac51a7 100644 --- a/owner/src/main/java/org/aeonbits/owner/Config.java +++ b/owner/src/main/java/org/aeonbits/owner/Config.java @@ -115,6 +115,56 @@ public interface Config extends Serializable { Class value() default IdentityDecryptor.class; } + /** + * The description that appears in created property file. + */ + @Retention(RUNTIME) + @Target(METHOD) + @Documented + @interface Description { + String value(); + } + + /** + * If this annotation is put on a property, it will not write on property file. + */ + @Retention(RUNTIME) + @Target(METHOD) + @Documented + @interface NoProperty { + } + + /** + * Value to assign to the property in the property file. + */ + @Retention(RUNTIME) + @Target(METHOD) + @Documented + @interface ValorizedAs { + String value(); + } + + /** + * Order to follow for groups in property file. + */ + @Retention(RUNTIME) + @Target(TYPE) + @Documented + @interface GroupOrder { + String[] value(); + } + + /** + * Group of a property. + * Can be more than one as group and subgroup. + */ + @Retention(RUNTIME) + @Target(METHOD) + @Documented + @interface Group { + String[] value(); + } + /** * Specifies the policy type to use to load the {@link org.aeonbits.owner.Config.Sources} files for properties. * diff --git a/owner/src/main/java/org/aeonbits/owner/creator/Creator.java b/owner/src/main/java/org/aeonbits/owner/creator/Creator.java new file mode 100644 index 00000000..b70b98e8 --- /dev/null +++ b/owner/src/main/java/org/aeonbits/owner/creator/Creator.java @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2012-2015, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package org.aeonbits.owner.creator; + +/** + * Interface to convert Config class into something else. + * + * @author Luca Taddeo + */ +public interface Creator { + boolean parse(Class clazz, String output, String headerName, String projectName) throws Exception; +} diff --git a/owner/src/main/java/org/aeonbits/owner/creator/PropertiesFileCreator.java b/owner/src/main/java/org/aeonbits/owner/creator/PropertiesFileCreator.java new file mode 100644 index 00000000..6f80560a --- /dev/null +++ b/owner/src/main/java/org/aeonbits/owner/creator/PropertiesFileCreator.java @@ -0,0 +1,393 @@ +/* + * Copyright (c) 2012-2015, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package org.aeonbits.owner.creator; + +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; +import java.io.PrintWriter; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import org.aeonbits.owner.Config.DefaultValue; +import org.aeonbits.owner.Config.Description; +import org.aeonbits.owner.Config.GroupOrder; +import org.aeonbits.owner.Config.Key; +import org.aeonbits.owner.Config.NoProperty; +import org.aeonbits.owner.Config.ValorizedAs; + +/** + * PropertiesFileCreator helps you to automate the process of properties creation. + * + * @author Luca Taddeo + */ +public class PropertiesFileCreator implements Creator { + + public String header = "#\n" + + "#\n" + + "# Properties file created for\n"; + + public String footer = "\n\n\n# Properties file autogenerated by OWNER :: PropertyCreator\n" + + "# Created [%s] in %s ms\n"; + + public long lastExecutionTime = 0; + + /** + * Method to parse the class and write file in the choosen output. + * + * @param clazz class to parse + * @param output output file path + * + * @return if the file was written correctly. + * @throws Exception + */ + public boolean parse(Class clazz, String output, String headerName, String projectName) throws Exception { + boolean valid = true; + long startTime = System.currentTimeMillis(); + + Group[] groups = parseMethods(clazz); + long finishTime = System.currentTimeMillis(); + + lastExecutionTime = finishTime - startTime; + + String result = toPropertiesString(groups, headerName, projectName); + + valid = writeProperties(output, result); + + return valid; + } + + /** + * Method to get group array with subgroups and properties. + * + * @param clazz class to parse + * @return array of groups + */ + private Group[] parseMethods(Class clazz) { + List groups = new ArrayList(); + Group unknownGroup = new Group(); + unknownGroup.title = "GENERIC PROPERTIES"; + groups.add(unknownGroup); + String[] groupsOrder = new String[0]; + + // Retrieve the groups order if there is the annotation + if (clazz.isAnnotationPresent(GroupOrder.class)) { + GroupOrder order = (GroupOrder) clazz.getAnnotation(GroupOrder.class); + + groupsOrder = order.value(); + } + + try { + for (Method method : clazz.getMethods()) { + Property prop = new Property(); + + prop.deprecated = method.isAnnotationPresent(Deprecated.class); + + prop.addToPropertyFile = !method.isAnnotationPresent(NoProperty.class); + + if (method.isAnnotationPresent(Key.class)) { + Key val = method.getAnnotation(Key.class); + prop.name = val.value(); + } else { + prop.name = method.getName(); + } + + if (method.isAnnotationPresent(DefaultValue.class)) { + DefaultValue val = method.getAnnotation(DefaultValue.class); + prop.defaultValue = val.value(); + } + + if (method.isAnnotationPresent(ValorizedAs.class)) { + ValorizedAs val = method.getAnnotation(ValorizedAs.class); + prop.valorizedAs = val.value(); + } + + if (method.isAnnotationPresent(Description.class)) { + Description val = method.getAnnotation(Description.class); + prop.description = val.value(); + } + + if (method.isAnnotationPresent(org.aeonbits.owner.Config.Group.class)) { + org.aeonbits.owner.Config.Group group = method.getAnnotation(org.aeonbits.owner.Config.Group.class); + Group currentGroup = getOrAddGroup(group.value(), groups); + + currentGroup.properties.add(prop); + } else { + unknownGroup.properties.add(prop); + } + } + } catch (Throwable ex) { + throw new RuntimeException("Something wrong happened during class conversion", ex); + } + + return orderGroup(groups, groupsOrder); + } + + /** + * Order groups based on passed order. + * + * @param groups groups to order + * @param groupsOrder order to follow + * @return ordered groups + */ + private Group[] orderGroup(List groups, String[] groupsOrder) { + LinkedList groupsOrdered = new LinkedList(); + + List remained = new ArrayList(groups); + + for (String order : groupsOrder) { + for (Group remain : remained) { + if (remain.title.equals(order)) { + groupsOrdered.add(remain); + remained.remove(remain); + break; + } + } + } + + groupsOrdered.addAll(remained); + + return groupsOrdered.toArray(new Group[groupsOrdered.size()]); + } + + /** + * Get specific group from the list or ad new one if there isn't the choosen + * group. + * + * @param groupsName group to find + * @param groups list to search for + * @return Found/added groups + */ + private Group getOrAddGroup(String[] groupsName, List groups) { + List currentLevel = groups; + Group lastFound = null; + for (String groupName : groupsName) { + Group found = null; + for (Group current : currentLevel) { + if (groupName.equals(current.title)) { + found = current; + break; + } + } + + if (found != null) { + currentLevel = found.subGroups; + } else { + lastFound = new Group(); + lastFound.title = groupName; + currentLevel.add(lastFound); + currentLevel = lastFound.subGroups; + } + } + + return lastFound; + } + + /** + * Convert groups list into string. + * + * @param groups + * @return + */ + private String toPropertiesString(Group[] groups, String headerName, String projectName) { + StringBuilder result = new StringBuilder(); + + // Append header + result.append(generateFileHeader(headerName, projectName)); + + // Append properties + for (Group group : groups) { + result.append(group.toString()); + } + + // Append footer + result.append(generateFileFooter()); + + return result.toString(); + } + + /** + * Generate header for the file. + * + * @return + */ + private String generateFileHeader(String headerName, String projectName) { + String headerAscii = ""; + String projectNameTemp = "# " + projectName + "\n" + + "#\n\n"; + + if (headerName != null && !headerName.isEmpty()) { + headerAscii = createAscii(headerName) + "\n"; + } + + return String.format(headerAscii + header + projectNameTemp); + } + + + /** + * Generate footer for the file. + * + * @return + */ + private String generateFileFooter() { + DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); + Date date = new Date(); + String dateString = dateFormat.format(date); + return String.format(footer, dateString, lastExecutionTime); + } + + /** + * Write string to file. + * + * @param output output file + * @param propertiesString string to write + * @return + */ + private boolean writeProperties(String output, String propertiesString) { + boolean written = false; + PrintWriter out = null; + try { + out = new PrintWriter(output); + out.println(propertiesString); + written = true; + } catch (Throwable ex) { + throw new RuntimeException("Output File problems", ex); + } finally { + try { + if (out != null) { + out.close(); + } + } catch (Throwable ex) { + } + } + return written; + } + + /** + * Create an ascii graphic string based on passed text. + * + * @param text + * @return + */ + public static String createAscii(String text) { + StringBuilder finalStr = new StringBuilder(); + int width = 100; + int height = 30; + + //BufferedImage image = ImageIO.read(new File("/Users/mkyong/Desktop/logo.jpg")); + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); + Graphics g = image.getGraphics(); + g.setFont(new Font("SansSerif", Font.BOLD, 15)); + + Graphics2D graphics = (Graphics2D) g; + graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, + RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + graphics.drawString(text, 0, 20); + + for (int y = 0; y < height; y++) { + StringBuilder sb = new StringBuilder(); + for (int x = 0; x < width; x++) { + sb.append(image.getRGB(x, y) == -16777216 ? " " : "$"); + } + + if (sb.toString().trim().isEmpty()) { + continue; + } + + finalStr.append(sb.insert(0, "# ").toString().trim()).append("\n"); + } + + return finalStr.toString().trim(); + } +} + +class Group { + + public String title = ""; + public List subGroups = new ArrayList(); + public List properties = new ArrayList(); + + public String toString(boolean subHeader) { + StringBuilder group = new StringBuilder(); + + if (subHeader) { + group.append("#----------------\n") + .append("# - ").append(title).append(" -\n") + .append("#----------------\n\n"); + } else { + group.append("#//----------------------------------------------------------\n") + .append("#// ").append(title).append("\n") + .append("#//----------------------------------------------------------\n\n"); + } + + for (Property property : properties) { + if (property.addToPropertyFile) { + group.append(property.toString()); + } + } + + for (Group current : subGroups) { + group.append(current.toString(true)); + } + + return group.toString(); + } + + @Override + public String toString() { + return toString(false); + } +} + +class Property { + + public String name = ""; + public String defaultValue = ""; + public String valorizedAs = ""; + public String description = ""; + public boolean addToPropertyFile = true; + public boolean deprecated = false; + + @Override + public String toString() { + StringBuilder property = new StringBuilder(); + + property.append("# \n"); + + if (deprecated) { + property.append("# DEPRECATED PROPERTY\n") + .append("# \n"); + } + + String[] descriptionLines = description.split("\n"); + + for (String line : descriptionLines) { + property.append("# ").append(line).append("\n"); + } + + property.append("# \n") + .append("# Default(\"").append(defaultValue).append("\")\n"); + + if (valorizedAs != null && !valorizedAs.isEmpty()) { + property.append(name).append("=").append(valorizedAs); + } else { + property.append("#").append(name).append("=").append(defaultValue); + } + + property.append("\n\n"); + + return property.toString(); + } +} diff --git a/owner/src/test/java/org/aeonbits/owner/creator/PropertiesFileCreatorTest.java b/owner/src/test/java/org/aeonbits/owner/creator/PropertiesFileCreatorTest.java new file mode 100644 index 00000000..6cb1bc86 --- /dev/null +++ b/owner/src/test/java/org/aeonbits/owner/creator/PropertiesFileCreatorTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2012-2015, Luigi R. Viggiano + * All rights reserved. + * + * This software is distributable under the BSD license. + * See the terms of the BSD license in the documentation provided with this software. + */ + +package org.aeonbits.owner.creator; + +import java.util.logging.Level; +import java.util.logging.Logger; +import org.aeonbits.owner.Config; +import org.aeonbits.owner.Config.GroupOrder; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author Luca Taddeo + */ +public class PropertiesFileCreatorTest { + + @GroupOrder({"second", "first"}) + interface MyConfig extends Config { + + @Group({"first"}) + @Key("valuekey") + @DefaultValue("value") + String value(); + + @DefaultValue("value2") + String value2(); + + @Group({"second"}) + @DefaultValue("value3") + String value3(); + + @Group({"second", "sub"}) + @DefaultValue("value4") + String value4(); + } + + /** + * Test of parse method, of class PropertiesFileCreator. + */ + @Test + public void testParse() { + + try { + PropertiesFileCreator creator = new PropertiesFileCreator(); + + assertTrue(creator.parse(MyConfig.class, "./text.properties", "test", "project test")); + } catch (Exception ex) { + Logger.getLogger(PropertiesFileCreatorTest.class.getName()).log(Level.SEVERE, null, ex); + fail(); + } + } + +} diff --git a/pom.xml b/pom.xml index caf27f48..290adf99 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ owner-site owner-extras owner-assembly + owner-creator-maven-plugin 2012