From 04c12896d1e1a2fdd90585352dd85cbfe5c84aff Mon Sep 17 00:00:00 2001 From: Markus Kreth Date: Wed, 13 Mar 2019 22:26:23 +0100 Subject: [PATCH] Generator can generate Java Interface from property File. --- .classpath | 39 ++++ .gitignore | 3 + .project | 23 +++ pom.xml | 166 +++++++++++++++++ .../de/kreth/property2java/Configuration.java | 47 +++++ .../de/kreth/property2java/Generator.java | 59 ++++++ .../cli/ArgumentConfiguration.java | 97 ++++++++++ .../de/kreth/property2java/cli/CliConfig.java | 52 ++++++ .../property2java/ConfigurationTest.java | 48 +++++ .../kreth/property2java/GeneratorTests.java | 175 ++++++++++++++++++ 10 files changed, 709 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 pom.xml create mode 100644 src/main/java/de/kreth/property2java/Configuration.java create mode 100644 src/main/java/de/kreth/property2java/Generator.java create mode 100644 src/main/java/de/kreth/property2java/cli/ArgumentConfiguration.java create mode 100644 src/main/java/de/kreth/property2java/cli/CliConfig.java create mode 100644 src/test/java/de/kreth/property2java/ConfigurationTest.java create mode 100644 src/test/java/de/kreth/property2java/GeneratorTests.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..4721a1e --- /dev/null +++ b/.classpath @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8070aba --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ + +\.settings/ +target diff --git a/.project b/.project new file mode 100644 index 0000000..9c40d07 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + PropertyToJavaGenerator + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..07481a8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,166 @@ + + + 4.0.0 + de.kreth.property2java + PropertyToJavaGenerator + 0.0.1-SNAPSHOT + + + + UTF-8 + UTF-8 + 11 + 5.3.0-M1 + 1.7.21 + 2.11.0 + + ${maven.build.timestamp} + yyyy-MM-dd HH:mm:ss + + + target/surefire-reports + jacoco + jacoco + reuseReports + ${project.basedir}/target/jacoco.exec + **/src/main/webapp/VAADIN/**/* + + + + + + + org.junit + junit-bom + 5.3.2 + pom + import + + + + + + + org.apache.commons + commons-lang3 + 3.8.1 + + + + org.slf4j + slf4j-api + ${org.slf4j} + + + org.apache.logging.log4j + log4j-api + ${org.apache.logging.log4j} + runtime + + + org.apache.logging.log4j + log4j-core + ${org.apache.logging.log4j} + runtime + + + org.apache.logging.log4j + log4j-slf4j-impl + ${org.apache.logging.log4j} + runtime + + + + org.hamcrest + hamcrest-all + 1.3 + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.junit.platform + junit-platform-launcher + test + + + org.junit.platform + junit-platform-runner + test + + + + org.apache.commons + commons-text + 1.6 + + + org.mockito + mockito-core + 2.24.5 + + + org.mockito + mockito-junit-jupiter + 2.24.5 + + + commons-cli + commons-cli + 1.4 + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + ${java.version} + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.1 + + + + org.jacoco + jacoco-maven-plugin + 0.8.2 + + + default-jacoco-prepare-agent + + prepare-agent + + + + default-jacoco-report + prepare-package + + report + + + + + + + + \ No newline at end of file diff --git a/src/main/java/de/kreth/property2java/Configuration.java b/src/main/java/de/kreth/property2java/Configuration.java new file mode 100644 index 0000000..7060d01 --- /dev/null +++ b/src/main/java/de/kreth/property2java/Configuration.java @@ -0,0 +1,47 @@ +package de.kreth.property2java; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Path; +import java.util.Map; +import java.util.regex.Pattern; + +import org.apache.commons.text.WordUtils; + +public interface Configuration { + + static final Pattern REGEX = Pattern.compile("_[a-z]{2}(_[A-Z]{2})?\\."); + + /** + * Package for generated Java Classes eg. "de.kreth.property2java". If null - no package line is generated. + * @return + */ + String getPackage(); + + /** + * Filename to InputReader Entries + * @return + */ + Map getInput(); + + /** + * Path of java source folder. + * @return + */ + Path getRootPath(); + + default Writer outWriter(String fileName) throws IOException { + return new FileWriter(new File(getRootPath().toFile(), mapFilenameToClassName(fileName))); + } + + default String mapFilenameToClassName(String fileName) { + + String path = REGEX.matcher(fileName).replaceAll(".").replaceAll("\\.", "_").replaceAll(" ", "_"); + path = WordUtils.capitalize(path, '_'); + return path; + } + +} diff --git a/src/main/java/de/kreth/property2java/Generator.java b/src/main/java/de/kreth/property2java/Generator.java new file mode 100644 index 0000000..dd605e2 --- /dev/null +++ b/src/main/java/de/kreth/property2java/Generator.java @@ -0,0 +1,59 @@ +package de.kreth.property2java; + +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.util.Enumeration; +import java.util.Map.Entry; +import java.util.Properties; + +import de.kreth.property2java.cli.ArgumentConfiguration; + +public class Generator { + + private Configuration config; + + public Generator(Configuration config) { + this.config = config; + } + + public void start() throws IOException { + + for (Entry entry : config.getInput().entrySet()) { + String fileName = entry.getKey(); + Writer out = config.outWriter(fileName); + Properties properties = new Properties(); + properties.load(entry.getValue()); + generate(properties, out, fileName, config); + } + } + + void generate(Properties properties, Writer out, String fileName, Configuration config) throws IOException { + + @SuppressWarnings("unchecked") + Enumeration propertyNames = (Enumeration) properties.propertyNames(); + String packageName = config.getPackage(); + if (packageName != null && !packageName.isBlank()) { + out.write("package "); + out.write(packageName); + out.write(";\n\n"); + } + out.write("public interface "); + out.write(config.mapFilenameToClassName(fileName)); + out.write(" {\n"); + while (propertyNames.hasMoreElements()) { + String key = propertyNames.nextElement(); + out.write( + "\tpublic static String " + key.toUpperCase().replaceAll("[\\.-]", "_") + " = \"" + key + "\";\n"); + } + out.write(" }\n"); + out.flush(); + out.close(); + } + + public static void main(String[] args) throws IOException { + Generator generator = new Generator(ArgumentConfiguration.parse(args)); + generator.start(); + } + +} diff --git a/src/main/java/de/kreth/property2java/cli/ArgumentConfiguration.java b/src/main/java/de/kreth/property2java/cli/ArgumentConfiguration.java new file mode 100644 index 0000000..88d5154 --- /dev/null +++ b/src/main/java/de/kreth/property2java/cli/ArgumentConfiguration.java @@ -0,0 +1,97 @@ +package de.kreth.property2java.cli; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import de.kreth.property2java.Configuration; + +public class ArgumentConfiguration implements Configuration { + + private final String packageName; + + private final Map files; + + private final Path rootPath; + + private ArgumentConfiguration(Builder builder) throws IOException { + this.packageName = builder.packageName; + rootPath = new File(builder.target).toPath(); + files = new HashMap<>(); + for (String filePath : builder.propFiles) { + File f = new File(filePath); + files.put(f.getName(), new FileReader(f)); + } + + } + + @Override + public String getPackage() { + return packageName; + } + + @Override + public Map getInput() { + return files; + } + + @Override + public Path getRootPath() { + return rootPath; + } + + @Override + public Writer outWriter(String fileName) throws IOException { + File dir; + if (packageName != null && packageName.isBlank() == false) { + dir = new File(rootPath.toFile(), packageName.replace('.', File.separatorChar)); + } + else { + dir = rootPath.toFile(); + } + return new FileWriter(new File(dir, mapFilenameToClassName(fileName) + ".java"), false); + } + + public static Configuration parse(String[] args) throws IOException { + CliConfig cliConfig = new CliConfig(); + + Builder builder = new Builder(); + cliConfig.fill(builder, args); + return builder.build(); + } + + static class Builder { + String target; + + List propFiles = new ArrayList<>(); + + String packageName; + + public Builder setTarget(String target) { + this.target = target; + return this; + } + + public Builder addPropFile(String propFile) { + this.propFiles.add(propFile); + return this; + } + + public Builder setPackageName(String packageName) { + this.packageName = packageName; + return this; + } + + public Configuration build() throws IOException { + return new ArgumentConfiguration(this); + } + } +} diff --git a/src/main/java/de/kreth/property2java/cli/CliConfig.java b/src/main/java/de/kreth/property2java/cli/CliConfig.java new file mode 100644 index 0000000..f4e5f7a --- /dev/null +++ b/src/main/java/de/kreth/property2java/cli/CliConfig.java @@ -0,0 +1,52 @@ +package de.kreth.property2java.cli; + +import java.io.IOException; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.HelpFormatter; +import org.apache.commons.cli.MissingOptionException; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +import de.kreth.property2java.cli.ArgumentConfiguration.Builder; + +public class CliConfig { + + private final Options options = options(); + + private Options options() { + Options retVal = new Options(); + retVal.addOption(Option.builder("t").longOpt("targetSourcePath").hasArg().required().build()); + retVal.addOption(Option.builder("f").longOpt("files").hasArgs().required().valueSeparator(',').build()); + retVal.addOption(Option.builder("p").longOpt("package").hasArg().required(false).build()); + return retVal; + } + + public void fill(Builder builder, String[] args) throws IOException { + + CommandLineParser parser = new DefaultParser(); + try { + CommandLine cmd = parser.parse(options, args); + builder.setTarget(cmd.getOptionValue("t", ".")); + builder.setPackageName(cmd.getOptionValue("p")); + for (String value : cmd.getOptionValues("f")) { + builder.addPropFile(value); + } + } + catch (MissingOptionException e) { + printHelp(); + throw new IllegalStateException(e); + } + catch (ParseException e) { + throw new IOException("Unable to parse Arguments", e); + } + } + + public void printHelp() { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Generator", options); + } +} diff --git a/src/test/java/de/kreth/property2java/ConfigurationTest.java b/src/test/java/de/kreth/property2java/ConfigurationTest.java new file mode 100644 index 0000000..5df1040 --- /dev/null +++ b/src/test/java/de/kreth/property2java/ConfigurationTest.java @@ -0,0 +1,48 @@ +package de.kreth.property2java; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +import java.io.FileWriter; +import java.io.IOException; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class ConfigurationTest { + + private Configuration config; + + @BeforeEach + void initConfig() { + + config = Mockito.mock(Configuration.class); + } + + @Test + void defaultWriterIsFileWriter() throws IOException { + + when(config.outWriter(anyString())).thenCallRealMethod(); + when(config.mapFilenameToClassName(anyString())).thenCallRealMethod(); + + assertTrue(config.outWriter("application.properties") instanceof FileWriter); + } + + @Test + void testPathMapping() { + String className = config.mapFilenameToClassName("application.properties"); + assertEquals("Application_Properties", className); + } + + @Test + void testPathMappingLocalized() { + String className = config.mapFilenameToClassName("application_de_DE.properties"); + assertEquals("Application_Properties", className); + className = config.mapFilenameToClassName("application_en_US.properties"); + assertEquals("Application_Properties", className); + } + +} diff --git a/src/test/java/de/kreth/property2java/GeneratorTests.java b/src/test/java/de/kreth/property2java/GeneratorTests.java new file mode 100644 index 0000000..1429197 --- /dev/null +++ b/src/test/java/de/kreth/property2java/GeneratorTests.java @@ -0,0 +1,175 @@ +package de.kreth.property2java; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.StringTokenizer; +import java.util.stream.Collectors; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class GeneratorTests { + + private String path = "application.properties"; + + private Configuration config; + + private Generator generator; + + @BeforeEach + void setUp() throws Exception { + Map input = new HashMap<>(); + input.put(path, testProperties()); + + config = mock(Configuration.class); + when(config.getInput()).thenReturn(input); + when(config.mapFilenameToClassName(anyString())).thenCallRealMethod(); + + generator = new Generator(config); + } + + @Test + void testClassDefinition() throws IOException { + + when(config.getPackage()).thenReturn("de.kreth.property2java"); + + StringWriter out = new StringWriter(); + when(config.outWriter(anyString())).thenReturn(out); + + generator.start(); + + String sourceCode = out.toString().trim(); + StringTokenizer sourceTokenizer = new StringTokenizer(sourceCode, "\n"); + String linePackage = null; + String lineClass = null; + int countOpenBaces = 0; + int countCloseBaces = 0; + while (sourceTokenizer.hasMoreTokens()) { + String line = sourceTokenizer.nextToken(); + if (line.trim().startsWith("package")) { + linePackage = line; + } + else if (line.trim().startsWith("public interface")) { + lineClass = line; + } + if (line.contains("{")) { + countOpenBaces++; + } + if (line.contains("}")) { + countCloseBaces++; + } + } + + assertEquals(countCloseBaces, countOpenBaces, + "Count of Braces doesn't match. Open = " + countOpenBaces + ", Close = " + countCloseBaces); + + assertNotNull(linePackage); + assertNotNull(lineClass); + + assertThat(linePackage, + Matchers.stringContainsInOrder(Arrays.asList("package", "de.kreth.property2java", ";"))); + + assertThat(lineClass, + Matchers.stringContainsInOrder(Arrays.asList("public", "interface", "Application_Properties"))); + + } + + @Test + void testOneInputGeneratesOneOutput() throws IOException { + + Writer out = mock(Writer.class); + Writer nonOut = mock(Writer.class); + when(config.outWriter(anyString())).thenReturn(out, nonOut); + generator.start(); + verify(out).close(); + verify(nonOut, never()).close(); + verify(nonOut, never()).flush(); + } + + @Test + void testKeys() throws IOException { + + StringWriter out = new StringWriter(); + when(config.outWriter(anyString())).thenReturn(out); + generator.start(); + + List lines = out.toString().lines().filter(line -> line.contains(" String ")) + .collect(Collectors.toList()); + + assertEquals(21, lines.size()); + assertLineMatch(lines, "label", "label"); + assertLineMatch(lines, "label_addarticle", "label.addarticle"); + assertLineMatch(lines, "label_user_register", "label.user.register"); + assertLineMatch(lines, "message_article_priceerror", "message.article.priceerror"); + assertLineMatch(lines, "message_invoiceitem_startbeforeend", "message.invoiceitem.startbeforeend"); + assertLineMatch(lines, "message_invoiceitem_allfieldsmustbeset", + "message.invoiceitem.allfieldsmustbeset"); + } + + private void assertLineMatch(List lines, String key, String expected) { + Optional found = lines.stream().filter(line -> keyMatches(line, key)) + .findFirst(); + assertTrue(found.isPresent(), "No line found with key = " + key); + final String line = found.get().trim(); + int indexEquals = line.indexOf('='); + String value = line.substring(indexEquals + 1).trim().substring(1); + value = value.substring(0, value.length() - 2); + assertEquals(expected, value, "Line \"" + line + "\" don't match expected Value \"" + expected + "\""); + + assertEquals(';', line.charAt(line.length() - 1), "Line \"" + line + "\" don't end with ;"); + } + + private boolean keyMatches(String line, String key) { + line = line.toLowerCase(); + key = key.toLowerCase(); + return line.contains(" " + key + " "); + } + + private StringReader testProperties() { + return new StringReader("\r\n" + + "label = \r\n" + + "\r\n" + + "label.addarticle = Add Article\r\n" + + "label.cancel = Cancel\r\n" + + "label.close = Close\r\n" + + "label.delete = Delete\r\n" + + "label.discart = Discart\r\n" + + "label.loggedin = Logged in:\r\n" + + "label.logout = Logout\r\n" + + "label.ok = OK\r\n" + + "label.store = Store\r\n" + + "label.preview = Preview\r\n" + + "label.open = Open\r\n" + + "label.user.register = Register\r\n" + + "\r\n" + + "message.article.priceerror = Please set the price.\r\n" + + "message.delete.text = Delete {0}?\r\n" + + "message.delete.title = Really delete?\r\n" + + "message.invoiceitem.allfieldsmustbeset = Start, end and article must not be \\r\\n" + + " empty!\r\n" + + "message.invoiceitem.startbeforeend = End must be later than start.\r\n" + + "message.user.create.success = Thanks {0} created!\r\n" + + "message.user.loginfailure = Login Error! Wrong user or password?\r\n" + + "message.user.passwordmissmatch = Passwords don't match.\r\n" + + ""); + } +}