/* * Copyright (C) 2015 Google Inc. * * 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 interactivespaces.util.data.dynamic; import static java.lang.reflect.Proxy.getInvocationHandler; import static java.lang.reflect.Proxy.isProxyClass; import com.google.common.base.Preconditions; import com.google.common.collect.Maps; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.Map; /** * Dynamic object factory. Given an interface and a map, creates an object with that interface * with state (JavaBean properties) stored in the map. * <p/> * In current implementation calls to {@code hashCode()} and {@code toString()} are delegated to the backing map. * Two dynamic objects are equal if they are of the same type (implement the same interface) * and have the same state (backing maps are equal). * <p/> * Primitive getters will throw an exception if the corresponding value is {@code null}. * One exception is primitive {@code boolean} getter, which in this case will return {@code false}. * * @author Oleksandr Kelepko */ public final class InterfaceMap { /** * Prevent instantiation of the utility class. */ private InterfaceMap() { } /** * Reflectively creates an instance of a given interface, that is backed by a given map. * The interface may contain JavaBean properties (getters and/or setters) of the following types: * <ul> * <li>{@link java.lang.Integer} and {@code int}</li> * <li>{@link java.lang.Long} and {@code long}</li> * <li>{@link java.lang.Float} and {@code float}</li> * <li>{@link java.lang.Double} and {@code double}</li> * <li>{@link java.lang.Boolean} and {@code boolean}</li> * <li>{@link java.lang.String}</li> * <li>another JavaBean interface that meets these requirements</li> * <li>{@link java.util.List} of elements of any of the supported types (no wildcards)</li> * <li>{@link java.util.Map} with {link String} keys and values of any of the supported types (no wildcards)</li> * </ul> * * <p> * List getters return an immutable (possibly transformed) snapshot of a list in the backing map. * * @param interfaceClass * type of the object to create * @param backingMap * backing map, the source of values * @param <T> * type of the object to create * * @return instance of the {@code interfaceClass}, backed by {@code backingMap} * * @throws java.lang.IllegalArgumentException * if {@code interfaceClass} is not an interface * @throws java.lang.NullPointerException * if any argument is {@code null} */ @SuppressWarnings("unchecked") public static <T> T createInstance(Class<T> interfaceClass, Map<String, Object> backingMap) { return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[] { interfaceClass }, new BackingMapInvocationHandler(backingMap, interfaceClass)); } /** * Create a new instance of the given class with an empty backing map. * * @param interfaceClass * class of object to create * @param <T> * type of object * * @return new dynamic object instance with empty backing map */ public static <T> T createInstance(Class<T> interfaceClass) { Map<String, Object> backingMap = Maps.newConcurrentMap(); return createInstance(interfaceClass, backingMap); } /** * Is this a dynamic object? * * @param object * dynamic object to query * * @return {@code true} if this is, in fact, a dynamic object */ public static boolean isDynamicObject(Object object) { if (object == null) { return false; } Class<?> c = object.getClass(); if (isProxyClass(c)) { InvocationHandler handler = getInvocationHandler(object); return handler instanceof BackingMapInvocationHandler; } return false; } /** * What's the type of the given dynamic object? * * @param dynamicObject * dynamic object to query * * @return interface class represented by the dynamic object */ public static Class<?> getClass(Object dynamicObject) { Preconditions.checkArgument(isDynamicObject(dynamicObject), "Object must be DynamicObject"); return dynamicObject.getClass().getInterfaces()[0]; } /** * Get the backing map for the given dynamic object. * * @param dynamicObject * object to get map from * * @return backing map for object */ public static Map<String, Object> getBackingMap(Object dynamicObject) { InvocationHandler handler = getInvocationHandler(dynamicObject); return ((BackingMapInvocationHandler) handler).getBackingMap(); } }