1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
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
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 }