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