You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by st...@apache.org on 2022/04/12 08:05:32 UTC

[johnzon] branch master updated (a38e0c3 -> 23b49f3)

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

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


    from a38e0c3  JOHNZON-364 JsonbVisibility always wins over default rules
     new 46a1bb8  fix DefaultPropertyVisibilityStrategy to respect the spec, ensure we clean the cache + fix backward compatibility of the FieldAndMethodAccessMode
     new 23b49f3  JOHNZON-364 use reflection to avoid caches.

The 2 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.


Summary of changes:
 .../org/apache/johnzon/core/util/ClassUtil.java    |  32 +++++
 ...nBuilderFactoryTest.java => ClassUtilTest.java} |  30 ++---
 .../jsonb/DefaultPropertyVisibilityStrategy.java   | 107 +++++++++++-----
 .../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  |   8 +-
 .../org/apache/johnzon/mapper/MapperBuilder.java   |   2 +-
 .../java/org/apache/johnzon/mapper/Mappings.java   |  92 +++++++-------
 .../mapper/access/FieldAndMethodAccessMode.java    |  42 ++++++-
 .../access/FieldAndMethodAccessModeTest.java       | 138 +++++++++++++++++++++
 12 files changed, 429 insertions(+), 118 deletions(-)
 copy johnzon-core/src/test/java/org/apache/johnzon/core/{JsonBuilderFactoryTest.java => ClassUtilTest.java} (58%)
 copy johnzon-websocket/src/main/java/org/apache/johnzon/websocket/jsr/package-info.java => johnzon-mapper/src/main/java/org/apache/johnzon/mapper/Cleanable.java (89%)
 create mode 100644 johnzon-mapper/src/test/java/org/apache/johnzon/mapper/access/FieldAndMethodAccessModeTest.java


[johnzon] 02/02: JOHNZON-364 use reflection to avoid caches.

Posted by st...@apache.org.
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 23b49f3d517a07e3d51f1ea1d32cf94a474cd7d4
Author: Mark Struberg <st...@apache.org>
AuthorDate: Tue Apr 12 10:03:36 2022 +0200

    JOHNZON-364 use reflection to avoid caches.
---
 .../org/apache/johnzon/core/util/ClassUtil.java    | 32 ++++++++++++++++++
 .../org/apache/johnzon/core/ClassUtilTest.java     | 38 ++++++++++++++++++++++
 .../jsonb/DefaultPropertyVisibilityStrategy.java   | 23 +++++++++----
 3 files changed, 87 insertions(+), 6 deletions(-)

diff --git a/johnzon-core/src/main/java/org/apache/johnzon/core/util/ClassUtil.java b/johnzon-core/src/main/java/org/apache/johnzon/core/util/ClassUtil.java
index 1dc189b..1eebe6f 100644
--- a/johnzon-core/src/main/java/org/apache/johnzon/core/util/ClassUtil.java
+++ b/johnzon-core/src/main/java/org/apache/johnzon/core/util/ClassUtil.java
@@ -58,4 +58,36 @@ public final class ClassUtil {
         }
     }
 
+    /**
+     * Calculate the name of a getter based on the name of it's field and the type
+     *
+     * @param fieldName
+     * @param type      of the field
+     * @return "get" or "is" method name for the field
+     */
+    public static String getterName(String fieldName, Class<?> type) {
+        StringBuilder sb = new StringBuilder(50);
+        sb.append(type == Boolean.class || type == boolean.class ? "is" : "get");
+        sb.append(Character.toUpperCase(fieldName.charAt(0))).append(fieldName.substring(1));
+        return sb.toString();
+    }
+
+    /**
+     * Calculate the name of a setter based on the name of it's field
+     *
+     * @param fieldName
+     * @return "set" method name for the field
+     */
+    public static String setterName(String fieldName) {
+        StringBuilder sb = new StringBuilder(50);
+        sb.append("set");
+        sb.append(Character.toUpperCase(fieldName.charAt(0))).append(fieldName.substring(1));
+        return sb.toString();
+    }
+
+    public static String capitalizeName(String fieldName) {
+        StringBuilder sb = new StringBuilder(50);
+        sb.append(Character.toUpperCase(fieldName.charAt(0))).append(fieldName.substring(1));
+        return sb.toString();
+    }
 }
\ No newline at end of file
diff --git a/johnzon-core/src/test/java/org/apache/johnzon/core/ClassUtilTest.java b/johnzon-core/src/test/java/org/apache/johnzon/core/ClassUtilTest.java
new file mode 100644
index 0000000..299d146
--- /dev/null
+++ b/johnzon-core/src/test/java/org/apache/johnzon/core/ClassUtilTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.core;
+
+import org.apache.johnzon.core.util.ClassUtil;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class ClassUtilTest {
+
+    @Test
+    public void testGetterNames() {
+        assertEquals("getMyName", ClassUtil.getterName("myName", Integer.class));
+        assertEquals("isEnabled", ClassUtil.getterName("enabled", Boolean.class));
+        assertEquals("isEnabled", ClassUtil.getterName("enabled", boolean.class));
+    }
+
+    @Test
+    public void testSetterNames() {
+        assertEquals("setMyName", ClassUtil.setterName("myName"));
+        assertEquals("setEnabled", ClassUtil.setterName("enabled"));
+    }
+}
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 fe4d449..33e0be5 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,6 +18,7 @@
  */
 package org.apache.johnzon.jsonb;
 
+import org.apache.johnzon.core.util.ClassUtil;
 import org.apache.johnzon.mapper.Cleanable;
 
 import javax.json.bind.annotation.JsonbProperty;
@@ -36,8 +37,6 @@ import static java.util.Optional.ofNullable;
 
 class DefaultPropertyVisibilityStrategy implements javax.json.bind.config.PropertyVisibilityStrategy, Cleanable<Class<?>> {
     private final ConcurrentMap<Class<?>, PropertyVisibilityStrategy> strategies = 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;
 
@@ -61,9 +60,23 @@ class DefaultPropertyVisibilityStrategy implements javax.json.bind.config.Proper
         // 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 !hasMethod(root, ClassUtil.setterName(field.getName()));
+        }
+        final String capitalizedName = ClassUtil.capitalizeName(field.getName());
+        return !hasMethod(root, "get" + capitalizedName) ||  hasMethod(root, "is" + capitalizedName);
+    }
+
+    private boolean hasMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
+        try {
+            clazz.getDeclaredMethod(methodName, paramTypes);
+            return true;
+        } catch (NoSuchMethodException e) {
+            final Class<?> superclass = clazz.getSuperclass();
+            if (superclass == Object.class) {
+                return false;
+            }
+            return hasMethod(superclass, methodName, paramTypes);
         }
-        return getters.computeIfAbsent(root, this::calculateGetters).getOrDefault(field.getName(), true);
     }
 
     /**
@@ -165,8 +178,6 @@ class DefaultPropertyVisibilityStrategy implements javax.json.bind.config.Proper
 
     @Override
     public void clean(final Class<?> clazz) {
-        getters.remove(clazz);
-        setters.remove(clazz);
         strategies.remove(clazz);
     }
 }


[johnzon] 01/02: fix DefaultPropertyVisibilityStrategy to respect the spec, ensure we clean the cache + fix backward compatibility of the FieldAndMethodAccessMode

Posted by st...@apache.org.
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 <rm...@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;
+        }
+    }
+}