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:29 UTC

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

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
 }