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.commons.reflect.ReflectionUtils.*; 020import static org.apache.juneau.commons.utils.AssertionUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.ThrowableUtils.*; 023import static org.apache.juneau.commons.utils.Utils.*; 024 025import java.lang.reflect.*; 026import java.util.*; 027import java.util.concurrent.*; 028 029import org.apache.juneau.annotation.*; 030import org.apache.juneau.commons.collections.*; 031import org.apache.juneau.commons.reflect.*; 032import org.apache.juneau.commons.utils.*; 033import org.apache.juneau.cp.*; 034 035/** 036 * A lookup table for resolving bean types by name. 037 * 038 * <p> 039 * In a nutshell, provides a simple mapping of bean class objects to identifying names. 040 * 041 * <p> 042 * Class names are defined through the {@link Bean#typeName() @Bean(typeName)} annotation. 043 * 044 * <p> 045 * The dictionary is used by the framework in the following ways: 046 * <ul> 047 * <li>If a class type cannot be inferred through reflection during parsing, then a helper <js>"_type"</js> is added 048 * to the serialized output using the name defined for that class in this dictionary. This helps determine the 049 * real class at parse time. 050 * <li>The dictionary name is used as element names when serialized to XML. 051 * </ul> 052 * 053 */ 054public class BeanRegistry { 055 056 private final Map<String,ClassMeta<?>> map; 057 private final Map<Class<?>,String> reverseMap; 058 private final BeanContext bc; 059 private final AnnotationProvider ap; 060 private final boolean isEmpty; 061 062 BeanRegistry(BeanContext bc, BeanRegistry parent, List<ClassInfo> classes) { 063 assertArgNotNull("bc", bc); 064 assertArgNotNull("classes", classes); 065 this.bc = bc; 066 this.ap = bc.getAnnotationProvider(); 067 this.map = new ConcurrentHashMap<>(); 068 this.reverseMap = new ConcurrentHashMap<>(); 069 bc.getBeanDictionary().forEach(this::addClass); 070 if (nn(parent)) 071 parent.map.forEach(this::addToMap); 072 classes.forEach(this::addClass); 073 isEmpty = map.isEmpty(); 074 } 075 076 /** 077 * Gets the class metadata for the specified bean type name. 078 * 079 * @param typeName 080 * The bean type name as defined by {@link Bean#typeName() @Bean(typeName)}. 081 * Can include multi-dimensional array type names (e.g. <js>"X^^"</js>). 082 * @return The class metadata for the bean. 083 */ 084 public ClassMeta<?> getClassMeta(String typeName) { 085 if (isEmpty || typeName == null) 086 return null; 087 var cm = map.get(typeName); 088 if (nn(cm)) 089 return cm; 090 if (typeName.charAt(typeName.length() - 1) == '^') { 091 cm = getClassMeta(typeName.substring(0, typeName.length() - 1)); 092 if (nn(cm)) { 093 cm = bc.getClassMeta(Array.newInstance(cm.inner(), 1).getClass()); 094 map.put(typeName, cm); 095 } 096 return cm; 097 } 098 return null; 099 } 100 101 /** 102 * Given the specified class, return the dictionary name for it. 103 * 104 * @param c The class to lookup in this registry. 105 * @return The dictionary name for the specified class in this registry, or <jk>null</jk> if not found. 106 */ 107 public String getTypeName(ClassMeta<?> c) { 108 return isEmpty ? null : reverseMap.get(c.inner()); 109 } 110 111 /** 112 * Returns <jk>true</jk> if this dictionary has an entry for the specified type name. 113 * 114 * @param typeName The bean type name. 115 * @return <jk>true</jk> if this dictionary has an entry for the specified type name. 116 */ 117 public boolean hasName(String typeName) { 118 return nn(getClassMeta(typeName)); 119 } 120 121 protected FluentMap<String,Object> properties() { 122 var m = filteredBeanPropertyMap(); 123 map.forEach((k, v) -> m.a(k, v.toString(true))); 124 return m; 125 } 126 127 @Override 128 public String toString() { 129 return r(properties()); 130 } 131 132 private void addClass(ClassInfo ci) { 133 try { 134 if (nn(ci) && nn(ci.inner())) { 135 if (ci.isChildOf(Collection.class)) { 136 Collection<?> cc = BeanCreator.of(Collection.class).type(ci).run(); 137 cc.forEach(x -> { 138 if (x instanceof Class<?> x2) 139 addClass(info(x2)); 140 else 141 throw bex("Collection class ''{0}'' passed to BeanRegistry does not contain Class objects.", ci.getName()); 142 }); 143 } else if (ci.isChildOf(Map.class)) { 144 Map<?,?> m = BeanCreator.of(Map.class).type(ci).run(); 145 m.forEach((k, v) -> { 146 var typeName = s(k); 147 var val = (ClassMeta<?>)null; 148 if (v instanceof Type v2) 149 val = bc.getClassMeta(v2); 150 else if (isArray(v)) 151 val = getTypedClassMeta(v); 152 else 153 throw bex("Class ''{0}'' was passed to BeanRegistry but value of type ''{1}'' found in map is not a Type object.", ci.getName(), cn(v)); 154 addToMap(typeName, val); 155 }); 156 } else { 157 // @formatter:off 158 var typeName = ap.find(Bean.class, ci) 159 .stream() 160 .map(x -> x.inner().typeName()) 161 .filter(Utils::ne) 162 .findFirst() 163 .orElseThrow(() -> bex("Class ''{0}'' was passed to BeanRegistry but it doesn't have a @Bean(typeName) annotation defined.", ci.getName())); 164 // @formatter:on 165 addToMap(typeName, bc.getClassMeta(ci.inner())); 166 } 167 } 168 } catch (BeanRuntimeException e) { 169 throw e; 170 } catch (Exception e) { 171 throw bex(e); 172 } 173 } 174 175 private void addToMap(String typeName, ClassMeta<?> cm) { 176 map.put(typeName, cm); 177 reverseMap.put(cm.inner(), typeName); 178 } 179 180 private ClassMeta<?> getTypedClassMeta(Object array) { 181 var len = Array.getLength(array); 182 if (len == 0) 183 throw bex("Map entry had an empty array value."); 184 var type = (Type)Array.get(array, 0); 185 var args = new Type[len - 1]; 186 for (var i = 1; i < len; i++) 187 args[i - 1] = (Type)Array.get(array, i); 188 return bc.getClassMeta(type, args); 189 } 190}