Repository: groovy
Updated Branches:
  refs/heads/master 57fe0c773 -> 5de34ce58


GROOVY-8866: Implement `GProperties` to handle properties file smartly(closes 
#818)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/5de34ce5
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/5de34ce5
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/5de34ce5

Branch: refs/heads/master
Commit: 5de34ce58417ec48f0e1cdedfe78cb2600e9c913
Parents: 57fe0c7
Author: Daniel Sun <sun...@apache.org>
Authored: Sat Nov 3 00:40:04 2018 +0800
Committer: Daniel Sun <sun...@apache.org>
Committed: Sat Nov 3 00:40:04 2018 +0800

----------------------------------------------------------------------
 src/main/groovy/groovy/lang/Binding.java        |  13 ++
 src/main/groovy/groovy/lang/GroovyShell.java    |   4 +
 src/main/groovy/groovy/util/GProperties.groovy  | 204 +++++++++++++++++++
 .../groovy/util/gproperties.properties          |  28 +++
 .../groovy/util/gproperties_import.properties   |  21 ++
 .../groovy/util/gproperties_import2.properties  |  20 ++
 .../groovy/util/gproperties_import3.properties  |  20 ++
 src/test/groovy/util/GPropertiesTest.groovy     | 101 +++++++++
 8 files changed, 411 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/main/groovy/groovy/lang/Binding.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/Binding.java 
b/src/main/groovy/groovy/lang/Binding.java
index 6505ea5..add048c 100644
--- a/src/main/groovy/groovy/lang/Binding.java
+++ b/src/main/groovy/groovy/lang/Binding.java
@@ -77,6 +77,19 @@ public class Binding extends GroovyObjectSupport {
             variables = new LinkedHashMap();
         variables.put(name, value);
     }
+
+    /**
+     * remove the variable with the specified name
+     *
+     * @param name the name of the variable to remove
+     */
+    public void removeVariable(String name) {
+        if (null == variables) {
+            return;
+        }
+
+        variables.remove(name);
+    }
     
     /**
      * Simple check for whether the binding contains a particular variable or 
not.

http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/main/groovy/groovy/lang/GroovyShell.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/lang/GroovyShell.java 
b/src/main/groovy/groovy/lang/GroovyShell.java
index 3570809..a802d3c 100644
--- a/src/main/groovy/groovy/lang/GroovyShell.java
+++ b/src/main/groovy/groovy/lang/GroovyShell.java
@@ -429,6 +429,10 @@ public class GroovyShell extends GroovyObjectSupport {
         context.setVariable(name, value);
     }
 
+    public void removeVariable(String name) {
+        context.removeVariable(name);
+    }
+
     /**
      * Evaluates some script against the current Binding and returns the result
      *

http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/main/groovy/groovy/util/GProperties.groovy
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/GProperties.groovy 
b/src/main/groovy/groovy/util/GProperties.groovy
new file mode 100644
index 0000000..500baf5
--- /dev/null
+++ b/src/main/groovy/groovy/util/GProperties.groovy
@@ -0,0 +1,204 @@
+/*
+ *  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 groovy.util
+
+import groovy.transform.CompileStatic
+
+import java.util.regex.Pattern
+
+/**
+ * Represents an enhanced properties, which supports interpolating in the 
values and importing other properties in classpath
+ *
+ * Usage:
+ * 1) Interpolating with {...}, e.g.
+ * <pre>
+ *      # gproperties.properties
+ *      groovy.greeting=Hello
+ *      some.name=Daniel
+ *      greeting.daniel={groovy.greeting},{some.name}
+ *
+ *      // groovy script
+ *      def gp = new GProperties()
+ *      
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+ *      assert 'Hello,Daniel' == gp.getProperty('greeting.daniel')
+ * </pre>
+ *
+ * 2) Importing with import.properties, e.g.
+ * <pre>
+ *      # gproperties.properties
+ *      
import.properties=/groovy/util/gproperties_import.properties,/groovy/util/gproperties_import2.properties
+ *      greeting.daniel={groovy.greeting},{some.name}
+ *
+ *      # gproperties_import.properties
+ *      groovy.greeting=Hello
+ *
+ *      # gproperties_import2.properties
+ *      some.name=Daniel
+ *
+ *      // groovy script
+ *      def gp = new GProperties()
+ *      
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+ *      assert 'Hello,Daniel' == gp.getProperty('greeting.daniel')
+ * </pre>
+ *
+ * 3) Evaluating with ${...}, e.g.
+ * <pre>
+ *      # gproperties.properties
+ *      greeting.daniel=Hello,Daniel in ${new 
java.text.SimpleDateFormat('yyyyMMdd').format(new Date())}
+ *
+ *      // groovy script
+ *      def gp = new GProperties(true) // Note: we should enable evaluating 
script manually
+ *      
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+ *      assert 'Hello,Daniel in 2018' == gp.getProperty('greeting.daniel') // 
Given running the script in 2018
+ * </pre>
+ *
+ * 4) Escaping with {{...}}, ${{...}}, e.g.
+ * <pre>
+ *      # gproperties.properties
+ *      greeting.daniel={{groovy.greeting}},{{some.name}} in ${{new 
java.text.SimpleDateFormat('yyyyMMdd').format(new Date())}}
+ *
+ *      // groovy script
+ *      def gp = new GProperties(true)
+ *      
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+ *      assert '{groovy.greeting},{some.name} in ${new 
java.text.SimpleDateFormat('yyyyMMdd').format(new Date())}' == 
gp.getProperty('greeting.daniel')
+ * </pre>
+ *
+ * @since 3.0.0
+ */
+@CompileStatic
+class GProperties extends Properties {
+    private static final long serialVersionUID = 6112578636029876860L
+    public static final String IMPORT_PROPERTIES_KEY = 'import.properties'
+    public static final String VAR_KEY = 'key'
+    public static final String VAR_PROPERTIES = 'properties'
+    private static final Pattern GROOVY_SCRIPT_PATTERN = 
Pattern.compile(/[$][{]\s*(.+?)\s*[}]/)
+    private static final Pattern INTERPOLATE_PATTERN = 
Pattern.compile(/[{]\s*(.+?)\s*[}]/)
+    private static final Pattern ESCAPE_PATTERN = 
Pattern.compile(/[{]([{].+?[}])[}]/)
+    private static final String LEFT_CURLY_BRACE = '{'
+    private static final String RIGHT_CURLY_BRACE = '}'
+    private static final String COMMA = ','
+    private final boolean evaluateScriptEnabled
+    private final GroovyShell groovyShell
+    private final List<GProperties> importPropertiesList = new ArrayList<>()
+
+    GProperties(boolean evaluateScriptEnabled=false, GroovyShell 
groovyShell=new GroovyShell()) {
+        this(null, evaluateScriptEnabled, groovyShell)
+    }
+
+    GProperties(Properties defaults, boolean evaluateScriptEnabled=false, 
GroovyShell groovyShell=new GroovyShell()) {
+        super(defaults)
+        this.evaluateScriptEnabled = evaluateScriptEnabled
+        this.groovyShell = groovyShell
+    }
+
+    @Override
+    String getProperty(String key) {
+        String value = super.getProperty(key)
+
+        if (!value) {
+            for (GProperties importProperties : importPropertiesList) {
+                value = importProperties.getProperty(key)
+
+                if (value) {
+                    break
+                }
+            }
+        }
+
+        if (!value) {
+            return value
+        }
+
+        if (evaluateScriptEnabled) {
+            value = value.replaceAll(GROOVY_SCRIPT_PATTERN) { String _0, 
String _1 ->
+                if (_1.startsWith(LEFT_CURLY_BRACE) && 
_1.endsWith(RIGHT_CURLY_BRACE)) {
+                    return _0
+                }
+                processImplicitVariables(key) {
+                    groovyShell.evaluate(_1)
+                }
+            }
+        }
+
+        value = value.replaceAll(INTERPOLATE_PATTERN) { String _0, String _1 ->
+            if (_1.startsWith(LEFT_CURLY_BRACE) && 
_1.endsWith(RIGHT_CURLY_BRACE)) {
+                return _0
+            }
+
+            this.getProperty(_1) ?: _0
+        }
+
+        value.replaceAll(ESCAPE_PATTERN) { String _0, String _1 ->
+            _1
+        }
+    }
+
+    @Override
+    synchronized void load(Reader reader) throws IOException {
+        reader.withReader { it ->
+            super.load(it)
+            importProperties()
+        }
+    }
+
+    @Override
+    synchronized void load(InputStream inStream) throws IOException {
+        inStream.withStream {
+            super.load(it)
+            importProperties()
+        }
+    }
+
+    private void importProperties() {
+        String importPropertiesPaths = super.getProperty(IMPORT_PROPERTIES_KEY)
+
+        if (!importPropertiesPaths?.trim()) {
+            return
+        }
+
+        importPropertiesPaths.split(COMMA).collect { it.trim() }.each { String 
importPropertiesPath ->
+            if (!importPropertiesPath) {
+                return
+            }
+
+            GProperties importProperties = new GProperties()
+            def inputstream = 
GProperties.getResourceAsStream(importPropertiesPath)
+
+            if (!inputstream) {
+                throw new IOException("${importPropertiesPath} does not exist")
+            }
+
+            inputstream.withStream {
+                importProperties.load(it)
+            }
+
+            importPropertiesList << importProperties
+        }
+    }
+
+    private synchronized Object processImplicitVariables(String key, Closure 
c) {
+        groovyShell.setVariable(VAR_KEY, key)
+        groovyShell.setVariable(VAR_PROPERTIES, this)
+        def v = c()
+        groovyShell.removeVariable(VAR_PROPERTIES)
+        groovyShell.removeVariable(VAR_KEY)
+
+        return v
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/test-resources/groovy/util/gproperties.properties
----------------------------------------------------------------------
diff --git a/src/test-resources/groovy/util/gproperties.properties 
b/src/test-resources/groovy/util/gproperties.properties
new file mode 100644
index 0000000..e4f488d
--- /dev/null
+++ b/src/test-resources/groovy/util/gproperties.properties
@@ -0,0 +1,28 @@
+#
+#  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.
+#
+
+import.properties=/groovy/util/gproperties_import.properties,/groovy/util/gproperties_import3.properties
+groovy.greeting={greeting.word},{some.name}
+groovy.greeting.with.smile={groovy.greeting} :)
+groovy.greeting.with.time={groovy.greeting.with.smile} in ${new 
java.text.SimpleDateFormat('yyyyMMdd').format(new Date())}
+groovy.greeting.with.missing=Hello,{none} {0}
+groovy.greeting.with.script=Hello,${properties.getProperty('some.name')}
+groovy.greeting.with.key=Hello,${key}
+groovy.greeting.with.escapes=Hello,{{some.name}}
+groovy.greeting.with.escapes2=Hello,${{properties.getProperty('some.name')}}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/test-resources/groovy/util/gproperties_import.properties
----------------------------------------------------------------------
diff --git a/src/test-resources/groovy/util/gproperties_import.properties 
b/src/test-resources/groovy/util/gproperties_import.properties
new file mode 100644
index 0000000..7016e97
--- /dev/null
+++ b/src/test-resources/groovy/util/gproperties_import.properties
@@ -0,0 +1,21 @@
+#
+#  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.
+#
+import.properties=/groovy/util/gproperties_import2.properties
+some.name=Daniel
+greeting.daniel={greeting.word},{some.name}

http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/test-resources/groovy/util/gproperties_import2.properties
----------------------------------------------------------------------
diff --git a/src/test-resources/groovy/util/gproperties_import2.properties 
b/src/test-resources/groovy/util/gproperties_import2.properties
new file mode 100644
index 0000000..7d41c9f
--- /dev/null
+++ b/src/test-resources/groovy/util/gproperties_import2.properties
@@ -0,0 +1,20 @@
+#
+#  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.
+#
+
+greeting.word=H${'ell'}o

http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/test-resources/groovy/util/gproperties_import3.properties
----------------------------------------------------------------------
diff --git a/src/test-resources/groovy/util/gproperties_import3.properties 
b/src/test-resources/groovy/util/gproperties_import3.properties
new file mode 100644
index 0000000..616c9a4
--- /dev/null
+++ b/src/test-resources/groovy/util/gproperties_import3.properties
@@ -0,0 +1,20 @@
+#
+#  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.
+#
+
+greeting.word2=Hi

http://git-wip-us.apache.org/repos/asf/groovy/blob/5de34ce5/src/test/groovy/util/GPropertiesTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/groovy/util/GPropertiesTest.groovy 
b/src/test/groovy/util/GPropertiesTest.groovy
new file mode 100644
index 0000000..fe9ae0d
--- /dev/null
+++ b/src/test/groovy/util/GPropertiesTest.groovy
@@ -0,0 +1,101 @@
+/*
+ *  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 groovy.util
+
+class GPropertiesTest extends GroovyTestCase {
+    void testImportProperties() {
+        def gp = new GProperties(true)
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert 
'/groovy/util/gproperties_import.properties,/groovy/util/gproperties_import3.properties'
 == gp.getProperty('import.properties')
+        assert 'Daniel' == gp.getProperty('some.name')
+        assert 'Hello' == gp.getProperty('greeting.word')
+        assert 'Hi' == gp.getProperty('greeting.word2')
+    }
+
+    void testInterpolate() {
+        def gp = new GProperties(true)
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert 'Hello,Daniel' == gp.getProperty('groovy.greeting')
+    }
+
+    void testInterpolate2() {
+        def gp = new GProperties(true)
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert 'Hello,Daniel :)' == 
gp.getProperty('groovy.greeting.with.smile')
+    }
+
+    void testInterpolate3() {
+        def gp = new GProperties()
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert 'Hello,{none} {0}' == 
gp.getProperty('groovy.greeting.with.missing')
+    }
+
+    void testInterpolate4() {
+        def gp = new GProperties(true)
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert 'Hello,Daniel' == gp.getProperty('greeting.daniel')
+    }
+
+    void testInterpolate5() {
+        def gp = new GProperties()
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert '''H${'ell'}o,Daniel''' == gp.getProperty('greeting.daniel')
+    }
+
+    void testEscape() {
+        def gp = new GProperties()
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert 'Hello,{some.name}' == 
gp.getProperty('groovy.greeting.with.escapes')
+    }
+
+    void testEscape2() {
+        def gp = new GProperties()
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert '''Hello,${properties.getProperty('some.name')}''' == 
gp.getProperty('groovy.greeting.with.escapes2')
+    }
+
+    void testInterpolateGroovyScript() {
+        def gp = new GProperties(true)
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert "Hello,Daniel :) in ${new 
java.text.SimpleDateFormat('yyyyMMdd').format(new Date())}" == 
gp.getProperty('groovy.greeting.with.time')
+    }
+
+    void testInterpolateGroovyScript2() {
+        def gp = new GProperties(true)
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert "Hello,Daniel" == gp.getProperty('groovy.greeting.with.script')
+    }
+
+    void testInterpolateGroovyScript3() {
+        def gp = new GProperties(true)
+        
gp.load(GPropertiesTest.getResourceAsStream('/groovy/util/gproperties.properties'))
+
+        assert "Hello,groovy.greeting.with.key" == 
gp.getProperty('groovy.greeting.with.key')
+    }
+}

Reply via email to