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().