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 String typeName = null; 093 for (Bean b : ci.getAnnotations(Bean.class, beanContext)) 094 if (! b.typeName().isEmpty()) 095 typeName = b.typeName(); 096 if (typeName == null) 097 throw new BeanRuntimeException("Class ''{0}'' was passed to BeanRegistry but it doesn't have a @Bean(typeName) annotation defined.", c.getName()); 098 addToMap(typeName, beanContext.getClassMeta(c)); 099 } 100 } 101 } catch (BeanRuntimeException e) { 102 throw e; 103 } catch (Exception e) { 104 throw new BeanRuntimeException(e); 105 } 106 } 107 108 private ClassMeta<?> getTypedClassMeta(Object array) { 109 int len = Array.getLength(array); 110 if (len == 0) 111 throw new BeanRuntimeException("Map entry had an empty array value."); 112 Type type = (Type)Array.get(array, 0); 113 Type[] args = new Type[len-1]; 114 for (int i = 1; i < len; i++) 115 args[i-1] = (Type)Array.get(array, i); 116 return beanContext.getClassMeta(type, args); 117 } 118 119 private void addToMap(String typeName, ClassMeta<?> cm) { 120 map.put(typeName, cm); 121 reverseMap.put(cm.innerClass, typeName); 122 } 123 124 /** 125 * Gets the class metadata for the specified bean type name. 126 * 127 * @param typeName 128 * The bean type name as defined by {@link Bean#typeName() @Bean(typeName)}. 129 * Can include multi-dimensional array type names (e.g. <js>"X^^"</js>). 130 * @return The class metadata for the bean. 131 */ 132 public ClassMeta<?> getClassMeta(String typeName) { 133 if (isEmpty) 134 return null; 135 if (typeName == null) 136 return null; 137 ClassMeta<?> cm = map.get(typeName); 138 if (cm != null) 139 return cm; 140 if (typeName.charAt(typeName.length()-1) == '^') { 141 cm = getClassMeta(typeName.substring(0, typeName.length()-1)); 142 if (cm != null) { 143 cm = beanContext.getClassMeta(Array.newInstance(cm.innerClass, 1).getClass()); 144 map.put(typeName, cm); 145 } 146 return cm; 147 } 148 return null; 149 } 150 151 /** 152 * Given the specified class, return the dictionary name for it. 153 * 154 * @param c The class to lookup in this registry. 155 * @return The dictionary name for the specified class in this registry, or <jk>null</jk> if not found. 156 */ 157 public String getTypeName(ClassMeta<?> c) { 158 if (isEmpty) 159 return null; 160 return reverseMap.get(c.innerClass); 161 } 162 163 /** 164 * Returns <jk>true</jk> if this dictionary has an entry for the specified type name. 165 * 166 * @param typeName The bean type name. 167 * @return <jk>true</jk> if this dictionary has an entry for the specified type name. 168 */ 169 public boolean hasName(String typeName) { 170 return getClassMeta(typeName) != null; 171 } 172 173 @Override 174 public String toString() { 175 StringBuilder sb = new StringBuilder(); 176 sb.append('{'); 177 for (Map.Entry<String,ClassMeta<?>> e : map.entrySet()) 178 sb.append(e.getKey()).append(":").append(e.getValue().toString(true)).append(", "); 179 sb.append('}'); 180 return sb.toString(); 181 } 182}