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 2021/10/03 20:13:34 UTC

[freemarker-generator] branch FREEMARKER-195 created (now 18eb24b)

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

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


      at 18eb24b  FREEMARKER-195 [freemarker-generator] Improve exposure of DataSources using TemplateHashModelEx2

This branch includes the following new commits:

     new 18eb24b  FREEMARKER-195 [freemarker-generator] Improve exposure of DataSources using TemplateHashModelEx2

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


[freemarker-generator] 01/01: FREEMARKER-195 [freemarker-generator] Improve exposure of DataSources using TemplateHashModelEx2

Posted by sg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 18eb24ba9f5c2022ebf410e5ce79f8295a4791dc
Author: Siegfried Goeschl <si...@gmail.com>
AuthorDate: Sun Oct 3 22:13:23 2021 +0200

    FREEMARKER-195 [freemarker-generator] Improve exposure of DataSources using TemplateHashModelEx2
---
 .../generator/base/datasource/DataSources.java     |   9 ++
 freemarker-generator-cli/CHANGELOG.md              |   9 +-
 .../src/app/examples/templates/datasources.ftl     | 105 +++++++------
 .../cli/config/ConfigurationSupplier.java          |   5 +
 .../generator/cli/model/DataSourcesAdapter.java    | 165 +++++++++++++++++++++
 .../model/FreeMarkerGeneratorObjectWrapper.java    |  39 +++++
 .../generator/cli/task/FreeMarkerTask.java         |   3 +-
 .../freemarker/generator/cli/ExamplesTest.java     |   1 +
 .../cli/config/ConfigurationSupplierTest.java      |   3 +-
 .../generator/cli/config/SuppliersTest.java        |   2 -
 pom.xml                                            |   2 +-
 11 files changed, 292 insertions(+), 51 deletions(-)

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 fcb3432..e6b4c3f 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
@@ -89,6 +89,15 @@ public class DataSources implements Closeable {
     }
 
     /**
+     * Get an array representation of the underlying data sources.
+     *
+     * @return list of data sources
+     */
+    public DataSource[] toArray() {
+        return dataSources.toArray(new DataSource[0]);
+    }
+
+    /**
      * Get a list representation of the underlying data sources.
      *
      * @return list of data sources
diff --git a/freemarker-generator-cli/CHANGELOG.md b/freemarker-generator-cli/CHANGELOG.md
index 8a250ec..7e3ccbc 100644
--- a/freemarker-generator-cli/CHANGELOG.md
+++ b/freemarker-generator-cli/CHANGELOG.md
@@ -2,7 +2,12 @@
 
 All notable changes to this project will be documented in this file. We try to adhere to https://github.com/olivierlacan/keep-a-changelog.
 
-## 0.1.0-SNAPSHOT
+## 0.2.0-SNAPSHOT
+
+## 0.1.0-SNAPSHOT (unreleased)
+
+### Changed
+* [FREEMARKER-195] Improve exposure of DataSources using TemplateHashModelEx2
 
 ### Added
 * Use `-Xverify:none -XX:TieredStopAtLevel=1` to improve startup time of CLI
@@ -22,7 +27,6 @@ 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))
 * [FREEMARKER-129] Provide a `toString()` method for all tools
 
-### Changed
 * [FREEMARKER-182] Upgrade to Apache FreeMarker 2.3.31
 * [FREEMARKER-175] Use latest FreeMarker version
 * [FREEMARKER-173] Allow to pass arbitrary key/value pairs to DataSource when using NamedURIs
@@ -85,4 +89,5 @@ All notable changes to this project will be documented in this file. We try to a
 [FREEMARKER-181]: https://issues.apache.org/jira/browse/FREEMARKER-181
 [FREEMARKER-182]: https://issues.apache.org/jira/browse/FREEMARKER-182
 [FREEMARKER-188]: https://issues.apache.org/jira/browse/FREEMARKER-188
+[FREEMARKER-195]: https://issues.apache.org/jira/browse/FREEMARKER-195
 
diff --git a/freemarker-generator-cli/src/app/examples/templates/datasources.ftl b/freemarker-generator-cli/src/app/examples/templates/datasources.ftl
index 4c7d11f..ec02a83 100644
--- a/freemarker-generator-cli/src/app/examples/templates/datasources.ftl
+++ b/freemarker-generator-cli/src/app/examples/templates/datasources.ftl
@@ -1,4 +1,4 @@
-<#ftl output_format="plainText">
+<#ftl output_format="plainText" strip_whitespace=true>
 <#--
   Licensed to the Apache Software Foundation (ASF) under one
   or more contributor license agreements.  See the NOTICE file
@@ -17,64 +17,83 @@
 -->
 Support FreeMarker Directives
 ==============================================================================
-has_content: ${dataSources?has_content?c}
-size: ${dataSources?size}
+dataSources?has_content: ${dataSources?has_content?c}
+dataSources?size: ${dataSources?size}
 
 Use FTL Array-style Access
 ==============================================================================
-${dataSources?values[0].name}
-${dataSources?values?first.name}
+<#if dataSources?has_content>
+    dataSources[0]: ${dataSources[0].name}
+<#else>
+    No data sources provided ...
+</#if>
 
-Get Document Names As Keys
+Iterate Over DataSources as List
 ==============================================================================
-<#list dataSources?keys as name>
-    ${name}<#lt>
+<#list dataSources as dataSource>
+    dataSource[${dataSource?index}] => ${dataSource.uri}<#lt>
 </#list>
 
-Iterate Over Names & DataSources
+Iterate Over DataSources as Map
 ==============================================================================
 <#list dataSources as name, dataSource>
-    ${name} => ${dataSource.uri}<#lt>
+    dataSource["${name}"] => ${dataSource.uri}<#lt>
+</#list>
+
+Iterate Over DataSources as Values
+==============================================================================
+<#list dataSources?values as dataSource>
+    dataSource[${dataSource?index}] => ${dataSource.uri}<#lt>
+</#list>
+
+Get Document Names As Keys
+==============================================================================
+<#list dataSources?keys as name>
+    - ${name}<#lt>
 </#list>
 
+Access Underlying DataSources API
+==============================================================================
+DataSources.getNames(): ${dataSources?api.names?size}
+DataSources.getGroups(): ${dataSources?api.getGroups()?size}
+DataSources.find(): ${dataSources?api.find("*")?size}
+
+Iterate Over DataSources Using Wildcard Search
+==============================================================================
 <#if dataSources?has_content>
-    <#list dataSources?values as dataSource>
-        <@writeDataSource dataSource/>
+    <#list dataSources?api.find("*") as dataSource>
+        - ${dataSource.name}
     </#list>
 <#else>
-    No data sources found ...
+    No data sources provided ...
 </#if>
 
-<#macro writeDataSource dataSource>
-
-${dataSource.name}
-==============================================================================
+<#if dataSources?has_content>
+    <#list dataSources?values as dataSource>
+        [#${dataSource?counter}] - ${dataSource.name}
+        ==============================================================================
 
-Invoke Arbitrary Methods On DataSource
----------------------------------------------------------------------------
-<#assign dataSource=dataSources?values?first>
-Name                : ${dataSource.name}
-Group               : ${dataSource.group}
-Nr of lines         : ${dataSource.lines?size}
-ContentType         : ${dataSource.contentType}
-MimeType            : ${dataSource.mimeType}
-Charset             : ${dataSource.charset}
-Extension           : ${dataSource.extension}
-Nr of chars         : ${dataSource.text?length}
-Nr of bytes         : ${dataSource.bytes?size}
-File name           : ${dataSource.fileName}
-URI schema          : ${dataSource.uri.scheme}
-Relative File Path  : ${dataSource.relativeFilePath}
+        Invoke Arbitrary Methods On DataSource
+        ---------------------------------------------------------------------------
+        <#assign dataSource=dataSources?values?first>
+        Name                : ${dataSource.name}
+        Nr of lines         : ${dataSource.lines?size}
+        Content Type        : ${dataSource.contentType}
+        Charset             : ${dataSource.charset}
+        Extension           : ${dataSource.extension}
+        Nr of chars         : ${dataSource.text?length}
+        Nr of bytes         : ${dataSource.bytes?size}
 
-Iterating Over Metadata Of A Datasource
----------------------------------------------------------------------------
-<#list dataSource.metadata as name, value>
-${name?right_pad(15)} : ${value}
-</#list>
+        Iterating Over Metadata Of A Datasource
+        ---------------------------------------------------------------------------
+        <#list dataSource.metadata as name, value>
+            ${name?right_pad(19)} : ${value}
+        </#list>
 
-Iterating Over Properties Of A Datasource
----------------------------------------------------------------------------
-<#list dataSource.properties as name, value>
-${name?right_pad(15)} : ${value}
-</#list>
-</#macro>
+        Iterating Over Properties Of A Datasource
+        ---------------------------------------------------------------------------
+        <#list dataSource.properties as name, value>
+            ${name?right_pad(19)} : ${value}
+        </#list>
+    </#list>
+</#if>
\ No newline at end of file
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplier.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplier.java
index 593f7f7..63f21c0 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplier.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplier.java
@@ -20,6 +20,7 @@ import freemarker.cache.TemplateLoader;
 import freemarker.template.Configuration;
 import freemarker.template.Version;
 import org.apache.freemarker.generator.base.util.PropertiesTransformer;
+import org.apache.freemarker.generator.cli.model.FreeMarkerGeneratorObjectWrapper;
 
 import java.util.Properties;
 import java.util.function.Supplier;
@@ -50,6 +51,10 @@ public class ConfigurationSupplier implements Supplier<Configuration> {
         try {
             final Configuration configuration = new Configuration(FREEMARKER_VERSION);
 
+            // support a custom "DataSourcesAdaptor"
+            configuration.setAPIBuiltinEnabled(true);
+            configuration.setObjectWrapper(new FreeMarkerGeneratorObjectWrapper(configuration.getIncompatibleImprovements()));
+
             // apply all "freemarker.configuration.setting" values
             configuration.setSettings(freeMarkerConfigurationSettings());
 
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/model/DataSourcesAdapter.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/model/DataSourcesAdapter.java
new file mode 100644
index 0000000..2fd3582
--- /dev/null
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/model/DataSourcesAdapter.java
@@ -0,0 +1,165 @@
+/*
+ * 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.model;
+
+import freemarker.core._DelayedJQuote;
+import freemarker.core._TemplateModelException;
+import freemarker.ext.util.WrapperTemplateModel;
+import freemarker.template.AdapterTemplateModel;
+import freemarker.template.MapKeyValuePairIterator;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.SimpleCollection;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateHashModelEx2;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelWithAPISupport;
+import freemarker.template.TemplateSequenceModel;
+import freemarker.template.WrappingTemplateModel;
+import freemarker.template.utility.ObjectWrapperWithAPISupport;
+import org.apache.freemarker.generator.base.datasource.DataSource;
+import org.apache.freemarker.generator.base.datasource.DataSources;
+
+import java.io.Serializable;
+import java.util.Map;
+import java.util.SortedMap;
+
+/**
+ * Wraps a map of <code>DataSorces</code> into a FreeMarker template model
+ * providing sequence and hash type access.
+ */
+public class DataSourcesAdapter extends WrappingTemplateModel
+        implements TemplateHashModelEx2, AdapterTemplateModel, WrapperTemplateModel, TemplateModelWithAPISupport, TemplateSequenceModel,
+        Serializable {
+
+    private final DataSources dataSources;
+    private final Map<String, DataSource> map;
+
+    /**
+     * Factory method for creating new adapter instances.
+     *
+     * @param dataSources The dataSources to adapt; can't be {@code null}.
+     * @param wrapper     The {@link ObjectWrapper} used to wrap the items in the array.
+     */
+    public static DataSourcesAdapter create(DataSources dataSources, ObjectWrapperWithAPISupport wrapper) {
+        return new DataSourcesAdapter(dataSources, wrapper);
+    }
+
+    private DataSourcesAdapter(DataSources dataSources, ObjectWrapper wrapper) {
+        super(wrapper);
+        this.dataSources = dataSources;
+        this.map = dataSources.toMap();
+    }
+
+    @Override
+    public TemplateModel get(String key) throws TemplateModelException {
+        Object val;
+        try {
+            val = map.get(key);
+        } catch (ClassCastException e) {
+            throw new _TemplateModelException(e,
+                    "ClassCastException while getting Map entry with String key ",
+                    new _DelayedJQuote(key));
+        } catch (NullPointerException e) {
+            throw new _TemplateModelException(e,
+                    "NullPointerException while getting Map entry with String key ",
+                    new _DelayedJQuote(key));
+        }
+
+        if (val == null) {
+            // Check for Character key if this is a single-character string.
+            // In SortedMap-s, however, we can't do that safely, as it can cause ClassCastException.
+            if (key.length() == 1 && !(map instanceof SortedMap)) {
+                final Character charKey = key.charAt(0);
+                try {
+                    val = map.get(charKey);
+                    if (val == null) {
+                        final TemplateModel wrappedNull = wrap(null);
+                        if (wrappedNull == null || !(map.containsKey(key) || map.containsKey(charKey))) {
+                            return null;
+                        } else {
+                            return wrappedNull;
+                        }
+                    }
+                } catch (ClassCastException e) {
+                    throw new _TemplateModelException(e,
+                            "Class casting exception while getting Map entry with Character key ",
+                            new _DelayedJQuote(charKey));
+                } catch (NullPointerException e) {
+                    throw new _TemplateModelException(e,
+                            "NullPointerException while getting Map entry with Character key ",
+                            new _DelayedJQuote(charKey));
+                }
+            } else {  // No char key fallback was possible
+                final TemplateModel wrappedNull = wrap(null);
+                if (wrappedNull == null || !map.containsKey(key)) {
+                    return null;
+                } else {
+                    return wrappedNull;
+                }
+            }
+        }
+
+        return wrap(val);
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return map.isEmpty();
+    }
+
+    @Override
+    public int size() {
+        return map.size();
+    }
+
+    @Override
+    public TemplateCollectionModel keys() {
+        return new SimpleCollection(map.keySet(), getObjectWrapper());
+    }
+
+    @Override
+    public TemplateCollectionModel values() {
+        return new SimpleCollection(map.values(), getObjectWrapper());
+    }
+
+    @Override
+    public KeyValuePairIterator keyValuePairIterator() {
+        return new MapKeyValuePairIterator(map, getObjectWrapper());
+    }
+
+    @Override
+    public TemplateModel get(int index) throws TemplateModelException {
+        final DataSource[] array = this.dataSources.toArray();
+        return index >= 0 && index < array.length ? wrap(array[index]) : null;
+    }
+
+    @Override
+    public Object getAdaptedObject(Class hint) {
+        return dataSources;
+    }
+
+    @Override
+    public Object getWrappedObject() {
+        return dataSources;
+    }
+
+    @Override
+    public TemplateModel getAPI() throws TemplateModelException {
+        return ((ObjectWrapperWithAPISupport) getObjectWrapper()).wrapAsAPI(dataSources);
+    }
+}
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/model/FreeMarkerGeneratorObjectWrapper.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/model/FreeMarkerGeneratorObjectWrapper.java
new file mode 100644
index 0000000..08027ce
--- /dev/null
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/model/FreeMarkerGeneratorObjectWrapper.java
@@ -0,0 +1,39 @@
+/*
+ * 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.model;
+
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.Version;
+import org.apache.freemarker.generator.base.datasource.DataSources;
+
+public class FreeMarkerGeneratorObjectWrapper extends DefaultObjectWrapper {
+
+    public FreeMarkerGeneratorObjectWrapper(Version incompatibleImprovements) {
+        super(incompatibleImprovements);
+    }
+
+    @Override
+    protected TemplateModel handleUnknownType(final Object obj) throws TemplateModelException {
+        if (obj instanceof DataSources) {
+            return DataSourcesAdapter.create((DataSources) obj, this);
+        }
+
+        return super.handleUnknownType(obj);
+    }
+}
diff --git a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
index 914bd79..8e158e4 100644
--- a/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
+++ b/freemarker-generator-cli/src/main/java/org/apache/freemarker/generator/cli/task/FreeMarkerTask.java
@@ -138,8 +138,7 @@ public class FreeMarkerTask implements Callable<Integer> {
     private static Map<String, Object> toTemplateDataModel(DataSources dataSources, Map<String, Object>... maps) {
         final Map<String, Object> result = new HashMap<>();
         Arrays.stream(maps).forEach(result::putAll);
-        // expose only the map and not the "DataSources" instance (see FREEMARKER-174)
-        result.put(Model.DATASOURCES, dataSources.toMap());
+        result.put(Model.DATASOURCES, dataSources);
         return result;
     }
 
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 a5192e4..7bbe810 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
@@ -49,6 +49,7 @@ public class ExamplesTest extends AbstractMainTest {
 
     @Test
     public void shouldRunDataSourceExamples() throws IOException {
+        assertValid(execute("-t src/app/examples/templates/datasources.ftl"));
         assertValid(execute("-t src/app/examples/templates/datasources.ftl -s :csv=src/app/examples/data/csv"));
     }
 
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java
index ae9fa68..b2daff2 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/ConfigurationSupplierTest.java
@@ -41,11 +41,12 @@ public class ConfigurationSupplierTest {
         assertTrue(configuration.isOutputEncodingSet());
 
         assertFalse(configuration.isCacheStorageExplicitlySet());
-        assertFalse(configuration.isObjectWrapperExplicitlySet());
+        assertTrue(configuration.isObjectWrapperExplicitlySet());
         assertFalse(configuration.isOutputFormatExplicitlySet());
         assertFalse(configuration.isTemplateExceptionHandlerExplicitlySet());
         assertFalse(configuration.isTimeZoneExplicitlySet());
         assertFalse(configuration.isWrapUncheckedExceptionsExplicitlySet());
+        assertTrue(configuration.isAPIBuiltinEnabled());
     }
 
     private ConfigurationSupplier configurationSupplier(Settings settings) {
diff --git a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SuppliersTest.java b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SuppliersTest.java
index a772bb2..fa9b47e 100644
--- a/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SuppliersTest.java
+++ b/freemarker-generator-cli/src/test/java/org/apache/freemarker/generator/cli/config/SuppliersTest.java
@@ -39,7 +39,6 @@ import java.util.Properties;
 
 import static java.util.Collections.singletonList;
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -95,7 +94,6 @@ public class SuppliersTest {
 
         assertNotNull(configuration.getSharedVariable(Model.TOOLS));
         assertTrue(configuration.isTemplateLoaderExplicitlySet());
-        assertFalse(configuration.isObjectWrapperExplicitlySet());
     }
 
     @Test
diff --git a/pom.xml b/pom.xml
index 6d71dfb..5a0c552 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@
     <groupId>org.apache.freemarker.generator</groupId>
     <artifactId>freemarker-generator</artifactId>
     <packaging>pom</packaging>
-    <version>0.1.0-SNAPSHOT</version>
+    <version>0.2.0-SNAPSHOT</version>
     <name>Apache FreeMarker Generator</name>
     <url>https://freemarker-generator.apache.org/</url>