JOHNZON-89 JOHNZON-88 switching default access mode to field+method + fixing this mode + upgrading tomee for websocket tests
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 25bdf12..28c7211 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
@@ -127,7 +127,7 @@
         this.order = orderValue;
         this.visibility = visibilityStrategy;
         this.caseSensitive = caseSensitive;
-        this.delegate = new FieldAndMethodAccessMode(true, true);
+        this.delegate = new FieldAndMethodAccessMode(true, true, false);
         this.defaultConverters = defaultConverters;
         this.factory = factory;
         this.parserFactory = parserFactory;
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 944ed9b..b3e57eb 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
@@ -167,10 +167,10 @@
                 accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, true);
             } else if ("strict-method".equalsIgnoreCase(accessModeName)) {
                 accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, false);
-            } else if ("both".equalsIgnoreCase(accessModeName)) {
-                accessMode = new FieldAndMethodAccessMode(supportConstructors, supportHiddenAccess);
+            } else if ("both".equalsIgnoreCase(accessModeName) || accessModeName == null) {
+                accessMode = new FieldAndMethodAccessMode(supportConstructors, supportHiddenAccess, useGetterForCollections);
             } else {
-                accessMode = new MethodAccessMode(supportConstructors, supportHiddenAccess, useGetterForCollections);
+                throw new IllegalArgumentException("Unsupported access mode: " + accessModeName);
             }
         }
         if (!ignoredForFields.isEmpty()) {
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 0e4d9e7..607bf2d 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
@@ -19,52 +19,174 @@
 package org.apache.johnzon.mapper.access;
 
 import org.apache.johnzon.mapper.Adapter;
+import org.apache.johnzon.mapper.JohnzonIgnore;
+import org.apache.johnzon.mapper.JohnzonProperty;
 import org.apache.johnzon.mapper.ObjectConverter;
 
+import java.beans.Introspector;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.HashMap;
 import java.util.Map;
 
-// methods override fields
+// annotated entity overrides the other one, methods are used instead of field if both are there
 public class FieldAndMethodAccessMode extends BaseAccessMode {
     private final FieldAccessMode fields;
     private final MethodAccessMode methods;
 
-    public FieldAndMethodAccessMode(final boolean useConstructor, final boolean acceptHiddenConstructor) {
+    public FieldAndMethodAccessMode(final boolean useConstructor, final boolean acceptHiddenConstructor,
+                                    final boolean useGettersAsWriter) {
         super(useConstructor, acceptHiddenConstructor);
         this.fields = new FieldAccessMode(useConstructor, acceptHiddenConstructor);
-        this.methods = new MethodAccessMode(useConstructor, acceptHiddenConstructor, false);
+        this.methods = new MethodAccessMode(useConstructor, acceptHiddenConstructor, useGettersAsWriter);
     }
 
     @Override
     public Map<String, Reader> doFindReaders(final Class<?> clazz) {
-        final Map<String, Reader> readers = new HashMap<String, Reader>(fields.findReaders(clazz));
-        for (final Map.Entry<String, Reader> entry : methods.findReaders(clazz).entrySet()) {
+        final Map<String, Reader> fieldsReaders = this.fields.findReaders(clazz);
+        final Map<String, Reader> methodReaders = this.methods.findReaders(clazz);
+
+        final Map<String, Reader> readers = new HashMap<String, Reader>();
+
+        for (final Map.Entry<String, Reader> entry : fieldsReaders.entrySet()) {
+            final String key = entry.getKey();
+            Method m = getMethod("get" + Character.toUpperCase(key.charAt(0)) + (key.length() > 1 ? key.substring(1) : ""), clazz);
+            if (m == null && (boolean.class == entry.getValue().getType() || Boolean.class == entry.getValue().getType())) {
+                m = getMethod("is" + Character.toUpperCase(key.charAt(0)) + (key.length() > 1 ? key.substring(1) : ""), clazz);
+            }
+            boolean skip = false;
+            if (m != null) {
+                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) {
+                            skip = true;
+                        }
+                        break;
+                    }
+                }
+            }
+            if (skip) {
+                continue;
+            }
+            readers.put(entry.getKey(), entry.getValue());
+        }
+
+        for (final Map.Entry<String, Reader> entry : methodReaders.entrySet()) {
+            final Method mr = MethodAccessMode.MethodDecoratedType.class.cast(entry.getValue()).getMethod();
+            final String fieldName = Introspector.decapitalize(mr.getName().startsWith("is") ? mr.getName().substring(2) : mr.getName().substring(3));
+            final Field f = getField(fieldName, clazz);
+            boolean skip = false;
+            if (f != null) {
+                for (final Reader w : fieldsReaders.values()) {
+                    if (FieldAccessMode.FieldDecoratedType.class.cast(w).getField().equals(f)) {
+                        if (w.getAnnotation(JohnzonProperty.class) != null || w.getAnnotation(JohnzonIgnore.class) != null) {
+                            skip = true;
+                        }
+                        break;
+                    }
+                }
+            }
+            if (skip) {
+                continue;
+            }
+
             final Reader existing = readers.get(entry.getKey());
             if (existing == null) {
                 readers.put(entry.getKey(), entry.getValue());
             } else {
-                readers.put(entry.getKey(), new CompositeReader(existing, entry.getValue()));
+                readers.put(entry.getKey(), new CompositeReader(entry.getValue(), existing));
             }
         }
+
         return readers;
     }
 
+    private Method getMethod(final String methodName, final Class<?> type, final Class<?>... args) {
+        try {
+            return type.getMethod(methodName, args);
+        } catch (final NoSuchMethodException e) {
+            return null;
+        }
+    }
+
+    private Field getField(final String fieldName, final Class<?> type) {
+        Class<?> t = type;
+        while (t != Object.class && t != null) {
+            try {
+                return t.getDeclaredField(fieldName);
+            } catch (final NoSuchFieldException e) {
+                // no-op
+            }
+            t = t.getSuperclass();
+        }
+        return null;
+    }
+
     @Override
     public Map<String, Writer> doFindWriters(final Class<?> clazz) {
-        final Map<String, Writer> writers = new HashMap<String, Writer>(fields.findWriters(clazz));
-        for (final Map.Entry<String, Writer> entry : methods.findWriters(clazz).entrySet()) {
+        final Map<String, Writer> fieldWriters = this.fields.findWriters(clazz);
+        final Map<String, Writer> metodWriters = this.methods.findWriters(clazz);
+
+        final Map<String, Writer> writers = new HashMap<String, Writer>();
+
+        for (final Map.Entry<String, Writer> entry : fieldWriters.entrySet()) {
+            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) {
+                for (final Writer w : metodWriters.values()) {
+                    if (MethodAccessMode.MethodDecoratedType.class.cast(w).getMethod().equals(m)) {
+                        if (w.getAnnotation(JohnzonProperty.class) != null) {
+                            skip = true;
+                        }
+                        break;
+                    }
+                }
+            }
+            if (skip) {
+                continue;
+            }
+            writers.put(entry.getKey(), entry.getValue());
+        }
+
+        for (final Map.Entry<String, Writer> entry : metodWriters.entrySet()) {
+            final Method mr = MethodAccessMode.MethodDecoratedType.class.cast(entry.getValue()).getMethod();
+            final String fieldName = Introspector.decapitalize(mr.getName().startsWith("is") ? mr.getName().substring(2) : mr.getName().substring(3));
+            final Field f = getField(fieldName, clazz);
+            boolean skip = false;
+            if (f != null) {
+                for (final Writer w : fieldWriters.values()) {
+                    if (FieldAccessMode.FieldDecoratedType.class.cast(w).getField().equals(f)) {
+                        if (w.getAnnotation(JohnzonProperty.class) != null) {
+                            skip = true;
+                        }
+                        break;
+                    }
+                }
+            }
+            if (skip) {
+                continue;
+            }
+
             final Writer existing = writers.get(entry.getKey());
             if (existing == null) {
                 writers.put(entry.getKey(), entry.getValue());
             } else {
-                writers.put(entry.getKey(), new CompositeWriter(existing, entry.getValue()));
+                writers.put(entry.getKey(), new CompositeWriter(entry.getValue(), existing));
             }
         }
         return writers;
     }
 
+    private Class<?> toType(final Type type) {
+        return Class.class.isInstance(type) ? Class.class.cast(type) :
+                (ParameterizedType.class.isInstance(type) ? toType(ParameterizedType.class.cast(type).getRawType()) :
+                Object.class /*fallback*/);
+    }
+
     public static abstract class CompositeDecoratedType implements DecoratedType {
         protected final DecoratedType type1;
         private final DecoratedType type2;
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
index 302b4f4..c81c070 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
@@ -52,7 +52,7 @@
                 if (isIgnored(descriptor.getName()) || Meta.getAnnotation(readMethod, JohnzonAny.class) != null) {
                     continue;
                 }
-                readers.put(extractKey(descriptor), new MethodReader(readMethod, fixType(clazz, readMethod.getGenericReturnType())));
+                readers.put(extractKey(descriptor.getName(), readMethod, null), new MethodReader(readMethod, fixType(clazz, readMethod.getGenericReturnType())));
             }
         }
         return readers;
@@ -68,20 +68,24 @@
             }
             final Method writeMethod = descriptor.getWriteMethod();
             if (writeMethod != null) {
-                writers.put(extractKey(descriptor), new MethodWriter(writeMethod, fixType(clazz, writeMethod.getGenericParameterTypes()[0])));
+                writers.put(extractKey(descriptor.getName(), writeMethod, descriptor.getReadMethod()),
+                        new MethodWriter(writeMethod, fixType(clazz, writeMethod.getGenericParameterTypes()[0])));
             } else if (supportGetterAsWritter
                     && Collection.class.isAssignableFrom(descriptor.getPropertyType())
                     && descriptor.getReadMethod() != null) {
                 final Method readMethod = descriptor.getReadMethod();
-                writers.put(extractKey(descriptor), new MethodGetterAsWriter(readMethod, fixType(clazz, readMethod.getGenericReturnType())));
+                writers.put(extractKey(descriptor.getName(), readMethod, null), new MethodGetterAsWriter(readMethod, fixType(clazz, readMethod.getGenericReturnType())));
             }
         }
         return writers;
     }
 
-    private String extractKey(final PropertyDescriptor f) {
-        final JohnzonProperty property = f.getReadMethod() == null ? null : Meta.getAnnotation(f.getReadMethod(), JohnzonProperty.class);
-        return property != null ? property.value() : f.getName();
+    private String extractKey(final String name, final Method from, final Method or) {
+        JohnzonProperty property = Meta.getAnnotation(from, JohnzonProperty.class);
+        if (property == null && or != null) {
+            property = Meta.getAnnotation(or, JohnzonProperty.class);
+        }
+        return property != null ? property.value() : name;
     }
 
     protected boolean isIgnored(final String name) {
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GetterSetterRespectTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GetterSetterRespectTest.java
new file mode 100644
index 0000000..a129e11
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/GetterSetterRespectTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class GetterSetterRespectTest {
+    @Test
+    public void run() {
+        final Mapper mapper = new MapperBuilder().build();
+        assertEquals("ok", Mapped.class.cast(mapper.readObject("{\"name_\":\"ok\"}", Mapped.class)).name);
+
+        final Mapped mapped = new Mapped();
+        mapped.name = "ok";
+        assertEquals("{\"_name\":\"ok\"}", mapper.writeObjectAsString(mapped));
+    }
+
+    public static class Mapped {
+        private String name;
+
+        @JohnzonProperty("_name")
+        public String getName() {
+            return name;
+        }
+
+        @JohnzonProperty("name_")
+        public void setName(final String name) {
+            this.name = name;
+        }
+    }
+}
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java
index cb52d74..6ec0daf 100644
--- a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/JohnzonAnyMappingTest.java
@@ -58,6 +58,8 @@
 
     public static class AnyMe {
         private String name;
+
+        @JohnzonIgnore
         private Map<String, Object> any = new TreeMap<String, Object>();
 
         public String getName() {
diff --git a/johnzon-websocket/pom.xml b/johnzon-websocket/pom.xml
index b319669..661ab59 100644
--- a/johnzon-websocket/pom.xml
+++ b/johnzon-websocket/pom.xml
@@ -29,8 +29,8 @@
   <name>Johnzon :: WebSocket</name>
 
   <properties>
-    <tomcat.version>7.0.59</tomcat.version>
-    <tomee.version>1.7.4</tomee.version>
+    <tomcat.version>8.5.3</tomcat.version>
+    <tomee.version>7.0.1</tomee.version>
     <staging.directory>${project.parent.reporting.outputDirectory}</staging.directory>
   </properties>
 
@@ -61,7 +61,7 @@
       <scope>test</scope>
     </dependency>
      <dependency>
-      <groupId>org.apache.openejb</groupId>
+      <groupId>org.apache.tomee</groupId>
       <artifactId>arquillian-tomee-remote</artifactId>
       <version>${tomee.version}</version>
       <scope>test</scope>
@@ -87,16 +87,22 @@
       </exclusions>
      </dependency> 
     <dependency>
-      <groupId>org.apache.openejb</groupId>
+      <groupId>org.apache.tomee</groupId>
       <artifactId>apache-tomee</artifactId>
       <version>${tomee.version}</version>
       <type>zip</type>
-      <classifier>jaxrs</classifier>
+      <classifier>webprofile</classifier>
       <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.tomcat</groupId>
-      <artifactId>tomcat7-websocket</artifactId>
+      <artifactId>tomcat-websocket</artifactId>
+      <version>${tomcat.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.tomcat</groupId>
+      <artifactId>tomcat-api</artifactId>
       <version>${tomcat.version}</version>
       <scope>test</scope>
     </dependency>
diff --git a/johnzon-websocket/src/test/resources/arquillian.xml b/johnzon-websocket/src/test/resources/arquillian.xml
index c86d7a3..1f2d9d0 100644
--- a/johnzon-websocket/src/test/resources/arquillian.xml
+++ b/johnzon-websocket/src/test/resources/arquillian.xml
@@ -24,7 +24,6 @@
               http://jboss.org/schema/arquillian/arquillian_1_0.xsd">
   <container qualifier="tomee" default="true">
     <configuration>
-      <property name="classifier">jaxrs</property>
       <property name="httpsPort">-1</property>
       <property name="httpPort">-1</property>
       <property name="stopPort">-1</property>
diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md
index 8c97a99..87fe66b 100644
--- a/src/site/markdown/index.md
+++ b/src/site/markdown/index.md
@@ -189,6 +189,7 @@
 
 <pre class="prettyprint linenums"><![CDATA[
 public class AnyMe {
+    @JohnzonAny // ignore normal serialization of this field
     private String name; // known
     private Map<String, Object> any = new TreeMap<String, Object>(); // unknown
 
@@ -221,6 +222,7 @@
 * field: to use fields model and ignore getters/setters
 * method: use getters/setters (means if you have a getter but no setter you will serialize the property but not read it)
 * strict-method (default based on Pojo convention): same as method but getters for collections are not used to write
+* both: field and method accessors are merged together
 
 You can use these names with setAccessModeName().