/* * Copyright 2000-2016 Vaadin Ltd. * * 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 com.vaadin.client.metadata; import java.util.ArrayList; import java.util.Collection; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArrayString; import com.vaadin.client.FastStringMap; import com.vaadin.client.FastStringSet; import com.vaadin.client.JsArrayObject; import com.vaadin.client.annotations.OnStateChange; import com.vaadin.client.communication.JSONSerializer; import com.vaadin.shared.annotations.NoLayout; public class TypeDataStore { public static enum MethodAttribute { DELAYED, LAST_ONLY, NO_LAYOUT, NO_LOADING_INDICATOR; } private static final String CONSTRUCTOR_NAME = "!new"; private final FastStringMap> identifiers = FastStringMap.create(); private final FastStringMap serializerFactories = FastStringMap .create(); private final FastStringMap proxyHandlers = FastStringMap .create(); private final FastStringMap delegateToWidgetProperties = FastStringMap .create(); private final FastStringMap presentationTypes = FastStringMap .create(); /** * Maps connector class -> state property name -> hander method data */ private final FastStringMap>> onStateChangeMethods = FastStringMap .create(); private final FastStringMap methodAttributes = FastStringMap .create(); private final FastStringMap returnTypes = FastStringMap.create(); private final FastStringMap invokers = FastStringMap.create(); private final FastStringMap paramTypes = FastStringMap.create(); private final FastStringMap delegateToWidget = FastStringMap .create(); private final JavaScriptObject jsTypeData = JavaScriptObject.createObject(); public static TypeDataStore get() { return ConnectorBundleLoader.get().getTypeDataStore(); } public void setClass(String identifier, Class type) { identifiers.put(identifier, type); } public static Class getClass(String identifier) throws NoDataException { Class class1 = get().identifiers.get(identifier); if (class1 == null) { throw new NoDataException( "There is not class for identifier " + identifier); } return class1; } // this is a very inefficient implementation for getting all the identifiers // for a class public FastStringSet findIdentifiersFor(Class type) { FastStringSet result = FastStringSet.create(); JsArrayString keys = identifiers.getKeys(); for (int i = 0; i < keys.length(); i++) { String key = keys.get(i); if (identifiers.get(key) == type) { result.add(key); } } return result; } public static Type getType(Class clazz) { return new Type(clazz); } public static Type getReturnType(Method method) throws NoDataException { Type type = get().returnTypes.get(method.getLookupKey()); if (type == null) { throw new NoDataException( "There is no return type for " + method.getSignature()); } return type; } public static Invoker getInvoker(Method method) throws NoDataException { Invoker invoker = get().invokers.get(method.getLookupKey()); if (invoker == null) { throw new NoDataException( "There is no invoker for " + method.getSignature()); } return invoker; } public static Invoker getConstructor(Type type) throws NoDataException { Invoker invoker = get().invokers .get(new Method(type, CONSTRUCTOR_NAME).getLookupKey()); if (invoker == null) { throw new NoDataException( "There is no constructor for " + type.getSignature()); } return invoker; } public static Object getValue(Property property, Object target) throws NoDataException { return getJsPropertyValue(get().jsTypeData, property.getBeanType().getBaseTypeName(), property.getName(), target); } public static String getDelegateToWidget(Property property) { return get().delegateToWidget.get(property.getLookupKey()); } public static JsArrayString getDelegateToWidgetProperites(Type type) { return get().delegateToWidgetProperties.get(type.getBaseTypeName()); } public static Type getPresentationType(Class type) { return get().presentationTypes.get(getType(type).getBaseTypeName()); } public void setDelegateToWidget(Class clazz, String propertyName, String delegateValue) { Type type = getType(clazz); delegateToWidget.put(new Property(type, propertyName).getLookupKey(), delegateValue); JsArrayString typeProperties = delegateToWidgetProperties .get(type.getBaseTypeName()); if (typeProperties == null) { typeProperties = JavaScriptObject.createArray().cast(); delegateToWidgetProperties.put(type.getBaseTypeName(), typeProperties); } typeProperties.push(propertyName); } public void setPresentationType(Class type, Class presentationType) { presentationTypes.put(getType(type).getBaseTypeName(), getType(presentationType)); } public void setReturnType(Class type, String methodName, Type returnType) { returnTypes.put(new Method(getType(type), methodName).getLookupKey(), returnType); } public void setConstructor(Class type, Invoker constructor) { setInvoker(type, CONSTRUCTOR_NAME, constructor); } public void setInvoker(Class type, String methodName, Invoker invoker) { invokers.put(new Method(getType(type), methodName).getLookupKey(), invoker); } public static Type[] getParamTypes(Method method) throws NoDataException { Type[] types = get().paramTypes.get(method.getLookupKey()); if (types == null) { throw new NoDataException("There are no parameter type data for " + method.getSignature()); } return types; } public void setParamTypes(Class type, String methodName, Type[] paramTypes) { this.paramTypes.put( new Method(getType(type), methodName).getLookupKey(), paramTypes); } public static boolean hasIdentifier(String identifier) { return get().identifiers.containsKey(identifier); } public static ProxyHandler getProxyHandler(Type type) throws NoDataException { ProxyHandler proxyHandler = get().proxyHandlers .get(type.getBaseTypeName()); if (proxyHandler == null) { throw new NoDataException( "No proxy handler for " + type.getSignature()); } return proxyHandler; } public void setProxyHandler(Class type, ProxyHandler proxyHandler) { proxyHandlers.put(getType(type).getBaseTypeName(), proxyHandler); } public static boolean isDelayed(Method method) { return hasMethodAttribute(method, MethodAttribute.DELAYED); } public static boolean isNoLoadingIndicator(Method method) { return hasMethodAttribute(method, MethodAttribute.NO_LOADING_INDICATOR); } private static boolean hasMethodAttribute(Method method, MethodAttribute attribute) { FastStringSet attributes = get().methodAttributes .get(method.getLookupKey()); return attributes != null && attributes.contains(attribute.name()); } public void setMethodAttribute(Class type, String methodName, MethodAttribute attribute) { String key = getType(type).getMethod(methodName).getLookupKey(); FastStringSet attributes = methodAttributes.get(key); if (attributes == null) { attributes = FastStringSet.create(); methodAttributes.put(key, attributes); } attributes.add(attribute.name()); } public static boolean isLastOnly(Method method) { return hasMethodAttribute(method, MethodAttribute.LAST_ONLY); } /** * @param type * @return * @throws NoDataException * * @deprecated As of 7.0.1, use {@link #getPropertiesAsArray(Type)} instead * for improved performance */ @Deprecated public static Collection getProperties(Type type) throws NoDataException { JsArrayObject propertiesArray = getPropertiesAsArray(type); int size = propertiesArray.size(); ArrayList properties = new ArrayList<>(size); for (int i = 0; i < size; i++) { properties.add(propertiesArray.get(i)); } return properties; } public static JsArrayObject getPropertiesAsArray(Type type) throws NoDataException { JsArrayString names = getJsPropertyNames(get().jsTypeData, type.getBaseTypeName()); // Create Property instances for each property name JsArrayObject properties = JavaScriptObject.createArray() .cast(); for (int i = 0; i < names.length(); i++) { properties.add(new Property(type, names.get(i))); } return properties; } public static Type getType(Property property) throws NoDataException { return getJsPropertyType(get().jsTypeData, property.getBeanType().getBaseTypeName(), property.getName()); } public void setPropertyType(Class clazz, String propertyName, Type type) { setJsPropertyType(jsTypeData, clazz.getName(), propertyName, type); } public static void setValue(Property property, Object target, Object value) { setJsPropertyValue(get().jsTypeData, property.getBeanType().getBaseTypeName(), property.getName(), target, value); } public void setSerializerFactory(Class clazz, Invoker factory) { serializerFactories.put(getType(clazz).getBaseTypeName(), factory); } public static JSONSerializer findSerializer(Type type) { Invoker factoryCreator = get().serializerFactories .get(type.getBaseTypeName()); if (factoryCreator == null) { return null; } return (JSONSerializer) factoryCreator.invoke(null); } public static boolean hasProperties(Type type) { return hasJsProperties(get().jsTypeData, type.getBaseTypeName()); } public void setSuperClass(Class baseClass, Class superClass) { String superClassName = superClass == null ? null : superClass.getName(); setSuperClass(jsTypeData, baseClass.getName(), superClassName); } public void setPropertyData(Class type, String propertyName, JavaScriptObject propertyData) { setPropertyData(jsTypeData, type.getName(), propertyName, propertyData); } private static native void setPropertyData(JavaScriptObject typeData, String className, String propertyName, JavaScriptObject propertyData) /*-{ typeData[className][propertyName] = propertyData; }-*/; /* * This method sets up prototypes chain for baseClassName. * Precondition is : superClassName had to be handled before * its child baseClassName. * * It makes all properties defined in the superClassName * available for baseClassName as well. */ private static native void setSuperClass(JavaScriptObject typeData, String baseClassName, String superClassName) /*-{ var parentType = typeData[superClassName]; if (parentType !== undefined ){ var ctor = function () {}; ctor.prototype = parentType; typeData[baseClassName] = new ctor; } else { typeData[baseClassName] = {}; } }-*/; private static native boolean hasGetter(JavaScriptObject typeData, String beanName, String propertyName) /*-{ return typeData[beanName][propertyName].getter !== undefined; }-*/; private static native boolean hasSetter(JavaScriptObject typeData, String beanName, String propertyName) /*-{ return typeData[beanName][propertyName].setter !== undefined; }-*/; private static native boolean hasNoLayout(JavaScriptObject typeData, String beanName, String propertyName) /*-{ // Data is not available for Javascript state object properties as GWT knows nothing about them return typeData[beanName][propertyName] && typeData[beanName][propertyName].noLayout !== undefined; }-*/; private static native Object getJsPropertyValue(JavaScriptObject typeData, String beanName, String propertyName, Object beanInstance) /*-{ return typeData[beanName][propertyName].getter(beanInstance); }-*/; private static native void setJsPropertyValue(JavaScriptObject typeData, String beanName, String propertyName, Object beanInstance, Object value) /*-{ typeData[beanName][propertyName].setter(beanInstance, value); }-*/; private static native Type getJsPropertyType(JavaScriptObject typeData, String beanName, String propertyName) /*-{ return typeData[beanName][propertyName].type; }-*/; private static native void setJsPropertyType(JavaScriptObject typeData, String beanName, String propertyName, Type type) /*-{ typeData[beanName][propertyName].type = type; }-*/; private static native JsArrayString getJsPropertyNames( JavaScriptObject typeData, String beanName) /*-{ var names = []; for(var name in typeData[beanName]) { names.push(name); } return names; }-*/; private static native boolean hasJsProperties(JavaScriptObject typeData, String beanName) /*-{ return typeData[beanName] !== undefined ; }-*/; /** * Gets data for all methods annotated with {@link OnStateChange} in the * given connector type. * * @since 7.2 * @param type * the connector type * @return a map of state property names to handler method data */ public static FastStringMap> getOnStateChangeMethods( Class type) { return get().onStateChangeMethods.get(getType(type).getSignature()); } /** * Adds data about a method annotated with {@link OnStateChange} for the * given connector type. * * @since 7.2 * @param clazz * the connector type * @param method * the state change method data */ public void addOnStateChangeMethod(Class clazz, OnStateChangeMethod method) { FastStringMap> handlers = getOnStateChangeMethods( clazz); if (handlers == null) { handlers = FastStringMap.create(); onStateChangeMethods.put(getType(clazz).getSignature(), handlers); } for (String property : method.getProperties()) { JsArrayObject propertyHandlers = handlers .get(property); if (propertyHandlers == null) { propertyHandlers = JsArrayObject.createArray().cast(); handlers.put(property, propertyHandlers); } propertyHandlers.add(method); } } /** * Checks whether the provided method is annotated with {@link NoLayout}. * * @param method * the rpc method to check * * @since 7.4 * * @return true if the method has a NoLayout annotation; * otherwise false */ public static boolean isNoLayoutRpcMethod(Method method) { return hasMethodAttribute(method, MethodAttribute.NO_LAYOUT); } /** * Checks whether the provided property is annotated with {@link NoLayout}. * * @param property * the property to check * * @since 7.4 * * @return true if the property has a NoLayout annotation; * otherwise false */ public static boolean isNoLayoutProperty(Property property) { return hasNoLayout(get().jsTypeData, property.getBeanType().getSignature(), property.getName()); } }