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/07/07 19:54:10 UTC

[freemarker-generator] branch master updated: Removing `DataSources.first` and use `DataSources.get(0)` instead (plus some minor improvements)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f6f41bf  Removing `DataSources.first` and use `DataSources.get(0)` instead (plus some minor improvements)
f6f41bf is described below

commit f6f41bf8e823abbb4926a29bf441acb0c7f5a9d3
Author: Siegfried Goeschl <si...@gmail.com>
AuthorDate: Tue Jul 7 21:53:48 2020 +0200

    Removing `DataSources.first` and use `DataSources.get(0)` instead (plus some minor improvements)
---
 .../generator/base/datasource/DataSource.java      | 122 ++++++++++++++++-----
 .../base/datasource/DataSourceFactory.java         |  22 +++-
 .../generator/base/datasource/DataSources.java     |  19 ++--
 .../generator/base/file/RecursiveFileSupplier.java |   2 +-
 .../generator/base/util/StringUtils.java           |   4 -
 .../generator/datasource/DataSourceTest.java       |  43 +++++---
 .../generator/datasource/DataSourcesTest.java      |  17 +--
 freemarker-generator-cli/CHANGELOG.md              |   1 +
 freemarker-generator-cli/run-examples.bat          |   6 +-
 freemarker-generator-cli/run-examples.sh           |   6 +-
 .../src/main/scripts/run-examples.bat              |   6 +-
 .../src/main/scripts/run-examples.sh               |   6 +-
 .../site/markdown/cli/usage/running-examples.md    |   8 +-
 .../freemarker/generator/cli/ExamplesTest.java     |   6 +-
 freemarker-generator-cli/templates/info.ftl        |   2 +-
 freemarker-generator-maven-plugin/pom.xml          |   1 +
 .../commonscsv/impl/CommonsCSVPrinterFacade.java   |  16 +++
 .../tools/dataframe/DataFrameToolTest.java         |   2 +-
 pom.xml                                            |  25 ++++-
 19 files changed, 218 insertions(+), 96 deletions(-)

diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
index 3a94c7b..0b82014 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSource.java
@@ -23,11 +23,14 @@ import org.apache.freemarker.generator.base.activation.ByteArrayDataSource;
 import org.apache.freemarker.generator.base.activation.StringDataSource;
 import org.apache.freemarker.generator.base.mime.MimetypeParser;
 import org.apache.freemarker.generator.base.util.CloseableReaper;
+import org.apache.freemarker.generator.base.util.StringUtils;
+import org.apache.freemarker.generator.base.util.Validate;
 
 import javax.activation.FileDataSource;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.io.StringWriter;
 import java.net.URI;
 import java.nio.charset.Charset;
@@ -35,10 +38,8 @@ import java.util.List;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.Objects.requireNonNull;
-import static org.apache.commons.io.IOUtils.lineIterator;
 import static org.apache.freemarker.generator.base.FreeMarkerConstants.DATASOURCE_UNKNOWN_LENGTH;
-import static org.apache.freemarker.generator.base.util.StringUtils.emptyToNull;
-import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
 
 /**
  * Data source which encapsulates data to be used for rendering
@@ -49,7 +50,7 @@ import static org.apache.freemarker.generator.base.util.StringUtils.isNotEmpty;
  * the content type &amp; charset might be determined using a network
  * call.
  */
-public class DataSource implements Closeable {
+public class DataSource implements Closeable, javax.activation.DataSource {
 
     /** Human-readable name of the data source */
     private final String name;
@@ -63,7 +64,7 @@ public class DataSource implements Closeable {
     /** The underlying "javax.activation.DataSource" */
     private final javax.activation.DataSource dataSource;
 
-    /** Optional user-supplied content type */
+    /** Optional content type */
     private final String contentType;
 
     /** Optional charset for directly accessing text-based content */
@@ -80,7 +81,7 @@ public class DataSource implements Closeable {
             String contentType,
             Charset charset) {
         this.name = requireNonNull(name);
-        this.group = emptyToNull(group);
+        this.group = StringUtils.emptyToNull(group);
         this.uri = requireNonNull(uri);
         this.dataSource = requireNonNull(dataSource);
         this.contentType = contentType;
@@ -88,10 +89,41 @@ public class DataSource implements Closeable {
         this.closables = new CloseableReaper();
     }
 
+    @Override
     public String getName() {
         return name;
     }
 
+    /**
+     * Get the content type.
+     *
+     * @return content type
+     */
+    @Override
+    public String getContentType() {
+        return contentType();
+    }
+
+    /**
+     * Get an input stream which is closed together with this data source.
+     *
+     * @return InputStream
+     */
+    @Override
+    public InputStream getInputStream() {
+        return closables.add(getUnsafeInputStream());
+    }
+
+    @Override
+    public OutputStream getOutputStream() {
+        throw new RuntimeException("No output stream supported");
+    }
+
+    @Override
+    public void close() {
+        closables.close();
+    }
+
     public String getGroup() {
         return group;
     }
@@ -109,11 +141,11 @@ public class DataSource implements Closeable {
     }
 
     /**
-     * Get the content type without additional parameters, e.g. "charset".
+     * Get the mimetype , i.e. content type without additional charset parameter.
      *
-     * @return content type
+     * @return mimetype
      */
-    public String getContentType() {
+    public String getMimetype() {
         return MimetypeParser.getMimetype(contentType());
     }
 
@@ -139,15 +171,6 @@ public class DataSource implements Closeable {
     }
 
     /**
-     * Get an input stream which is closed together with this data source.
-     *
-     * @return InputStream
-     */
-    public InputStream getInputStream() {
-        return closables.add(getUnsafeInputStream());
-    }
-
-    /**
      * Get an input stream which needs to be closed by the caller.
      *
      * @return InputStream
@@ -165,6 +188,7 @@ public class DataSource implements Closeable {
     }
 
     public String getText(String charsetName) {
+        Validate.notEmpty(charsetName, "No charset name provided");
         final StringWriter writer = new StringWriter();
         try (InputStream is = getUnsafeInputStream()) {
             IOUtils.copy(is, writer, Charset.forName(charsetName));
@@ -192,6 +216,7 @@ public class DataSource implements Closeable {
      * @return the list of Strings, never null
      */
     public List<String> getLines(String charsetName) {
+        Validate.notEmpty(charsetName, "No charset name provided");
         try (InputStream inputStream = getUnsafeInputStream()) {
             return IOUtils.readLines(inputStream, charsetName);
         } catch (IOException e) {
@@ -218,8 +243,9 @@ public class DataSource implements Closeable {
      * @return line iterator
      */
     public LineIterator getLineIterator(String charsetName) {
+        Validate.notEmpty(charsetName, "No charset name provided");
         try {
-            return closables.add(lineIterator(getUnsafeInputStream(), Charset.forName(charsetName)));
+            return closables.add(IOUtils.lineIterator(getUnsafeInputStream(), Charset.forName(charsetName)));
         } catch (IOException e) {
             throw new RuntimeException("Failed to create line iterator: " + toString(), e);
         }
@@ -234,6 +260,19 @@ public class DataSource implements Closeable {
     }
 
     /**
+     * Matches a metadata entry with a wildcard expression.
+     *
+     * @param part     part, e.g. "name", "basename", "extension", "uri", "group"
+     * @param wildcard the wildcard string to match against
+     * @return true if the wildcard expression matches
+     * @see <a href="https://commons.apache.org/proper/commons-io/javadocs/api-2.7/org/apache/commons/io/FilenameUtils.html#wildcardMatch-java.lang.String-java.lang.String-">Apache Commons IO</a>
+     */
+    public boolean match(String part, String wildcard) {
+        final String value = getPart(part);
+        return FilenameUtils.wildcardMatch(value, wildcard);
+    }
+
+    /**
      * Some tools create a {@link java.io.Closeable} which can bound to the
      * lifecycle of the data source. When the data source is closed all the
      * associated {@link java.io.Closeable} are closed as well.
@@ -247,11 +286,6 @@ public class DataSource implements Closeable {
     }
 
     @Override
-    public void close() {
-        closables.close();
-    }
-
-    @Override
     public String toString() {
         return "DataSource{" +
                 "name='" + name + '\'' +
@@ -260,7 +294,43 @@ public class DataSource implements Closeable {
                 '}';
     }
 
+    /**
+     * If there is no content type we ask the underlying data source. E.g. for
+     * an URL data source this information is fetched from the server.
+     *
+     * @return content type
+     */
     private String contentType() {
-        return isNotEmpty(contentType) ? contentType : dataSource.getContentType();
+        if (StringUtils.isNotEmpty(contentType)) {
+            return contentType;
+        } else {
+            return StringUtils.firstNonEmpty(dataSource.getContentType(), MIME_APPLICATION_OCTET_STREAM);
+        }
+    }
+
+    private String getPart(String part) {
+        Validate.notEmpty(part, "No metadata part provided");
+        switch (part.toLowerCase()) {
+            case "basename":
+                return getBaseName();
+            case "contenttype":
+                return getContentType();
+            case "extension":
+                return getExtension();
+            case "group":
+                return getGroup();
+            case "mimetype":
+                return getContentType();
+            case "name":
+                return getName();
+            case "path":
+                return uri.getPath();
+            case "scheme":
+                return uri.getScheme();
+            case "uri":
+                return uri.toASCIIString();
+            default:
+                throw new IllegalArgumentException("Unknown part: " + part);
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
index df33351..ed69fff 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSourceFactory.java
@@ -40,6 +40,7 @@ import java.net.URI;
 import java.net.URL;
 import java.nio.charset.Charset;
 import java.util.Properties;
+import java.util.UUID;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
@@ -120,7 +121,7 @@ public abstract class DataSourceFactory {
 
     public static DataSource fromString(String name, String group, String content, String contentType) {
         final StringDataSource dataSource = new StringDataSource(name, content, contentType, UTF_8);
-        final URI uri = UriUtils.toURI(Location.STRING, Integer.toString(content.hashCode()));
+        final URI uri = UriUtils.toURI(Location.STRING, UUID.randomUUID().toString());
         return create(name, group, uri, dataSource, contentType, UTF_8);
     }
 
@@ -185,11 +186,22 @@ public abstract class DataSourceFactory {
 
     // == General ===========================================================
 
-    public static DataSource create(String str) {
-        if (UriUtils.isUri(str)) {
-            return fromNamedUri(str);
+    /**
+     * Create a data source based on a
+     * <ul>
+     *  <li>URI</li>
+     *  <li>Named URI</li>
+     *  <li>file name</li>
+     * </ul>
+     *
+     * @param source source of the data source
+     * @return DataSource
+     */
+    public static DataSource create(String source) {
+        if (UriUtils.isUri(source)) {
+            return fromNamedUri(source);
         } else {
-            final File file = new File(str);
+            final File file = new File(source);
             return fromFile(file.getName(), DEFAULT_GROUP, file, UTF_8);
         }
     }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java
index 62838a1..1981b74 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/datasource/DataSources.java
@@ -16,7 +16,6 @@
  */
 package org.apache.freemarker.generator.base.datasource;
 
-import org.apache.commons.io.FilenameUtils;
 import org.apache.freemarker.generator.base.util.ClosableUtils;
 import org.apache.freemarker.generator.base.util.StringUtils;
 import org.apache.freemarker.generator.base.util.Validate;
@@ -34,6 +33,7 @@ import static java.util.stream.Collectors.toList;
  */
 public class DataSources implements Closeable {
 
+    /** The underlying list of data sources */
     private final List<DataSource> dataSources;
 
     public DataSources(Collection<DataSource> dataSources) {
@@ -75,10 +75,6 @@ public class DataSources implements Closeable {
         return dataSources.isEmpty();
     }
 
-    public DataSource getFirst() {
-        return dataSources.get(0);
-    }
-
     public List<DataSource> getList() {
         return new ArrayList<>(dataSources);
     }
@@ -109,26 +105,29 @@ public class DataSources implements Closeable {
     }
 
     /**
-     * Find data sources based on their name and globbing pattern.
+     * Find data sources based on their name and a wildcard.
      *
      * @param wildcard the wildcard string to match against
      * @return list of matching data sources
+     * @see <a href="https://commons.apache.org/proper/commons-io/javadocs/api-2.7/org/apache/commons/io/FilenameUtils.html#wildcardMatch-java.lang.String-java.lang.String-">Apache Commons IO</a>
      */
     public List<DataSource> find(String wildcard) {
         return dataSources.stream()
-                .filter(d -> FilenameUtils.wildcardMatch(d.getName(), wildcard))
+                .filter(dataSource -> dataSource.match("name", wildcard))
                 .collect(toList());
     }
 
     /**
-     * Find data sources based on their group and and globbing pattern.
+     * Find data sources based on their metadata part and wildcard.
      *
+     * @param part part of metadata to match
      * @param wildcard the wildcard string to match against
      * @return list of matching data sources
+     * @see <a href="https://commons.apache.org/proper/commons-io/javadocs/api-2.7/org/apache/commons/io/FilenameUtils.html#wildcardMatch-java.lang.String-java.lang.String-">Apache Commons IO</a>
      */
-    public List<DataSource> findByGroup(String wildcard) {
+    public List<DataSource> find(String part, String wildcard) {
         return dataSources.stream()
-                .filter(d -> FilenameUtils.wildcardMatch(d.getGroup(), wildcard))
+                .filter(dataSource -> dataSource.match(part, wildcard))
                 .collect(toList());
     }
 
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/file/RecursiveFileSupplier.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/file/RecursiveFileSupplier.java
index 9ffb03c..b2498d8 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/file/RecursiveFileSupplier.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/file/RecursiveFileSupplier.java
@@ -83,7 +83,7 @@ public class RecursiveFileSupplier implements Supplier<List<File>> {
         if (file.isFile()) {
             return resolveFile(file);
         } else if (file.isDirectory()) {
-            return new ArrayList<>(resolveDirectory(file));
+            return resolveDirectory(file);
         } else {
             throw new IllegalArgumentException("Unable to find source: " + source);
         }
diff --git a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
index a36bf1e..3e480fa 100644
--- a/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
+++ b/freemarker-generator-base/src/main/java/org/apache/freemarker/generator/base/util/StringUtils.java
@@ -30,10 +30,6 @@ public class StringUtils {
         return value != null && value.trim().isEmpty() ? null : value;
     }
 
-    public static String nullToEmpty(String value) {
-        return value == null ? "" : value;
-    }
-
     public static String firstNonEmpty(final String... values) {
         if (values != null) {
             for (final String value : values) {
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
index cff981b..1992c09 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourceTest.java
@@ -31,7 +31,7 @@ import java.util.Iterator;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.freemarker.generator.base.FreeMarkerConstants.DEFAULT_GROUP;
-import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_APPLICATION_OCTET_STREAM;
+import static org.apache.freemarker.generator.base.mime.Mimetypes.MIME_TEXT_HTML;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -40,12 +40,12 @@ public class DataSourceTest {
 
     private static final String ANY_GROUP = "group";
     private static final String ANY_TEXT = "Hello World";
-    private static final String ANY_XML_FILE_NAME = "pom.xml";
+    private static final String ANY_FILE_NAME = "pom.xml";
     private static final Charset ANY_CHAR_SET = UTF_8;
-    private static final File ANY_FILE = new File(ANY_XML_FILE_NAME);
+    private static final File ANY_FILE = new File(ANY_FILE_NAME);
 
     @Test
-    public void shouldSupportTextDataSource() throws IOException {
+    public void shouldSupportTextDataSource() {
         try (DataSource dataSource = DataSourceFactory.fromString("stdin", ANY_GROUP, ANY_TEXT, Mimetypes.MIME_TEXT_PLAIN)) {
             assertEquals("stdin", dataSource.getName());
             assertEquals(ANY_GROUP, dataSource.getGroup());
@@ -56,13 +56,15 @@ public class DataSourceTest {
             assertEquals("text/plain", dataSource.getContentType());
             assertTrue(dataSource.getLength() > 0);
             assertEquals(ANY_TEXT, dataSource.getText());
+            assertTrue(dataSource.match("name", "stdin"));
+            assertTrue(dataSource.match("uri", "string:///*"));
         }
     }
 
     @Test
-    public void shouldSupportFileDataSource() throws IOException {
+    public void shouldSupportFileDataSource() {
         try (DataSource dataSource = DataSourceFactory.fromFile(ANY_FILE, ANY_CHAR_SET)) {
-            assertEquals(ANY_XML_FILE_NAME, dataSource.getName());
+            assertEquals(ANY_FILE_NAME, dataSource.getName());
             assertEquals(DEFAULT_GROUP, dataSource.getGroup());
             assertEquals("pom", dataSource.getBaseName());
             assertEquals("xml", dataSource.getExtension());
@@ -71,20 +73,25 @@ public class DataSourceTest {
             assertEquals("application/xml", dataSource.getContentType());
             assertTrue(dataSource.getLength() > 0);
             assertFalse(dataSource.getText().isEmpty());
+            assertTrue(dataSource.match("name", ANY_FILE_NAME));
+            assertTrue(dataSource.match("uri", "file:/*/pom.xml"));
+            assertTrue(dataSource.match("extension", "xml"));
+            assertTrue(dataSource.match("basename", "pom"));
+            assertTrue(dataSource.match("path", "*/pom.xml"));
         }
     }
 
-    @Ignore("Requires internet conenection")
+    @Ignore("Requires internet connection")
     @Test
-    public void shouldSupportUrlDataSource() throws IOException {
-        try (DataSource dataSource = DataSourceFactory.create("https://google.com?foo=bar")) {
-            assertEquals("google.com", dataSource.getName());
+    public void shouldSupportUrlDataSource() {
+        try (DataSource dataSource = DataSourceFactory.create("https://www.google.com/?foo=bar")) {
+            assertEquals("www.google.com", dataSource.getName());
             assertEquals(DEFAULT_GROUP, dataSource.getGroup());
-            assertEquals("google", dataSource.getBaseName());
+            assertEquals("www.google", dataSource.getBaseName());
             assertEquals("com", dataSource.getExtension());
-            assertEquals("https://google.com?foo=bar", dataSource.getUri().toString());
-            assertEquals(MIME_APPLICATION_OCTET_STREAM, dataSource.getContentType());
-            assertEquals(UTF_8, dataSource.getCharset());
+            assertEquals("https://www.google.com/?foo=bar", dataSource.getUri().toString());
+            assertEquals(MIME_TEXT_HTML, dataSource.getMimetype());
+            assertEquals("ISO-8859-1", dataSource.getCharset().name());
             assertEquals(-1, dataSource.getLength());
             assertFalse(dataSource.getText().isEmpty());
         }
@@ -93,14 +100,14 @@ public class DataSourceTest {
     @Test
     public void shouldSupportLineIterator() throws IOException {
         try (DataSource dataSource = textDataSource()) {
-            try (LineIterator iterator = dataSource.getLineIterator(ANY_CHAR_SET.name())) {
+            try (LineIterator iterator = dataSource.getLineIterator()) {
                 assertEquals(1, count(iterator));
             }
         }
     }
 
     @Test
-    public void shouldReadLines() throws IOException {
+    public void shouldReadLines() {
         try (DataSource dataSource = textDataSource()) {
             assertEquals(1, dataSource.getLines().size());
             assertEquals(ANY_TEXT, dataSource.getLines().get(0));
@@ -108,7 +115,7 @@ public class DataSourceTest {
     }
 
     @Test
-    public void shouldGetBytes() throws IOException {
+    public void shouldGetBytes() {
         try (DataSource dataSource = textDataSource()) {
             assertEquals(11, dataSource.getBytes().length);
         }
@@ -144,7 +151,7 @@ public class DataSourceTest {
         private boolean closed = false;
 
         @Override
-        public void close() throws IOException {
+        public void close() {
             closed = true;
         }
 
diff --git a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
index c97de8f..5639adf 100644
--- a/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
+++ b/freemarker-generator-base/src/test/java/org/apache/freemarker/generator/datasource/DataSourcesTest.java
@@ -39,6 +39,7 @@ public class DataSourcesTest {
     private static final String ANY_FILE_EXTENSION = "xml";
     private static final File ANY_FILE = new File(ANY_FILE_NAME);
     private static final String ANY_URL = "https://server.invalid?foo=bar";
+    private static final String GROUP_PART = "group";
 
     @Test
     public void shouldFindByName() {
@@ -63,18 +64,18 @@ public class DataSourcesTest {
     }
 
     @Test
-    public void shouldFindByGroup() {
+    public void shouldFindByGroupPart() {
         final DataSources dataSources = dataSources();
 
-        assertEquals(0, dataSources.findByGroup(null).size());
-        assertEquals(0, dataSources.findByGroup("").size());
+        assertEquals(0, dataSources.find(GROUP_PART, null).size());
+        assertEquals(0, dataSources.find(GROUP_PART, "").size());
 
-        assertEquals(0, dataSources.findByGroup("unknown").size());
+        assertEquals(0, dataSources.find(GROUP_PART, "unknown").size());
 
-        assertEquals(3, dataSources.findByGroup("*").size());
-        assertEquals(3, dataSources.findByGroup("default").size());
-        assertEquals(3, dataSources.findByGroup("d*").size());
-        assertEquals(3, dataSources.findByGroup("d??????").size());
+        assertEquals(3, dataSources.find(GROUP_PART, "*").size());
+        assertEquals(3, dataSources.find(GROUP_PART, "default").size());
+        assertEquals(3, dataSources.find(GROUP_PART, "d*").size());
+        assertEquals(3, dataSources.find(GROUP_PART, "d??????").size());
 
     }
 
diff --git a/freemarker-generator-cli/CHANGELOG.md b/freemarker-generator-cli/CHANGELOG.md
index 32a0788..f5c636e 100644
--- a/freemarker-generator-cli/CHANGELOG.md
+++ b/freemarker-generator-cli/CHANGELOG.md
@@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. We try to a
 * [FREEMARKER-129] Migrate `freemarker-cli` into `freemarker-generator` project (see [https://github.com/sgoeschl/freemarker-cli](https://github.com/sgoeschl/freemarker-cli))
 
 ### Changed
+* Removing `DataSources.first` and use `DataSources.get(0)` instead
 * [FREEMARKER-146] Cleanly separate example templates and data from user-supplied content
 * `DataSource` use `uri` instead of `location`
 * [FREEMARKER-138] freemarker-generator: Rename `Datasource` to `DataSource`
diff --git a/freemarker-generator-cli/run-examples.bat b/freemarker-generator-cli/run-examples.bat
index e7abc87..c4864b1 100644
--- a/freemarker-generator-cli/run-examples.bat
+++ b/freemarker-generator-cli/run-examples.bat
@@ -41,9 +41,9 @@ REM =========================================================================
 REM Interactive Mode
 REM =========================================================================
 
-%FREEMARKER_CMD% -i '${JsonPathTool.parse(DataSources.first).read("""$.info.title""")}' examples\data\json\swagger-spec.json > target\out\interactive-json.txt
-%FREEMARKER_CMD% -i '${XmlTool.parse(DataSources.first)["""recipients/person[1]/name"""]}' examples\data\xml\recipients.xml > target\out\interactive-xml.txt
-%FREEMARKER_CMD% -i '${JsoupTool.parse(DataSources.first).select("""a""")[0]}' examples\data\html\dependencies.html > target\out\interactive-html.txt
+%FREEMARKER_CMD% -i '${JsonPathTool.parse(DataSources.get(0)).read("""$.info.title""")}' examples\data\json\swagger-spec.json > target\out\interactive-json.txt
+%FREEMARKER_CMD% -i '${XmlTool.parse(DataSources.get(0))["""recipients/person[1]/name"""]}' examples\data\xml\recipients.xml > target\out\interactive-xml.txt
+%FREEMARKER_CMD% -i '${JsoupTool.parse(DataSources.get(0)).select("""a""")[0]}' examples\data\html\dependencies.html > target\out\interactive-html.txt
 %FREEMARKER_CMD% -i '${GsonTool.toJson(YamlTool.parse(DataSources.get(0)))}' examples\data\yaml\swagger-spec.yaml > target\out\interactive-swagger.json
 %FREEMARKER_CMD% -i '${YamlTool.toYaml(GsonTool.parse(DataSources.get(0)))}' examples\data\json\swagger-spec.json > target\out\interactive-swagger.yaml
 %FREEMARKER_CMD% -i '${DataFrameTool.print(DataFrameTool.fromMaps(GsonTool.parse(DataSources.get(0))))}' examples\data\json\github-users.json > target\out\interactive-dataframe.txt
diff --git a/freemarker-generator-cli/run-examples.sh b/freemarker-generator-cli/run-examples.sh
index f1ad546..2c3d152 100755
--- a/freemarker-generator-cli/run-examples.sh
+++ b/freemarker-generator-cli/run-examples.sh
@@ -46,9 +46,9 @@ $FREEMARKER_CMD -t examples/templates/demo.ftl README.md > target/out/demo.txt |
 # Interactive Mode
 #############################################################################
 
-$FREEMARKER_CMD -i '${JsonPathTool.parse(DataSources.first).read("$.info.title")}' examples/data/json/swagger-spec.json > target/out/interactive-json.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
-$FREEMARKER_CMD -i '${XmlTool.parse(DataSources.first)["recipients/person[1]/name"]}' examples/data/xml/recipients.xml > target/out/interactive-xml.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
-$FREEMARKER_CMD -i '${JsoupTool.parse(DataSources.first).select("a")[0]}' examples/data/html/dependencies.html > target/out/interactive-html.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
+$FREEMARKER_CMD -i '${JsonPathTool.parse(DataSources.get(0)).read("$.info.title")}' examples/data/json/swagger-spec.json > target/out/interactive-json.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
+$FREEMARKER_CMD -i '${XmlTool.parse(DataSources.get(0))["recipients/person[1]/name"]}' examples/data/xml/recipients.xml > target/out/interactive-xml.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
+$FREEMARKER_CMD -i '${JsoupTool.parse(DataSources.get(0)).select("a")[0]}' examples/data/html/dependencies.html > target/out/interactive-html.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
 $FREEMARKER_CMD -i '${GsonTool.toJson(YamlTool.parse(DataSources.get(0)))}' examples/data/yaml/swagger-spec.yaml > target/out/interactive-swagger.json || { echo >&2 "Test failed.  Aborting."; exit 1; }
 $FREEMARKER_CMD -i '${YamlTool.toYaml(GsonTool.parse(DataSources.get(0)))}' examples/data/json/swagger-spec.json > target/out/interactive-swagger.yaml || { echo >&2 "Test failed.  Aborting."; exit 1; }
 $FREEMARKER_CMD -i '${DataFrameTool.print(DataFrameTool.fromMaps(GsonTool.parse(DataSources.get(0))))}' examples/data/json/github-users.json > target/out/interactive-dataframe.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
diff --git a/freemarker-generator-cli/src/main/scripts/run-examples.bat b/freemarker-generator-cli/src/main/scripts/run-examples.bat
index 5b4ed44..c5e25be 100644
--- a/freemarker-generator-cli/src/main/scripts/run-examples.bat
+++ b/freemarker-generator-cli/src/main/scripts/run-examples.bat
@@ -41,9 +41,9 @@ REM =========================================================================
 REM Interactive Mode
 REM =========================================================================
 
-%FREEMARKER_CMD% -i '${JsonPathTool.parse(DataSources.first).read("""$.info.title""")}' examples\data\json\swagger-spec.json > target\out\interactive-json.txt
-%FREEMARKER_CMD% -i '${XmlTool.parse(DataSources.first)["""recipients/person[1]/name"""]}' examples\data\xml\recipients.xml > target\out\interactive-xml.txt
-%FREEMARKER_CMD% -i '${JsoupTool.parse(DataSources.first).select("""a""")[0]}' examples\data\html\dependencies.html > target\out\interactive-html.txt
+%FREEMARKER_CMD% -i '${JsonPathTool.parse(DataSources.get(0)).read("""$.info.title""")}' examples\data\json\swagger-spec.json > target\out\interactive-json.txt
+%FREEMARKER_CMD% -i '${XmlTool.parse(DataSources.get(0))["""recipients/person[1]/name"""]}' examples\data\xml\recipients.xml > target\out\interactive-xml.txt
+%FREEMARKER_CMD% -i '${JsoupTool.parse(DataSources.get(0)).select("""a""")[0]}' examples\data\html\dependencies.html > target\out\interactive-html.txt
 %FREEMARKER_CMD% -i '${GsonTool.toJson(YamlTool.parse(DataSources.get(0)))}' examples\data\yaml\swagger-spec.yaml > target\out\interactive-swagger.json
 %FREEMARKER_CMD% -i '${YamlTool.toYaml(GsonTool.parse(DataSources.get(0)))}' examples\data\json\swagger-spec.json > target\out\interactive-swagger.yaml
 %FREEMARKER_CMD% -i '${DataFrameTool.print(DataFrameTool.fromMaps(GsonTool.parse(DataSources.get(0))))}' examples\data\json\github-users.json > target\out\interactive-dataframe.txt
diff --git a/freemarker-generator-cli/src/main/scripts/run-examples.sh b/freemarker-generator-cli/src/main/scripts/run-examples.sh
index 4bca33a..91f750e 100755
--- a/freemarker-generator-cli/src/main/scripts/run-examples.sh
+++ b/freemarker-generator-cli/src/main/scripts/run-examples.sh
@@ -46,9 +46,9 @@ $FREEMARKER_CMD -t examples/templates/demo.ftl README.md > target/out/demo.txt |
 # Interactive Mode
 #############################################################################
 
-$FREEMARKER_CMD -i '${JsonPathTool.parse(DataSources.first).read("$.info.title")}' examples/data/json/swagger-spec.json > target/out/interactive-json.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
-$FREEMARKER_CMD -i '${XmlTool.parse(DataSources.first)["recipients/person[1]/name"]}' examples/data/xml/recipients.xml > target/out/interactive-xml.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
-$FREEMARKER_CMD -i '${JsoupTool.parse(DataSources.first).select("a")[0]}' examples/data/html/dependencies.html > target/out/interactive-html.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
+$FREEMARKER_CMD -i '${JsonPathTool.parse(DataSources.get(0)).read("$.info.title")}' examples/data/json/swagger-spec.json > target/out/interactive-json.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
+$FREEMARKER_CMD -i '${XmlTool.parse(DataSources.get(0))["recipients/person[1]/name"]}' examples/data/xml/recipients.xml > target/out/interactive-xml.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
+$FREEMARKER_CMD -i '${JsoupTool.parse(DataSources.get(0)).select("a")[0]}' examples/data/html/dependencies.html > target/out/interactive-html.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
 $FREEMARKER_CMD -i '${GsonTool.toJson(YamlTool.parse(DataSources.get(0)))}' examples/data/yaml/swagger-spec.yaml > target/out/interactive-swagger.json || { echo >&2 "Test failed.  Aborting."; exit 1; }
 $FREEMARKER_CMD -i '${YamlTool.toYaml(GsonTool.parse(DataSources.get(0)))}' examples/data/json/swagger-spec.json > target/out/interactive-swagger.yaml || { echo >&2 "Test failed.  Aborting."; exit 1; }
 $FREEMARKER_CMD -i '${DataFrameTool.print(DataFrameTool.fromMaps(GsonTool.parse(DataSources.get(0))))}' examples/data/json/github-users.json > target/out/interactive-dataframe.txt || { echo >&2 "Test failed.  Aborting."; exit 1; }
diff --git a/freemarker-generator-cli/src/site/markdown/cli/usage/running-examples.md b/freemarker-generator-cli/src/site/markdown/cli/usage/running-examples.md
index 9b329a1..26b220c 100644
--- a/freemarker-generator-cli/src/site/markdown/cli/usage/running-examples.md
+++ b/freemarker-generator-cli/src/site/markdown/cli/usage/running-examples.md
@@ -768,16 +768,16 @@ Sometime you need to apply a CSS, JSON or XPath query in ad ad-hoc way without i
 > bin/freemarker-cli -i 'Hello ${SystemTool.envs["USER"]}'; echo
 Hello sgoeschl
 
-> bin/freemarker-cli -i '${JsonPathTool.parse(DataSources.first).read("$.info.title")}' examples/data/json/swagger-spec.json; echo
+> bin/freemarker-cli -i '${JsonPathTool.parse(DataSources.get(0)).read("$.info.title")}' examples/data/json/swagger-spec.json; echo
 Swagger Petstore
 
-> bin/freemarker-cli -i 'Post Title : ${JsonPathTool.parse(DataSources.first).read("$.title")}' https://jsonplaceholder.typicode.com/posts/2; echo
+> bin/freemarker-cli -i 'Post Title : ${JsonPathTool.parse(DataSources.get(0)).read("$.title")}' https://jsonplaceholder.typicode.com/posts/2; echo
 Post Title : qui est esse
 
-> bin/freemarker-cli -i '${XmlTool.parse(DataSources.first)["recipients/person[1]/name"]}' examples/data/xml/recipients.xml; echo
+> bin/freemarker-cli -i '${XmlTool.parse(DataSources.get(0))["recipients/person[1]/name"]}' examples/data/xml/recipients.xml; echo
 John Smith
 
-> bin/freemarker-cli -i '${JsoupTool.parse(DataSources.first).select("a")[0]}' examples/data/html/dependencies.html; echo
+> bin/freemarker-cli -i '${JsoupTool.parse(DataSources.get(0)).select("a")[0]}' examples/data/html/dependencies.html; echo
 <a href="${project.url}" title="FreeMarker CLI">FreeMarker CLI</a>
 
 > freemarker-cli -i '<#list SystemTool.envs as name,value>${name} ==> ${value}${"\n"}</#list>'
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
index e66d457..9ddd261 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/ExamplesTest.java
@@ -110,9 +110,9 @@ public class ExamplesTest extends AbstractMainTest {
 
     @Test
     public void shouldRunInteractiveTemplateExamples() throws IOException {
-        assertValid(execute("-i ${JsonPathTool.parse(DataSources.first).read(\"$.info.title\")} examples/data/json/swagger-spec.json"));
-        assertValid(execute("-i ${XmlTool.parse(DataSources.first)[\"recipients/person[1]/name\"]} examples/data/xml/recipients.xml"));
-        assertValid(execute("-i ${JsoupTool.parse(DataSources.first).select(\"a\")[0]} examples/data/html/dependencies.html"));
+        assertValid(execute("-i ${JsonPathTool.parse(DataSources.get(0)).read(\"$.info.title\")} examples/data/json/swagger-spec.json"));
+        assertValid(execute("-i ${XmlTool.parse(DataSources.get(0))[\"recipients/person[1]/name\"]} examples/data/xml/recipients.xml"));
+        assertValid(execute("-i ${JsoupTool.parse(DataSources.get(0)).select(\"a\")[0]} examples/data/html/dependencies.html"));
         assertValid(execute("-i ${GsonTool.toJson(YamlTool.parse(DataSources.get(0)))} examples/data/yaml/swagger-spec.yaml"));
         assertValid(execute("-i ${GsonTool.toJson(yaml)} -m yaml=examples/data/yaml/swagger-spec.yaml"));
         assertValid(execute("-i ${YamlTool.toYaml(GsonTool.parse(DataSources.get(0)))} examples/data/json/swagger-spec.json"));
diff --git a/freemarker-generator-cli/templates/info.ftl b/freemarker-generator-cli/templates/info.ftl
index 11275b6..d12e68d 100644
--- a/freemarker-generator-cli/templates/info.ftl
+++ b/freemarker-generator-cli/templates/info.ftl
@@ -49,7 +49,7 @@ FreeMarker CLI Data Model
 FreeMarker CLI DataSources
 ------------------------------------------------------------------------------
 <#list DataSources.list as dataSource>
-[#${dataSource?counter}], name=${dataSource.name}, group=${dataSource.group}, contentType=${dataSource.contentType}, charset=${dataSource.charset}, length=${dataSource.length} Bytes
+[#${dataSource?counter}], name=${dataSource.name}, group=${dataSource.group}, mimeType=${dataSource.mimetype}, charset=${dataSource.charset}, length=${dataSource.length} Bytes
 URI : ${dataSource.uri}
 </#list>
 </#if>
diff --git a/freemarker-generator-maven-plugin/pom.xml b/freemarker-generator-maven-plugin/pom.xml
index be360ec..30442ec 100644
--- a/freemarker-generator-maven-plugin/pom.xml
+++ b/freemarker-generator-maven-plugin/pom.xml
@@ -167,6 +167,7 @@
                 <artifactId>apache-rat-plugin</artifactId>
                 <configuration>
                     <excludes>
+                        <exclude>CHANGELOG.md</exclude>
                         <exclude>README.md</exclude>
                         <exclude>src/site/markdown/**/*.md</exclude>
                         <excludde>**/*.json</excludde>
diff --git a/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/commonscsv/impl/CommonsCSVPrinterFacade.java b/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/commonscsv/impl/CommonsCSVPrinterFacade.java
index c8e8ac5..9909d17 100644
--- a/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/commonscsv/impl/CommonsCSVPrinterFacade.java
+++ b/freemarker-generator-tools/src/main/java/org/apache/freemarker/generator/tools/commonscsv/impl/CommonsCSVPrinterFacade.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.tools.commonscsv.impl;
 
 import org.apache.commons.csv.CSVFormat;
diff --git a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/dataframe/DataFrameToolTest.java b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/dataframe/DataFrameToolTest.java
index 6965064..0e1d395 100644
--- a/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/dataframe/DataFrameToolTest.java
+++ b/freemarker-generator-tools/src/test/java/org/apache/freemarker/generator/tools/dataframe/DataFrameToolTest.java
@@ -126,7 +126,7 @@ public class DataFrameToolTest {
         assertNotNull(dataFrame.getColumn("Time"));
         assertNotNull(dataFrame.getColumn("Percentage"));
         assertNotNull(dataFrame.getColumn("Forumula"));
-        assertEquals("Row 1", dataFrame.getValue(0,0));
+        assertEquals("Row 1", dataFrame.getValue(0, 0));
         assertEquals("C3*F3", dataFrame.getColumn("Forumula").get(1));
 
     }
diff --git a/pom.xml b/pom.xml
index 45cb794..4e132e8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,7 +91,15 @@
         <developer>
             <name>Siegfried Goeschl</name>
             <organization>ASF</organization>
-            <organizationUrl>https://github.com/sgoeschl</organizationUrl>
+            <organizationUrl>https://www.apache.org</organizationUrl>
+        </developer>
+        <developer>
+            <name>Daniel Dekany</name>
+            <organization>ASF</organization>
+            <organizationUrl>https://www.apache.org</organizationUrl>
+        </developer>
+        <developer>
+            <name>Benjamin Jackson</name>
         </developer>
     </developers>
 
@@ -144,7 +152,7 @@
                 </plugin>
                 <plugin>
                     <artifactId>maven-assembly-plugin</artifactId>
-                    <version>3.2.0</version>                    
+                    <version>3.2.0</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-clean-plugin</artifactId>
@@ -164,7 +172,7 @@
                 </plugin>
                 <plugin>
                     <artifactId>maven-javadoc-plugin</artifactId>
-                    <version>3.1.1</version>
+                    <version>3.2.0</version>
                 </plugin>
                 <plugin>
                     <artifactId>maven-jxr-plugin</artifactId>
@@ -268,6 +276,17 @@
                     <linkXRef>false</linkXRef>
                 </configuration>
             </plugin>
+            <plugin>
+                <artifactId>maven-javadoc-plugin</artifactId>
+                <reportSets>
+                    <reportSet>
+                        <id>default</id>
+                        <reports>
+                            <report>javadoc</report>
+                        </reports>
+                    </reportSet>
+                </reportSets>
+            </plugin>
         </plugins>
     </reporting>
 </project>
\ No newline at end of file