/* * Copyright (c) 2018 Pantheon Technologies, s.r.o. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.mdsal.binding.model.api; import static com.google.common.base.Preconditions.checkArgument; import static java.util.Objects.requireNonNull; import com.google.common.annotations.Beta; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.opendaylight.yangtools.concepts.Identifier; import org.opendaylight.yangtools.concepts.Immutable; /** * A type name. This class encloses Java type naming rules laid down in * The Java Language Specification, notably * sections 4 and 8. It deals with primitive, array and reference types. * * @author Robert Varga */ @Beta @NonNullByDefault public abstract class JavaTypeName implements Identifier, Immutable { private static final class Primitive extends JavaTypeName { private static final long serialVersionUID = 1L; Primitive(final String simpleName) { super(simpleName); } @Override public String packageName() { return ""; } @Override public Optional immediatelyEnclosingClass() { return Optional.empty(); } @Override public boolean canCreateEnclosed(final String simpleName) { throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type " + simpleName); } @Override public JavaTypeName createEnclosed(final String simpleName) { throw new UnsupportedOperationException("Primitive type " + simpleName() + " cannot enclose type " + simpleName); } @Override public String localName() { return simpleName(); } @Override public List localNameComponents() { return ImmutableList.of(simpleName()); } @Override public JavaTypeName createSibling(final String simpleName) { return new Primitive(simpleName); } @Override public JavaTypeName topLevelClass() { return this; } @Override public String toString() { return simpleName(); } } private abstract static class Reference extends JavaTypeName { private static final long serialVersionUID = 1L; Reference(final String simpleName) { super(simpleName); } @Override public boolean canCreateEnclosed(final String simpleName) { return !simpleName.equals(simpleName()); } @Override public final JavaTypeName createEnclosed(final String simpleName) { checkValidName(requireNonNull(simpleName)); return new Nested(this, simpleName); } @Override public final String toString() { return appendClass(new StringBuilder()).toString(); } void checkValidName(final String nestedName) { checkArgument(canCreateEnclosed(nestedName), "Nested class name %s conflicts with enclosing class %s", nestedName, this); } abstract StringBuilder appendClass(StringBuilder sb); } private static final class TopLevel extends Reference { private static final long serialVersionUID = 1L; private final String packageName; TopLevel(final String packageName, final String simpleName) { super(simpleName); checkArgument(!packageName.isEmpty()); this.packageName = packageName; } @Override public String packageName() { return packageName; } @Override public JavaTypeName createSibling(final String simpleName) { return new TopLevel(packageName, simpleName); } @Override public Optional immediatelyEnclosingClass() { return Optional.empty(); } @Override public String localName() { return simpleName(); } @Override public List localNameComponents() { final List ret = new ArrayList<>(); ret.add(simpleName()); return ret; } @Override public JavaTypeName topLevelClass() { return this; } @Override StringBuilder appendClass(final StringBuilder sb) { return sb.append(packageName).append('.').append(simpleName()); } } private static final class Nested extends Reference { private static final long serialVersionUID = 1L; private final Reference immediatelyEnclosingClass; Nested(final Reference immediatelyEnclosingClass, final String simpleName) { super(simpleName); this.immediatelyEnclosingClass = requireNonNull(immediatelyEnclosingClass); } @Override public String packageName() { return immediatelyEnclosingClass.packageName(); } @Override public JavaTypeName createSibling(final String simpleName) { return immediatelyEnclosingClass.createEnclosed(simpleName); } @Override public Optional immediatelyEnclosingClass() { return Optional.of(immediatelyEnclosingClass); } @Override StringBuilder appendClass(final StringBuilder sb) { return immediatelyEnclosingClass.appendClass(sb).append('.').append(simpleName()); } @Override public boolean canCreateEnclosed(final String simpleName) { return super.canCreateEnclosed(simpleName) && immediatelyEnclosingClass.canCreateEnclosed(simpleName); } @Override public String localName() { return immediatelyEnclosingClass.localName() + "." + simpleName(); } @Override public List localNameComponents() { final List ret = immediatelyEnclosingClass.localNameComponents(); ret.add(simpleName()); return ret; } @Override public JavaTypeName topLevelClass() { return immediatelyEnclosingClass.topLevelClass(); } } private static final long serialVersionUID = 1L; private final String simpleName; JavaTypeName(final String simpleName) { checkArgument(!simpleName.isEmpty()); this.simpleName = simpleName; } /** * Create a TypeName for an existing class. * * @param clazz Class instance * @return A new TypeName * @throws NullPointerException if clazz is null */ public static JavaTypeName create(final Class clazz) { final Class enclosing = clazz.getEnclosingClass(); if (enclosing != null) { return create(enclosing).createEnclosed(clazz.getSimpleName()); } final Package pkg = clazz.getPackage(); return pkg == null ? new Primitive(clazz.getSimpleName()) : new TopLevel(pkg.getName(), clazz.getSimpleName()); } /** * Create a TypeName for a top-level class. * * @param packageName Class package name * @param simpleName Class simple name * @return A new TypeName. * @throws NullPointerException if any of the arguments is null * @throws IllegalArgumentException if any of the arguments is empty */ public static JavaTypeName create(final String packageName, final String simpleName) { return new TopLevel(packageName, simpleName); } /** * Check if an enclosed type with specified name can be created. * * @param simpleName Simple name of the enclosed class * @return True if the proposed simple name does not conflict with any enclosing types * @throws IllegalArgumentException if the simpleName is empty * @throws UnsupportedOperationException if this type name does not support nested type */ public abstract boolean canCreateEnclosed(final String simpleName); /** * Create a TypeName for a class immediately enclosed by this class. * * @param simpleName Simple name of the enclosed class * @return A new TypeName. * @throws NullPointerException if simpleName is null * @throws IllegalArgumentException if the simpleName hides any of the enclosing types or if it is empty * @throws UnsupportedOperationException if this type name does not support nested type */ public abstract JavaTypeName createEnclosed(String simpleName); /** * Create a TypeName for a class that is a sibling of this class. A sibling has the same package name, and the same * immediately enclosing class. * * @param simpleName Simple name of the sibling class * @return A new TypeName. * @throws NullPointerException if simpleName is null * @throws IllegalArgumentException if the simpleName is empty */ // TODO: we could detect/allocate names in this method such that they don't conflict. public abstract JavaTypeName createSibling(String simpleName); /** * Return the simple name of the class. * * @return Simple name of the class. */ public final String simpleName() { return simpleName; } /** * Return the package name in which this class resides. This does not account for any class nesting, i.e. for nested * classes this returns the package name of the top-level class in naming hierarchy. * * @return Package name of the class. */ public abstract String packageName(); /** * Return the enclosing class JavaTypeName, if present. * * @return Enclosing class JavaTypeName. */ public abstract Optional immediatelyEnclosingClass(); /** * Return the top-level class JavaTypeName which is containing this type, or self if this type is a top-level * one. * * @return Top-level JavaTypeName */ public abstract JavaTypeName topLevelClass(); /** * Return the package-local name by which this type can be referenced by classes living in the same package. * * @return Local name. */ public abstract String localName(); /** * Return broken-down package-local name components. * * @return List of package-local components. */ public abstract List localNameComponents(); @Override public final int hashCode() { return Objects.hash(simpleName, packageName(), immediatelyEnclosingClass()); } @Override public final boolean equals(final @Nullable Object obj) { if (obj == this) { return true; } if (!(obj instanceof JavaTypeName)) { return false; } final JavaTypeName other = (JavaTypeName) obj; return simpleName.equals(other.simpleName) && packageName().equals(other.packageName()) && immediatelyEnclosingClass().equals(other.immediatelyEnclosingClass()); } /** * Return the Fully-Qualified Class Name string of this TypeName. * * @return Fully-Qualified Class Name string of this TypeName. */ @Override public abstract String toString(); }