2 * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.model.api;
10 import static com.google.common.base.Preconditions.checkArgument;
11 import static java.util.Objects.requireNonNull;
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;
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.
32 * @author Robert Varga
36 public abstract sealed class JavaTypeName implements Identifier, Immutable {
37 private static final class Primitive extends JavaTypeName {
39 private static final long serialVersionUID = 1L;
41 Primitive(final String simpleName) {
46 public String packageName() {
51 public Optional<JavaTypeName> immediatelyEnclosingClass() {
52 return Optional.empty();
56 public boolean canCreateEnclosed(final String simpleName) {
57 throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type "
62 public JavaTypeName createEnclosed(final String simpleName) {
63 throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type "
68 public String localName() {
73 public List<String> localNameComponents() {
74 return ImmutableList.of(simpleName());
78 public JavaTypeName createSibling(final String simpleName) {
79 return new Primitive(simpleName);
83 public JavaTypeName topLevelClass() {
88 public String toString() {
93 private abstract static sealed class Reference extends JavaTypeName {
95 private static final long serialVersionUID = 1L;
97 Reference(final String simpleName) {
102 public boolean canCreateEnclosed(final String simpleName) {
103 return !simpleName.equals(simpleName());
107 public final JavaTypeName createEnclosed(final String simpleName) {
108 checkValidName(requireNonNull(simpleName));
109 return new Nested(this, simpleName);
113 public final String toString() {
114 return appendClass(new StringBuilder()).toString();
117 void checkValidName(final String nestedName) {
118 checkArgument(canCreateEnclosed(nestedName), "Nested class name %s conflicts with enclosing class %s",
122 abstract StringBuilder appendClass(StringBuilder sb);
125 private static final class TopLevel extends Reference {
127 private static final long serialVersionUID = 1L;
129 private final String packageName;
131 TopLevel(final String packageName, final String simpleName) {
133 checkArgument(!packageName.isEmpty());
134 this.packageName = packageName;
138 public String packageName() {
143 public JavaTypeName createSibling(final String simpleName) {
144 return new TopLevel(packageName, simpleName);
148 public Optional<JavaTypeName> immediatelyEnclosingClass() {
149 return Optional.empty();
153 public String localName() {
158 public List<String> localNameComponents() {
159 final var ret = new ArrayList<String>();
160 ret.add(simpleName());
165 public JavaTypeName topLevelClass() {
170 StringBuilder appendClass(final StringBuilder sb) {
171 return sb.append(packageName).append('.').append(simpleName());
175 private static final class Nested extends Reference {
177 private static final long serialVersionUID = 1L;
179 private final Reference immediatelyEnclosingClass;
181 Nested(final Reference immediatelyEnclosingClass, final String simpleName) {
183 this.immediatelyEnclosingClass = requireNonNull(immediatelyEnclosingClass);
187 public String packageName() {
188 return immediatelyEnclosingClass.packageName();
192 public JavaTypeName createSibling(final String simpleName) {
193 return immediatelyEnclosingClass.createEnclosed(simpleName);
197 public Optional<JavaTypeName> immediatelyEnclosingClass() {
198 return Optional.of(immediatelyEnclosingClass);
202 StringBuilder appendClass(final StringBuilder sb) {
203 return immediatelyEnclosingClass.appendClass(sb).append('.').append(simpleName());
207 public boolean canCreateEnclosed(final String simpleName) {
208 return super.canCreateEnclosed(simpleName) && immediatelyEnclosingClass.canCreateEnclosed(simpleName);
212 public String localName() {
213 return immediatelyEnclosingClass.localName() + "." + simpleName();
217 public List<String> localNameComponents() {
218 final List<String> ret = immediatelyEnclosingClass.localNameComponents();
219 ret.add(simpleName());
224 public JavaTypeName topLevelClass() {
225 return immediatelyEnclosingClass.topLevelClass();
229 private static final Logger LOG = LoggerFactory.getLogger(JavaTypeName.class);
231 private static final long serialVersionUID = 1L;
233 private final String simpleName;
235 JavaTypeName(final String simpleName) {
236 checkArgument(!simpleName.isEmpty());
237 this.simpleName = simpleName;
241 * Create a TypeName for an existing class.
243 * @param clazz Class instance
244 * @return A new TypeName
245 * @throws NullPointerException if clazz is null
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());
252 final Package pkg = clazz.getPackage();
253 return pkg == null ? new Primitive(clazz.getSimpleName()) : new TopLevel(pkg.getName(), clazz.getSimpleName());
257 * Create a TypeName for a top-level class.
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
265 public static JavaTypeName create(final String packageName, final String simpleName) {
266 return new TopLevel(packageName, simpleName);
270 * Check if an enclosed type with specified name can be created.
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
277 public abstract boolean canCreateEnclosed(String simpleName);
280 * Create a TypeName for a class immediately enclosed by this class.
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
288 public abstract JavaTypeName createEnclosed(String simpleName);
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.
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
300 * @throws UnsupportedOperationException if this type name does not support nested type
302 @SuppressWarnings("checkstyle:hiddenField")
303 public final JavaTypeName createEnclosed(final String simpleName, final String fallbackSuffix) {
304 checkArgument(!simpleName.isEmpty());
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);
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.
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
323 // TODO: we could detect/allocate names in this method such that they don't conflict.
324 public abstract JavaTypeName createSibling(String simpleName);
327 * Return the simple name of the class.
329 * @return Simple name of the class.
331 public final String simpleName() {
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.
339 * @return Package name of the class.
341 public abstract String packageName();
344 * Return the enclosing class JavaTypeName, if present.
346 * @return Enclosing class JavaTypeName.
348 public abstract Optional<JavaTypeName> immediatelyEnclosingClass();
351 * Return the top-level class JavaTypeName which is containing this type, or self if this type is a top-level
354 * @return Top-level JavaTypeName
356 public abstract JavaTypeName topLevelClass();
359 * Return the package-local name by which this type can be referenced by classes living in the same package.
361 * @return Local name.
363 public abstract String localName();
366 * Return broken-down package-local name components.
368 * @return List of package-local components.
370 public abstract List<String> localNameComponents();
373 public final int hashCode() {
374 return Objects.hash(simpleName, packageName(), immediatelyEnclosingClass());
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());
385 * Return the Fully-Qualified Class Name string of this TypeName.
387 * @return Fully-Qualified Class Name string of this TypeName.
390 public abstract String toString();