Rework Java import tracking
[mdsal.git] / binding / mdsal-binding-generator-api / src / main / java / org / opendaylight / mdsal / binding / model / api / JavaTypeName.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.model.api;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import com.google.common.collect.ImmutableList;
15 import java.util.ArrayList;
16 import java.util.List;
17 import java.util.Objects;
18 import java.util.Optional;
19 import org.eclipse.jdt.annotation.NonNullByDefault;
20 import org.eclipse.jdt.annotation.Nullable;
21 import org.opendaylight.yangtools.concepts.Identifier;
22 import org.opendaylight.yangtools.concepts.Immutable;
23
24 /**
25  * A type name. This class encloses Java type naming rules laid down in
26  * <a href="https://docs.oracle.com/javase/specs/jls/se9/html/index.html">The Java Language Specification</a>, notably
27  * sections 4 and 8. It deals with primitive, array and reference types.
28  *
29  * @author Robert Varga
30  */
31 @Beta
32 @NonNullByDefault
33 public abstract class JavaTypeName implements Identifier, Immutable {
34     private static final class Primitive extends JavaTypeName {
35         private static final long serialVersionUID = 1L;
36
37         Primitive(final String simpleName) {
38             super(simpleName);
39         }
40
41         @Override
42         public String packageName() {
43             return "";
44         }
45
46         @Override
47         public Optional<JavaTypeName> immediatelyEnclosingClass() {
48             return Optional.empty();
49         }
50
51         @Override
52         public boolean canCreateEnclosed(final String simpleName) {
53             throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type "
54                     + simpleName);
55         }
56
57         @Override
58         public JavaTypeName createEnclosed(final String simpleName) {
59             throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type "
60                     + simpleName);
61         }
62
63         @Override
64         public String localName() {
65             return simpleName();
66         }
67
68         @Override
69         public List<String> localNameComponents() {
70             return ImmutableList.of(simpleName());
71         }
72
73         @Override
74         public JavaTypeName createSibling(final String simpleName) {
75             return new Primitive(simpleName);
76         }
77
78         @Override
79         public JavaTypeName topLevelClass() {
80             return this;
81         }
82
83         @Override
84         public String toString() {
85             return simpleName();
86         }
87     }
88
89     private abstract static class Reference extends JavaTypeName {
90         private static final long serialVersionUID = 1L;
91
92         Reference(final String simpleName) {
93             super(simpleName);
94         }
95
96         @Override
97         public boolean canCreateEnclosed(final String simpleName) {
98             return !simpleName.equals(simpleName());
99         }
100
101         @Override
102         public final JavaTypeName createEnclosed(final String simpleName) {
103             checkValidName(requireNonNull(simpleName));
104             return new Nested(this, simpleName);
105         }
106
107         @Override
108         public final String toString() {
109             return appendClass(new StringBuilder()).toString();
110         }
111
112         void checkValidName(final String nestedName) {
113             checkArgument(canCreateEnclosed(nestedName), "Nested class name %s conflicts with enclosing class %s",
114                 nestedName, this);
115         }
116
117         abstract StringBuilder appendClass(StringBuilder sb);
118     }
119
120     private static final class TopLevel extends Reference {
121         private static final long serialVersionUID = 1L;
122
123         private final String packageName;
124
125         TopLevel(final String packageName, final String simpleName) {
126             super(simpleName);
127             checkArgument(!packageName.isEmpty());
128             this.packageName = packageName;
129         }
130
131         @Override
132         public String packageName() {
133             return packageName;
134         }
135
136         @Override
137         public JavaTypeName createSibling(final String simpleName) {
138             return new TopLevel(packageName, simpleName);
139         }
140
141         @Override
142         public Optional<JavaTypeName> immediatelyEnclosingClass() {
143             return Optional.empty();
144         }
145
146         @Override
147         public String localName() {
148             return simpleName();
149         }
150
151         @Override
152         public List<String> localNameComponents() {
153             final List<String> ret = new ArrayList<>();
154             ret.add(simpleName());
155             return ret;
156         }
157
158         @Override
159         public JavaTypeName topLevelClass() {
160             return this;
161         }
162
163         @Override
164         StringBuilder appendClass(final StringBuilder sb) {
165             return sb.append(packageName).append('.').append(simpleName());
166         }
167     }
168
169     private static final class Nested extends Reference {
170         private static final long serialVersionUID = 1L;
171
172         private final Reference immediatelyEnclosingClass;
173
174         Nested(final Reference immediatelyEnclosingClass, final String simpleName) {
175             super(simpleName);
176             this.immediatelyEnclosingClass = requireNonNull(immediatelyEnclosingClass);
177         }
178
179         @Override
180         public String packageName() {
181             return immediatelyEnclosingClass.packageName();
182         }
183
184         @Override
185         public JavaTypeName createSibling(final String simpleName) {
186             return immediatelyEnclosingClass.createEnclosed(simpleName);
187         }
188
189         @Override
190         public Optional<JavaTypeName> immediatelyEnclosingClass() {
191             return Optional.of(immediatelyEnclosingClass);
192         }
193
194         @Override
195         StringBuilder appendClass(final StringBuilder sb) {
196             return immediatelyEnclosingClass.appendClass(sb).append('.').append(simpleName());
197         }
198
199         @Override
200         public boolean canCreateEnclosed(final String simpleName) {
201             return super.canCreateEnclosed(simpleName) && immediatelyEnclosingClass.canCreateEnclosed(simpleName);
202         }
203
204         @Override
205         public String localName() {
206             return immediatelyEnclosingClass.localName() + "." + simpleName();
207         }
208
209         @Override
210         public List<String> localNameComponents() {
211             final List<String> ret = immediatelyEnclosingClass.localNameComponents();
212             ret.add(simpleName());
213             return ret;
214         }
215
216         @Override
217         public JavaTypeName topLevelClass() {
218             return immediatelyEnclosingClass.topLevelClass();
219         }
220     }
221
222     private static final long serialVersionUID = 1L;
223
224     private final String simpleName;
225
226     JavaTypeName(final String simpleName) {
227         checkArgument(!simpleName.isEmpty());
228         this.simpleName = simpleName;
229     }
230
231     /**
232      * Create a TypeName for an existing class.
233      *
234      * @param clazz Class instance
235      * @return A new TypeName
236      * @throws NullPointerException if clazz is null
237      */
238     public static JavaTypeName create(final Class<?> clazz) {
239         final Class<?> enclosing = clazz.getEnclosingClass();
240         if (enclosing != null) {
241             return create(enclosing).createEnclosed(clazz.getSimpleName());
242         }
243         final Package pkg = clazz.getPackage();
244         return pkg == null ? new Primitive(clazz.getSimpleName()) : new TopLevel(pkg.getName(), clazz.getSimpleName());
245     }
246
247     /**
248      * Create a TypeName for a top-level class.
249      *
250      * @param packageName Class package name
251      * @param simpleName Class simple name
252      * @return A new TypeName.
253      * @throws NullPointerException if any of the arguments is null
254      * @throws IllegalArgumentException if any of the arguments is empty
255      */
256     public static JavaTypeName create(final String packageName, final String simpleName) {
257         return new TopLevel(packageName, simpleName);
258     }
259
260     /**
261      * Check if an enclosed type with specified name can be created.
262      *
263      * @param simpleName Simple name of the enclosed class
264      * @return True if the proposed simple name does not conflict with any enclosing types
265      * @throws IllegalArgumentException if the simpleName is empty
266      * @throws UnsupportedOperationException if this type name does not support nested type
267      */
268     public abstract boolean canCreateEnclosed(final String simpleName);
269
270     /**
271      * Create a TypeName for a class immediately enclosed by this class.
272      *
273      * @param simpleName Simple name of the enclosed class
274      * @return A new TypeName.
275      * @throws NullPointerException if simpleName is null
276      * @throws IllegalArgumentException if the simpleName hides any of the enclosing types or if it is empty
277      * @throws UnsupportedOperationException if this type name does not support nested type
278      */
279     public abstract JavaTypeName createEnclosed(String simpleName);
280
281     /**
282      * Create a TypeName for a class that is a sibling of this class. A sibling has the same package name, and the same
283      * immediately enclosing class.
284      *
285      * @param simpleName Simple name of the sibling class
286      * @return A new TypeName.
287      * @throws NullPointerException if simpleName is null
288      * @throws IllegalArgumentException if the simpleName is empty
289      */
290     // TODO: we could detect/allocate names in this method such that they don't conflict.
291     public abstract JavaTypeName createSibling(String simpleName);
292
293     /**
294      * Return the simple name of the class.
295      *
296      * @return Simple name of the class.
297      */
298     public final String simpleName() {
299         return simpleName;
300     }
301
302     /**
303      * Return the package name in which this class resides. This does not account for any class nesting, i.e. for nested
304      * classes this returns the package name of the top-level class in naming hierarchy.
305      *
306      * @return Package name of the class.
307      */
308     public abstract String packageName();
309
310     /**
311      * Return the enclosing class JavaTypeName, if present.
312      *
313      * @return Enclosing class JavaTypeName.
314      */
315     public abstract Optional<JavaTypeName> immediatelyEnclosingClass();
316
317     /**
318      * Return the top-level class JavaTypeName which is containing this type, or self if this type is a top-level
319      * one.
320      *
321      * @return Top-level JavaTypeName
322      */
323     public abstract JavaTypeName topLevelClass();
324
325     /**
326      * Return the package-local name by which this type can be referenced by classes living in the same package.
327      *
328      * @return Local name.
329      */
330     public abstract String localName();
331
332     /**
333      * Return broken-down package-local name components.
334      *
335      * @return List of package-local components.
336      */
337     public abstract List<String> localNameComponents();
338
339     @Override
340     public final int hashCode() {
341         return Objects.hash(simpleName, packageName(), immediatelyEnclosingClass());
342     }
343
344     @Override
345     public final boolean equals(final @Nullable Object obj) {
346         if (obj == this) {
347             return true;
348         }
349         if (!(obj instanceof JavaTypeName)) {
350             return false;
351         }
352         final JavaTypeName other = (JavaTypeName) obj;
353         return simpleName.equals(other.simpleName) && packageName().equals(other.packageName())
354                 && immediatelyEnclosingClass().equals(other.immediatelyEnclosingClass());
355     }
356
357     /**
358      * Return the Fully-Qualified Class Name string of this TypeName.
359      *
360      * @return Fully-Qualified Class Name string of this TypeName.
361      */
362     @Override
363     public abstract String toString();
364 }