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