| /* |
| * 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 static org.apache.johnzon.mapper.reflection.Records.isRecord; |
| |
| 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 org.apache.johnzon.mapper.util.BeanUtil; |
| |
| 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; |
| import java.util.Map; |
| |
| // 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; |
| private final boolean alwaysPreferMethodVisibility; |
| private final boolean ignoreVisibilityFilter; |
| |
| public FieldAndMethodAccessMode(final boolean useConstructor, final boolean acceptHiddenConstructor, |
| 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.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); |
| } |
| |
| |
| @Override |
| public Map<String, Reader> doFindReaders(final Class<?> clazz) { |
| final Map<String, Reader> methodReaders = this.methods.findReaders(clazz); |
| final boolean record = isRecord(clazz); |
| if (record) { |
| return methodReaders; |
| } |
| |
| final Map<String, Reader> fieldsReaders = this.fields.findReaders(clazz); |
| final Map<String, Reader> readers = new HashMap<>(); |
| |
| for (final Map.Entry<String, Reader> entry : fieldsReaders.entrySet()) { |
| final String key = entry.getKey(); |
| Method m = record ? |
| getMethod(key, clazz) : |
| getMethod("get" + Character.toUpperCase(key.charAt(0)) + (key.length() > 1 ? key.substring(1) : ""), clazz); |
| if (m == null && !record && (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 && (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) { |
| skip = true; |
| } |
| break; |
| } |
| } |
| } else if (!ignoreVisibilityFilter && m != null) { |
| continue; |
| } |
| 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 = record ? |
| mr.getName() : |
| BeanUtil.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) { |
| if (f != null) { // useful to hold the Field and transient state for example, just as fallback |
| readers.put(entry.getKey(), new CompositeReader( |
| entry.getValue(), new FieldAccessMode.FieldReader(f, f.getType()))); |
| } else { |
| readers.put(entry.getKey(), entry.getValue()); |
| } |
| } else { |
| readers.put(entry.getKey(), new CompositeReader(entry.getValue(), existing)); |
| } |
| } |
| |
| return readers; |
| } |
| |
| 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; |
| } |
| } |
| |
| 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> 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 && (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) { |
| skip = true; |
| } |
| break; |
| } |
| } |
| } else if (!ignoreVisibilityFilter && m != null) { |
| continue; |
| } |
| 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 = BeanUtil.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) { |
| if (f != null) { // useful to hold the Field and transient state for example, just as fallback |
| writers.put(entry.getKey(), new CompositeWriter( |
| entry.getValue(), new FieldAccessMode.FieldWriter(f, f.getType()))); |
| } else { |
| writers.put(entry.getKey(), entry.getValue()); |
| } |
| } else { |
| 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<T extends DecoratedType> implements DecoratedType { |
| protected final T type1; |
| protected final T type2; |
| |
| private CompositeDecoratedType(final T type1, final T type2) { |
| this.type1 = type1; |
| this.type2 = type2; |
| } |
| |
| @Override |
| public <T extends Annotation> T getClassOrPackageAnnotation(final Class<T> clazz) { |
| final T found = type1.getClassOrPackageAnnotation(clazz); |
| return found == null ? type2.getClassOrPackageAnnotation(clazz) : found; |
| } |
| |
| @Override |
| public Adapter<?, ?> findConverter() { |
| final Adapter<?, ?> converter = type1.findConverter(); |
| return converter != null ? converter : type2.findConverter(); |
| } |
| |
| @Override |
| public <T extends Annotation> T getAnnotation(final Class<T> clazz) { |
| final T found = type1.getAnnotation(clazz); |
| return found == null ? type2.getAnnotation(clazz) : found; |
| } |
| |
| @Override |
| public Type getType() { |
| return type1.getType(); |
| } |
| |
| @Override |
| public boolean isNillable(final boolean global) { |
| return type1.isNillable(global) || type2.isNillable(global); |
| } |
| |
| public DecoratedType getType1() { |
| return type1; |
| } |
| |
| public DecoratedType getType2() { |
| return type2; |
| } |
| |
| @Override |
| public String toString() { |
| return "CompositeDecoratedType{" + |
| "type1=" + type1 + |
| ", type2=" + type2 + |
| '}'; |
| } |
| } |
| |
| public static final class CompositeReader extends CompositeDecoratedType<Reader> implements Reader { |
| private CompositeReader(final Reader type1, final Reader type2) { |
| super(type1, type2); |
| } |
| |
| @Override |
| public Object read(final Object instance) { |
| return type1.read(instance); |
| } |
| |
| @Override |
| public ObjectConverter.Writer<?> findObjectConverterWriter() { |
| final ObjectConverter.Writer<?> objectConverter = type2.findObjectConverterWriter(); |
| return objectConverter == null ? type1.findObjectConverterWriter() : objectConverter; |
| } |
| } |
| |
| public static final class CompositeWriter extends CompositeDecoratedType<Writer> implements Writer { |
| private CompositeWriter(final Writer type1, final Writer type2) { |
| super(type1, type2); |
| } |
| |
| @Override |
| public void write(final Object instance, final Object value) { |
| type1.write(instance, value); |
| } |
| |
| @Override |
| public ObjectConverter.Reader<?> findObjectConverterReader() { |
| final ObjectConverter.Reader<?> objectConverter = type2.findObjectConverterReader(); |
| return objectConverter == null ? type1.findObjectConverterReader() : objectConverter; |
| } |
| } |
| } |