001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau; 014 015import static org.apache.juneau.internal.ClassUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017 018import java.lang.reflect.*; 019import java.util.*; 020import java.util.concurrent.*; 021 022import org.apache.juneau.annotation.*; 023import org.apache.juneau.reflect.*; 024 025/** 026 * A lookup table for resolving bean types by name. 027 * 028 * <p> 029 * In a nutshell, provides a simple mapping of bean class objects to identifying names. 030 * 031 * <p> 032 * Class names are defined through the {@link Bean#typeName() @Bean(typeName)} annotation. 033 * 034 * <p> 035 * The dictionary is used by the framework in the following ways: 036 * <ul> 037 * <li>If a class type cannot be inferred through reflection during parsing, then a helper <js>"_type"</js> is added 038 * to the serialized output using the name defined for that class in this dictionary. This helps determine the 039 * real class at parse time. 040 * <li>The dictionary name is used as element names when serialized to XML. 041 * </ul> 042 */ 043public class BeanRegistry { 044 045 private final Map<String,ClassMeta<?>> map; 046 private final Map<Class<?>,String> reverseMap; 047 private final BeanContext beanContext; 048 private final boolean isEmpty; 049 050 BeanRegistry(BeanContext beanContext, BeanRegistry parent, Class<?>...classes) { 051 this.beanContext = beanContext; 052 this.map = new ConcurrentHashMap<>(); 053 this.reverseMap = new ConcurrentHashMap<>(); 054 for (Class<?> c : beanContext.getBeanDictionaryClasses()) 055 addClass(c); 056 if (parent != null) 057 for (Map.Entry<String,ClassMeta<?>> e : parent.map.entrySet()) 058 addToMap(e.getKey(), e.getValue()); 059 for (Class<?> c : classes) 060 addClass(c); 061 isEmpty = map.isEmpty(); 062 } 063 064 private void addClass(Class<?> c) { 065 try { 066 if (c != null) { 067 ClassInfo ci = ClassInfo.of(c); 068 if (ci.isChildOf(Collection.class)) { 069 @SuppressWarnings("rawtypes") 070 Collection cc = castOrCreate(Collection.class, c); 071 for (Object o : cc) { 072 if (o instanceof Class) 073 addClass((Class<?>)o); 074 else 075 throw new BeanRuntimeException("Collection class ''{0}'' passed to BeanRegistry does not contain Class objects.", c.getName()); 076 } 077 } else if (ci.isChildOf(Map.class)) { 078 Map<?,?> m = castOrCreate(Map.class, c); 079 for (Map.Entry<?,?> e : m.entrySet()) { 080 String typeName = stringify(e.getKey()); 081 Object v = e.getValue(); 082 ClassMeta<?> val = null; 083 if (v instanceof Type) 084 val = beanContext.getClassMeta((Type)v); 085 else if (v.getClass().isArray()) 086 val = getTypedClassMeta(v); 087 else 088 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()); 089 addToMap(typeName, val); 090 } 091 } else { 092 Bean b = ci.getAnnotation(Bean.class, beanContext); 093 if (b == null || b.typeName().isEmpty()) 094 throw new BeanRuntimeException("Class ''{0}'' was passed to BeanRegistry but it doesn't have a @Bean(typeName) annotation defined.", c.getName()); 095 addToMap(b.typeName(), beanContext.getClassMeta(c)); 096 } 097 } 098 } catch (BeanRuntimeException e) { 099 throw e; 100 } catch (Exception e) { 101 throw new BeanRuntimeException(e); 102 } 103 } 104 105 private ClassMeta<?> getTypedClassMeta(Object array) { 106 int len = Array.getLength(array); 107 if (len == 0) 108 throw new BeanRuntimeException("Map entry had an empty array value."); 109 Type type = (Type)Array.get(array, 0); 110 Type[] args = new Type[len-1]; 111 for (int i = 1; i < len; i++) 112 args[i-1] = (Type)Array.get(array, i); 113 return beanContext.getClassMeta(type, args); 114 } 115 116 private void addToMap(String typeName, ClassMeta<?> cm) { 117 map.put(typeName, cm); 118 reverseMap.put(cm.innerClass, typeName); 119 } 120 121 /** 122 * Gets the class metadata for the specified bean type name. 123 * 124 * @param typeName 125 * The bean type name as defined by {@link Bean#typeName() @Bean(typeName)}. 126 * Can include multi-dimensional array type names (e.g. <js>"X^^"</js>). 127 * @return The class metadata for the bean. 128 */ 129 public ClassMeta<?> getClassMeta(String typeName) { 130 if (isEmpty) 131 return null; 132 if (typeName == null) 133 return null; 134 ClassMeta<?> cm = map.get(typeName); 135 if (cm != null) 136 return cm; 137 if (typeName.charAt(typeName.length()-1) == '^') { 138 cm = getClassMeta(typeName.substring(0, typeName.length()-1)); 139 if (cm != null) { 140 cm = beanContext.getClassMeta(Array.newInstance(cm.innerClass, 1).getClass()); 141 map.put(typeName, cm); 142 } 143 return cm; 144 } 145 return null; 146 } 147 148 /** 149 * Given the specified class, return the dictionary name for it. 150 * 151 * @param c The class to lookup in this registry. 152 * @return The dictionary name for the specified class in this registry, or <jk>null</jk> if not found. 153 */ 154 public String getTypeName(ClassMeta<?> c) { 155 if (isEmpty) 156 return null; 157 return reverseMap.get(c.innerClass); 158 } 159 160 /** 161 * Returns <jk>true</jk> if this dictionary has an entry for the specified type name. 162 * 163 * @param typeName The bean type name. 164 * @return <jk>true</jk> if this dictionary has an entry for the specified type name. 165 */ 166 public boolean hasName(String typeName) { 167 return getClassMeta(typeName) != null; 168 } 169 170 @Override 171 public String toString() { 172 StringBuilder sb = new StringBuilder(); 173 sb.append('{'); 174 for (Map.Entry<String,ClassMeta<?>> e : map.entrySet()) 175 sb.append(e.getKey()).append(":").append(e.getValue().toString(true)).append(", "); 176 sb.append('}'); 177 return sb.toString(); 178 } 179}