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