org.apache.juneau.BeanRegistry Maven / Gradle / Ivy
// ***************************************************************************************************************************
// * 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.*;
/**
* A lookup table for resolving bean types by name.
*
*
* In a nutshell, provides a simple mapping of bean class objects to identifying names.
*
*
* Class names are defined through the {@link Bean#typeName() @Bean(typeName)} annotation.
*
*
* The dictionary is used by the framework in the following ways:
*
* - If a class type cannot be inferred through reflection during parsing, then a helper
"_type" 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.
* - The dictionary name is used as element names when serialized to XML.
*
*/
public class BeanRegistry {
private final Map> map;
private final Map,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> 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) {
if (isParentClass(Collection.class, c)) {
@SuppressWarnings("rawtypes")
Collection cc = beanContext.newInstance(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 (isParentClass(Map.class, c)) {
Map,?> m = beanContext.newInstance(Map.class, c);
for (Map.Entry,?> e : m.entrySet()) {
String typeName = asString(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 = c.getAnnotation(Bean.class);
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. "X^^" ).
* @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 null if not found.
*/
public String getTypeName(ClassMeta> c) {
if (isEmpty)
return null;
return reverseMap.get(c.innerClass);
}
/**
* Returns true if this dictionary has an entry for the specified type name.
*
* @param typeName The bean type name.
* @return true 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> e : map.entrySet())
sb.append(e.getKey()).append(":").append(e.getValue().toString(true)).append(", ");
sb.append('}');
return sb.toString();
}
}