Fixup BuilderTemplate decomposition
[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 com.google.common.collect.ImmutableSet.toImmutableSet;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.collect.ImmutableMap;
14 import com.google.common.collect.ImmutableMap.Builder;
15 import com.google.common.collect.ImmutableSet;
16 import com.google.common.collect.Iterables;
17 import java.util.HashMap;
18 import java.util.Map;
19 import java.util.Set;
20 import javax.annotation.concurrent.NotThreadSafe;
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.
34  *
35  * @author Robert Varga
36  */
37 @NonNullByDefault
38 @NotThreadSafe
39 abstract class AbstractJavaGeneratedType {
40     private final Map<JavaTypeName, @Nullable String> nameCache = new HashMap<>();
41     private final Map<String, NestedJavaGeneratedType> enclosedTypes;
42     private final Set<String> conflictingNames;
43
44     private final JavaTypeName name;
45
46     AbstractJavaGeneratedType(final GeneratedType genType) {
47         name = genType.getIdentifier();
48         final Builder<String, NestedJavaGeneratedType> b = ImmutableMap.builder();
49         for (GeneratedType type : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
50             b.put(type.getIdentifier().simpleName(), new NestedJavaGeneratedType(this, type));
51         }
52         collectInheritedEnclosedTypes(b, genType);
53
54         enclosedTypes = b.build();
55
56         if (genType instanceof Enumeration) {
57             conflictingNames = ((Enumeration) genType).getValues().stream().map(Pair::getMappedName)
58                     .collect(toImmutableSet());
59         } else {
60             conflictingNames = ImmutableSet.of();
61         }
62     }
63
64     private void collectInheritedEnclosedTypes(Builder<String, NestedJavaGeneratedType> builder,
65             GeneratedType type) {
66         for (Type impl : type.getImplements()) {
67             if (impl instanceof GeneratedType) {
68                 final GeneratedType genType = (GeneratedType) impl;
69                 for (GeneratedType inner : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
70                     builder.put(inner.getIdentifier().simpleName(), new NestedJavaGeneratedType(this, inner));
71                 }
72                 collectInheritedEnclosedTypes(builder, genType);
73             }
74         }
75     }
76
77     final JavaTypeName getName() {
78         return name;
79     }
80
81     final String getSimpleName() {
82         return name.simpleName();
83     }
84
85     final String getReferenceString(final Type type) {
86         if (!(type instanceof ParameterizedType)) {
87             return getReferenceString(type.getIdentifier());
88         }
89
90         final StringBuilder sb = new StringBuilder();
91         sb.append(getReferenceString(type.getIdentifier())).append('<');
92         final Type[] types = ((ParameterizedType) type).getActualTypeArguments();
93         if (types.length == 0) {
94             return sb.append("?>").toString();
95         }
96
97         for (int i = 0; i < types.length; i++) {
98             final Type t = types[i];
99             if (t instanceof WildcardType) {
100                 sb.append("? extends ");
101             }
102             sb.append(getReferenceString(t));
103             if (i != types.length - 1) {
104                 sb.append(", ");
105             }
106         }
107
108         return sb.append('>').toString();
109     }
110
111     final String getReferenceString(final JavaTypeName type) {
112         if (type.packageName().isEmpty()) {
113             // This is a packageless primitive type, refer to it directly
114             return type.simpleName();
115         }
116
117         // Self-reference, return simple name
118         if (name.equals(type)) {
119             return name.simpleName();
120         }
121
122         // Fast path: we have already resolved how to refer to this type
123         final String existing = nameCache.get(type);
124         if (existing != null) {
125             return existing;
126         }
127
128         // Fork based on whether the class is in this compilation unit, package or neither
129         final String result;
130         if (name.topLevelClass().equals(type.topLevelClass())) {
131             result = localTypeName(type);
132         } else if (name.packageName().equals(type.packageName())) {
133             result = packageTypeName(type);
134         } else {
135             result = foreignTypeName(type);
136         }
137
138         nameCache.put(type, result);
139         return result;
140     }
141
142     final NestedJavaGeneratedType getEnclosedType(final JavaTypeName type) {
143         return requireNonNull(enclosedTypes.get(type.simpleName()));
144     }
145
146     final boolean checkAndImportType(final JavaTypeName type) {
147         // We can import the type only if it does not conflict with us or our immediately-enclosed types
148         final String simpleName = type.simpleName();
149         return !simpleName.equals(getSimpleName()) && !enclosedTypes.containsKey(simpleName)
150                 && !conflictingNames.contains(simpleName) && importCheckedType(type);
151     }
152
153     abstract boolean importCheckedType(JavaTypeName type);
154
155     abstract String localTypeName(JavaTypeName type);
156
157     private String foreignTypeName(final JavaTypeName type) {
158         return checkAndImportType(type) ? type.simpleName() : type.toString();
159     }
160
161     private String packageTypeName(final JavaTypeName type) {
162         // Try to anchor the top-level type and use a local reference
163         return checkAndImportType(type.topLevelClass()) ? type.localName() : type.toString();
164     }
165 }