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.common.internal.StringUtils.*; 016import static org.apache.juneau.internal.ClassUtils.*; 017 018import java.lang.reflect.*; 019import java.util.*; 020import java.util.concurrent.*; 021 022import org.apache.juneau.annotation.*; 023import org.apache.juneau.cp.*; 024import org.apache.juneau.reflect.*; 025 026/** 027 * A lookup table for resolving bean types by name. 028 * 029 * <p> 030 * In a nutshell, provides a simple mapping of bean class objects to identifying names. 031 * 032 * <p> 033 * Class names are defined through the {@link Bean#typeName() @Bean(typeName)} annotation. 034 * 035 * <p> 036 * The dictionary is used by the framework in the following ways: 037 * <ul> 038 * <li>If a class type cannot be inferred through reflection during parsing, then a helper <js>"_type"</js> is added 039 * to the serialized output using the name defined for that class in this dictionary. This helps determine the 040 * real class at parse time. 041 * <li>The dictionary name is used as element names when serialized to XML. 042 * </ul> 043 * 044 * <h5 class='section'>See Also:</h5><ul> 045 * </ul> 046 */ 047public class BeanRegistry { 048 049 private final Map<String,ClassMeta<?>> map; 050 private final Map<Class<?>,String> reverseMap; 051 private final BeanContext beanContext; 052 private final boolean isEmpty; 053 054 BeanRegistry(BeanContext beanContext, BeanRegistry parent, Class<?>...classes) { 055 this.beanContext = beanContext; 056 this.map = new ConcurrentHashMap<>(); 057 this.reverseMap = new ConcurrentHashMap<>(); 058 beanContext.getBeanDictionary().forEach(c -> addClass(c)); 059 if (parent != null) 060 parent.map.forEach((k,v) -> addToMap(k, v)); 061 for (Class<?> c : classes) 062 addClass(c); 063 isEmpty = map.isEmpty(); 064 } 065 066 private void addClass(Class<?> c) { 067 try { 068 if (c != null) { 069 ClassInfo ci = ClassInfo.of(c); 070 if (ci.isChildOf(Collection.class)) { 071 Collection<?> cc = BeanCreator.of(Collection.class).type(c).run(); 072 cc.forEach(x -> { 073 if (x instanceof Class) 074 addClass((Class<?>)x); 075 else 076 throw new BeanRuntimeException("Collection class ''{0}'' passed to BeanRegistry does not contain Class objects.", className(c)); 077 }); 078 } else if (ci.isChildOf(Map.class)) { 079 Map<?,?> m = BeanCreator.of(Map.class).type(c).run(); 080 m.forEach((k,v) -> { 081 String typeName = stringify(k); 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.", className(c), className(v)); 089 addToMap(typeName, val); 090 }); 091 } else { 092 Value<String> typeName = Value.empty(); 093 ci.forEachAnnotation(beanContext, Bean.class, x -> isNotEmpty(x.typeName()), x -> typeName.set(x.typeName())); 094 addToMap( 095 typeName.orElseThrow(() -> new BeanRuntimeException("Class ''{0}'' was passed to BeanRegistry but it doesn't have a @Bean(typeName) annotation defined.", className(c))), 096 beanContext.getClassMeta(c) 097 ); 098 } 099 } 100 } catch (BeanRuntimeException e) { 101 throw e; 102 } catch (Exception e) { 103 throw new BeanRuntimeException(e); 104 } 105 } 106 107 private ClassMeta<?> getTypedClassMeta(Object array) { 108 int len = Array.getLength(array); 109 if (len == 0) 110 throw new BeanRuntimeException("Map entry had an empty array value."); 111 Type type = (Type)Array.get(array, 0); 112 Type[] args = new Type[len-1]; 113 for (int i = 1; i < len; i++) 114 args[i-1] = (Type)Array.get(array, i); 115 return beanContext.getClassMeta(type, args); 116 } 117 118 private void addToMap(String typeName, ClassMeta<?> cm) { 119 map.put(typeName, cm); 120 reverseMap.put(cm.innerClass, typeName); 121 } 122 123 /** 124 * Gets the class metadata for the specified bean type name. 125 * 126 * @param typeName 127 * The bean type name as defined by {@link Bean#typeName() @Bean(typeName)}. 128 * Can include multi-dimensional array type names (e.g. <js>"X^^"</js>). 129 * @return The class metadata for the bean. 130 */ 131 public ClassMeta<?> getClassMeta(String typeName) { 132 if (isEmpty || 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 map.forEach((k,v) -> sb.append(k).append(":").append(v.toString(true)).append(", ")); 175 sb.append('}'); 176 return sb.toString(); 177 } 178}