You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by sg...@apache.org on 2020/02/29 00:06:09 UTC

[freemarker-generator] 01/06: FREEMARKER-135 Support user-supplied names for datasources

This is an automated email from the ASF dual-hosted git repository.

sgoeschl pushed a commit to branch FREEMARKER-135
in repository https://gitbox.apache.org/repos/asf/freemarker-generator.git

commit 8f7415e7e66eb83f5b261812c42847ba5e09e9b9
Author: Siegfried Goeschl <si...@gmail.com>
AuthorDate: Fri Feb 28 20:46:51 2020 +0100

    FREEMARKER-135 Support user-supplied names for datasources
---
 .../freemarker/generator/base/uri/NamedUri.java    | 77 ++++++++++++++++++++
 .../NamedUriFragmentParser.java}                   | 28 +++++---
 .../generator/base/uri/NamedUriParser.java         | 81 +++++++++++++++++++++
 .../generator/base/util/CachingSupplier.java       | 16 +++++
 .../generator/base/util/LocaleUtils.java           |  1 +
 .../generator/uri/NamedUriParserTest.java          | 83 ++++++++++++++++++++++
 .../org/apache/freemarker/generator/cli/Main.java  | 43 ++++++-----
 .../freemarker/generator/cli/PicocliTest.java      | 74 +++++++++++++++++++
 8 files changed, 377 insertions(+), 26 deletions(-)

diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
new file mode 100644
index 0000000..cc0b52c
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUri.java
@@ -0,0 +1,77 @@
+/*
+ * 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.freemarker.generator.base.uri;
+
+import java.net.URI;
+import java.util.Map;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Caputeres the information of a user-supplied "named URI".
+ */
+public class NamedUri {
+
+    /** User-supplied name */
+    private final String name;
+
+    /** The URI */
+    private final URI uri;
+
+    /** Name/value pairs parsed from URI fragment */
+    private final Map<String, String> parameters;
+
+    public NamedUri(URI uri) {
+        this.name = null;
+        this.uri = requireNonNull(uri);
+        this.parameters = emptyMap();
+    }
+
+    public NamedUri(URI uri, Map<String, String> parameters) {
+        this.name = null;
+        this.uri = requireNonNull(uri);
+        this.parameters = requireNonNull(parameters);
+    }
+
+    public NamedUri(String name, URI uri, Map<String, String> parameters) {
+        this.name = requireNonNull(name);
+        this.uri = requireNonNull(uri);
+        this.parameters = requireNonNull(parameters);
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public URI getUri() {
+        return uri;
+    }
+
+    public Map<String, String> getParameters() {
+        return parameters;
+    }
+
+    @Override
+    public String toString() {
+        return "NamedUri{" +
+                "name='" + name + '\'' +
+                ", uri=" + uri +
+                ", parameters=" + parameters +
+                '}';
+    }
+}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
similarity index 52%
copy from freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
copy to freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
index 5edd405..a94d3da 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriFragmentParser.java
@@ -14,20 +14,32 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.freemarker.generator.base.util;
+package org.apache.freemarker.generator.base.uri;
 
-import java.util.Locale;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
 
+import static java.util.stream.Collectors.toMap;
 import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
 
-public class LocaleUtils {
+/**
+ * Parses the URI fragment as list of name/value pairs seperated by an ampersand.
+ */
+public class NamedUriFragmentParser {
 
-    public static Locale parseLocale(String value) {
-        if (isEmpty(value) || value.equalsIgnoreCase("JVM default") || value.equalsIgnoreCase("default")) {
-            return Locale.getDefault();
+    public static Map<String, String> parse(String fragment) {
+        if (isEmpty(fragment)) {
+            return Collections.emptyMap();
         }
 
-        final String[] parts = value.split("_");
-        return parts.length == 1 ? new Locale(parts[0]) : new Locale(parts[0], parts[1]);
+        try {
+            final String[] nameValuePairs = fragment.split("&");
+            return Arrays.stream(nameValuePairs)
+                    .map(s -> s.split("="))
+                    .collect(toMap(parts -> parts[0], parts -> parts[1]));
+        } catch (RuntimeException e) {
+            throw new RuntimeException("Unable to parse URI fragment: " + fragment, e);
+        }
     }
 }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java
new file mode 100644
index 0000000..a18d914
--- /dev/null
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/uri/NamedUriParser.java
@@ -0,0 +1,81 @@
+/*
+ * 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.freemarker.generator.base.uri;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Parses a named URI provided by the caller.
+ * <ul>
+ *     <li>users.csv</li>
+ *     <li>file:///users.csv</li>
+ *     <li>user=file:///users.csv</li>
+ *     <li>users=file:///users.csv#charset=UTF-16&mimetype=text/csv</li>
+ * </ul>
+ */
+public class NamedUriParser {
+
+    private static final String NAME_GROUP = "name";
+    private static final String URI_GROUP = "uri";
+    private static final int NR_OF_NAMED_GROUPS = 2;
+    private static final Pattern NAMED_URI_REGEXP = Pattern.compile("^(?<name>[a-zA-Z0-9-_]*)=(?<uri>.*)");
+
+    public static NamedUri parse(String value) {
+        final String sanitzedUri = requireNonNull(value).trim();
+
+        if (sanitzedUri.isEmpty()) {
+            throw new IllegalArgumentException("Empty named URI");
+        }
+
+        try {
+            // avoid invoking the regexp if it can't match anyway
+            if (isSimpleUri(sanitzedUri)) {
+                return new NamedUri(new URI(sanitzedUri));
+            }
+
+            final Matcher matcher = NAMED_URI_REGEXP.matcher(sanitzedUri);
+
+            if (!matcher.matches() || matcher.groupCount() > NR_OF_NAMED_GROUPS) {
+                throw new IllegalArgumentException("Invalid named URI: " + value);
+            }
+
+            if (matcher.groupCount() == NR_OF_NAMED_GROUPS) {
+                final String name = matcher.group(NAME_GROUP);
+                final URI uri = new URI(matcher.group(URI_GROUP));
+                final Map<String, String> parameters = NamedUriFragmentParser.parse(uri.getFragment());
+                return new NamedUri(name, uri, parameters);
+            } else {
+                final URI uri = new URI(matcher.group(sanitzedUri));
+                final Map<String, String> parameters = NamedUriFragmentParser.parse(uri.getFragment());
+                return new NamedUri(uri, parameters);
+            }
+        } catch (URISyntaxException | RuntimeException e) {
+            throw new RuntimeException("Failed to parse named URI: " + value, e);
+        }
+    }
+
+    private static boolean isSimpleUri(String uri) {
+        return !uri.contains("=");
+    }
+
+}
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.java
index fbada67..dab4334 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/CachingSupplier.java
@@ -1,3 +1,19 @@
+/*
+ * 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.freemarker.generator.base.util;
 
 import java.util.function.Supplier;
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
index 5edd405..95f6a7f 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/LocaleUtils.java
@@ -23,6 +23,7 @@ import static org.apache.freemarker.generator.base.util.StringUtils.isEmpty;
 public class LocaleUtils {
 
     public static Locale parseLocale(String value) {
+        // "JVM default" is a special value defined by FreeMarker
         if (isEmpty(value) || value.equalsIgnoreCase("JVM default") || value.equalsIgnoreCase("default")) {
             return Locale.getDefault();
         }
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java
new file mode 100644
index 0000000..37819a3
--- /dev/null
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/uri/NamedUriParserTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.freemarker.generator.uri;
+
+import org.apache.freemarker.generator.base.uri.NamedUri;
+import org.apache.freemarker.generator.base.uri.NamedUriParser;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class NamedUriParserTest {
+
+    @Test
+    public void shouldParseFileName() {
+        final NamedUri namedURI = parse("users.csv");
+
+        assertNull(namedURI.getName());
+        assertEquals("users.csv", namedURI.getUri().toString());
+        assertEquals(0, namedURI.getParameters().size());
+    }
+
+    @Test
+    public void shouldParseDirectoryName() {
+        final NamedUri namedURI = parse("users/");
+
+        assertNull(namedURI.getName());
+        assertEquals("users/", namedURI.getUri().toString());
+        assertEquals(0, namedURI.getParameters().size());
+    }
+
+    @Test
+    public void shouldParseSimpleFileUri() {
+        final NamedUri namedURI = parse("file:///users.csv");
+
+        assertNull(namedURI.getName());
+        assertEquals("file:///users.csv", namedURI.getUri().toString());
+        assertEquals(0, namedURI.getParameters().size());
+    }
+
+    @Test
+    public void shouldParseNamedFileUri() {
+        final NamedUri namedURI = parse("users=file:///users.csv");
+
+        assertEquals("users", namedURI.getName());
+        assertEquals("file:///users.csv", namedURI.getUri().toString());
+        assertEquals(0, namedURI.getParameters().size());
+    }
+
+    @Test
+    public void shouldParseNamedFileUriWithFragment() {
+        final NamedUri namedURI = parse("users=file:///users.csv#charset=UTF-16&mimetype=text/csv");
+
+        assertEquals("users", namedURI.getName());
+        assertEquals("file:///users.csv#charset=UTF-16&mimetype=text/csv", namedURI.getUri().toString());
+        assertEquals(2, namedURI.getParameters().size());
+        assertEquals("UTF-16", namedURI.getParameters().get("charset"));
+        assertEquals("text/csv", namedURI.getParameters().get("mimetype"));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void shouldThrowIllegalArgumentExceptionForEmptyUri() {
+        parse("");
+    }
+
+    private static NamedUri parse(String value) {
+        return NamedUriParser.parse(value);
+    }
+}
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
index 82dbc6f..93ea256 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/Main.java
@@ -51,9 +51,9 @@ public class Main implements Callable<Integer> {
     private static final String FREEMARKER_CLI_PROPERTY_FILE = "freemarker-cli.properties";
 
     @ArgGroup(multiplicity = "1")
-    private TemplateSourceOptions templateSourceOptions;
+    TemplateSourceOptions templateSourceOptions;
 
-    private static final class TemplateSourceOptions {
+    static final class TemplateSourceOptions {
         @Option(names = { "-t", "--template" }, description = "FreeMarker template to render")
         private String template;
 
@@ -62,52 +62,59 @@ public class Main implements Callable<Integer> {
     }
 
     @Option(names = { "-b", "--basedir" }, description = "Optional template base directory")
-    private String baseDir;
+    String baseDir;
+
+    @Option(names = { "-d", "--datasource" }, description = "Datasource used for rendering")
+    List<String> datasources;
 
     @Option(names = { "-D", "--system-property" }, description = "Set system property")
-    private Properties systemProperties;
+    Properties systemProperties;
 
     @Option(names = { "-e", "--input-encoding" }, description = "Encoding of datasource", defaultValue = "UTF-8")
-    private String inputEncoding;
+    String inputEncoding;
 
     @Option(names = { "-E", "--expose-env" }, description = "Expose environment variables and user-supplied properties globally")
-    private boolean isEnvironmentExposed;
+    boolean isEnvironmentExposed;
 
     @Option(names = { "-l", "--locale" }, description = "Locale being used for the output, e.g. 'en_US'")
-    private String locale;
+    String locale;
 
     @Option(names = { "-o", "--output" }, description = "Output file")
-    private String outputFile;
+    String outputFile;
 
     @Option(names = { "-P", "--param" }, description = "Set parameter")
-    private Map<String, String> parameters;
+    Map<String, String> parameters;
 
     @Option(names = { "--config" }, defaultValue = FREEMARKER_CLI_PROPERTY_FILE, description = "FreeMarker CLI configuration file")
-    private String configFile;
+    String configFile;
 
     @Option(names = { "--include" }, description = "File pattern for datasource input directory")
-    private String include;
+    String include;
 
     @Option(names = { "--exclude" }, description = "File pattern for datasource input directory")
-    private String exclude;
+    String exclude;
 
     @Option(names = { "--output-encoding" }, description = "Encoding of output, e.g. UTF-8", defaultValue = "UTF-8")
-    private String outputEncoding;
+    String outputEncoding;
 
     @Option(names = { "--stdin" }, description = "Read datasource from stdin")
-    private boolean readFromStdin;
+    boolean readFromStdin;
 
     @Option(names = { "--times" }, defaultValue = "1", description = "Re-run X times for profiling")
-    private int times;
+    int times;
 
     @Parameters(description = "List of input files and/or input directories")
-    private List<String> sources;
+    List<String> sources;
 
     /** User-supplied command line parameters */
-    private final String[] args;
+    final String[] args;
 
     /** User-supplied writer (used mainly for unit testing) */
-    private Writer userSuppliedWriter;
+    Writer userSuppliedWriter;
+
+    Main() {
+        this.args = new String[0];
+    }
 
     private Main(String[] args) {
         this.args = requireNonNull(args);
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
new file mode 100644
index 0000000..95662f0
--- /dev/null
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/PicocliTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.freemarker.generator.cli;
+
+import org.junit.Test;
+import picocli.CommandLine;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class PicocliTest {
+
+    private static final String TEMPLATE = "template.ftl";
+    private static final String ANY_FILE = "users.csv";
+    private static final String ANY_NAMED_FILE = "users=users.csv";
+    private static final String OTHER_FILE = "transctions.csv";
+    private static final String ANY_FILE_URI = "file:///users.csv";
+    private static final String OTHER_FILE_URI = "file:///transctions.csv";
+
+    @Test
+    public void testSinglePositionalParameter() {
+        assertEquals(ANY_FILE_URI, parse("-t", TEMPLATE, ANY_FILE_URI).sources.get(0));
+        assertNull(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE_URI).datasources);
+    }
+
+    @Test
+    public void testMultiplePositionalParameter() {
+        assertEquals(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE).sources.get(0));
+        assertEquals(OTHER_FILE, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE).sources.get(1));
+
+        assertEquals(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE_URI).sources.get(0));
+        assertEquals(OTHER_FILE_URI, parse("-t", TEMPLATE, ANY_FILE, OTHER_FILE_URI).sources.get(1));
+
+        assertEquals(ANY_FILE_URI, parse("-t", TEMPLATE, ANY_FILE_URI, OTHER_FILE_URI).sources.get(0));
+        assertEquals(OTHER_FILE_URI, parse("-t", TEMPLATE, ANY_FILE_URI, OTHER_FILE_URI).sources.get(1));
+    }
+
+    @Test
+    public void testSingleNamedDatasource() {
+        assertEquals(ANY_FILE, parse("-t", TEMPLATE, ANY_FILE).sources.get(0));
+        assertEquals(ANY_FILE, parse("-t", TEMPLATE, "-d", ANY_FILE).datasources.get(0));
+        assertEquals(ANY_FILE, parse("-t", TEMPLATE, "--datasource", ANY_FILE).datasources.get(0));
+        assertEquals(ANY_FILE_URI, parse("-t", TEMPLATE, "--datasource", ANY_FILE_URI).datasources.get(0));
+    }
+
+    @Test
+    public void testMultipleNamedDatasource() {
+        final Main main = parse("-t", TEMPLATE, "-d", ANY_FILE, "--datasource", OTHER_FILE_URI);
+
+        assertEquals(ANY_FILE, main.datasources.get(0));
+        assertEquals(OTHER_FILE_URI, main.datasources.get(1));
+        assertNull(main.sources);
+    }
+
+    private static Main parse(String... args) {
+        final Main main = new Main();
+        new CommandLine(main).parseArgs(args);
+        return main;
+    }
+}