| // *************************************************************************************************************************** |
| // * 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.juneau; |
| |
| import static org.apache.juneau.internal.ClassUtils.*; |
| import static org.apache.juneau.internal.StringUtils.*; |
| |
| import java.lang.reflect.*; |
| import java.util.*; |
| import java.util.concurrent.*; |
| |
| import org.apache.juneau.annotation.*; |
| |
| /** |
| * A lookup table for resolving bean types by name. |
| * |
| * <p> |
| * In a nutshell, provides a simple mapping of bean class objects to identifying names. |
| * |
| * <p> |
| * Class names are defined through the {@link Bean#typeName() @Bean(typeName)} annotation. |
| * |
| * <p> |
| * The dictionary is used by the framework in the following ways: |
| * <ul> |
| * <li>If a class type cannot be inferred through reflection during parsing, then a helper <js>"_type"</js> is added |
| * to the serialized output using the name defined for that class in this dictionary. This helps determine the |
| * real class at parse time. |
| * <li>The dictionary name is used as element names when serialized to XML. |
| * </ul> |
| */ |
| public class BeanRegistry { |
| |
| private final Map<String,ClassMeta<?>> map; |
| private final Map<Class<?>,String> reverseMap; |
| private final BeanContext beanContext; |
| private final boolean isEmpty; |
| |
| BeanRegistry(BeanContext beanContext, BeanRegistry parent, Class<?>...classes) { |
| this.beanContext = beanContext; |
| this.map = new ConcurrentHashMap<>(); |
| this.reverseMap = new ConcurrentHashMap<>(); |
| for (Class<?> c : beanContext.getBeanDictionaryClasses()) |
| addClass(c); |
| if (parent != null) |
| for (Map.Entry<String,ClassMeta<?>> e : parent.map.entrySet()) |
| addToMap(e.getKey(), e.getValue()); |
| for (Class<?> c : classes) |
| addClass(c); |
| isEmpty = map.isEmpty(); |
| } |
| |
| private void addClass(Class<?> c) { |
| try { |
| if (c != null) { |
| if (isParentClass(Collection.class, c)) { |
| @SuppressWarnings("rawtypes") |
| Collection cc = beanContext.newInstance(Collection.class, c); |
| for (Object o : cc) { |
| if (o instanceof Class) |
| addClass((Class<?>)o); |
| else |
| throw new BeanRuntimeException("Collection class ''{0}'' passed to BeanRegistry does not contain Class objects.", c.getName()); |
| } |
| } else if (isParentClass(Map.class, c)) { |
| Map<?,?> m = beanContext.newInstance(Map.class, c); |
| for (Map.Entry<?,?> e : m.entrySet()) { |
| String typeName = asString(e.getKey()); |
| Object v = e.getValue(); |
| ClassMeta<?> val = null; |
| if (v instanceof Type) |
| val = beanContext.getClassMeta((Type)v); |
| else if (v.getClass().isArray()) |
| val = getTypedClassMeta(v); |
| else |
| throw new BeanRuntimeException("Class ''{0}'' was passed to BeanRegistry but value of type ''{1}'' found in map is not a Type object.", c.getName(), v.getClass().getName()); |
| addToMap(typeName, val); |
| } |
| } else { |
| Bean b = c.getAnnotation(Bean.class); |
| if (b == null || b.typeName().isEmpty()) |
| throw new BeanRuntimeException("Class ''{0}'' was passed to BeanRegistry but it doesn't have a @Bean(typeName) annotation defined.", c.getName()); |
| addToMap(b.typeName(), beanContext.getClassMeta(c)); |
| } |
| } |
| } catch (BeanRuntimeException e) { |
| throw e; |
| } catch (Exception e) { |
| throw new BeanRuntimeException(e); |
| } |
| } |
| |
| private ClassMeta<?> getTypedClassMeta(Object array) { |
| int len = Array.getLength(array); |
| if (len == 0) |
| throw new BeanRuntimeException("Map entry had an empty array value."); |
| Type type = (Type)Array.get(array, 0); |
| Type[] args = new Type[len-1]; |
| for (int i = 1; i < len; i++) |
| args[i-1] = (Type)Array.get(array, i); |
| return beanContext.getClassMeta(type, args); |
| } |
| |
| private void addToMap(String typeName, ClassMeta<?> cm) { |
| map.put(typeName, cm); |
| reverseMap.put(cm.innerClass, typeName); |
| } |
| |
| /** |
| * Gets the class metadata for the specified bean type name. |
| * |
| * @param typeName |
| * The bean type name as defined by {@link Bean#typeName() @Bean(typeName)}. |
| * Can include multi-dimensional array type names (e.g. <js>"X^^"</js>). |
| * @return The class metadata for the bean. |
| */ |
| public ClassMeta<?> getClassMeta(String typeName) { |
| if (isEmpty) |
| return null; |
| if (typeName == null) |
| return null; |
| ClassMeta<?> cm = map.get(typeName); |
| if (cm != null) |
| return cm; |
| if (typeName.charAt(typeName.length()-1) == '^') { |
| cm = getClassMeta(typeName.substring(0, typeName.length()-1)); |
| if (cm != null) { |
| cm = beanContext.getClassMeta(Array.newInstance(cm.innerClass, 1).getClass()); |
| map.put(typeName, cm); |
| } |
| return cm; |
| } |
| return null; |
| } |
| |
| /** |
| * Given the specified class, return the dictionary name for it. |
| * |
| * @param c The class to lookup in this registry. |
| * @return The dictionary name for the specified class in this registry, or <jk>null</jk> if not found. |
| */ |
| public String getTypeName(ClassMeta<?> c) { |
| if (isEmpty) |
| return null; |
| return reverseMap.get(c.innerClass); |
| } |
| |
| /** |
| * Returns <jk>true</jk> if this dictionary has an entry for the specified type name. |
| * |
| * @param typeName The bean type name. |
| * @return <jk>true</jk> if this dictionary has an entry for the specified type name. |
| */ |
| public boolean hasName(String typeName) { |
| return getClassMeta(typeName) != null; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append('{'); |
| for (Map.Entry<String,ClassMeta<?>> e : map.entrySet()) |
| sb.append(e.getKey()).append(":").append(e.getValue().toString(true)).append(", "); |
| sb.append('}'); |
| return sb.toString(); |
| } |
| } |