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

struberg pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/johnzon.git

commit 46a1bb8103e9f6dc0cd6b174b5a85a22a578e499
Author: Romain Manni-Bucau <rmannibu...@gmail.com>
AuthorDate: Sun Apr 10 20:06:50 2022 +0200

    fix DefaultPropertyVisibilityStrategy to respect the spec, ensure we clean 
the cache + fix backward compatibility of the FieldAndMethodAccessMode
---
 .../jsonb/DefaultPropertyVisibilityStrategy.java   |  96 +++++++++-----
 .../org/apache/johnzon/jsonb/JohnzonBuilder.java   |   2 +-
 .../org/apache/johnzon/jsonb/JsonbAccessMode.java  |  45 +++++--
 .../DefaultPropertyVisibilityStrategyTest.java     |  45 ++++++-
 .../jsonb/api/experimental/JsonbExtensionTest.java |   4 -
 .../java/org/apache/johnzon/mapper/Cleanable.java  |  23 ++++
 .../org/apache/johnzon/mapper/MapperBuilder.java   |   2 +-
 .../java/org/apache/johnzon/mapper/Mappings.java   |  92 +++++++-------
 .../mapper/access/FieldAndMethodAccessMode.java    |  42 ++++++-
 .../access/FieldAndMethodAccessModeTest.java       | 138 +++++++++++++++++++++
 10 files changed, 393 insertions(+), 96 deletions(-)

diff --git 
a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategy.java
 
b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategy.java
index 141b5a0..fe4d449 100644
--- 
a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategy.java
+++ 
b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategy.java
@@ -18,64 +18,96 @@
  */
 package org.apache.johnzon.jsonb;
 
-import static java.util.Optional.ofNullable;
+import org.apache.johnzon.mapper.Cleanable;
 
-import java.beans.Introspector;
+import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbVisibility;
+import javax.json.bind.config.PropertyVisibilityStrategy;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
-import java.util.ArrayList;
 import java.util.Collections;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
-import javax.json.bind.annotation.JsonbProperty;
-import javax.json.bind.annotation.JsonbVisibility;
-import javax.json.bind.config.PropertyVisibilityStrategy;
+import static java.util.Optional.ofNullable;
 
-class DefaultPropertyVisibilityStrategy implements 
javax.json.bind.config.PropertyVisibilityStrategy {
+class DefaultPropertyVisibilityStrategy implements 
javax.json.bind.config.PropertyVisibilityStrategy, Cleanable<Class<?>> {
     private final ConcurrentMap<Class<?>, PropertyVisibilityStrategy> 
strategies = new ConcurrentHashMap<>();
-    private final ConcurrentMap<Class<?>, List<String>> getters = new 
ConcurrentHashMap<>();
+    private final ConcurrentMap<Class<?>, Map<String, Boolean>> getters = new 
ConcurrentHashMap<>();
+    private final ConcurrentMap<Class<?>, Map<String, Boolean>> setters = new 
ConcurrentHashMap<>();
 
     private volatile boolean skipGetpackage;
 
     @Override
     public boolean isVisible(final Field field) {
+        return isVisible(field, field.getDeclaringClass(), true);
+    }
+
+    public boolean isVisible(final Field field, final Class<?> root, final 
boolean useGetter) {
         if (field.getAnnotation(JsonbProperty.class) != null) {
             return true;
         }
-        final PropertyVisibilityStrategy strategy = strategies.computeIfAbsent(
-                field.getDeclaringClass(), this::visibilityStrategy);
-        return strategy == this ? isFieldVisible(field) : 
strategy.isVisible(field);
+        final PropertyVisibilityStrategy strategy = 
strategies.computeIfAbsent(root, this::visibilityStrategy);
+        return strategy == this ? isFieldVisible(field, root, useGetter) : 
strategy.isVisible(field);
     }
 
-    private boolean isFieldVisible(Field field) {
+    private boolean isFieldVisible(final Field field, final Class<?> root, 
final boolean useGetter) {
         if (!Modifier.isPublic(field.getModifiers())) {
             return false;
         }
-        // also check if there is any setter, in which case the field should 
be treated as non-visible as well.
-        return !getters.computeIfAbsent(field.getDeclaringClass(), 
this::calculateGetters).contains(field.getName());
+        // 3.7.1. Scope and Field access strategy
+        // note: we should bind the class since a field of a parent class can 
have a getter in a child
+        if (!useGetter) {
+            return setters.computeIfAbsent(root, 
this::calculateSetters).getOrDefault(field.getName(), true);
+        }
+        return getters.computeIfAbsent(root, 
this::calculateGetters).getOrDefault(field.getName(), true);
     }
 
     /**
      * Calculate all the getters of the given class.
      */
-    private List<String> calculateGetters(Class<?> clazz) {
-        List<String> getters = new ArrayList<>();
-        for (Method m : clazz.getDeclaredMethods()) {
-            if (m.getParameterCount() == 0) {
-                if (m.getName().startsWith("get")) {
-                    
getters.add(Introspector.decapitalize(m.getName().substring(3)));
-                } else if (m.getName().startsWith("is")) {
-                    
getters.add(Introspector.decapitalize(m.getName().substring(2)));
-                }
+    private Map<String, Boolean> calculateGetters(final Class<?> clazz) {
+        final Map<String, Boolean> getters = new HashMap<>();
+        for (final Method m : clazz.getDeclaredMethods()) {
+            if (m.getParameterCount() > 0) {
+                continue;
+            }
+            if (m.getName().startsWith("get") && m.getName().length() > 3) {
+                getters.put(
+                        Character.toLowerCase(m.getName().charAt(3)) + 
m.getName().substring(4),
+                        Modifier.isPublic(m.getModifiers()));
+            } else if (m.getName().startsWith("is") && m.getName().length() > 
2) {
+                getters.put(
+                        Character.toLowerCase(m.getName().charAt(2)) + 
m.getName().substring(3),
+                        Modifier.isPublic(m.getModifiers()));
+            }
+        }
+        final Class<?> superclass = clazz.getSuperclass();
+        if (superclass != Object.class && superclass != null && 
!"java.lang.Record".equals(superclass.getName())) {
+            calculateGetters(superclass).forEach(getters::putIfAbsent); // 
don't override child getter if exists
+        }
+        return getters.isEmpty() ? Collections.emptyMap() : getters;
+    }
+
+    private Map<String, Boolean> calculateSetters(final Class<?> clazz) {
+        final Map<String, Boolean> result = new HashMap<>();
+        for (final Method m : clazz.getDeclaredMethods()) {
+            if (m.getParameterCount() != 1) {
+                continue;
+            }
+            if (m.getName().startsWith("set") && m.getName().length() > 3) {
+                result.put(
+                        Character.toLowerCase(m.getName().charAt(3)) + 
m.getName().substring(4),
+                        Modifier.isPublic(m.getModifiers()));
             }
         }
         if (clazz.getSuperclass() != Object.class) {
-            getters.addAll(calculateGetters(clazz.getSuperclass()));
+            
calculateSetters(clazz.getSuperclass()).forEach(result::putIfAbsent);
         }
-        return getters.isEmpty() ? Collections.emptyList() : getters;
+        return result.isEmpty() ? Collections.emptyMap() : result;
     }
 
     @Override
@@ -120,8 +152,9 @@ class DefaultPropertyVisibilityStrategy implements 
javax.json.bind.config.Proper
             }
             if (p == null) {
                 try {
-                    p = 
ofNullable(type.getClassLoader()).orElseGet(ClassLoader::getSystemClassLoader)
-                                                         .loadClass(parentPack 
+ ".package-info").getPackage();
+                    p = ofNullable(type.getClassLoader())
+                            .orElseGet(ClassLoader::getSystemClassLoader)
+                             .loadClass(parentPack + 
".package-info").getPackage();
                 } catch (final ClassNotFoundException e) {
                     // no-op
                 }
@@ -129,4 +162,11 @@ class DefaultPropertyVisibilityStrategy implements 
javax.json.bind.config.Proper
         }
         return this;
     }
+
+    @Override
+    public void clean(final Class<?> clazz) {
+        getters.remove(clazz);
+        setters.remove(clazz);
+        strategies.remove(clazz);
+    }
 }
diff --git 
a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java 
b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
index b304bd1..c068809 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JohnzonBuilder.java
@@ -221,7 +221,7 @@ public class JohnzonBuilder implements JsonbBuilder {
                         factory, jsonp, builderFactorySupplier, 
parserFactoryProvider,
                         config.getProperty("johnzon.accessModeDelegate")
                                 .map(this::toAccessMode)
-                                .orElseGet(() -> new 
FieldAndMethodAccessMode(true, true, false)),
+                                .orElseGet(() -> new 
FieldAndMethodAccessMode(true, true, false, false, true)),
                         // this changes in v3 of the spec so let's use this 
behavior which makes everyone happy by default
                         
config.getProperty("johnzon.failOnMissingCreatorValues")
                                 .map(this::toBool)
diff --git 
a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java 
b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
index 4bf1911..fbcb4a8 100644
--- a/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
+++ b/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
@@ -33,6 +33,7 @@ import 
org.apache.johnzon.jsonb.serializer.JohnzonDeserializationContext;
 import org.apache.johnzon.jsonb.serializer.JohnzonSerializationContext;
 import org.apache.johnzon.jsonb.spi.JohnzonAdapterFactory;
 import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.Cleanable;
 import org.apache.johnzon.mapper.Converter;
 import org.apache.johnzon.mapper.JohnzonAny;
 import org.apache.johnzon.mapper.JohnzonConverter;
@@ -127,7 +128,7 @@ import static java.util.stream.Collectors.toSet;
 import static org.apache.johnzon.mapper.reflection.Converters.matches;
 import static org.apache.johnzon.mapper.reflection.Records.isRecord;
 
-public class JsonbAccessMode implements AccessMode, Closeable {
+public class JsonbAccessMode implements AccessMode, Closeable, 
Cleanable<Class<?>> {
     private final PropertyNamingStrategy naming;
     private final String order;
     private final PropertyVisibilityStrategy visibility;
@@ -542,7 +543,7 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
                     return initialReader.isNillable(globalConfig);
                 }
             } : initialReader;
-            if (isTransient(initialReader, visibility)) {
+            if (isTransient(initialReader, visibility, clazz, true)) {
                 validateAnnotationsOnTransientField(initialReader);
                 continue;
             }
@@ -670,7 +671,7 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
         final Map<String, Writer> result = keyComparator == null ? new 
HashMap<>() : new TreeMap<>(keyComparator);
         for (final Map.Entry<String, Writer> entry : writers.entrySet()) {
             Writer initialWriter = entry.getValue();
-            if (isTransient(initialWriter, visibility)) {
+            if (isTransient(initialWriter, visibility, clazz, false)) {
                 validateAnnotationsOnTransientField(initialWriter);
                 continue;
             }
@@ -860,17 +861,17 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
                 
isOptional(GenericArrayType.class.cast(value.getType()).getGenericComponentType());
     }
 
-    private boolean isTransient(final DecoratedType dt, final 
PropertyVisibilityStrategy visibility) {
+    private boolean isTransient(final DecoratedType dt, final 
PropertyVisibilityStrategy visibility, final Class<?> root, final boolean read) 
{
         if 
(!FieldAndMethodAccessMode.CompositeDecoratedType.class.isInstance(dt)) {
-            return isTransient(dt) || shouldSkip(visibility, dt);
+            return isTransient(dt) || shouldSkip(visibility, dt, root, read);
         }
         final FieldAndMethodAccessMode.CompositeDecoratedType cdt = 
FieldAndMethodAccessMode.CompositeDecoratedType.class.cast(dt);
         return isTransient(cdt.getType1()) || isTransient(cdt.getType2()) ||
-                (shouldSkip(visibility, cdt.getType1()) && 
shouldSkip(visibility, cdt.getType2()));
+                (shouldSkip(visibility, cdt.getType1(), root, read) && 
shouldSkip(visibility, cdt.getType2(), root, read));
     }
 
-    private boolean shouldSkip(final PropertyVisibilityStrategy visibility, 
final DecoratedType t) {
-        return isNotVisible(visibility, t);
+    private boolean shouldSkip(final PropertyVisibilityStrategy visibility, 
final DecoratedType t, final Class<?> root, final boolean read) {
+        return isNotVisible(visibility, t, root, read);
     }
 
     private boolean isTransient(final DecoratedType t) {
@@ -885,11 +886,22 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
         return false;
     }
 
-    private boolean isNotVisible(PropertyVisibilityStrategy visibility, 
DecoratedType t) {
-        return !(FieldAccessMode.FieldDecoratedType.class.isInstance(t) ?
-                
visibility.isVisible(FieldAccessMode.FieldDecoratedType.class.cast(t).getField())
-                : (MethodAccessMode.MethodDecoratedType.class.isInstance(t) &&
-                
visibility.isVisible(MethodAccessMode.MethodDecoratedType.class.cast(t).getMethod())));
+    private boolean isNotVisible(final PropertyVisibilityStrategy visibility,
+                                 final DecoratedType t,
+                                 final Class<?> root,
+                                 final boolean read) {
+        if (FieldAccessMode.FieldDecoratedType.class.isInstance(t)) {
+            final Field field = 
FieldAccessMode.FieldDecoratedType.class.cast(t).getField();
+            if 
(DefaultPropertyVisibilityStrategy.class.isInstance(visibility)) {
+                return 
!DefaultPropertyVisibilityStrategy.class.cast(visibility).isVisible(field, 
root, read);
+            }
+            return !visibility.isVisible(field);
+        }
+        if (MethodAccessMode.MethodDecoratedType.class.isInstance(t)) {
+            final Method method = 
MethodAccessMode.MethodDecoratedType.class.cast(t).getMethod();
+            return !visibility.isVisible(method);
+        }
+        return false;
     }
 
     private Comparator<String> orderComparator(final Class<?> clazz) {
@@ -967,6 +979,13 @@ public class JsonbAccessMode implements AccessMode, 
Closeable {
         return Meta.findMeta(param.getAnnotations(), api);
     }
 
+    @Override
+    public void clean(final Class<?> value) {
+        if (Cleanable.class.isInstance(visibility)) {
+            Cleanable.class.cast(visibility).clean(value);
+        }
+    }
+
     private class ReaderConverters {
         private Adapter<?, ?> converter;
         private ObjectConverter.Reader reader;
diff --git 
a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategyTest.java
 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategyTest.java
index 17eeab2..b780497 100644
--- 
a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategyTest.java
+++ 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/DefaultPropertyVisibilityStrategyTest.java
@@ -18,6 +18,7 @@
  */
 package org.apache.johnzon.jsonb;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
 import java.lang.reflect.Field;
@@ -26,6 +27,7 @@ import java.lang.reflect.Method;
 import javax.json.bind.Jsonb;
 import javax.json.bind.JsonbBuilder;
 import javax.json.bind.annotation.JsonbProperty;
+import javax.json.bind.annotation.JsonbTransient;
 import javax.json.bind.annotation.JsonbVisibility;
 
 import org.junit.Test;
@@ -47,6 +49,26 @@ public class DefaultPropertyVisibilityStrategyTest {
         }
     }
 
+    @Test
+    public void hiddenGetter() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create()) {
+            assertFalse(jsonb.fromJson("{\"foo\":true}", 
HideAllModel.class).isFoo());
+            assertFalse(jsonb.fromJson("{\"foo\":true}", 
HideAllDefaultModel.class).isFoo());
+        }
+    }
+
+    public static class HideAll extends DefaultPropertyVisibilityStrategy {
+        @Override
+        public boolean isVisible(final Field field) {
+            return false;
+        }
+
+        @Override
+        public boolean isVisible(final Method method) {
+            return false;
+        }
+    }
+
     public static class MyVisibility extends DefaultPropertyVisibilityStrategy 
{
         @Override
         public boolean isVisible(final Field field) {
@@ -64,7 +86,7 @@ public class DefaultPropertyVisibilityStrategyTest {
         @JsonbProperty
         private boolean foo;
 
-        public final boolean isFoo() {
+        public boolean isFoo() {
             return foo;
         }
     }
@@ -73,7 +95,26 @@ public class DefaultPropertyVisibilityStrategyTest {
         @JsonbProperty
         private boolean foo;
 
-        public final boolean isFoo() {
+        public boolean isFoo() {
+            return foo;
+        }
+    }
+
+    @JsonbVisibility(HideAll.class)
+    public static final class HideAllModel {
+        protected boolean foo;
+
+        @JsonbTransient
+        public boolean isFoo() {
+            return foo;
+        }
+    }
+
+    public static final class HideAllDefaultModel {
+        protected boolean foo;
+
+        @JsonbTransient
+        public boolean isFoo() {
             return foo;
         }
     }
diff --git 
a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java
 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java
index fc8eacd..a2d612c 100644
--- 
a/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java
+++ 
b/johnzon-jsonb/src/test/java/org/apache/johnzon/jsonb/api/experimental/JsonbExtensionTest.java
@@ -218,10 +218,6 @@ public class JsonbExtensionTest {
             return value;
         }
 
-        public void setValue(Object value) {
-            this.value = value;
-        }
-
         @Override
         public boolean equals(final Object obj) { // for test
             return Wrapper.class.isInstance(obj) &&
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Cleanable.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Cleanable.java
new file mode 100644
index 0000000..e90a363
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Cleanable.java
@@ -0,0 +1,23 @@
+/*
+ * 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.johnzon.mapper;
+
+public interface Cleanable<A> {
+    void clean(A value);
+}
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
index b02455f..5075ed4 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/MapperBuilder.java
@@ -169,7 +169,7 @@ public class MapperBuilder {
             } else if ("strict-method".equalsIgnoreCase(accessModeName)) {
                 accessMode = new MethodAccessMode(supportConstructors, 
supportHiddenAccess, false);
             } else if ("both".equalsIgnoreCase(accessModeName) || 
accessModeName == null) {
-                accessMode = new FieldAndMethodAccessMode(supportConstructors, 
supportHiddenAccess, useGetterForCollections);
+                accessMode = new FieldAndMethodAccessMode(supportConstructors, 
supportHiddenAccess, useGetterForCollections, true, false);
             } else {
                 throw new IllegalArgumentException("Unsupported access mode: " 
+ accessModeName);
             }
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
index 686f285..7c1776e 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Mappings.java
@@ -470,59 +470,65 @@ public class Mappings {
         final Map<String, Getter> getters = fieldComparator == null ? 
newOrderedMap(Getter.class) : new TreeMap<>(fieldComparator);
         final Map<String, Setter> setters = fieldComparator == null ? 
newOrderedMap(Setter.class) : new TreeMap<>(fieldComparator);
 
-        final Map<String, AccessMode.Reader> readers = 
accessMode.findReaders(clazz);
-        final Map<String, AccessMode.Writer> writers = 
accessMode.findWriters(clazz);
-
-        final Collection<String> virtualFields = new HashSet<String>();
-        {
-            final JohnzonVirtualObjects virtualObjects = 
clazz.getAnnotation(JohnzonVirtualObjects.class);
-            if (virtualObjects != null) {
-                for (final JohnzonVirtualObject virtualObject : 
virtualObjects.value()) {
+        try {
+            final Map<String, AccessMode.Reader> readers = 
accessMode.findReaders(clazz);
+            final Map<String, AccessMode.Writer> writers = 
accessMode.findWriters(clazz);
+
+            final Collection<String> virtualFields = new HashSet<String>();
+            {
+                final JohnzonVirtualObjects virtualObjects = 
clazz.getAnnotation(JohnzonVirtualObjects.class);
+                if (virtualObjects != null) {
+                    for (final JohnzonVirtualObject virtualObject : 
virtualObjects.value()) {
+                        handleVirtualObject(virtualFields, virtualObject, 
getters, setters, readers, writers, copyDate, clazz);
+                    }
+                }
+
+                final JohnzonVirtualObject virtualObject = 
clazz.getAnnotation(JohnzonVirtualObject.class);
+                if (virtualObject != null) {
                     handleVirtualObject(virtualFields, virtualObject, getters, 
setters, readers, writers, copyDate, clazz);
                 }
             }
 
-            final JohnzonVirtualObject virtualObject = 
clazz.getAnnotation(JohnzonVirtualObject.class);
-            if (virtualObject != null) {
-                handleVirtualObject(virtualFields, virtualObject, getters, 
setters, readers, writers, copyDate, clazz);
+            for (final Map.Entry<String, AccessMode.Reader> reader : 
readers.entrySet()) {
+                final String key = reader.getKey();
+                if (virtualFields.contains(key)) {
+                    continue;
+                }
+                addGetterIfNeeded(getters, key, reader.getValue(), copyDate, 
resolvedTypes);
             }
-        }
 
-        for (final Map.Entry<String, AccessMode.Reader> reader : 
readers.entrySet()) {
-            final String key = reader.getKey();
-            if (virtualFields.contains(key)) {
-                continue;
+            for (final Map.Entry<String, AccessMode.Writer> writer : 
writers.entrySet()) {
+                final String key = writer.getKey();
+                if (virtualFields.contains(key)) {
+                    continue;
+                }
+                addSetterIfNeeded(setters, key, writer.getValue(), copyDate, 
clazz, resolvedTypes);
             }
-            addGetterIfNeeded(getters, key, reader.getValue(), copyDate, 
resolvedTypes);
-        }
 
-        for (final Map.Entry<String, AccessMode.Writer> writer : 
writers.entrySet()) {
-            final String key = writer.getKey();
-            if (virtualFields.contains(key)) {
-                continue;
+            final Method anyGetter = accessMode.findAnyGetter(clazz);
+            final Field anyField = accessMode.findAnyField(clazz);
+            final ClassMapping mapping = new ClassMapping(
+                    clazz, accessMode.findFactory(clazz), getters, setters,
+                    accessMode.findAdapter(clazz),
+                    accessMode.findReader(clazz),
+                    accessMode.findWriter(clazz),
+                    anyGetter != null ? new Getter(
+                            new MethodAccessMode.MethodReader(anyGetter, 
anyGetter.getReturnType()),
+                            false,false, false, false, true, null, null, -1, 
null) :
+                            (anyField != null ? new Getter(new 
FieldAccessMode.FieldReader(anyField, anyField.getGenericType()),
+                            false,false, false, false, true, null, null, -1, 
null) : null),
+                    accessMode.findAnySetter(clazz),
+                    anyField,
+                    Map.class.isAssignableFrom(clazz) ? 
accessMode.findMapAdder(clazz) : null);
+
+            accessMode.afterParsed(clazz);
+
+            return mapping;
+        } finally {
+            if (Cleanable.class.isInstance(accessMode)) {
+                ((Cleanable<Class<?>>) accessMode).clean(clazz);
             }
-            addSetterIfNeeded(setters, key, writer.getValue(), copyDate, 
clazz, resolvedTypes);
         }
-
-        final Method anyGetter = accessMode.findAnyGetter(clazz);
-        final Field anyField = accessMode.findAnyField(clazz);
-        final ClassMapping mapping = new ClassMapping(
-                clazz, accessMode.findFactory(clazz), getters, setters,
-                accessMode.findAdapter(clazz),
-                accessMode.findReader(clazz),
-                accessMode.findWriter(clazz),
-                anyGetter != null ? new Getter(
-                        new MethodAccessMode.MethodReader(anyGetter, 
anyGetter.getReturnType()),
-                        false,false, false, false, true, null, null, -1, null) 
:
-                        (anyField != null ? new Getter(new 
FieldAccessMode.FieldReader(anyField, anyField.getGenericType()),
-                        false,false, false, false, true, null, null, -1, null) 
: null),
-                accessMode.findAnySetter(clazz),
-                anyField,
-                Map.class.isAssignableFrom(clazz) ? 
accessMode.findMapAdder(clazz) : null);
-
-        accessMode.afterParsed(clazz);
-
-        return mapping;
     }
 
     protected Class<?> findModelClass(final Class<?> inClazz) {
diff --git 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
index a651d6d..c06de48 100644
--- 
a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
+++ 
b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessMode.java
@@ -29,6 +29,7 @@ import java.beans.Introspector;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.HashMap;
@@ -38,12 +39,31 @@ import java.util.Map;
 public class FieldAndMethodAccessMode extends BaseAccessMode {
     private final FieldAccessMode fields;
     private final MethodAccessMode methods;
+    private final boolean alwaysPreferMethodVisibility;
+    private final boolean ignoreVisibilityFilter;
 
     public FieldAndMethodAccessMode(final boolean useConstructor, final 
boolean acceptHiddenConstructor,
-                                    final boolean useGettersAsWriter) {
+                                    final boolean useGettersAsWriter, final 
boolean alwaysPreferMethodVisibility,
+                                    final boolean ignoreVisibilityFilter) {
         super(useConstructor, acceptHiddenConstructor);
         this.fields = new FieldAccessMode(useConstructor, 
acceptHiddenConstructor);
-        this.methods = new MethodAccessMode(useConstructor, 
acceptHiddenConstructor, useGettersAsWriter);
+        this.methods = new MethodAccessMode(
+                useConstructor, acceptHiddenConstructor, useGettersAsWriter);
+        this.alwaysPreferMethodVisibility = alwaysPreferMethodVisibility;
+        this.ignoreVisibilityFilter = ignoreVisibilityFilter;
+    }
+
+    @Deprecated // backward compat
+    public FieldAndMethodAccessMode(final boolean useConstructor, final 
boolean acceptHiddenConstructor,
+                                    final boolean useGettersAsWriter, final 
boolean alwaysPreferMethodVisibility) {
+        this(useConstructor, acceptHiddenConstructor, useGettersAsWriter, 
alwaysPreferMethodVisibility, false);
+    }
+
+    // backward compatibility, don't delete since it can be used from user 
code in jsonb delegate access mode property
+    @Deprecated
+    public FieldAndMethodAccessMode(final boolean useConstructor, final 
boolean acceptHiddenConstructor,
+                                    final boolean useGettersAsWriter) {
+        this(useConstructor, acceptHiddenConstructor, useGettersAsWriter, 
true, false);
     }
 
 
@@ -67,7 +87,7 @@ public class FieldAndMethodAccessMode extends BaseAccessMode {
                 m = getMethod("is" + Character.toUpperCase(key.charAt(0)) + 
(key.length() > 1 ? key.substring(1) : ""), clazz);
             }
             boolean skip = false;
-            if (m != null) {
+            if (m != null && (ignoreVisibilityFilter || 
Modifier.isPublic(m.getModifiers()))) {
                 for (final Reader w : methodReaders.values()) {
                     if 
(MethodAccessMode.MethodDecoratedType.class.cast(w).getMethod().equals(m)) {
                         if (w.getAnnotation(JohnzonProperty.class) != null || 
w.getAnnotation(JohnzonIgnore.class) != null) {
@@ -76,6 +96,8 @@ public class FieldAndMethodAccessMode extends BaseAccessMode {
                         break;
                     }
                 }
+            } else if (!ignoreVisibilityFilter && m != null) {
+                continue;
             }
             if (skip) {
                 continue;
@@ -123,8 +145,18 @@ public class FieldAndMethodAccessMode extends 
BaseAccessMode {
 
     private Method getMethod(final String methodName, final Class<?> type, 
final Class<?>... args) {
         try {
+            if (alwaysPreferMethodVisibility) {
+                return type.getDeclaredMethod(methodName, args);
+            }
             return type.getMethod(methodName, args);
         } catch (final NoSuchMethodException e) {
+            if (alwaysPreferMethodVisibility) {
+                try {
+                    return type.getMethod(methodName, args);
+                } catch (final NoSuchMethodException e2) {
+                    // no-op
+                }
+            }
             return null;
         }
     }
@@ -153,7 +185,7 @@ public class FieldAndMethodAccessMode extends 
BaseAccessMode {
             final String key = entry.getKey();
             final Method m = getMethod("set" + 
Character.toUpperCase(key.charAt(0)) + (key.length() > 1 ? key.substring(1) : 
""), clazz, toType(entry.getValue().getType()));
             boolean skip = false;
-            if (m != null) {
+            if (m != null && (ignoreVisibilityFilter || 
Modifier.isPublic(m.getModifiers()))) {
                 for (final Writer w : metodWriters.values()) {
                     if 
(MethodAccessMode.MethodDecoratedType.class.cast(w).getMethod().equals(m)) {
                         if (w.getAnnotation(JohnzonProperty.class) != null) {
@@ -162,6 +194,8 @@ public class FieldAndMethodAccessMode extends 
BaseAccessMode {
                         break;
                     }
                 }
+            } else if (!ignoreVisibilityFilter && m != null) {
+                continue;
             }
             if (skip) {
                 continue;
diff --git 
a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessModeTest.java
 
b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessModeTest.java
new file mode 100644
index 0000000..4c2e948
--- /dev/null
+++ 
b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessModeTest.java
@@ -0,0 +1,138 @@
+/*
+ * 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.johnzon.mapper.access;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class FieldAndMethodAccessModeTest {
+    @Parameterized.Parameters
+    public static Collection<Object[]> data() {
+        return asList(
+                new Object[]{
+                        new FieldAndMethodAccessMode(true, true, false, true, 
false),
+                        POJO.class,
+                        singletonList("foo"),
+                        singletonList("foo")
+                },
+                new Object[]{
+                        new FieldAndMethodAccessMode(true, true, false, true, 
false),
+                        POJOProtectedSetter.class,
+                        singletonList("foo"),
+                        emptyList()
+                },
+                new Object[]{
+                        new FieldAndMethodAccessMode(true, true, false, true, 
false),
+                        POJOProtectedGetter.class,
+                        emptyList(),
+                        singletonList("foo")
+                },
+                new Object[]{
+                        new FieldAndMethodAccessMode(true, true, false, true, 
true),
+                        POJO.class,
+                        singletonList("foo"),
+                        singletonList("foo")
+                },
+                new Object[]{
+                        new FieldAndMethodAccessMode(true, true, false, true, 
true),
+                        POJOProtectedSetter.class,
+                        singletonList("foo"),
+                        singletonList("foo")
+                },
+                new Object[]{
+                        new FieldAndMethodAccessMode(true, true, false, true, 
true),
+                        POJOProtectedGetter.class,
+                        singletonList("foo"),
+                        singletonList("foo")
+                }
+        );
+    }
+
+    private final FieldAndMethodAccessMode accessMode;
+    private final Class<?> model;
+    private final Collection<String> resultGetters;
+    private final Collection<String> resultSetters;
+
+    public FieldAndMethodAccessModeTest(final FieldAndMethodAccessMode 
accessMode,
+                                        final Class<?> model,
+                                        final List<String> resultGetters,
+                                        final List<String> resultSetters) {
+        this.accessMode = accessMode;
+        this.model = model;
+        this.resultGetters = new HashSet<>(resultGetters);
+        this.resultSetters = new HashSet<>(resultSetters);
+    }
+
+    @Test
+    public void getters() {
+        assertEquals(resultGetters, accessMode.findReaders(model).keySet());
+    }
+
+    @Test
+    public void setters() {
+        assertEquals(resultSetters, accessMode.findWriters(model).keySet());
+    }
+
+    public static class POJO {
+        private int foo;
+
+        public int getFoo() {
+            return foo;
+        }
+
+        public void setFoo(final int foo) {
+            this.foo = foo;
+        }
+    }
+
+    public static class POJOProtectedSetter {
+        private int foo;
+
+        public int getFoo() {
+            return foo;
+        }
+
+        protected void setFoo(final int foo) {
+            this.foo = foo;
+        }
+    }
+
+    public static class POJOProtectedGetter {
+        private int foo;
+
+        protected int getFoo() {
+            return foo;
+        }
+
+        public void setFoo(final int foo) {
+            this.foo = foo;
+        }
+    }
+}

Reply via email to