feat: field visibilty buggy for booleans and not compliant with specification

Signed-off-by: Jean-Louis Monteiro <jlmonteiro@tomitribe.com>
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 919a895..67ea85c 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
@@ -50,6 +50,11 @@
         return strategy == this ? isFieldVisible(field, root, useGetter) : strategy.isVisible(field);
     }
 
+    /**
+     * If the field is not public then it's of course not visible. If the field is public then we need to look at the
+     * accessors. If there is a private/protected/default accessor for it then it overrides and the field is not visible
+     * But if there is no accessor for it, then it's visible.
+     */
     private boolean isFieldVisible(final Field field, final Class<?> root, final boolean useGetter) {
         if (!Modifier.isPublic(field.getModifiers())) {
             return false;
@@ -57,16 +62,16 @@
         // 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 !hasMethod(root, BeanUtil.setterName(field.getName()));
+            return !hasMethod(root, BeanUtil.setterName(field.getName()), field.getType());
         }
-        final String capitalizedName = BeanUtil.capitalize(field.getName());
-        return !hasMethod(root, "get" + capitalizedName) ||  hasMethod(root, "is" + capitalizedName);
+        return !hasMethod(root, BeanUtil.getterName(field.getName(), field.getType()));
     }
 
     private boolean hasMethod(Class<?> clazz, String methodName, Class<?>... paramTypes) {
         try {
-            clazz.getDeclaredMethod(methodName, paramTypes);
-            return true;
+            final Method declaredMethod = clazz.getDeclaredMethod(methodName, paramTypes);
+            return !Modifier.isPublic(declaredMethod.getModifiers());
+
         } catch (NoSuchMethodException e) {
             final Class<?> superclass = clazz.getSuperclass();
             if (superclass == Object.class) {
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 25e9b5f..ac32834 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.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
@@ -57,6 +58,61 @@
         }
     }
 
+    @Test
+    public void matchingPrivateAccessors() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create()) {
+            assertEquals("{}", jsonb.toJson(new PublicFieldWithPrivateAccessors()));
+            assertEquals("{}", jsonb.toJson(new PublicBooleanFieldWithPrivateAccessors()));
+
+            {
+                final PublicFieldWithPrivateAccessors unmarshalledObject =
+                    jsonb.fromJson("{\"foo\":\"bad\"}", PublicFieldWithPrivateAccessors.class);
+                assertEquals("bar", unmarshalledObject.foo);
+            }
+            {
+                final PublicBooleanFieldWithPrivateAccessors unmarshalledObject =
+                    jsonb.fromJson("{\"foo\":\"false\"}", PublicBooleanFieldWithPrivateAccessors.class);
+                assertEquals(true, unmarshalledObject.foo);
+            }
+        }
+    }
+
+    @Test
+    public void noMatchingPrivateAccessors() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create()) {
+            assertEquals("{\"foo\":\"bar\"}", jsonb.toJson(new PublicFieldWithoutMatchingPrivateAccessors()));
+
+            final PublicFieldWithoutMatchingPrivateAccessors unmarshalledObject =
+                jsonb.fromJson("{\"foo\":\"good\"}", PublicFieldWithoutMatchingPrivateAccessors.class);
+            assertEquals("good", unmarshalledObject.foo);
+        }
+    }
+
+    @Test
+    public void oneAccessorOnly() throws Exception {
+        try (final Jsonb jsonb = JsonbBuilder.create()) {
+            assertEquals("{}", jsonb.toJson(new PublicFieldWithMatchingPrivateGetterAccessors()));
+            assertEquals("{\"foo\":\"bar\"}", jsonb.toJson(new PublicFieldWithMatchingPrivateSetterAccessors()));
+            assertEquals("{\"foo\":\"bar\"}", jsonb.toJson(new PublicFieldWithPublicAccessors()));
+
+            {
+                final PublicFieldWithMatchingPrivateGetterAccessors unmarshalledObject =
+                    jsonb.fromJson("{\"foo\":\"good\"}", PublicFieldWithMatchingPrivateGetterAccessors.class);
+                assertEquals("good", unmarshalledObject.foo);
+            }
+            {
+                final PublicFieldWithMatchingPrivateSetterAccessors unmarshalledObject =
+                    jsonb.fromJson("{\"foo\":\"bad\"}", PublicFieldWithMatchingPrivateSetterAccessors.class);
+                assertEquals("bar", unmarshalledObject.foo);
+            }
+            {
+                final PublicFieldWithPublicAccessors unmarshalledObject =
+                    jsonb.fromJson("{\"foo\":\"good\"}", PublicFieldWithPublicAccessors.class);
+                assertEquals("good", unmarshalledObject.foo);
+            }
+        }
+    }
+
     public static class HideAll extends DefaultPropertyVisibilityStrategy {
         @Override
         public boolean isVisible(final Field field) {
@@ -81,6 +137,64 @@
         }
     }
 
+    // if there is a matching private getter/setter, the field even if it's public must be ignored
+    public static final class PublicFieldWithPrivateAccessors {
+        public String foo = "bar";
+
+        private String getFoo() {
+            return foo;
+        }
+
+        private void setFoo(final String foo) {
+            this.foo = foo;
+        }
+    }
+
+    public static final class PublicFieldWithPublicAccessors {
+        public String foo = "bar";
+
+        public String getFoo() {
+            return foo;
+        }
+
+        public void setFoo(final String foo) {
+            this.foo = foo;
+        }
+    }
+
+    public static final class PublicBooleanFieldWithPrivateAccessors {
+        public boolean foo = true;
+
+        private boolean isFoo() {
+            return foo;
+        }
+
+        private void setFoo(final boolean foo) {
+            this.foo = foo;
+        }
+    }
+
+    // if there is not matching getter/setter for a public field, then the field is used directly
+    public static final class PublicFieldWithoutMatchingPrivateAccessors {
+        public String foo = "bar";
+    }
+
+    public static final class PublicFieldWithMatchingPrivateGetterAccessors {
+        public String foo = "bar";
+
+        private String getFoo() {
+            return foo;
+        }
+    }
+
+    public static final class PublicFieldWithMatchingPrivateSetterAccessors {
+        public String foo = "bar";
+
+        private void setFoo(final String foo) {
+            this.foo = foo;
+        }
+    }
+
     @JsonbVisibility(MyVisibility.class)
     public static final class TheClass {
         @JsonbProperty