All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.amazonaws.services.dynamodbv2.datamodeling.StandardBeanProperties Maven / Gradle / Ivy

Go to download

The AWS Java SDK for Amazon DynamoDB module holds the client classes that are used for communicating with Amazon DynamoDB Service

There is a newer version: 1.9.11
Show newest version
/*
 * Copyright 2016-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed 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://aws.amazon.com/apache2.0
 *
 * This file 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 com.amazonaws.services.dynamodbv2.datamodeling;

import com.amazonaws.annotation.SdkInternalApi;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.Id;
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapperFieldModel.Reflect;
import com.amazonaws.services.dynamodbv2.datamodeling.StandardAnnotationMaps.FieldMap;
import com.amazonaws.services.dynamodbv2.datamodeling.StandardParameterTypes.ParamType;
import com.amazonaws.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Reflection assistant for {@link DynamoDBMapper}
 */
@SdkInternalApi
final class StandardBeanProperties {

    /**
     * Gets the bean properties for a given class.
     * @param clazz The class.
     * @return The bean properties.
     */
    static final  Map> of(final Class clazz) {
        return ((Beans)Beans.CACHE).of(clazz);
    }

    /**
     * Cache of {@link Bean} mappings by class type.
     */
    static final class Beans {
        private static final Beans CACHE = new Beans();
        private final ConcurrentMap,Map>> cache = new ConcurrentHashMap,Map>>();
        public final Map> of(final Class clazz) {
            if (!cache.containsKey(clazz)) {
                cache.putIfAbsent(clazz, new Builder(clazz, false).build());
            }
            return cache.get(clazz);
        }
    }

    /**
     * Holds the reflection bean properties for a given property.
     */
    static final class Bean implements Reflect {
        private final MethodReflect reflect;
        private final FieldMap annotations;
        private final ParamType type;
        private final Id id;

        /**
         * Constructs an object property mapping for the specified method.
         * @param reflect The reflection property.
         * @param annotations The annotations.
         * @param id The field identifier.
         */
        private Bean(final MethodReflect reflect, final FieldMap annotations, final Id id) {
            this.type = StandardParameterTypes.of(reflect.getter.getGenericReturnType());
            this.annotations = annotations;
            this.reflect = reflect;
            this.id = id;
        }

        /**
         * Gets the ID.
         * @return The ID.
         */
        final Id id() {
            return this.id;
        }

        /**
         * Gets the annotations.
         * @return The annotations.
         */
        final FieldMap annotations() {
            return this.annotations;
        }

        /**
         * Gets the parameter type.
         * @return The parameter type.
         */
        final ParamType type() {
            return this.type;
        }

        /**
         * Gets the property's value type.
         * @return The value type.
         */
        final Reflect reflect() {
            return this.reflect;
        }

        /**
         * Gets the getter method for this property.
         * @return The getter method.
         */
        final Method getter() {
            return reflect.getter;
        }

        /**
         * Gets the setter method for this property.
         * @return The setter method.
         */
        final Method setter() {
            if (reflect.setter == null) {
                throw new DynamoDBMappingException("no access to public/one-argument setter for " + reflect.getter);
            }
            return reflect.setter;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public V get(final T object) {
            return reflect.get(object);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void set(final T object, final V value) {
            reflect.set(object, value);
        }
    }

    /**
     * Get/set reflection operations.
     */
    private static class MethodReflect implements Reflect {
        private final Method getter, setter;

        /**
         * Constructs a new method reflection property.
         * @param getter The getter method.
         */
        private MethodReflect(final Method getter) {
            this.setter = declaredSetterOf(getter);
            this.getter = getter;
        }

        /**
         * Gets the target type.
         * @return The target type.
         */
        public final Class targetType() {
            return (Class)getter.getReturnType();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public V get(final T object) {
            try {
                return (V)getter.invoke(object);
            } catch (final Exception e) {
                throw new DynamoDBMappingException("could not invoke " + getter + " on " + object.getClass(), e);
            }
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void set(T object, final V value) {
            try {
                setter.invoke(object, value);
            } catch (final Exception e) {
                throw new DynamoDBMappingException("could not invoke " + setter + " on " + object.getClass(), e);
            }
        }
    }

    /**
     * Get/set reflection operations with a declaring property.
     */
    private static final class DeclaringMethodReflect extends MethodReflect {
        private final MethodReflect declaring;

        /**
         * Constructs a new declaring method reflection property.
         * @param getter The getter method.
         * @param declaring The declaring reflection property.
         */
        private DeclaringMethodReflect(final Method getter, final MethodReflect declaring) {
            super(getter);
            this.declaring = declaring;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final V get(final T object) {
            final T declaringObject = declaring.get(object);
            if (declaringObject == null) {
                return null;
            }
            return super.get(declaringObject);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public final void set(final T object, final V value) {
            T declaringObject = declaring.get(object);
            if (declaringObject == null) {
                try {
                    declaringObject = declaring.targetType().newInstance();
                } catch (final Exception e) {
                    throw new DynamoDBMappingException("could not instantiate " + declaring.targetType(), e);
                }
                declaring.set(object, declaringObject);
            }
            super.set(declaringObject, value);
        }
    }

    /**
     * {@link Bean} properties builder.
     */
    static final class Builder {
        private final Map> map = new LinkedHashMap>();
        private final Class targetType;
        private final boolean inherited;

        /**
         * Constructs a new builder instance.
         * @param targetType The object type.
         * @param inherited Indicates if we should ignore table/document restrictions.
         */
        Builder(final Class targetType, final boolean inherited) {
            this.targetType = targetType;
            this.inherited = inherited;
        }

        /**
         * Builds the bean properties mapping.
         * @return The built bean properties mapping.
         */
        public Map> build() {
            for (final Method m : this.targetType.getMethods()) {
                if (!isGetter(m)) {
                    continue;
                } else if (!inherited && m.getDeclaringClass() != this.targetType) {
                    if (!StandardAnnotationMaps.of(m.getDeclaringClass()).typed()) {
                        continue;
                    }
                }
                flatten(new MethodReflect(m), null);
            }
            return Collections.unmodifiableMap(this.map);
        }

        /**
         * Flattens or adds the bean to the mapping.
         * @param bean The bean property.
         * @param name The attribute name override.
         */
        private void flatten(final MethodReflect reflect, String name) {
            final FieldMap annotations = StandardAnnotationMaps.of(reflect.getter);
            if (annotations.ignore() != null) {
                return;
            }
            final Id id = new Id(this.targetType, name == null ? annotations.attributeName() : name);
            if (annotations.flattened() == null) {
                if (this.map.put(id.name(), new Bean(reflect, annotations, id)) != null) {
                    throw new DynamoDBMappingException(id.format("duplicate attribute name"));
                }
            } else {
                final Map attributes = annotations.attributes();
                for (final Method m : reflect.targetType().getMethods()) {
                    if (isGetter(m) && (name = attributes.remove(nameOf(m, null))) != null) {
                        flatten(new DeclaringMethodReflect(m, (MethodReflect)reflect), name);
                    }
                }
                if (!attributes.isEmpty()) { //<- this should be empty by now
                    throw new DynamoDBMappingException(id.format("contains unknown flattened attribute(s): " + attributes));
                }
            }
        }
    }

    /**
     * Returns true if the method is a valid getter property.
     * @param method The getter method.
     * @return True if a getter method, false otherwise.
     */
    static final boolean isGetter(final Method method) {
        if (!method.getName().startsWith("get") && !method.getName().startsWith("is")) {
            return false;
        } else if (method.getParameterTypes().length != 0) {
            return false;
        } else if (method.getReturnType() == Void.TYPE) {
            return false;
        } else if (method.isBridge()) {
            return false;
        } else if (method.isSynthetic()) {
            return false;
        } else if (method.getDeclaringClass() == Object.class) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Gets the property name of the specified getter method.
     * @param getter The getter method.
     * @param prefix An optional prefix to apply (for setter method).
     * @return The property name.
     */
    static final String nameOf(final Method getter, final String prefix) {
        String name = getter.getName();
        name = name.substring(name.startsWith("is") ? "is".length() : "get".length());
        if (name.length() == 0) {
            throw new DynamoDBMappingException("getter must begin with 'get' or 'is', and contain at least one character: " + getter);
        } else if (prefix == null) {
            return StringUtils.lowerCase(name.substring(0, 1)) + name.substring(1);
        } else {
            return prefix + name;
        }
    }

    /**
     * Gets the declared field from the getter method.
     * @param getter The getter method.
     * @return The field, or null if none.
     */
    static final Field declaredFieldOf(final Method getter) {
        final String name = nameOf(getter, null);
        try {
            return getter.getDeclaringClass().getDeclaredField(name);
        } catch (final SecurityException e) {
            throw new DynamoDBMappingException("no access to field " + name + " for " + getter, e);
        } catch (final NoSuchFieldException no) {}
        return null;
    }

    /**
     * Gets the declared setter from the getter method.
     * @param getter The getter method.
     * @return The setter, or null if none.
     */
    static final Method declaredSetterOf(final Method getter) {
        final String name = nameOf(getter, "set");
        try {
            return getter.getDeclaringClass().getMethod(name, getter.getReturnType());
        } catch (final Exception no) {}
        return null;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy