Populate enumeration conflicting names
[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 com.google.common.collect.Streams;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.Set;
21 import javax.annotation.concurrent.NotThreadSafe;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jdt.annotation.Nullable;
24 import org.opendaylight.mdsal.binding.model.api.Enumeration;
25 import org.opendaylight.mdsal.binding.model.api.Enumeration.Pair;
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
27 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
28 import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
29 import org.opendaylight.mdsal.binding.model.api.Type;
30 import org.opendaylight.mdsal.binding.model.api.WildcardType;
31
32 /**
33  * Abstract class representing a generated type, either top-level or nested. It takes care of tracking references
34  * to other Java types and resolving them as best as possible.
35  *
36  * @author Robert Varga
37  */
38 @NonNullByDefault
39 @NotThreadSafe
40 abstract class AbstractJavaGeneratedType {
41     private final Map<JavaTypeName, @Nullable String> nameCache = new HashMap<>();
42     private final Map<String, NestedJavaGeneratedType> enclosedTypes;
43     private final Set<String> conflictingNames;
44
45     private final JavaTypeName name;
46
47     AbstractJavaGeneratedType(final GeneratedType genType) {
48         name = genType.getIdentifier();
49         final Builder<String, NestedJavaGeneratedType> b = ImmutableMap.builder();
50         for (GeneratedType type : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
51             b.put(type.getIdentifier().simpleName(), new NestedJavaGeneratedType(this, type));
52         }
53         enclosedTypes = b.build();
54         conflictingNames = genType instanceof Enumeration
55                 ? ((Enumeration) genType).getValues().stream().map(Pair::getMappedName).collect(toImmutableSet())
56                         : ImmutableSet.of();
57     }
58
59     AbstractJavaGeneratedType(final JavaTypeName name, final GeneratedType genType) {
60         this.name = requireNonNull(name);
61         enclosedTypes = ImmutableMap.of();
62
63         // This is a workaround for BuilderTemplate, which does not model itself correctly -- it should generate
64         // a GeneratedType for the Builder with a nested type for the implementation, which really should be
65         // a different template which gets generated as an inner type.
66         conflictingNames = Streams.concat(genType.getEnclosedTypes().stream(), genType.getEnumerations().stream())
67         .map(type -> type.getIdentifier().simpleName()).collect(toImmutableSet());
68     }
69
70     final JavaTypeName getName() {
71         return name;
72     }
73
74     final String getSimpleName() {
75         return name.simpleName();
76     }
77
78     final String getReferenceString(final Type type) {
79         if (!(type instanceof ParameterizedType)) {
80             return getReferenceString(type.getIdentifier());
81         }
82
83         final StringBuilder sb = new StringBuilder();
84         sb.append(getReferenceString(type.getIdentifier())).append('<');
85         final Type[] types = ((ParameterizedType) type).getActualTypeArguments();
86         if (types.length == 0) {
87             return sb.append("?>").toString();
88         }
89
90         for (int i = 0; i < types.length; i++) {
91             final Type t = types[i];
92             if (t instanceof WildcardType) {
93                 sb.append("? extends ");
94             }
95             sb.append(getReferenceString(t));
96             if (i != types.length - 1) {
97                 sb.append(", ");
98             }
99         }
100
101         return sb.append('>').toString();
102     }
103
104     final String getReferenceString(final JavaTypeName type) {
105         if (type.packageName().isEmpty()) {
106             // This is a packageless primitive type, refer to it directly
107             return type.simpleName();
108         }
109
110         // Self-reference, return simple name
111         if (name.equals(type)) {
112             return name.simpleName();
113         }
114
115         // Fast path: we have already resolved how to refer to this type
116         final String existing = nameCache.get(type);
117         if (existing != null) {
118             return existing;
119         }
120
121         // Fork based on whether the class is in this compilation unit, package or neither
122         final String result;
123         if (name.topLevelClass().equals(type.topLevelClass())) {
124             result = localTypeName(type);
125         } else if (name.packageName().equals(type.packageName())) {
126             result = packageTypeName(type);
127         } else {
128             result = foreignTypeName(type);
129         }
130
131         nameCache.put(type, result);
132         return result;
133     }
134
135     final NestedJavaGeneratedType getEnclosedType(final JavaTypeName type) {
136         return requireNonNull(enclosedTypes.get(type.simpleName()));
137     }
138
139     final boolean checkAndImportType(final JavaTypeName type) {
140         // We can import the type only if it does not conflict with us or our immediately-enclosed types
141         final String simpleName = type.simpleName();
142         return !simpleName.equals(getSimpleName()) && !enclosedTypes.containsKey(simpleName)
143                 && !conflictingNames.contains(simpleName) && importCheckedType(type);
144     }
145
146     abstract boolean importCheckedType(JavaTypeName type);
147
148     abstract String localTypeName(JavaTypeName type);
149
150     private String foreignTypeName(final JavaTypeName type) {
151         return checkAndImportType(type) ? type.simpleName() : type.toString();
152     }
153
154     private String packageTypeName(final JavaTypeName type) {
155         // Try to anchor the top-level type and use a local reference
156         return checkAndImportType(type.topLevelClass()) ? type.localName() : type.toString();
157     }
158 }