Rework Java import tracking
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / AbstractJavaGeneratedType.java
diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractJavaGeneratedType.java b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractJavaGeneratedType.java
new file mode 100644 (file)
index 0000000..fe60bbb
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ * 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.java.api.generator;
+
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableMap.Builder;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Streams;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.concurrent.NotThreadSafe;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+import org.opendaylight.mdsal.binding.model.api.ParameterizedType;
+import org.opendaylight.mdsal.binding.model.api.Type;
+import org.opendaylight.mdsal.binding.model.api.WildcardType;
+
+/**
+ * Abstract class representing a generated type, either top-level or nested. It takes care of tracking references
+ * to other Java types and resolving them as best as possible.
+ *
+ * @author Robert Varga
+ */
+@NonNullByDefault
+@NotThreadSafe
+abstract class AbstractJavaGeneratedType {
+    private final Map<JavaTypeName, @Nullable String> nameCache = new HashMap<>();
+    private final Map<String, NestedJavaGeneratedType> enclosedTypes;
+    private final Set<String> conflictingNames;
+
+    private final JavaTypeName name;
+
+    AbstractJavaGeneratedType(final GeneratedType genType) {
+        name = genType.getIdentifier();
+        final Builder<String, NestedJavaGeneratedType> b = ImmutableMap.builder();
+        for (GeneratedType type : Iterables.concat(genType.getEnclosedTypes(), genType.getEnumerations())) {
+            b.put(type.getIdentifier().simpleName(), new NestedJavaGeneratedType(this, type));
+        }
+        enclosedTypes = b.build();
+        conflictingNames = ImmutableSet.of();
+    }
+
+    AbstractJavaGeneratedType(final JavaTypeName name, final GeneratedType genType) {
+        this.name = requireNonNull(name);
+        enclosedTypes = ImmutableMap.of();
+
+        // This is a workaround for BuilderTemplate, which does not model itself correctly -- it should generate
+        // a GeneratedType for the Builder with a nested type for the implementation, which really should be
+        // a different template which gets generated as an inner type.
+        conflictingNames = Streams.concat(genType.getEnclosedTypes().stream(), genType.getEnumerations().stream())
+        .map(type -> type.getIdentifier().simpleName()).collect(ImmutableSet.toImmutableSet());
+    }
+
+    final JavaTypeName getName() {
+        return name;
+    }
+
+    final String getSimpleName() {
+        return name.simpleName();
+    }
+
+    final String getReferenceString(final Type type) {
+        if (!(type instanceof ParameterizedType)) {
+            return getReferenceString(type.getIdentifier());
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(getReferenceString(type.getIdentifier())).append('<');
+        final Type[] types = ((ParameterizedType) type).getActualTypeArguments();
+        if (types.length == 0) {
+            return sb.append("?>").toString();
+        }
+
+        for (int i = 0; i < types.length; i++) {
+            final Type t = types[i];
+            if (t instanceof WildcardType) {
+                sb.append("? extends ");
+            }
+            sb.append(getReferenceString(t));
+            if (i != types.length - 1) {
+                sb.append(", ");
+            }
+        }
+
+        return sb.append('>').toString();
+    }
+
+    final String getReferenceString(final JavaTypeName type) {
+        if (type.packageName().isEmpty()) {
+            // This is a packageless primitive type, refer to it directly
+            return type.simpleName();
+        }
+
+        // Self-reference, return simple name
+        if (name.equals(type)) {
+            return name.simpleName();
+        }
+
+        // Fast path: we have already resolved how to refer to this type
+        final String existing = nameCache.get(type);
+        if (existing != null) {
+            return existing;
+        }
+
+        // Fork based on whether the class is in this compilation unit, package or neither
+        final String result;
+        if (name.topLevelClass().equals(type.topLevelClass())) {
+            result = localTypeName(type);
+        } else if (name.packageName().equals(type.packageName())) {
+            result = packageTypeName(type);
+        } else {
+            result = foreignTypeName(type);
+        }
+
+        nameCache.put(type, result);
+        return result;
+    }
+
+    final NestedJavaGeneratedType getEnclosedType(final JavaTypeName type) {
+        return requireNonNull(enclosedTypes.get(type.simpleName()));
+    }
+
+    final boolean checkAndImportType(final JavaTypeName type) {
+        // We can import the type only if it does not conflict with us or our immediately-enclosed types
+        final String simpleName = type.simpleName();
+        return !simpleName.equals(getSimpleName()) && !enclosedTypes.containsKey(simpleName)
+                && !conflictingNames.contains(simpleName) && importCheckedType(type);
+    }
+
+    abstract boolean importCheckedType(JavaTypeName type);
+
+    abstract String localTypeName(JavaTypeName type);
+
+    private String foreignTypeName(final JavaTypeName type) {
+        return checkAndImportType(type) ? type.simpleName() : type.toString();
+    }
+
+    private String packageTypeName(final JavaTypeName type) {
+        // Try to anchor the top-level type and use a local reference
+        return checkAndImportType(type.topLevelClass()) ? type.localName() : type.toString();
+    }
+}