You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2022/01/04 19:57:28 UTC

[groovy] branch GROOVY-5169 created (now f6ab340)

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

emilles pushed a change to branch GROOVY-5169
in repository https://gitbox.apache.org/repos/asf/groovy.git.


      at f6ab340  GROOVY-5169: JSON output: exclude non-public properties

This branch includes the following new commits:

     new f6ab340  GROOVY-5169: JSON output: exclude non-public properties

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.


[groovy] 01/01: GROOVY-5169: JSON output: exclude non-public properties

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

emilles pushed a commit to branch GROOVY-5169
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit f6ab340292465749edac4dec377fe526aded8d28
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue Jan 4 13:29:38 2022 -0600

    GROOVY-5169: JSON output: exclude non-public properties
---
 src/main/java/groovy/lang/MetaClassImpl.java       | 47 ++++++-------
 .../java/groovy/json/DefaultJsonGenerator.java     | 33 ++++++---
 .../groovy/json/DefaultJsonGeneratorTest.groovy    |  5 ++
 .../test/groovy/groovy/json/JsonOutputTest.groovy  | 80 +++++++++++++++++-----
 4 files changed, 112 insertions(+), 53 deletions(-)

diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java
index 93f78ee..45ca41c 100644
--- a/src/main/java/groovy/lang/MetaClassImpl.java
+++ b/src/main/java/groovy/lang/MetaClassImpl.java
@@ -2305,51 +2305,46 @@ public class MetaClassImpl implements MetaClass, MutableMetaClass {
     @Override
     public List<MetaProperty> getProperties() {
         checkInitalised();
-        LinkedHashMap<String, MetaProperty> propertyMap = classPropertyIndex.get(theCachedClass);
+        Map<String, MetaProperty> propertyMap = classPropertyIndex.get(theCachedClass);
         if (propertyMap == null) {
-            // GROOVY-6903: May happen in some special environment, like under Android, due
-            // to classloading issues
-            propertyMap = new LinkedHashMap<>();
+            // GROOVY-6903: May happen in some special environment, like Android, due to class-loading issues
+            propertyMap = Collections.emptyMap();
         }
         // simply return the values of the metaproperty map as a List
         List<MetaProperty> ret = new ArrayList<>(propertyMap.size());
-        for (Map.Entry<String, MetaProperty> stringMetaPropertyEntry : propertyMap.entrySet()) {
-            MetaProperty element = stringMetaPropertyEntry.getValue();
-            if (element instanceof CachedField) continue;
-            // filter out DGM beans
-            if (element instanceof MetaBeanProperty) {
-                MetaBeanProperty mp = (MetaBeanProperty) element;
-                boolean setter = true;
-                boolean getter = true;
-                MetaMethod getterMetaMethod = mp.getGetter();
+        for (MetaProperty mp : propertyMap.values()) {
+            if (mp instanceof CachedField) continue;
+
+            if (mp instanceof MetaBeanProperty) {
+                MetaBeanProperty mbp = (MetaBeanProperty) mp;
+                // filter out DGM beans
+                boolean getter = true, setter = true;
+                MetaMethod getterMetaMethod = mbp.getGetter();
                 if (getterMetaMethod == null || getterMetaMethod instanceof GeneratedMetaMethod || getterMetaMethod instanceof NewInstanceMetaMethod) {
                     getter = false;
                 }
-                MetaMethod setterMetaMethod = mp.getSetter();
+                MetaMethod setterMetaMethod = mbp.getSetter();
                 if (setterMetaMethod == null || setterMetaMethod instanceof GeneratedMetaMethod || setterMetaMethod instanceof NewInstanceMetaMethod) {
                     setter = false;
                 }
                 if (!setter && !getter) continue;
+
 //  TODO: I (ait) don't know why these strange tricks needed and comment following as it effects some Grails tests
-//                if (!setter && mp.getSetter() != null) {
-//                    element = new MetaBeanProperty(mp.getName(), mp.getType(), mp.getGetter(), null);
+//                if (!setter && mbp.getSetter() != null) {
+//                    mp = new MetaBeanProperty(mbp.getName(), mbp.getType(), mbp.getGetter(), null);
 //                }
-//                if (!getter && mp.getGetter() != null) {
-//                    element = new MetaBeanProperty(mp.getName(), mp.getType(), null, mp.getSetter());
+//                if (!getter && mbp.getGetter() != null) {
+//                    mp = new MetaBeanProperty(mbp.getName(), mbp.getType(), null, mbp.getSetter());
 //                }
-            }
-
-            if (!permissivePropertyAccess) {
-                if (element instanceof MetaBeanProperty) {
-                    MetaBeanProperty mbp = (MetaBeanProperty) element;
-                    boolean getterAccessible = canAccessLegally(mbp.getGetter());
-                    boolean setterAccessible = canAccessLegally(mbp.getSetter());
 
+                if (!permissivePropertyAccess) {
+                    boolean getterAccessible = canAccessLegally(getterMetaMethod);
+                    boolean setterAccessible = canAccessLegally(setterMetaMethod);
                     if (!(getterAccessible && setterAccessible)) continue;
                 }
             }
 
-            ret.add(element);
+            ret.add(mp);
         }
         return ret;
     }
diff --git a/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java b/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java
index 5766843..360ff72 100644
--- a/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java
+++ b/subprojects/groovy-json/src/main/java/groovy/json/DefaultJsonGenerator.java
@@ -21,8 +21,9 @@ package groovy.json;
 import org.apache.groovy.json.internal.CharBuf;
 import org.apache.groovy.json.internal.Chr;
 import groovy.lang.Closure;
+import groovy.lang.MetaBeanProperty;
+import groovy.lang.MetaProperty;
 import groovy.util.Expando;
-import org.codehaus.groovy.runtime.DefaultGroovyMethods;
 
 import java.io.File;
 import java.math.BigDecimal;
@@ -36,6 +37,7 @@ import java.util.Date;
 import java.util.Enumeration;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
@@ -52,6 +54,7 @@ import static groovy.json.JsonOutput.EMPTY_MAP_CHARS;
 import static groovy.json.JsonOutput.EMPTY_STRING_CHARS;
 import static groovy.json.JsonOutput.OPEN_BRACE;
 import static groovy.json.JsonOutput.OPEN_BRACKET;
+import static java.lang.reflect.Modifier.isPublic;
 
 /**
  * A JsonGenerator that can be configured with various {@link JsonGenerator.Options}.
@@ -232,12 +235,27 @@ public class DefaultJsonGenerator implements JsonGenerator {
         }
     }
 
-    protected Map<?, ?> getObjectProperties(Object object) {
-        Map<?, ?> properties = DefaultGroovyMethods.getProperties(object);
-        properties.remove("class");
-        properties.remove("declaringClass");
-        properties.remove("metaClass");
-        return properties;
+    protected Map<?, ?> getObjectProperties(final Object object) {
+        List<MetaProperty> metaProperties = org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(object).getProperties();
+
+        Map<String, Object> namesAndValues = new LinkedHashMap<>(metaProperties.size());
+
+        for (MetaProperty mp : metaProperties) {
+            if (!isPublic(mp.getModifiers())) continue; // GROOVY-5169
+
+            // skip write-only property: see File
+            if (mp instanceof MetaBeanProperty) {
+                MetaBeanProperty mbp = (MetaBeanProperty) mp;
+                if (mbp.getField() == null && mbp.getGetter() == null) continue;
+            }
+
+            String name = mp.getName();
+            if (name.equals("class") || name.equals("metaClass") || name.equals("declaringClass")) continue;
+
+            namesAndValues.put(name, mp.getProperty(object));
+        }
+
+        return namesAndValues;
     }
 
     /**
@@ -530,5 +548,4 @@ public class DefaultJsonGenerator implements JsonGenerator {
             return super.toString() + "<" + this.type.toString() + ">";
         }
     }
-
 }
diff --git a/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy b/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy
index 48d7c60..a78290f 100644
--- a/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy
+++ b/subprojects/groovy-json/src/test/groovy/groovy/json/DefaultJsonGeneratorTest.groovy
@@ -291,6 +291,11 @@ class DefaultJsonGeneratorTest extends GroovyTestCase {
 
 }
 
+class JsonObject {
+    String name
+    Map properties
+}
+
 class JsonBar {
     String favoriteDrink
     Date lastVisit
diff --git a/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy b/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy
index 20c061d..2f053a5 100644
--- a/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy
+++ b/subprojects/groovy-json/src/test/groovy/groovy/json/JsonOutputTest.groovy
@@ -18,19 +18,22 @@
  */
 package groovy.json
 
-import groovy.test.GroovyTestCase
 import groovy.transform.Canonical
+import org.junit.Test
 
 import static groovy.json.JsonOutput.toJson
+import static groovy.test.GroovyAssert.assertScript
+import static groovy.test.GroovyAssert.shouldFail
 
-class JsonOutputTest extends GroovyTestCase {
+final class JsonOutputTest {
 
-    // Check for GROOVY-5918
+    @Test // GROOVY-5918
     void testExpando() {
         assert toJson(new Expando(a: 42)) == '{"a":42}'
         assert new JsonBuilder(new Expando(a: 42)).toString() == '{"a":42}'
     }
 
+    @Test
     void testBooleanValues() {
         assert toJson(Boolean.TRUE) == "true"
         assert toJson(Boolean.FALSE) == "false"
@@ -38,6 +41,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson(false) == "false"
     }
 
+    @Test
     void testNullValue() {
         assert toJson(null) == "null"
         // test every overloaded version
@@ -55,6 +59,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson((Map) null) == 'null'
     }
 
+    @Test
     void testNumbers() {
         assert toJson(-1) == "-1"
         assert toJson(1) == "1"
@@ -91,16 +96,19 @@ class JsonOutputTest extends GroovyTestCase {
         shouldFail { toJson(Float.NEGATIVE_INFINITY) }
     }
 
+    @Test
     void testEmptyListOrArray() {
         assert toJson([]) == "[]"
         assert toJson([] as Object[]) == "[]"
     }
 
+    @Test
     void testListOfPrimitives() {
         assert toJson([true, false, null, true, 4, 1.1234]) == "[true,false,null,true,4,1.1234]"
         assert toJson([true, [false, null], true, [4, [1.1234]]]) == "[true,[false,null],true,[4,[1.1234]]]"
     }
 
+    @Test
     void testPrimitiveArray() {
         assert toJson([1, 2, 3, 4] as byte[]) == "[1,2,3,4]"
         assert toJson([1, 2, 3, 4] as short[]) == "[1,2,3,4]"
@@ -108,16 +116,19 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson([1, 2, 3, 4] as long[]) == "[1,2,3,4]"
     }
 
+    @Test
     void testEmptyMap() {
         assert toJson([:]) == "{}"
     }
 
+    @Test
     void testMap() {
         assert toJson([a: 1]) == '{"a":1}'
         assert toJson([a: 1, b: 2]) == '{"a":1,"b":2}'
         assert toJson([a: 1, b: true, c: null, d: [], e: 'hello']) == '{"a":1,"b":true,"c":null,"d":[],"e":"hello"}'
     }
 
+    @Test
     void testString() {
         assert toJson("") == '""'
 
@@ -153,32 +164,38 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson("\u0019") == '"\\u0019"'
     }
 
+    @Test
     void testGString() {
         assert toJson("1 + 2 = ${1 + 2}") == '"1 + 2 = 3"'
     }
 
+    @Test
     void testStringBuilderBuffer() {
         assert toJson(new StringBuilder().append(14).append(' March ').append(2014)) == '"14 March 2014"'
         assert toJson(new StringBuffer().append(14).append(' March ').append(2014)) == '"14 March 2014"'
     }
 
+    @Test
     void testCharArray() {
         char[] charArray = ['a', 'b', 'c']
 
         assert toJson(charArray) == '["a","b","c"]'
     }
 
+    @Test
     void testDate() {
         def d = Date.parse("yyyy/MM/dd HH:mm:ss Z", "2008/03/04 13:50:00 +0100")
 
         assert toJson(d) == '"2008-03-04T12:50:00+0000"'
     }
 
+    @Test
     void testURL() {
         assert toJson(new URL("http://glaforge.appspot.com")) == '"http://glaforge.appspot.com"'
         assert toJson(new URL('file', '', 'C:\\this\\is\\windows\\path')) == '"file:C:\\\\this\\\\is\\\\windows\\\\path"' // GROOVY-6560
     }
 
+    @Test
     void testCalendar() {
         def c = GregorianCalendar.getInstance(TimeZone.getTimeZone('GMT+1'))
         c.clearTime()
@@ -187,6 +204,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson(c) == '"2008-03-04T12:50:00+0000"'
     }
 
+    @Test
     void testComplexObject() {
         assert toJson([name: 'Guillaume', age: 33, address: [line1: "1 main street", line2: "", zip: 1234], pets: ['dog', 'cat']]) ==
                 '{"name":"Guillaume","age":33,"address":{"line1":"1 main street","line2":"","zip":1234},"pets":["dog","cat"]}'
@@ -194,6 +212,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson([[:], [:]]) == '[{},{}]'
     }
 
+    @Test
     void testClosure() {
         assert toJson({
             a 1
@@ -208,11 +227,13 @@ class JsonOutputTest extends GroovyTestCase {
         }) == '{"a":1,"b":{"c":2,"d":{"e":[3,{"f":4}]}}}'
     }
 
+    @Test
     void testIteratorEnumeration() {
         assert toJson([1, 2, 3].iterator()) == '[1,2,3]'
         assert toJson(Collections.enumeration([1, 2, 3])) == '[1,2,3]'
     }
 
+    @Test
     void testPrettyPrint() {
         def json = new JsonBuilder()
 
@@ -271,6 +292,7 @@ class JsonOutputTest extends GroovyTestCase {
         return str.replaceAll(~/\s/, '')
     }
 
+    @Test
     void testPrettyPrintStringZeroLen() {
         def tree = [myStrings: [str3: 'abc', str0: '']]
         def result = stripWhiteSpace(new JsonBuilder(tree).toPrettyString())
@@ -278,6 +300,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert result == expected
     }
 
+    @Test
     void testPrettyPrintDoubleQuoteEscape() {
         def json = new JsonBuilder()
         json.text { content 'abc"def' }
@@ -289,6 +312,7 @@ class JsonOutputTest extends GroovyTestCase {
             }""".stripIndent()
     }
 
+    @Test
     void testSerializePogos() {
         def city = new JsonCity("Paris", [
                 new JsonDistrict(1, [
@@ -335,29 +359,38 @@ class JsonOutputTest extends GroovyTestCase {
             }'''.stripIndent()
     }
 
+    @Test
     void testMapWithNullKey() {
         shouldFail IllegalArgumentException, {
             toJson([(null): 1])
         }
     }
 
-    void testGROOVY5247() {
+    @Test // GROOVY-5247
+    void testTreeMap() {
         def m = new TreeMap()
         m.a = 1
         assert toJson(m) == '{"a":1}'
     }
 
-    void testObjectWithDeclaredPropertiesField() {
-        def person = new JsonObject(name: "pillow", properties: [state: "fluffy", color: "white"])
-        def json = toJson(person)
+    @Test
+    void testDeclaredPropertiesField() {
+        String json = toJson(new JsonObject(name: "pillow", properties: [state: "fluffy", color: "white"]))
         assert json == '{"name":"pillow","properties":{"state":"fluffy","color":"white"}}'
     }
 
-    void testGROOVY5494() {
-        def json = toJson(new JsonFoo(name: "foo"))
+    @Test // GROOVY-5494
+    void testDeclaredPropertiesMethod() {
+        String json = toJson(new Pogo5494(name: "foo"))
         assert json == '{"name":"foo","properties":0}'
     }
 
+    static class Pogo5494 {
+        String name
+        int getProperties() { return 0 }
+    }
+
+    @Test
     void testCharacter() {
         assert toJson('a' as char) == '"a"'
         assert toJson('"' as char) == '"\\""'
@@ -371,6 +404,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson('\u0002' as char) == '"\\u0002"'
     }
 
+    @Test
     void testEmptyValue() {
         assert toJson('') == '""'
         assert toJson(['']) == '[""]'
@@ -378,6 +412,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson(new Expando('': '')) == '{"":""}'
     }
 
+    @Test
     void testSpecialCharEscape() {
         // Map
         assert toJson(['"': 0]) == '{"\\"":0}'
@@ -413,6 +448,7 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson({'\u0002' 0}) == '{"\\u0002":0}'
     }
 
+    @Test
     void testFile() {
         def file  = File.createTempFile('test', 'file-json')
         file.deleteOnExit()
@@ -423,8 +459,24 @@ class JsonOutputTest extends GroovyTestCase {
         assert toJson(dir)
     }
 
+    @Test // GROOVY-5169
+    void testPropertyModifiers1() {
+        assertScript '''
+            class Pogo {
+                public    String foo = 'foo' //TODO
+                public    String getBar() { 'bar' }
+                protected String getBaz() { 'baz' }
+                private   String getBoo() { 'boo' }
+            }
+
+            String json = groovy.json.JsonOutput.toJson(new Pogo())
+            assert json == '{"bar":"bar"}'
+        '''
+    }
 }
 
+//------------------------------------------------------------------------------
+
 @Canonical
 class JsonCity {
     String name
@@ -443,16 +495,6 @@ class JsonStreet {
     JsonStreetKind kind
 }
 
-class JsonObject {
-    String name
-    Map properties
-}
-
-class JsonFoo {
-    String name
-    int getProperties() { return 0 }
-}
-
 enum JsonStreetKind {
     street, boulevard, avenue
 }