Split out isBitsType()
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / AbstractJavaGeneratedType.java
1 /*
2  * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.java.api.generator;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.collect.ImmutableMap;
13 import com.google.common.collect.ImmutableMap.Builder;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.common.collect.Iterables;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Map;
19 import java.util.Set;
20 import org.eclipse.jdt.annotation.NonNull;
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.opendaylight.mdsal.binding.model.api.Enumeration;
24 import org.opendaylight.mdsal.binding.model.api.Enumeration.Pair;
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
26 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
27 import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
28 import org.opendaylight.mdsal.binding.model.api.Type;
29 import org.opendaylight.mdsal.binding.model.api.WildcardType;
30
31 /**
32  * Abstract class representing a generated type, either top-level or nested. It takes care of tracking references
33  * to other Java types and resolving them as best as possible. This class is NOT thread-safe.
34  *
35  * @author Robert Varga
36  */
37 @NonNullByDefault
38 abstract class AbstractJavaGeneratedType {
39     private final Map<JavaTypeName, @Nullable String> nameCache = new HashMap<>();
40     private final ImmutableMap<String, NestedJavaGeneratedType> enclosedTypes;
41     private final ImmutableSet<String> conflictingNames;
42
43     private final JavaTypeName name;
44
45     AbstractJavaGeneratedType(final GeneratedType genType) {
46         name = genType.getIdentifier();
47         final Builder<String, NestedJavaGeneratedType> b = ImmutableMap.builder();
48         for (GeneratedType type : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
49             b.put(type.getIdentifier().simpleName(), new NestedJavaGeneratedType(this, type));
50         }
51         enclosedTypes = b.build();
52
53         final Set<String> cb = new HashSet<>();
54         if (genType instanceof Enumeration) {
55             ((Enumeration) genType).getValues().stream().map(Pair::getMappedName).forEach(cb::add);
56         }
57         // TODO: perhaps we can do something smarter to actually access the types
58         collectAccessibleTypes(cb, genType);
59
60         conflictingNames = ImmutableSet.copyOf(cb);
61     }
62
63     private void collectAccessibleTypes(final Set<String> set, final GeneratedType type) {
64         for (Type impl : type.getImplements()) {
65             if (impl instanceof GeneratedType) {
66                 final GeneratedType genType = (GeneratedType) impl;
67                 for (GeneratedType inner : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
68                     set.add(inner.getIdentifier().simpleName());
69                 }
70                 collectAccessibleTypes(set, genType);
71             }
72         }
73     }
74
75     final JavaTypeName getName() {
76         return name;
77     }
78
79     final String getSimpleName() {
80         return name.simpleName();
81     }
82
83     private String annotateReference(final String ref, final Type type, final String annotation) {
84         if (type instanceof ParameterizedType) {
85             return getReferenceString(annotate(ref, annotation), type,
86                 ((ParameterizedType) type).getActualTypeArguments());
87         }
88         return "byte[]".equals(ref) ? "byte @" + annotation + "[]" : annotate(ref, annotation).toString();
89     }
90
91     final String getFullyQualifiedReference(final Type type, final String annotation) {
92         return annotateReference(type.getFullyQualifiedName(), type ,annotation);
93     }
94
95     final String getReferenceString(final Type type) {
96         final String ref = getReferenceString(type.getIdentifier());
97         return type instanceof ParameterizedType ? getReferenceString(new StringBuilder(ref), type,
98             ((ParameterizedType) type).getActualTypeArguments())
99                 : ref;
100     }
101
102     final String getReferenceString(final Type type, final String annotation) {
103         // Package-private method, all callers who would be passing an empty array are bound to the more special
104         // case above, hence we know annotations.length >= 1
105         final String ref = getReferenceString(type.getIdentifier());
106         return annotateReference(ref, type, annotation);
107     }
108
109     private String getReferenceString(final StringBuilder sb, final Type type, final @NonNull Type[] arguments) {
110         if (arguments.length == 0) {
111             return sb.append("<?>").toString();
112         }
113
114         sb.append('<');
115         for (int i = 0; i < arguments.length; i++) {
116             final Type arg = arguments[i];
117             if (arg instanceof WildcardType) {
118                 sb.append("? extends ");
119             }
120             sb.append(getReferenceString(arg));
121             if (i != arguments.length - 1) {
122                 sb.append(", ");
123             }
124         }
125         return sb.append('>').toString();
126     }
127
128     final String getReferenceString(final JavaTypeName type) {
129         if (type.packageName().isEmpty()) {
130             // This is a packageless primitive type, refer to it directly
131             return type.simpleName();
132         }
133
134         // Self-reference, return simple name
135         if (name.equals(type)) {
136             return name.simpleName();
137         }
138
139         // Fast path: we have already resolved how to refer to this type
140         final String existing = nameCache.get(type);
141         if (existing != null) {
142             return existing;
143         }
144
145         // Fork based on whether the class is in this compilation unit, package or neither
146         final String result;
147         if (name.topLevelClass().equals(type.topLevelClass())) {
148             result = localTypeName(type);
149         } else if (name.packageName().equals(type.packageName())) {
150             result = packageTypeName(type);
151         } else {
152             result = foreignTypeName(type);
153         }
154
155         nameCache.put(type, result);
156         return result;
157     }
158
159     final NestedJavaGeneratedType getEnclosedType(final JavaTypeName type) {
160         return requireNonNull(enclosedTypes.get(type.simpleName()));
161     }
162
163     final boolean checkAndImportType(final JavaTypeName type) {
164         // We can import the type only if it does not conflict with us or our immediately-enclosed types
165         final String simpleName = type.simpleName();
166         return !simpleName.equals(getSimpleName()) && !enclosedTypes.containsKey(simpleName)
167                 && !conflictingNames.contains(simpleName) && importCheckedType(type);
168     }
169
170     abstract boolean importCheckedType(JavaTypeName type);
171
172     abstract String localTypeName(JavaTypeName type);
173
174     private String foreignTypeName(final JavaTypeName type) {
175         return checkAndImportType(type) ? type.simpleName() : type.toString();
176     }
177
178     private String packageTypeName(final JavaTypeName type) {
179         // Try to anchor the top-level type and use a local reference
180         return checkAndImportType(type.topLevelClass()) ? type.localName() : type.toString();
181     }
182
183     private static StringBuilder annotate(final String ref, final String annotation) {
184         final StringBuilder sb = new StringBuilder();
185         final int dot = ref.lastIndexOf('.');
186         if (dot != -1) {
187             sb.append(ref, 0, dot + 1);
188         }
189         return sb.append('@').append(annotation).append(' ').append(ref, dot + 1, ref.length());
190     }
191 }