Rework Java import tracking 09/70109/23
authorRobert Varga <robert.varga@pantheon.tech>
Sun, 25 Mar 2018 22:52:44 +0000 (00:52 +0200)
committerTom Pantelis <tompantelis@gmail.com>
Sat, 28 Apr 2018 12:06:08 +0000 (12:06 +0000)
In order to get nested class imports working, we need a proper
layer which understands how references inside Java code are
looked up.

Introduce AbstractJavaGeneratedType, which creates a tree structure
of how the class is layed out, thus it knows which types are declared
in a particular type's scope. There are two implementions:
- TopLevelJavaGeneratedType, which is responsible for managing what
  is imported into the compilation unit
- NestedJavaGeneratedType, which does not know about imports per se
  and delegates decisions to its containing type

Templates are updated to properly hook into the type hierarchy, so
their requests for type resolution are properly scoped.

One remaining wrinkle is BuilderTemplate, which abuses the template
system by using its built type as its type. This is worked around
in this patch and will need to be cleaned up later.

JIRA: MDSAL-327
Change-Id: Ie66a93ba85be26b056f118ba9fe14195e8d5a8ea
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
Signed-off-by: Jie Han <han.jie@zte.com.cn>
17 files changed:
binding/mdsal-binding-generator-api/src/main/java/org/opendaylight/mdsal/binding/model/api/JavaTypeName.java
binding/mdsal-binding-generator-api/src/test/java/org/opendaylight/mdsal/binding/model/api/JavaTypeNameTest.java
binding/mdsal-binding-generator-impl/src/main/java/org/opendaylight/mdsal/binding/generator/impl/AbstractTypeGenerator.java
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/AbstractJavaGeneratedType.java [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BaseTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/ClassTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/EnumTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/InterfaceTemplate.xtend
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/JavaFileTemplate.java
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/NestedJavaGeneratedType.java [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/TopLevelJavaGeneratedType.java [new file with mode: 0644]
binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/UnionTemplate.xtend
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/BuilderGeneratorTest.java
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/ClassCodeGeneratorTest.java
binding/mdsal-binding-java-api-generator/src/test/java/org/opendaylight/mdsal/binding/java/api/generator/test/CompilationTest.java
binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal327/test-imports.yang [new file with mode: 0644]

index 3ba1aaa5e23d19dcbd7141d058a8e6b1b371e3f7..7893682303fe17a6ee41ee6237f511135bc68d53 100644 (file)
@@ -11,6 +11,9 @@ 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;
@@ -57,11 +60,26 @@ public abstract class JavaTypeName implements Identifier, Immutable {
                     + simpleName);
         }
 
+        @Override
+        public String localName() {
+            return simpleName();
+        }
+
+        @Override
+        public List<String> 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();
@@ -125,6 +143,23 @@ public abstract class JavaTypeName implements Identifier, Immutable {
             return Optional.empty();
         }
 
+        @Override
+        public String localName() {
+            return simpleName();
+        }
+
+        @Override
+        public List<String> localNameComponents() {
+            final List<String> 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());
@@ -165,6 +200,23 @@ public abstract class JavaTypeName implements Identifier, Immutable {
         public boolean canCreateEnclosed(final String simpleName) {
             return super.canCreateEnclosed(simpleName) && immediatelyEnclosingClass.canCreateEnclosed(simpleName);
         }
+
+        @Override
+        public String localName() {
+            return immediatelyEnclosingClass.localName() + "." + simpleName();
+        }
+
+        @Override
+        public List<String> localNameComponents() {
+            final List<String> ret = immediatelyEnclosingClass.localNameComponents();
+            ret.add(simpleName());
+            return ret;
+        }
+
+        @Override
+        public JavaTypeName topLevelClass() {
+            return immediatelyEnclosingClass.topLevelClass();
+        }
     }
 
     private static final long serialVersionUID = 1L;
@@ -256,12 +308,34 @@ public abstract class JavaTypeName implements Identifier, Immutable {
     public abstract String packageName();
 
     /**
-     * Return the enclosing class TypeName, if present.
+     * Return the enclosing class JavaTypeName, if present.
      *
-     * @return Enclosing class TypeName.
+     * @return Enclosing class JavaTypeName.
      */
     public abstract Optional<JavaTypeName> 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<String> localNameComponents();
+
     @Override
     public final int hashCode() {
         return Objects.hash(simpleName, packageName(), immediatelyEnclosingClass());
index 8e9854347e40f672544be309cfcd27d653c40495..18e95063197149b8ffa19468d9b52e519d53bcc0 100644 (file)
@@ -7,8 +7,10 @@
  */
 package org.opendaylight.mdsal.binding.model.api;
 
+import static com.google.common.collect.ImmutableList.of;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 
 import java.util.Optional;
@@ -23,12 +25,18 @@ public class JavaTypeNameTest {
         assertEquals("byte", byteName.simpleName());
         assertEquals("byte", byteName.toString());
         assertEquals(Optional.empty(), byteName.immediatelyEnclosingClass());
+        assertSame(byteName, byteName.topLevelClass());
+        assertEquals(of("byte"), byteName.localNameComponents());
+        assertEquals("byte", byteName.localName());
 
         final JavaTypeName charName = byteName.createSibling("char");
         assertEquals("", charName.packageName());
         assertEquals("char", charName.simpleName());
         assertEquals("char", charName.toString());
         assertEquals(Optional.empty(), charName.immediatelyEnclosingClass());
+        assertSame(charName, charName.topLevelClass());
+        assertEquals(of("char"), charName.localNameComponents());
+        assertEquals("char", charName.localName());
 
         final JavaTypeName threadName = JavaTypeName.create(Thread.class);
         assertEquals("java.lang", threadName.packageName());
@@ -38,6 +46,9 @@ public class JavaTypeNameTest {
         assertTrue(threadName.canCreateEnclosed("Foo"));
         assertFalse(threadName.canCreateEnclosed("Thread"));
         assertEquals(threadName, JavaTypeName.create("java.lang", "Thread"));
+        assertSame(threadName, threadName.topLevelClass());
+        assertEquals(of("Thread"), threadName.localNameComponents());
+        assertEquals("Thread", threadName.localName());
 
         final JavaTypeName stringName = threadName.createSibling("String");
         assertEquals("java.lang", stringName.packageName());
@@ -51,6 +62,9 @@ public class JavaTypeNameTest {
         assertEquals("Foo", enclosedName.simpleName());
         assertEquals("java.lang.Thread.Foo", enclosedName.toString());
         assertEquals(Optional.of(threadName), enclosedName.immediatelyEnclosingClass());
+        assertSame(threadName, enclosedName.topLevelClass());
+        assertEquals(of("Thread", "Foo"), enclosedName.localNameComponents());
+        assertEquals("Thread.Foo", enclosedName.localName());
 
         final JavaTypeName uehName = JavaTypeName.create(Thread.UncaughtExceptionHandler.class);
         assertEquals("java.lang", uehName.packageName());
@@ -69,6 +83,11 @@ public class JavaTypeNameTest {
         assertTrue(siblingName.canCreateEnclosed("UncaughtExceptionHandler"));
         assertFalse(siblingName.canCreateEnclosed("Thread"));
         assertFalse(siblingName.canCreateEnclosed("Foo"));
+
+        assertTrue(threadName.equals(JavaTypeName.create(Thread.class)));
+        assertTrue(threadName.equals(threadName));
+        assertFalse(threadName.equals(null));
+        assertFalse(threadName.equals("foo"));
     }
 
     @Test(expected = IllegalArgumentException.class)
index 970bece0190e935269b6c040ad0a71d65ca1f1e7..047716a10f96d6fef14aff548a322357f7c4a2ed 100644 (file)
@@ -30,11 +30,9 @@ import static org.opendaylight.yangtools.yang.model.util.SchemaContextUtil.findP
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.Deque;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
@@ -1572,28 +1570,11 @@ abstract class AbstractTypeGenerator {
 
     private GeneratedTOBuilder createUnionBuilder(final GeneratedTOBuilder genTOBuilder,
             final GeneratedTypeBuilder typeBuilder) {
-        final StringBuilder sb = new StringBuilder();
-
         // Append enclosing path hierarchy without dots
-        // TODO: JavaTypeName could be giving us a Stream<String> or similar, but there is no sense in generalizing
-        //       unless there is another caller.
-        Optional<JavaTypeName> outerClass = genTOBuilder.getIdentifier().immediatelyEnclosingClass();
-        if (outerClass.isPresent()) {
-            final Deque<String> enclosingPath = new ArrayDeque<>();
-            do {
-                final JavaTypeName outerName = outerClass.get();
-                enclosingPath.push(outerName.simpleName());
-                outerClass = outerName.immediatelyEnclosingClass();
-            } while (outerClass.isPresent());
-
-            while (!enclosingPath.isEmpty()) {
-                sb.append(enclosingPath.pop());
-            }
-        }
-
-        sb.append(genTOBuilder.getName()).append("Builder");
+        final StringBuilder sb = new StringBuilder();
+        genTOBuilder.getIdentifier().localNameComponents().forEach(sb::append);
         final GeneratedTOBuilder unionBuilder = typeProvider.newGeneratedTOBuilder(
-            JavaTypeName.create(typeBuilder.getPackageName(), sb.toString()));
+            JavaTypeName.create(typeBuilder.getPackageName(), sb.append("Builder").toString()));
         unionBuilder.setIsUnionBuilder(true);
         return unionBuilder;
     }
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();
+    }
+}
index 63d426937ac5eb5eee792faff7ac8d8856adc5d0..6375119a6bbe3d1a1d9c6061a87b9e15bb36f92f 100644 (file)
@@ -61,6 +61,10 @@ abstract class BaseTemplate extends JavaFileTemplate {
         super(type)
     }
 
+    new(AbstractJavaGeneratedType javaType, GeneratedType type) {
+        super(javaType, type)
+    }
+
     final public def generate() {
         val _body = body()
         '''
index adbc98d08a4a085560e8767b6e0e7403314097cf..fb79ec096f7611d1474ec6af68b994a99ba2e1a4 100644 (file)
@@ -88,11 +88,16 @@ class BuilderTemplate extends BaseTemplate {
      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
      */
     new(GeneratedType genType) {
-        super(genType)
+        super(new TopLevelJavaGeneratedType(builderName(genType), genType), genType)
         this.properties = propertiesFromMethods(createMethods)
         addImport(Builder)
     }
 
+    def static builderName(GeneratedType genType) {
+        val name = genType.identifier
+        name.createSibling(name.simpleName + "Builder")
+    }
+
     /**
      * Returns set of method signature instances which contains all the methods of the <code>genType</code>
      * and all the methods of the implemented interfaces.
index 9b82cd1374709d3676acf9ebaa9f64a1768394f8..3e23a387a4fb198a194f2b81a37944b1bccff3f8 100644 (file)
@@ -8,7 +8,7 @@
 package org.opendaylight.mdsal.binding.java.api.generator
 
 import static java.util.Objects.requireNonNull
-import static extension org.apache.commons.text.StringEscapeUtils.escapeJava;
+import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
 
 import com.google.common.collect.ImmutableList
 import com.google.common.collect.Lists
@@ -26,7 +26,6 @@ import org.opendaylight.mdsal.binding.model.api.Constant
 import org.opendaylight.mdsal.binding.model.api.Enumeration
 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
-import org.opendaylight.mdsal.binding.model.api.GeneratedType
 import org.opendaylight.mdsal.binding.model.api.Restrictions
 import org.opendaylight.mdsal.binding.model.api.Type
 import org.opendaylight.mdsal.binding.model.util.TypeConstants
@@ -54,11 +53,6 @@ class ClassTemplate extends BaseTemplate {
      */
     protected val List<Constant> consts
 
-    /**
-     * List of generated types which are enclosed inside <code>genType</code>
-     */
-    protected val List<GeneratedType> enclosedGeneratedTypes;
-
     protected val GeneratedTransferObject genTO;
 
     private val AbstractRangeGenerator<?> rangeGenerator
@@ -69,7 +63,16 @@ class ClassTemplate extends BaseTemplate {
      * @param genType generated transfer object which will be transformed to JAVA class source code
      */
     new(GeneratedTransferObject genType) {
-        super(genType)
+        this(new TopLevelJavaGeneratedType(genType), genType)
+    }
+
+    /**
+     * Creates instance of this class with concrete <code>genType</code>.
+     *
+     * @param genType generated transfer object which will be transformed to JAVA class source code
+     */
+    new(AbstractJavaGeneratedType javaType, GeneratedTransferObject genType) {
+        super(javaType, genType)
         this.genTO = genType
         this.properties = genType.properties
         this.finalProperties = GeneratorUtil.resolveReadOnlyPropertiesFromTO(genTO.properties)
@@ -86,7 +89,6 @@ class ClassTemplate extends BaseTemplate {
         this.allProperties = sorted
         this.enums = genType.enumerations
         this.consts = genType.constantDefinitions
-        this.enclosedGeneratedTypes = genType.enclosedTypes
 
         if (restrictions !== null && restrictions.rangeConstraint.present) {
             rangeGenerator = requireNonNull(AbstractRangeGenerator.forType(findProperty(genType, "value").returnType))
@@ -180,12 +182,10 @@ class ClassTemplate extends BaseTemplate {
      * @return string with the source code for inner classes in JAVA format
      */
     def protected innerClassesDeclarations() '''
-        Â«IF !enclosedGeneratedTypes.empty»
-            Â«FOR innerClass : enclosedGeneratedTypes SEPARATOR "\n"»
+        Â«IF !type.enclosedTypes.empty»
+            Â«FOR innerClass : type.enclosedTypes SEPARATOR "\n"»
                 Â«IF (innerClass instanceof GeneratedTransferObject)»
-                    Â«val classTemplate = new ClassTemplate(innerClass)»
-                    Â«classTemplate.generateAsInnerClass»
-
+                    Â«new ClassTemplate(javaType.getEnclosedType(innerClass.identifier), innerClass).generateAsInnerClass»
                 Â«ENDIF»
             Â«ENDFOR»
         Â«ENDIF»
@@ -405,8 +405,7 @@ class ClassTemplate extends BaseTemplate {
     def protected enumDeclarations() '''
         Â«IF !enums.empty»
             Â«FOR e : enums SEPARATOR "\n"»
-                Â«val enumTemplate = new EnumTemplate(e)»
-                Â«enumTemplate.generateAsInnerClass»
+                Â«new EnumTemplate(javaType.getEnclosedType(e.identifier), e).generateAsInnerClass»
             Â«ENDFOR»
         Â«ENDIF»
     '''
index a322f842ced09ec828af7c274be98f91972e3bdf..5f814ae719a07d18c7099a10dd0bc073f50f17e7 100644 (file)
@@ -21,6 +21,16 @@ class EnumTemplate extends BaseTemplate {
      */
     val Enumeration enums
 
+    /**
+     * Constructs instance of this class with concrete <code>enums</code>.
+     *
+     * @param enums enumeration which will be transformed to JAVA source code
+     */
+    new(AbstractJavaGeneratedType javaType, Enumeration enums) {
+        super(javaType, enums as GeneratedType)
+        this.enums = enums
+    }
+
     /**
      * Constructs instance of this class with concrete <code>enums</code>.
      *
index 2d70587e06ac8d095be90b2c33c128a3bac1c188..81bd8067577270ed14680aa7c5f486c88cd4a8f3 100644 (file)
@@ -123,14 +123,13 @@ class InterfaceTemplate extends BaseTemplate {
         Â«IF !enclosedGeneratedTypes.empty»
             Â«FOR innerClass : enclosedGeneratedTypes SEPARATOR "\n"»
                 Â«IF (innerClass instanceof GeneratedTransferObject)»
+                    Â«val innerJavaType = javaType.getEnclosedType(innerClass.identifier)»
                     Â«IF innerClass.unionType»
-                        Â«val unionTemplate = new UnionTemplate(innerClass)»
+                        Â«val unionTemplate = new UnionTemplate(innerJavaType, innerClass)»
                         Â«unionTemplate.generateAsInnerClass»
-                        Â«addImports(unionTemplate)»
                     Â«ELSE»
-                        Â«val classTemplate = new ClassTemplate(innerClass)»
+                        Â«val classTemplate = new ClassTemplate(innerJavaType, innerClass)»
                         Â«classTemplate.generateAsInnerClass»
-                        Â«addImports(classTemplate)»
                     Â«ENDIF»
 
                 Â«ENDIF»
@@ -146,7 +145,7 @@ class InterfaceTemplate extends BaseTemplate {
     def private generateEnums() '''
         Â«IF !enums.empty»
             Â«FOR e : enums SEPARATOR "\n"»
-                Â«val enumTemplate = new EnumTemplate(e)»
+                Â«val enumTemplate = new EnumTemplate(javaType.getEnclosedType(e.identifier), e)»
                 Â«enumTemplate.generateAsInnerClass»
             Â«ENDFOR»
         Â«ENDIF»
index 591c64bcfe5dab98f776add1817a7deb0212f46c..f8aeba321c4ab282c171940648669303511ff239 100644 (file)
@@ -7,10 +7,9 @@
  */
 package org.opendaylight.mdsal.binding.java.api.generator;
 
+import static com.google.common.base.Verify.verify;
 import static java.util.Objects.requireNonNull;
 
-import java.util.HashMap;
-import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
@@ -24,15 +23,26 @@ import org.opendaylight.mdsal.binding.model.util.Types;
  * Base Java file template. Contains a non-null type and imports which the generated code refers to.
  */
 class JavaFileTemplate {
-    // Hidden to well-define operations
-    private final Map<String, JavaTypeName> importMap = new HashMap<>();
-
-    protected final GeneratedType type;
+    private final AbstractJavaGeneratedType javaType;
+    private final GeneratedType type;
 
     JavaFileTemplate(final GeneratedType type) {
+        this(new TopLevelJavaGeneratedType(type), type);
+    }
+
+    JavaFileTemplate(final AbstractJavaGeneratedType javaType, final GeneratedType type) {
+        this.javaType = requireNonNull(javaType);
         this.type = requireNonNull(type);
     }
 
+    final AbstractJavaGeneratedType javaType() {
+        return javaType;
+    }
+
+    final GeneratedType type() {
+        return type;
+    }
+
     final GeneratedProperty findProperty(final GeneratedTransferObject gto, final String name) {
         final Optional<GeneratedProperty> optProp = gto.getProperties().stream()
                 .filter(prop -> prop.getName().equals(name)).findFirst();
@@ -45,18 +55,13 @@ class JavaFileTemplate {
     }
 
     final String generateImportBlock() {
-        return importMap.entrySet().stream()
-                .filter(e -> isDefaultVisible(e.getValue()))
-                .sorted((e1, e2) -> {
-                    return e1.getValue().toString().compareTo(e2.getValue().toString());
-                })
-                .map(e -> "import " + e.getValue() + ";\n")
+        verify(javaType instanceof TopLevelJavaGeneratedType);
+        return ((TopLevelJavaGeneratedType) javaType).imports().map(name -> "import " + name + ";\n")
                 .collect(Collectors.joining());
     }
 
     final String importedName(final Type intype) {
-        GeneratorUtil.putTypeIntoImports(type, intype, importMap);
-        return GeneratorUtil.getExplicitType(type, intype, importMap);
+        return javaType.getReferenceString(intype);
     }
 
     final String importedName(final Class<?> cls) {
@@ -64,12 +69,7 @@ class JavaFileTemplate {
     }
 
     final void addImport(final Class<?> cls) {
-        final JavaTypeName name = JavaTypeName.create(cls);
-        importMap.put(name.simpleName(), name);
-    }
-
-    final void addImports(final JavaFileTemplate from) {
-        importMap.putAll(from.importMap);
+        javaType.getReferenceString(JavaTypeName.create(cls));
     }
 
     // Exposed for BuilderTemplate
@@ -77,18 +77,4 @@ class JavaFileTemplate {
         final Optional<JavaTypeName> optEnc = name.immediatelyEnclosingClass();
         return optEnc.isPresent() && type.getIdentifier().equals(optEnc.get());
     }
-
-    private boolean isDefaultVisible(final JavaTypeName name) {
-        return !hasSamePackage(name) || !isLocalInnerClass(name);
-    }
-
-    /**
-     * Checks if packages of generated type and imported type is the same
-     *
-     * @param importedTypePackageName the package name of imported type
-     * @return true if the packages are the same false otherwise
-     */
-    private boolean hasSamePackage(final JavaTypeName name) {
-        return type.getPackageName().equals(name.packageName());
-    }
 }
diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/NestedJavaGeneratedType.java b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/NestedJavaGeneratedType.java
new file mode 100644 (file)
index 0000000..5edbcf6
--- /dev/null
@@ -0,0 +1,86 @@
+/*
+ * 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 com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableList;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+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;
+
+/**
+ * A type which is nested inside some other type. It defers import decisions to its enclosing type, eventually arriving
+ * at a {@link TopLevelJavaGeneratedType}.
+ *
+ * @author Robert Varga
+ */
+@NonNullByDefault
+final class NestedJavaGeneratedType extends AbstractJavaGeneratedType {
+    private final AbstractJavaGeneratedType enclosingType;
+
+    NestedJavaGeneratedType(final AbstractJavaGeneratedType enclosingType, final GeneratedType genType) {
+        super(genType);
+        this.enclosingType = requireNonNull(enclosingType);
+    }
+
+    @Override
+    boolean importCheckedType(final JavaTypeName type) {
+        // Defer to enclosing type, which needs to re-run its checks
+        return enclosingType.checkAndImportType(type);
+    }
+
+    @Override
+    String localTypeName(final JavaTypeName type) {
+        // Check if the type is a reference to our immediately-enclosing type
+        if (enclosingType.getName().equals(type)) {
+            return enclosingType.getSimpleName();
+        }
+
+        final @Nullable List<String> descendant = findDescandantPath(type);
+        if (descendant == null) {
+            // The type is not present in our hierarchy, defer to our immediately-enclosing type, which may be able
+            // to find the target.
+            return enclosingType.localTypeName(type);
+        }
+
+        // Target type is a declared as a enclosed type of us and we have the path where it lurks.
+        final Iterator<String> it = descendant.iterator();
+        final StringBuilder sb = new StringBuilder().append(it.next());
+        while (it.hasNext()) {
+            sb.append('.').append(it.next());
+        }
+        return sb.toString();
+    }
+
+    private @Nullable List<String> findDescandantPath(final JavaTypeName type) {
+        Optional<JavaTypeName> optEnclosing = type.immediatelyEnclosingClass();
+        verify(optEnclosing.isPresent());
+
+        final Deque<String> queue = new ArrayDeque<>();
+        queue.addFirst(type.simpleName());
+        while (optEnclosing.isPresent()) {
+            final JavaTypeName enclosing = optEnclosing.get();
+            if (enclosing.equals(getName())) {
+                return ImmutableList.copyOf(queue);
+            }
+
+            queue.addFirst(enclosing.simpleName());
+            optEnclosing = enclosing.immediatelyEnclosingClass();
+        }
+
+        return null;
+    }
+}
diff --git a/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/TopLevelJavaGeneratedType.java b/binding/mdsal-binding-java-api-generator/src/main/java/org/opendaylight/mdsal/binding/java/api/generator/TopLevelJavaGeneratedType.java
new file mode 100644 (file)
index 0000000..93cb83e
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * 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 com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.Map.Entry;
+import java.util.stream.Stream;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.opendaylight.mdsal.binding.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+
+/**
+ * This class tracks types generated into a single Java compilation unit (file) and manages imports of that compilation
+ * unit. Since we are generating only public classes, this is synonymous to a top-level Java type.
+ */
+@NonNullByDefault
+final class TopLevelJavaGeneratedType extends AbstractJavaGeneratedType {
+    private final BiMap<JavaTypeName, String> importedTypes = HashBiMap.create();
+
+    TopLevelJavaGeneratedType(final GeneratedType genType) {
+        super(genType);
+    }
+
+    TopLevelJavaGeneratedType(final JavaTypeName name, final GeneratedType genType) {
+        super(name, genType);
+    }
+
+    @Override
+    String localTypeName(@NonNull final JavaTypeName type) {
+        // Locally-anchored type, this is simple: just strip the first local name component and concat the others
+        final Iterator<String> it = type.localNameComponents().iterator();
+        it.next();
+
+        final StringBuilder sb = new StringBuilder().append(it.next());
+        while (it.hasNext()) {
+            sb.append('.').append(it.next());
+        }
+        return sb.toString();
+    }
+
+    @Override
+    boolean importCheckedType(final JavaTypeName type) {
+        if (importedTypes.containsKey(type)) {
+            return true;
+        }
+        final String simpleName = type.simpleName();
+        if (importedTypes.containsValue(simpleName)) {
+            return false;
+        }
+        importedTypes.put(type, simpleName);
+        return true;
+    }
+
+    Stream<JavaTypeName> imports() {
+        return importedTypes.entrySet().stream().filter(this::needsExplicitImport).map(Entry::getKey)
+                .sorted(Comparator.comparing(JavaTypeName::toString));
+    }
+
+    private boolean needsExplicitImport(final Entry<JavaTypeName, String> entry) {
+        final JavaTypeName name = entry.getKey();
+
+        if (!getName().packageName().equals(name.packageName())) {
+            // Different package: need to import it
+            return true;
+        }
+
+        if (!name.immediatelyEnclosingClass().isPresent()) {
+            // This a top-level class import, we can skip it
+            return false;
+        }
+
+        // This is a nested class, we need to spell it out if the import entry points to the simple name
+        return entry.getValue().equals(name.simpleName());
+    }
+}
index 8ec3b584097e96d1444decbb0f0a109daeaee04b..71c80181d3b6b8b96c2402ca12b1bda80ac17da1 100644 (file)
@@ -23,6 +23,18 @@ import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
  */
 class UnionTemplate extends ClassTemplate {
 
+    /**
+     * Creates instance of this class with concrete <code>genType</code>.
+     *
+     * @param genType generated transfer object which will be transformed to JAVA class source code
+     */
+    new(NestedJavaGeneratedType javaType, GeneratedTransferObject genType) {
+        super(javaType, genType)
+        if (isBaseEncodingImportRequired) {
+            addImport(BaseEncoding)
+        }
+    }
+
     /**
      * Creates instance of this class with concrete <code>genType</code>.
      *
index 481bd924770946c4df975573a3d3e4ebef768814..c19625563ddd7827171391f62a39b02612e32681 100644 (file)
@@ -39,7 +39,7 @@ public class BuilderGeneratorTest {
         final GeneratedType genType = mockGenType("get" + TEST);
 
         assertEquals("@Override\n" +
-                "public java.lang.String toString() {\n" +
+                "public String toString() {\n" +
                 "    final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(\"test\");\n" +
                 "    CodeHelpers.appendValue(helper, \"_test\", _test);\n" +
                 "    return helper.toString();\n" +
@@ -49,7 +49,7 @@ public class BuilderGeneratorTest {
     @Test
     public void builderTemplateGenerateToStringWithoutAnyPropertyTest() throws Exception {
         assertEquals("@Override\n" +
-                "public java.lang.String toString() {\n" +
+                "public String toString() {\n" +
                 "    final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(\"test\");\n" +
                 "    return helper.toString();\n" +
                 "}\n", genToString(mockGenType(TEST)).toString());
@@ -58,7 +58,7 @@ public class BuilderGeneratorTest {
     @Test
     public void builderTemplateGenerateToStringWithMorePropertiesTest() throws Exception {
         assertEquals("@Override\n" +
-                "public java.lang.String toString() {\n" +
+                "public String toString() {\n" +
                 "    final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(\"test\");\n" +
                 "    CodeHelpers.appendValue(helper, \"_test1\", _test1);\n" +
                 "    CodeHelpers.appendValue(helper, \"_test2\", _test2);\n" +
@@ -69,7 +69,7 @@ public class BuilderGeneratorTest {
     @Test
     public void builderTemplateGenerateToStringWithoutPropertyWithAugmentTest() throws Exception {
         assertEquals("@Override\n" +
-                "public java.lang.String toString() {\n" +
+                "public String toString() {\n" +
                 "    final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(\"test\");\n" +
                 "    CodeHelpers.appendValue(helper, \"augmentation\", augmentation.values());\n" +
                 "    return helper.toString();\n" +
@@ -79,7 +79,7 @@ public class BuilderGeneratorTest {
     @Test
     public void builderTemplateGenerateToStringWithPropertyWithAugmentTest() throws Exception {
         assertEquals("@Override\n" +
-                "public java.lang.String toString() {\n" +
+                "public String toString() {\n" +
                 "    final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(\"test\");\n" +
                 "    CodeHelpers.appendValue(helper, \"_test\", _test);\n" +
                 "    CodeHelpers.appendValue(helper, \"augmentation\", augmentation.values());\n" +
@@ -90,7 +90,7 @@ public class BuilderGeneratorTest {
     @Test
     public void builderTemplateGenerateToStringWithMorePropertiesWithAugmentTest() throws Exception {
         assertEquals("@Override\n" +
-                "public java.lang.String toString() {\n" +
+                "public String toString() {\n" +
                 "    final MoreObjects.ToStringHelper helper = MoreObjects.toStringHelper(\"test\");\n" +
                 "    CodeHelpers.appendValue(helper, \"_test1\", _test1);\n" +
                 "    CodeHelpers.appendValue(helper, \"_test2\", _test2);\n" +
index 2bda5feb50b28994f5ddd98bef646e7bd9688905..558dfdee6d0c186229f85024fe45c6b776d9d5e6 100644 (file)
@@ -20,8 +20,8 @@ import org.opendaylight.mdsal.binding.java.api.generator.TOGenerator;
 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty;
 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject;
 import org.opendaylight.mdsal.binding.model.api.GeneratedType;
-import org.opendaylight.mdsal.binding.model.api.Type;
 import org.opendaylight.mdsal.binding.model.api.JavaTypeName;
+import org.opendaylight.mdsal.binding.model.api.Type;
 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedPropertyBuilder;
 import org.opendaylight.mdsal.binding.model.api.type.builder.GeneratedTOBuilder;
 import org.opendaylight.mdsal.binding.model.util.Types;
@@ -67,8 +67,7 @@ public class ClassCodeGeneratorTest {
                     final String outputStr = clsGen.generate(genTO);
 
                     assertNotNull(outputStr);
-                    assertTrue(outputStr.contains(
-                        "public CompositeKeyListKey(java.lang.Byte _key1, java.lang.String _key2)"));
+                    assertTrue(outputStr.contains("public CompositeKeyListKey(Byte _key1, String _key2)"));
 
                     assertEquals(2, propertyCount);
                     genTOsCount++;
index 221c52cc2af4e656311693a721852bb0eb3a10c2..a245f4c5198766a35057052cc89552e1856a33ba 100644 (file)
@@ -614,6 +614,15 @@ public class CompilationTest extends BaseCompilationTest {
         CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
     }
 
+    @Test
+    public void testMdsal327() throws Exception {
+        final File sourcesOutputDir = CompilationTestUtils.generatorOutput("mdsal327");
+        final File compiledOutputDir = CompilationTestUtils.compilerOutput("mdsal327");
+        generateTestSources("/compilation/mdsal327", sourcesOutputDir);
+        CompilationTestUtils.testCompilation(sourcesOutputDir, compiledOutputDir);
+        CompilationTestUtils.cleanUp(sourcesOutputDir, compiledOutputDir);
+    }
+
     @Test
     public void classNamesColisionTest() throws Exception {
         final File sourcesOutputDir = CompilationTestUtils.generatorOutput("class-name-collision");
diff --git a/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal327/test-imports.yang b/binding/mdsal-binding-java-api-generator/src/test/resources/compilation/mdsal327/test-imports.yang
new file mode 100644 (file)
index 0000000..3376cb5
--- /dev/null
@@ -0,0 +1,27 @@
+module test-imports {
+    yang-version 1;
+    namespace "urn:test:imports";
+    prefix "tet";
+
+    revision "2018-04-12" {
+    }
+
+    container map {
+        list class {
+            key "name";
+            leaf name {
+                type string;
+            }
+
+            leaf string {
+                type union {
+                  type string;
+                  type enumeration {
+                    enum string;
+                    enum int8;
+                  }
+                }
+            }
+       }
+    }
+}