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

org.httprpc.beans.BeanAdapter Maven / Gradle / Ivy

There is a newer version: 9.5
Show newest version
/*
 * 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://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.httprpc.beans;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Class that presents the properties of a Java Bean object as a map.
 */
public class BeanAdapter extends AbstractMap {
    // List adapter
    private static class ListAdapter extends AbstractList {
        private List list;
        private HashMap, HashMap> accessorCache;

        public ListAdapter(List list, HashMap, HashMap> accessorCache) {
            this.list = list;
            this.accessorCache = accessorCache;
        }

        @Override
        public Object get(int index) {
            return adapt(list.get(index), accessorCache);
        }

        @Override
        public int size() {
            return list.size();
        }

        @Override
        public Iterator iterator() {
            return new Iterator() {
                private Iterator iterator = list.iterator();

                @Override
                public boolean hasNext() {
                    return iterator.hasNext();
                }

                @Override
                public Object next() {
                    return adapt(iterator.next(), accessorCache);
                }
            };
        }
    }

    // Map adapter
    private static class MapAdapter extends AbstractMap {
        private Map map;
        private HashMap, HashMap> accessorCache;

        public MapAdapter(Map map, HashMap, HashMap> accessorCache) {
            this.map = map;
            this.accessorCache = accessorCache;
        }

        @Override
        public Object get(Object key) {
            return adapt(map.get(key), accessorCache);
        }

        @Override
        public Set> entrySet() {
            return new AbstractSet>() {
                @Override
                public int size() {
                    return map.size();
                }

                @Override
                public Iterator> iterator() {
                    return new Iterator>() {
                        private Iterator> iterator = map.entrySet().iterator();

                        @Override
                        public boolean hasNext() {
                            return iterator.hasNext();
                        }

                        @Override
                        public Entry next() {
                            return new Entry() {
                                private Entry entry = iterator.next();

                                @Override
                                public Object getKey() {
                                    return entry.getKey();
                                }

                                @Override
                                public Object getValue() {
                                    return adapt(entry.getValue(), accessorCache);
                                }

                                @Override
                                public Object setValue(Object value) {
                                    throw new UnsupportedOperationException();
                                }
                            };
                        }
                    };
                }
            };
        }
    }

    private Object bean;
    private HashMap, HashMap> accessorCache;

    private HashMap accessors;

    private static final String GET_PREFIX = "get";
    private static final String IS_PREFIX = "is";

    /**
     * Constructs a new Bean adapter.
     *
     * @param bean
     * The source Bean.
     */
    public BeanAdapter(Object bean) {
        this(bean, new HashMap<>());
    }

    private BeanAdapter(Object bean, HashMap, HashMap> accessorCache) {
        if (bean == null) {
            throw new IllegalArgumentException();
        }

        this.bean = bean;
        this.accessorCache = accessorCache;

        Class type = bean.getClass();

        if (accessors == null) {
            accessors = new HashMap<>();

            Method[] methods = type.getMethods();

            for (int i = 0; i < methods.length; i++) {
                Method method = methods[i];

                if (method.getDeclaringClass() != Object.class) {
                    String methodName = method.getName();

                    String prefix;
                    if (methodName.startsWith(GET_PREFIX)) {
                        prefix = GET_PREFIX;
                    } else if (methodName.startsWith(IS_PREFIX)) {
                        prefix = IS_PREFIX;
                    } else {
                        prefix = null;
                    }

                    if (prefix != null)  {
                        int j = prefix.length();
                        int n = methodName.length();

                        if (j < n && method.getParameterCount() == 0) {
                            char c = methodName.charAt(j++);

                            if (j == n || Character.isLowerCase(methodName.charAt(j))) {
                                c = Character.toLowerCase(c);
                            }

                            String key = c + methodName.substring(j);

                            accessors.put(key, method);
                        }
                    }
                }
            }

            accessorCache.put(type, accessors);
        }
    }

    /**
     * Retrieves a Bean property value. If the value is null or an
     * instance of one of the following types, it is returned as-is:
     * 
    *
  • {@link String}
  • *
  • {@link Number}
  • *
  • {@link Boolean}
  • *
  • {@link Date}
  • *
  • {@link LocalDate}
  • *
  • {@link LocalTime}
  • *
  • {@link LocalDateTime}
  • *
* If the value is a {@link List}, it is wrapped in an adapter that will * adapt the list's elements. If the value is a {@link Map}, it is wrapped * in an adapter that will adapt the map's values. Otherwise, the value is * considered a nested Bean and is wrapped in a {@link BeanAdapter}. * * @param key * The property name. * * @return * The property value. */ @Override public Object get(Object key) { if (key == null) { throw new IllegalArgumentException(); } Method method = accessors.get(key); Object value; if (method != null) { try { value = adapt(method.invoke(bean), accessorCache); } catch (InvocationTargetException | IllegalAccessException exception) { throw new RuntimeException(exception); } } else { value = null; } return value; } @Override public Set> entrySet() { return new AbstractSet>() { @Override public int size() { return accessors.size(); } @Override public Iterator> iterator() { return new Iterator>() { private Iterator keys = accessors.keySet().iterator(); @Override public boolean hasNext() { return keys.hasNext(); } @Override public Entry next() { String key = keys.next(); return new SimpleImmutableEntry<>(key, get(key)); } }; } }; } /** * Adapts a list instance. * * @param list * The list to adapt. * * @return * An adapter that will adapt the list's elements. */ public static List adapt(List list) { return new ListAdapter(list, new HashMap<>()); } /** * Adapts a map instance. * * @param map * The map to adapt. * * @return * An adapter that will adapt the map's values. */ public static Map adapt(Map map) { return new MapAdapter(map, new HashMap<>()); } @SuppressWarnings("unchecked") private static Object adapt(Object value, HashMap, HashMap> accessorCache) { if (!(value == null || value instanceof String || value instanceof Number || value instanceof Boolean || value instanceof Date || value instanceof LocalDate || value instanceof LocalTime || value instanceof LocalDateTime)) { if (value instanceof List) { value = new ListAdapter((List)value, accessorCache); } else if (value instanceof Map) { value = new MapAdapter((Map)value, accessorCache); } else { value = new BeanAdapter(value, accessorCache); } } return value; } }