blob: f39ba232d75f9bcf18e6edb176de5af8fef843ea [file] [log] [blame]
// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance *
// * with the License. You may obtain a copy of the License at *
// * *
// * http://www.apache.org/licenses/LICENSE-2.0 *
// * *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the *
// * specific language governing permissions and limitations under the License. *
// ***************************************************************************************************************************
package org.apache.juneau;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.reflect.*;
/**
* A lookup table for resolving bean types by name.
*
* <p>
* In a nutshell, provides a simple mapping of bean class objects to identifying names.
*
* <p>
* Class names are defined through the {@link Bean#typeName() @Bean(typeName)} annotation.
*
* <p>
* The dictionary is used by the framework in the following ways:
* <ul>
* <li>If a class type cannot be inferred through reflection during parsing, then a helper <js>"_type"</js> is added
* to the serialized output using the name defined for that class in this dictionary. This helps determine the
* real class at parse time.
* <li>The dictionary name is used as element names when serialized to XML.
* </ul>
*/
public class BeanRegistry {
private final Map<String,ClassMeta<?>> map;
private final Map<Class<?>,String> reverseMap;
private final BeanContext beanContext;
private final boolean isEmpty;
BeanRegistry(BeanContext beanContext, BeanRegistry parent, Class<?>...classes) {
this.beanContext = beanContext;
this.map = new ConcurrentHashMap<>();
this.reverseMap = new ConcurrentHashMap<>();
for (Class<?> c : beanContext.getBeanDictionaryClasses())
addClass(c);
if (parent != null)
for (Map.Entry<String,ClassMeta<?>> e : parent.map.entrySet())
addToMap(e.getKey(), e.getValue());
for (Class<?> c : classes)
addClass(c);
isEmpty = map.isEmpty();
}
private void addClass(Class<?> c) {
try {
if (c != null) {
ClassInfo ci = ClassInfo.of(c);
if (ci.isChildOf(Collection.class)) {
@SuppressWarnings("rawtypes")
Collection cc = castOrCreate(Collection.class, c);
for (Object o : cc) {
if (o instanceof Class)
addClass((Class<?>)o);
else
throw new BeanRuntimeException("Collection class ''{0}'' passed to BeanRegistry does not contain Class objects.", c.getName());
}
} else if (ci.isChildOf(Map.class)) {
Map<?,?> m = castOrCreate(Map.class, c);
for (Map.Entry<?,?> e : m.entrySet()) {
String typeName = stringify(e.getKey());
Object v = e.getValue();
ClassMeta<?> val = null;
if (v instanceof Type)
val = beanContext.getClassMeta((Type)v);
else if (v.getClass().isArray())
val = getTypedClassMeta(v);
else
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());
addToMap(typeName, val);
}
} else {
Bean b = ci.getAnnotation(Bean.class, beanContext);
if (b == null || b.typeName().isEmpty())
throw new BeanRuntimeException("Class ''{0}'' was passed to BeanRegistry but it doesn't have a @Bean(typeName) annotation defined.", c.getName());
addToMap(b.typeName(), beanContext.getClassMeta(c));
}
}
} catch (BeanRuntimeException e) {
throw e;
} catch (Exception e) {
throw new BeanRuntimeException(e);
}
}
private ClassMeta<?> getTypedClassMeta(Object array) {
int len = Array.getLength(array);
if (len == 0)
throw new BeanRuntimeException("Map entry had an empty array value.");
Type type = (Type)Array.get(array, 0);
Type[] args = new Type[len-1];
for (int i = 1; i < len; i++)
args[i-1] = (Type)Array.get(array, i);
return beanContext.getClassMeta(type, args);
}
private void addToMap(String typeName, ClassMeta<?> cm) {
map.put(typeName, cm);
reverseMap.put(cm.innerClass, typeName);
}
/**
* Gets the class metadata for the specified bean type name.
*
* @param typeName
* The bean type name as defined by {@link Bean#typeName() @Bean(typeName)}.
* Can include multi-dimensional array type names (e.g. <js>"X^^"</js>).
* @return The class metadata for the bean.
*/
public ClassMeta<?> getClassMeta(String typeName) {
if (isEmpty)
return null;
if (typeName == null)
return null;
ClassMeta<?> cm = map.get(typeName);
if (cm != null)
return cm;
if (typeName.charAt(typeName.length()-1) == '^') {
cm = getClassMeta(typeName.substring(0, typeName.length()-1));
if (cm != null) {
cm = beanContext.getClassMeta(Array.newInstance(cm.innerClass, 1).getClass());
map.put(typeName, cm);
}
return cm;
}
return null;
}
/**
* Given the specified class, return the dictionary name for it.
*
* @param c The class to lookup in this registry.
* @return The dictionary name for the specified class in this registry, or <jk>null</jk> if not found.
*/
public String getTypeName(ClassMeta<?> c) {
if (isEmpty)
return null;
return reverseMap.get(c.innerClass);
}
/**
* Returns <jk>true</jk> if this dictionary has an entry for the specified type name.
*
* @param typeName The bean type name.
* @return <jk>true</jk> if this dictionary has an entry for the specified type name.
*/
public boolean hasName(String typeName) {
return getClassMeta(typeName) != null;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('{');
for (Map.Entry<String,ClassMeta<?>> e : map.entrySet())
sb.append(e.getKey()).append(":").append(e.getValue().toString(true)).append(", ");
sb.append('}');
return sb.toString();
}
}