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.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;
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.
31 * @author Robert Varga
35 public abstract class JavaTypeName implements Identifier, Immutable {
36 private static final class Primitive extends JavaTypeName {
37 private static final long serialVersionUID = 1L;
39 Primitive(final String simpleName) {
44 public String packageName() {
49 public Optional<JavaTypeName> immediatelyEnclosingClass() {
50 return Optional.empty();
54 public boolean canCreateEnclosed(final String simpleName) {
55 throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type "
60 public JavaTypeName createEnclosed(final String simpleName) {
61 throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type "
66 public String localName() {
71 public List<String> localNameComponents() {
72 return ImmutableList.of(simpleName());
76 public JavaTypeName createSibling(final String simpleName) {
77 return new Primitive(simpleName);
81 public JavaTypeName topLevelClass() {
86 public String toString() {
91 private abstract static class Reference extends JavaTypeName {
92 private static final long serialVersionUID = 1L;
94 Reference(final String simpleName) {
99 public boolean canCreateEnclosed(final String simpleName) {
100 return !simpleName.equals(simpleName());
104 public final JavaTypeName createEnclosed(final String simpleName) {
105 checkValidName(requireNonNull(simpleName));
106 return new Nested(this, simpleName);
110 public final String toString() {
111 return appendClass(new StringBuilder()).toString();
114 void checkValidName(final String nestedName) {
115 checkArgument(canCreateEnclosed(nestedName), "Nested class name %s conflicts with enclosing class %s",
119 abstract StringBuilder appendClass(StringBuilder sb);
122 private static final class TopLevel extends Reference {
123 private static final long serialVersionUID = 1L;
125 private final String packageName;
127 TopLevel(final String packageName, final String simpleName) {
129 checkArgument(!packageName.isEmpty());
130 this.packageName = packageName;
134 public String packageName() {
139 public JavaTypeName createSibling(final String simpleName) {
140 return new TopLevel(packageName, simpleName);
144 public Optional<JavaTypeName> immediatelyEnclosingClass() {
145 return Optional.empty();
149 public String localName() {
154 public List<String> localNameComponents() {
155 final List<String> ret = new ArrayList<>();
156 ret.add(simpleName());
161 public JavaTypeName topLevelClass() {
166 StringBuilder appendClass(final StringBuilder sb) {
167 return sb.append(packageName).append('.').append(simpleName());
171 private static final class Nested extends Reference {
172 private static final long serialVersionUID = 1L;
174 private final Reference immediatelyEnclosingClass;
176 Nested(final Reference immediatelyEnclosingClass, final String simpleName) {
178 this.immediatelyEnclosingClass = requireNonNull(immediatelyEnclosingClass);
182 public String packageName() {
183 return immediatelyEnclosingClass.packageName();
187 public JavaTypeName createSibling(final String simpleName) {
188 return immediatelyEnclosingClass.createEnclosed(simpleName);
192 public Optional<JavaTypeName> immediatelyEnclosingClass() {
193 return Optional.of(immediatelyEnclosingClass);
197 StringBuilder appendClass(final StringBuilder sb) {
198 return immediatelyEnclosingClass.appendClass(sb).append('.').append(simpleName());
202 public boolean canCreateEnclosed(final String simpleName) {
203 return super.canCreateEnclosed(simpleName) && immediatelyEnclosingClass.canCreateEnclosed(simpleName);
207 public String localName() {
208 return immediatelyEnclosingClass.localName() + "." + simpleName();
212 public List<String> localNameComponents() {
213 final List<String> ret = immediatelyEnclosingClass.localNameComponents();
214 ret.add(simpleName());
219 public JavaTypeName topLevelClass() {
220 return immediatelyEnclosingClass.topLevelClass();
224 private static final Logger LOG = LoggerFactory.getLogger(JavaTypeName.class);
225 private static final long serialVersionUID = 1L;
227 private final String simpleName;
229 JavaTypeName(final String simpleName) {
230 checkArgument(!simpleName.isEmpty());
231 this.simpleName = simpleName;
235 * Create a TypeName for an existing class.
237 * @param clazz Class instance
238 * @return A new TypeName
239 * @throws NullPointerException if clazz is null
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());
246 final Package pkg = clazz.getPackage();
247 return pkg == null ? new Primitive(clazz.getSimpleName()) : new TopLevel(pkg.getName(), clazz.getSimpleName());
251 * Create a TypeName for a top-level class.
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
259 public static JavaTypeName create(final String packageName, final String simpleName) {
260 return new TopLevel(packageName, simpleName);
264 * Check if an enclosed type with specified name can be created.
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
271 public abstract boolean canCreateEnclosed(String simpleName);
274 * Create a TypeName for a class immediately enclosed by this class.
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
282 public abstract JavaTypeName createEnclosed(String simpleName);
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.
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
294 * @throws UnsupportedOperationException if this type name does not support nested type
296 @SuppressWarnings("checkstyle:hiddenField")
297 public final JavaTypeName createEnclosed(final String simpleName, final String fallbackSuffix) {
298 checkArgument(!simpleName.isEmpty());
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);
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.
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
317 // TODO: we could detect/allocate names in this method such that they don't conflict.
318 public abstract JavaTypeName createSibling(String simpleName);
321 * Return the simple name of the class.
323 * @return Simple name of the class.
325 public final String simpleName() {
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.
333 * @return Package name of the class.
335 public abstract String packageName();
338 * Return the enclosing class JavaTypeName, if present.
340 * @return Enclosing class JavaTypeName.
342 public abstract Optional<JavaTypeName> immediatelyEnclosingClass();
345 * Return the top-level class JavaTypeName which is containing this type, or self if this type is a top-level
348 * @return Top-level JavaTypeName
350 public abstract JavaTypeName topLevelClass();
353 * Return the package-local name by which this type can be referenced by classes living in the same package.
355 * @return Local name.
357 public abstract String localName();
360 * Return broken-down package-local name components.
362 * @return List of package-local components.
364 public abstract List<String> localNameComponents();
367 public final int hashCode() {
368 return Objects.hash(simpleName, packageName(), immediatelyEnclosingClass());
372 public final boolean equals(final @Nullable Object obj) {
376 if (!(obj instanceof JavaTypeName)) {
379 final JavaTypeName other = (JavaTypeName) obj;
380 return 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();