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