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