View Javadoc
1   /*
2    * Portions of this software was developed by employees of the National Institute
3    * of Standards and Technology (NIST), an agency of the Federal Government and is
4    * being made available as a public service. Pursuant to title 17 United States
5    * Code Section 105, works of NIST employees are not subject to copyright
6    * protection in the United States. This software may be subject to foreign
7    * copyright. Permission in the United States and in foreign countries, to the
8    * extent that NIST may hold copyright, to use, copy, modify, create derivative
9    * works, and distribute this software and its documentation without fee is hereby
10   * granted on a non-exclusive basis, provided that this notice and disclaimer
11   * of warranty appears in all copies.
12   *
13   * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
14   * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
15   * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
16   * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
17   * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
18   * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
19   * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
20   * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
21   * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
22   * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
23   * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
24   * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
25   */
26  
27  package gov.nist.secauto.metaschema.databind.codegen.typeinfo;
28  
29  import com.squareup.javapoet.ClassName;
30  
31  import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
32  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
33  import gov.nist.secauto.metaschema.core.model.IFlagContainer;
34  import gov.nist.secauto.metaschema.core.model.IModule;
35  import gov.nist.secauto.metaschema.core.model.INamedModelInstance;
36  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
37  import gov.nist.secauto.metaschema.databind.codegen.ClassUtils;
38  import gov.nist.secauto.metaschema.databind.codegen.config.IBindingConfiguration;
39  
40  import org.apache.commons.lang3.StringUtils;
41  import org.apache.logging.log4j.LogManager;
42  import org.apache.logging.log4j.Logger;
43  
44  import java.util.Collections;
45  import java.util.HashSet;
46  import java.util.Map;
47  import java.util.Set;
48  import java.util.concurrent.ConcurrentHashMap;
49  
50  import edu.umd.cs.findbugs.annotations.NonNull;
51  
52  class DefaultTypeResolver implements ITypeResolver {
53    private static final Logger LOGGER = LogManager.getLogger(DefaultTypeResolver.class);
54  
55    private final Map<String, Set<String>> packageToClassNamesMap = new ConcurrentHashMap<>();
56    private final Map<IFlagContainer, ClassName> definitionToTypeMap = new ConcurrentHashMap<>();
57    private final Map<IModule, ClassName> moduleToTypeMap = new ConcurrentHashMap<>();
58    private final Map<IAssemblyDefinition, IAssemblyDefinitionTypeInfo> assemblyDefinitionToTypeInfoMap
59        = new ConcurrentHashMap<>();
60    private final Map<IFieldDefinition, IFieldDefinitionTypeInfo> fieldDefinitionToTypeInfoMap
61        = new ConcurrentHashMap<>();
62  
63    @NonNull
64    private final IBindingConfiguration bindingConfiguration;
65  
66    public DefaultTypeResolver(@NonNull IBindingConfiguration bindingConfiguration) {
67      this.bindingConfiguration = bindingConfiguration;
68    }
69  
70    protected IBindingConfiguration getBindingConfiguration() {
71      return bindingConfiguration;
72    }
73  
74    @Override
75    public IAssemblyDefinitionTypeInfo getTypeInfo(@NonNull IAssemblyDefinition definition) {
76      return ObjectUtils.notNull(assemblyDefinitionToTypeInfoMap.computeIfAbsent(
77          definition,
78          (def) -> IAssemblyDefinitionTypeInfo.newTypeInfo(ObjectUtils.notNull(def), this)));
79    }
80  
81    @Override
82    public IFieldDefinitionTypeInfo getTypeInfo(@NonNull IFieldDefinition definition) {
83      return ObjectUtils.notNull(fieldDefinitionToTypeInfoMap.computeIfAbsent(
84          definition,
85          (def) -> IFieldDefinitionTypeInfo.newTypeInfo(ObjectUtils.notNull(def), this)));
86    }
87  
88    @Override
89    public IModelDefinitionTypeInfo getTypeInfo(@NonNull IFlagContainer definition) {
90      IModelDefinitionTypeInfo retval;
91      if (definition instanceof IAssemblyDefinition) {
92        retval = getTypeInfo((IAssemblyDefinition) definition);
93      } else if (definition instanceof IFieldDefinition) {
94        retval = getTypeInfo((IFieldDefinition) definition);
95      } else {
96        throw new IllegalStateException(String.format("Unknown type '%s'", definition.getClass().getName()));
97      }
98      return retval;
99    }
100 
101   @Override
102   public ClassName getClassName(@NonNull IFlagContainer definition) {
103     return ObjectUtils.notNull(definitionToTypeMap.computeIfAbsent(
104         definition,
105         (def) -> {
106           ClassName retval;
107           String packageName = getBindingConfiguration().getPackageNameForModule(def.getContainingModule());
108           if (def.isInline()) {
109             // this is a local definition, which means a child class needs to be generated
110             INamedModelInstance inlineInstance = def.getInlineInstance();
111             IFlagContainer parentDefinition = inlineInstance.getContainingDefinition();
112             ClassName parentClassName = getClassName(parentDefinition);
113             String name = generateClassName(ObjectUtils.notNull(parentClassName.canonicalName()), def);
114             retval = parentClassName.nestedClass(name);
115           } else {
116             String className = generateClassName(packageName, def);
117             retval = ClassName.get(packageName, className);
118           }
119           return retval;
120         }));
121   }
122 
123   @Override
124   public ClassName getClassName(IModule module) {
125     return ObjectUtils.notNull(moduleToTypeMap.computeIfAbsent(
126         module,
127         (meta) -> {
128           assert meta != null;
129           String packageName = getBindingConfiguration().getPackageNameForModule(meta);
130 
131           String className = getBindingConfiguration().getClassName(meta);
132           String classNameBase = className;
133           int index = 1;
134           while (isClassNameClash(packageName, className)) {
135             className = classNameBase + Integer.toString(index);
136           }
137           addClassName(packageName, className);
138           return ClassName.get(packageName, className);
139         }));
140   }
141 
142   @NonNull
143   protected Set<String> getClassNamesFor(@NonNull String packageOrTypeName) {
144     return ObjectUtils.notNull(packageToClassNamesMap.computeIfAbsent(
145         packageOrTypeName,
146         (pkg) -> Collections.synchronizedSet(new HashSet<>())));
147   }
148 
149   protected boolean isClassNameClash(@NonNull String packageOrTypeName, @NonNull String className) {
150     return getClassNamesFor(packageOrTypeName).contains(className);
151   }
152 
153   protected boolean addClassName(@NonNull String packageOrTypeName, @NonNull String className) {
154     return getClassNamesFor(packageOrTypeName).add(className);
155   }
156 
157   private String generateClassName(@NonNull String packageOrTypeName, @NonNull IFlagContainer definition) {
158     @NonNull String className = getBindingConfiguration().getClassName(definition);
159 
160     @NonNull String retval = className;
161     Set<String> classNames = getClassNamesFor(packageOrTypeName);
162     synchronized (classNames) {
163       boolean clash = false;
164       if (classNames.contains(className)) {
165         clash = true;
166         // first try to append the metaschema's short name
167         String metaschemaShortName = definition.getContainingModule().getShortName();
168         retval = ClassUtils.toClassName(className + StringUtils.capitalize(metaschemaShortName));
169       }
170 
171       String classNameBase = retval;
172       int index = 1;
173       while (classNames.contains(retval)) {
174         retval = classNameBase + Integer.toString(index++);
175       }
176       classNames.add(retval);
177 
178       if (clash && LOGGER.isWarnEnabled()) {
179         LOGGER.warn(String.format(
180             "Class name '%s', based on '%s' in '%s', clashes with another bound class. Using '%s' instead.",
181             className,
182             definition.getName(),
183             definition.getContainingModule().getLocation(),
184             retval));
185       }
186     }
187     return retval;
188   }
189 
190   @Override
191   public ClassName getBaseClassName(IFlagContainer definition) {
192     String className = bindingConfiguration.getQualifiedBaseClassName(definition);
193     ClassName retval = null;
194     if (className != null) {
195       retval = ClassName.bestGuess(className);
196     }
197     return retval;
198   }
199 
200   @Override
201   public String getPackageName(@NonNull IModule module) {
202     return bindingConfiguration.getPackageNameForModule(module);
203   }
204 }