You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@johnzon.apache.org by rm...@apache.org on 2016/06/28 21:45:51 UTC

johnzon git commit: JOHNZON-87 meta annotation support

Repository: johnzon
Updated Branches:
  refs/heads/master ab0b4af93 -> b28c4baa0


JOHNZON-87 meta annotation support


Project: http://git-wip-us.apache.org/repos/asf/johnzon/repo
Commit: http://git-wip-us.apache.org/repos/asf/johnzon/commit/b28c4baa
Tree: http://git-wip-us.apache.org/repos/asf/johnzon/tree/b28c4baa
Diff: http://git-wip-us.apache.org/repos/asf/johnzon/diff/b28c4baa

Branch: refs/heads/master
Commit: b28c4baa08536f012bbc7a8e5a544b005f3a723e
Parents: ab0b4af
Author: Romain manni-Bucau <rm...@gmail.com>
Authored: Tue Jun 28 23:45:37 2016 +0200
Committer: Romain manni-Bucau <rm...@gmail.com>
Committed: Tue Jun 28 23:45:37 2016 +0200

----------------------------------------------------------------------
 .../apache/johnzon/jsonb/JsonbAccessMode.java   |  30 ++--
 .../org/apache/johnzon/mapper/JohnzonAny.java   |   3 +-
 .../apache/johnzon/mapper/JohnzonConverter.java |   3 +-
 .../apache/johnzon/mapper/JohnzonIgnore.java    |   3 +-
 .../apache/johnzon/mapper/JohnzonProperty.java  |   3 +-
 .../johnzon/mapper/JohnzonVirtualObjects.java   |   3 +-
 .../johnzon/mapper/access/FieldAccessMode.java  |  10 +-
 .../org/apache/johnzon/mapper/access/Meta.java  |  99 +++++++++++++
 .../johnzon/mapper/access/MethodAccessMode.java |  10 +-
 .../apache/johnzon/mapper/MetaMapperTest.java   | 140 +++++++++++++++++++
 10 files changed, 279 insertions(+), 25 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-jsonb/src/main/java/org/apache/johnzon/jsonb/JsonbAccessMode.java
----------------------------------------------------------------------
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 d41a98d..25bdf12 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
@@ -36,6 +36,7 @@ import org.apache.johnzon.mapper.access.AccessMode;
 import org.apache.johnzon.mapper.access.BaseAccessMode;
 import org.apache.johnzon.mapper.access.FieldAccessMode;
 import org.apache.johnzon.mapper.access.FieldAndMethodAccessMode;
+import org.apache.johnzon.mapper.access.Meta;
 import org.apache.johnzon.mapper.access.MethodAccessMode;
 import org.apache.johnzon.mapper.converter.ReversedAdapter;
 import org.apache.johnzon.mapper.internal.AdapterKey;
@@ -172,12 +173,12 @@ public class JsonbAccessMode implements AccessMode, Closeable {
             itemConverters = new Adapter<?, ?>[types.length];
             int i = 0;
             for (final Parameter parameter : (finalConstructor == null ? finalFactory : finalConstructor).getParameters()) {
-                final JsonbProperty property = parameter.getAnnotation(JsonbProperty.class);
+                final JsonbProperty property = getAnnotation(parameter, JsonbProperty.class);
                 params[i] = property != null ? property.value() : parameter.getName();
 
-                final JsonbTypeAdapter adapter = parameter.getAnnotation(JsonbTypeAdapter.class);
-                final JsonbDateFormat dateFormat = parameter.getAnnotation(JsonbDateFormat.class);
-                final JsonbNumberFormat numberFormat = parameter.getAnnotation(JsonbNumberFormat.class);
+                final JsonbTypeAdapter adapter = getAnnotation(parameter, JsonbTypeAdapter.class);
+                final JsonbDateFormat dateFormat = getAnnotation(parameter, JsonbDateFormat.class);
+                final JsonbNumberFormat numberFormat = getAnnotation(parameter, JsonbNumberFormat.class);
                 if (adapter == null && dateFormat == null && numberFormat == null) {
                     converters[i] = defaultConverters.get(parameter.getType());
                     itemConverters[i] = null;
@@ -326,8 +327,8 @@ public class JsonbAccessMode implements AccessMode, Closeable {
 
     private ParameterizedType findPt(final Class<?> value, final Class<?> type) {
         return ParameterizedType.class.cast(
-                        Stream.of(value.getGenericInterfaces())
-                                .filter(i -> ParameterizedType.class.isInstance(i) && ParameterizedType.class.cast(i).getRawType() == type).findFirst().orElse(null));
+                Stream.of(value.getGenericInterfaces())
+                        .filter(i -> ParameterizedType.class.isInstance(i) && ParameterizedType.class.cast(i).getRawType() == type).findFirst().orElse(null));
     }
 
     private JohnzonAdapterFactory.Instance newInstance(final Class<?> value) {
@@ -603,12 +604,12 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         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())));
+                visibility.isVisible(MethodAccessMode.MethodDecoratedType.class.cast(t).getMethod())));
     }
 
     private Comparator<String> orderComparator(final Class<?> clazz) {
         final Comparator<String> keyComparator;
-        final JsonbPropertyOrder orderAnnotation = clazz.getAnnotation(JsonbPropertyOrder.class);
+        final JsonbPropertyOrder orderAnnotation = Meta.getAnnotation(clazz, JsonbPropertyOrder.class);
         if (orderAnnotation != null) {
             final List<String> indexed = new ArrayList<>(asList(orderAnnotation.value()));
             keyComparator = (o1, o2) -> {
@@ -645,6 +646,15 @@ public class JsonbAccessMode implements AccessMode, Closeable {
         toRelease.clear();
     }
 
+    // belongs to Meta but java 8
+    private static <T extends Annotation> T getAnnotation(final Parameter param, final Class<T> api) {
+        final T annotation = param.getAnnotation(api);
+        if (annotation != null) {
+            return annotation;
+        }
+        return Meta.findMeta(param.getAnnotations(), api);
+    }
+
     private class ReaderConverters {
         private Adapter<?, ?> converter;
         private ObjectConverter.Reader reader;
@@ -729,12 +739,12 @@ public class JsonbAccessMode implements AccessMode, Closeable {
 
         @Override
         public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
-            return annotations.getAnnotation(clazz);
+            return Meta.getAnnotation(annotations, clazz);
         }
 
         @Override
         public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
-            return null;
+            return Meta.getAnnotation(clazz.getPackage(), clazz);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
index b134de7..16338e3 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonAny.java
@@ -21,10 +21,11 @@ package org.apache.johnzon.mapper;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
 @Retention(RUNTIME)
-@Target(METHOD)
+@Target({METHOD, ANNOTATION_TYPE})
 public @interface JohnzonAny {
 }

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java
index b298792..85a8f5f 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonConverter.java
@@ -21,12 +21,13 @@ package org.apache.johnzon.mapper;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.ElementType.PARAMETER;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-@Target({ METHOD, FIELD, PARAMETER })
+@Target({ METHOD, FIELD, PARAMETER, ANNOTATION_TYPE })
 @Retention(RUNTIME)
 public @interface JohnzonConverter {
     Class<? extends MapperConverter> value();

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnore.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnore.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnore.java
index c589e58..a1f7d85 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnore.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonIgnore.java
@@ -21,11 +21,12 @@ package org.apache.johnzon.mapper;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-@Target({ METHOD, FIELD })
+@Target({ METHOD, FIELD, ANNOTATION_TYPE })
 @Retention(RUNTIME)
 public @interface JohnzonIgnore {
     /**

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonProperty.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonProperty.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonProperty.java
index 327f9e3..9c7d554 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonProperty.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonProperty.java
@@ -21,11 +21,12 @@ package org.apache.johnzon.mapper;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.FIELD;
 import static java.lang.annotation.ElementType.METHOD;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-@Target({ METHOD, FIELD })
+@Target({ METHOD, FIELD, ANNOTATION_TYPE })
 @Retention(RUNTIME)
 public @interface JohnzonProperty {
     /**

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
index e539b10..a5e60d2 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/JohnzonVirtualObjects.java
@@ -22,10 +22,11 @@ import java.lang.annotation.Inherited;
 import java.lang.annotation.Retention;
 import java.lang.annotation.Target;
 
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
 import static java.lang.annotation.ElementType.TYPE;
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
-@Target(TYPE)
+@Target({ TYPE, ANNOTATION_TYPE })
 @Retention(RUNTIME)
 @Inherited
 public @interface JohnzonVirtualObjects {

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
index 81e0b5d..cff4358 100644
--- a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/FieldAccessMode.java
@@ -41,7 +41,7 @@ public class FieldAccessMode extends BaseAccessMode {
         final Map<String, Reader> readers = new HashMap<String, Reader>();
         for (final Map.Entry<String, Field> f : fields(clazz).entrySet()) {
             final String key = f.getKey();
-            if (isIgnored(key) || f.getValue().getAnnotation(JohnzonAny.class) != null) {
+            if (isIgnored(key) || Meta.getAnnotation(f.getValue(), JohnzonAny.class) != null) {
                 continue;
             }
 
@@ -67,7 +67,7 @@ public class FieldAccessMode extends BaseAccessMode {
     }
 
     private String extractKey(final Field f, final String key) {
-        final JohnzonProperty property = f.getAnnotation(JohnzonProperty.class);
+        final JohnzonProperty property = Meta.getAnnotation(f, JohnzonProperty.class);
         return property != null ? property.value() : key;
     }
 
@@ -109,8 +109,8 @@ public class FieldAccessMode extends BaseAccessMode {
         @Override
         public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
             final Class<?> declaringClass = field.getDeclaringClass();
-            final T annotation = declaringClass.getAnnotation(clazz);
-            return annotation == null ? declaringClass.getPackage().getAnnotation(clazz) : annotation;
+            final T annotation = Meta.getAnnotation(declaringClass, clazz);
+            return annotation == null ? Meta.getAnnotation(declaringClass.getPackage(), clazz) : annotation;
         }
 
         @Override
@@ -129,7 +129,7 @@ public class FieldAccessMode extends BaseAccessMode {
 
         @Override
         public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
-            return field.getAnnotation(clazz);
+            return Meta.getAnnotation(field, clazz);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java
new file mode 100644
index 0000000..9e57c09
--- /dev/null
+++ b/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/Meta.java
@@ -0,0 +1,99 @@
+/*
+ * 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 java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Arrays.asList;
+
+public final class Meta {
+    private Meta() {
+        // no-op
+    }
+
+    public static <T extends Annotation> T getAnnotation(final AccessibleObject holder, final Class<T> api) {
+        final T annotation = holder.getAnnotation(api);
+        if (annotation != null) {
+            return annotation;
+        }
+        return findMeta(holder.getAnnotations(), api);
+    }
+
+    public static <T extends Annotation> T getAnnotation(final Class<?> clazz, final Class<T> api) {
+        final T annotation = clazz.getAnnotation(api);
+        if (annotation != null) {
+            return annotation;
+        }
+        return findMeta(clazz.getAnnotations(), api);
+    }
+
+    public static <T extends Annotation> T getAnnotation(final Package pck, final Class<T> api) {
+        final T annotation = pck.getAnnotation(api);
+        if (annotation != null) {
+            return annotation;
+        }
+        return findMeta(pck.getAnnotations(), api);
+    }
+
+    public static <T extends Annotation> T findMeta(final Annotation[] annotations, final Class<T> api) {
+        for (final Annotation a : annotations) {
+            final Class<? extends Annotation> userType = a.annotationType();
+            final T aa = userType.getAnnotation(api);
+            if (aa != null) {
+                boolean overriden = false;
+                final Map<String, Method> mapping = new HashMap<String, Method>();
+                for (final Class<?> cm : asList(api, userType)) {
+                    for (final Method m : cm.getMethods()) {
+                        overriden = mapping.put(m.getName(), m) != null || overriden;
+                    }
+                }
+                if (!overriden) {
+                    return aa;
+                }
+                return api.cast(newAnnotation(mapping, a, aa));
+            }
+        }
+        return null;
+    }
+
+    private static <T extends Annotation> T newAnnotation(final Map<String, Method> methodMapping, final Annotation user, final T johnzon) {
+        return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{johnzon.annotationType()},
+                new InvocationHandler() {
+                    @Override
+                    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
+                        final Method m = methodMapping.get(method.getName());
+                        try {
+                            if (m.getDeclaringClass() == user.annotationType()) {
+                                return m.invoke(user, args);
+                            }
+                            return m.invoke(johnzon, args);
+                        } catch (final InvocationTargetException ite) {
+                            throw ite.getTargetException();
+                        }
+                    }
+                });
+    }
+}

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/main/java/org/apache/johnzon/mapper/access/MethodAccessMode.java
----------------------------------------------------------------------
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 524123b..302b4f4 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
@@ -49,7 +49,7 @@ public class MethodAccessMode extends BaseAccessMode {
         for (final PropertyDescriptor descriptor : propertyDescriptors) {
             final Method readMethod = descriptor.getReadMethod();
             if (readMethod != null && readMethod.getDeclaringClass() != Object.class) {
-                if (isIgnored(descriptor.getName()) || readMethod.getAnnotation(JohnzonAny.class) != null) {
+                if (isIgnored(descriptor.getName()) || Meta.getAnnotation(readMethod, JohnzonAny.class) != null) {
                     continue;
                 }
                 readers.put(extractKey(descriptor), new MethodReader(readMethod, fixType(clazz, readMethod.getGenericReturnType())));
@@ -80,7 +80,7 @@ public class MethodAccessMode extends BaseAccessMode {
     }
 
     private String extractKey(final PropertyDescriptor f) {
-        final JohnzonProperty property = f.getReadMethod() == null ? null : f.getReadMethod().getAnnotation(JohnzonProperty.class);
+        final JohnzonProperty property = f.getReadMethod() == null ? null : Meta.getAnnotation(f.getReadMethod(), JohnzonProperty.class);
         return property != null ? property.value() : f.getName();
     }
 
@@ -113,8 +113,8 @@ public class MethodAccessMode extends BaseAccessMode {
         @Override
         public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) {
             final Class<?> declaringClass = method.getDeclaringClass();
-            final T annotation = declaringClass.getAnnotation(clazz);
-            return annotation == null ? declaringClass.getPackage().getAnnotation(clazz) : annotation;
+            final T annotation = Meta.getAnnotation(declaringClass, clazz);
+            return annotation == null ? Meta.getAnnotation(declaringClass.getPackage(), clazz) : annotation;
         }
 
         @Override
@@ -133,7 +133,7 @@ public class MethodAccessMode extends BaseAccessMode {
 
         @Override
         public <T extends Annotation> T getAnnotation(final Class<T> clazz) {
-            return method.getAnnotation(clazz);
+            return Meta.getAnnotation(method, clazz);
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/johnzon/blob/b28c4baa/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MetaMapperTest.java
----------------------------------------------------------------------
diff --git a/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MetaMapperTest.java b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MetaMapperTest.java
new file mode 100644
index 0000000..ba6c000
--- /dev/null
+++ b/johnzon-mapper/src/test/java/org/apache/johnzon/mapper/MetaMapperTest.java
@@ -0,0 +1,140 @@
+/*
+ * 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 java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.util.Comparator;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class MetaMapperTest {
+    @Test
+    public void customMappingAPI() {
+        final Mapper mapper = new MapperBuilder().setAttributeOrder(new Comparator<String>() {
+            @Override
+            public int compare(final String o1, final String o2) {
+                return o1.compareTo(o2);
+            }
+        }).setAccessModeName("field").build();
+
+        final String expectedJson = "{\"id\":123456,\"name\":\"Johnzon Mapper\",\"overriden\":\"API Rocks\",\"set\":\"yes\"}";
+        final User expectedUser = new User();
+        expectedUser.setId(123456);
+        expectedUser.setName("Johnzon Mapper");
+        expectedUser.setName2("You will not see me");
+        expectedUser.setCustom("API Rocks");
+        expectedUser.setCustom2("yes");
+
+        assertEquals(expectedJson, mapper.writeObjectAsString(expectedUser));
+
+        final User u = mapper.readObject(expectedJson.substring(0, expectedJson.length() - 1) + ",\"name2\":\"should be null\"}", User.class);
+        assertEquals(expectedUser.getId(), u.getId());
+        assertEquals(expectedUser.getName(), u.getName());
+        assertEquals(expectedUser.getCustom(), u.getCustom());
+        assertNull(u.getName2());
+    }
+
+    public static class User {
+        @Id
+        private long id;
+
+        @Name
+        private String name;
+
+        @Custom
+        private String custom;
+
+        @Custom("set")
+        private String custom2;
+
+        @Ignored
+        private String name2;
+
+        public long getId() {
+            return id;
+        }
+
+        public void setId(final long id) {
+            this.id = id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
+        public String getCustom() {
+            return custom;
+        }
+
+        public void setCustom(final String custom) {
+            this.custom = custom;
+        }
+
+        public String getName2() {
+            return name2;
+        }
+
+        public void setName2(final String name2) {
+            this.name2 = name2;
+        }
+
+        public String getCustom2() {
+            return custom2;
+        }
+
+        public void setCustom2(final String custom2) {
+            this.custom2 = custom2;
+        }
+    }
+
+    @Target(FIELD)
+    @Retention(RUNTIME)
+    @JohnzonProperty("id")
+    public @interface Id {
+    }
+
+    @Target(FIELD)
+    @Retention(RUNTIME)
+    @JohnzonIgnore
+    public @interface Ignored {
+    }
+
+    @Target(FIELD)
+    @Retention(RUNTIME)
+    @JohnzonProperty("name")
+    public @interface Name {
+    }
+
+    @Target(FIELD)
+    @Retention(RUNTIME)
+    @JohnzonProperty("ignored")
+    public @interface Custom {
+        String value() default "overriden";
+    }
+}