Merge pull request #1488 from lukasj/is1039

#1039: Call to JPackage.annotations() causes empty package-info.java to be generated
diff --git a/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JPackage.java b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JPackage.java
index 9343c48..1cfe13d 100644
--- a/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JPackage.java
+++ b/jaxb-ri/codemodel/codemodel/src/main/java/com/sun/codemodel/JPackage.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -46,18 +46,18 @@
     /**
      * List of classes contained within this package keyed by their name.
      */
-    private final Map<String,JDefinedClass> classes = new TreeMap<String,JDefinedClass>();
+    private final Map<String,JDefinedClass> classes = new TreeMap<>();
 
     /**
      * List of resources files inside this package.
      */
-    private final Set<JResourceFile> resources = new HashSet<JResourceFile>();
-    
+    private final Set<JResourceFile> resources = new HashSet<>();
+
     /**
      * All {@link JClass}s in this package keyed the upper case class name.
-     * 
+     *
      * This field is non-null only on Windows, to detect
-     * "Foo" and "foo" as a collision. 
+     * "Foo" and "foo" as a collision.
      */
     private final Map<String,JDefinedClass> upperCaseClassMap;
 
@@ -88,34 +88,39 @@
             String msg = "Package name . is not allowed";
             throw new IllegalArgumentException(msg);
         }
-        
+
         if(JCodeModel.isCaseSensitiveFileSystem)
             upperCaseClassMap = null;
         else
-            upperCaseClassMap = new HashMap<String,JDefinedClass>();
-        
+            upperCaseClassMap = new HashMap<>();
+
         this.name = name;
     }
 
 
+    @Override
     public JClassContainer parentContainer() {
         return parent();
     }
-    
+
     /**
      * Gets the parent package, or null if this class is the root package.
+     * @return
      */
     public JPackage parent() {
         if(name.length()==0)    return null;
-        
+
         int idx = name.lastIndexOf('.');
         return owner._package(name.substring(0,idx));
     }
 
+    @Override
     public boolean isClass() { return false; }
+    @Override
     public boolean isPackage() { return true; }
+    @Override
     public JPackage getPackage() { return this; }
-    
+
     /**
      * Add a class to this package.
      *
@@ -126,10 +131,11 @@
      *        Name of class to be added to this package
      *
      * @return Newly generated class
-     * 
+     *
      * @exception JClassAlreadyExistsException
      *      When the specified class/interface was already created.
      */
+    @Override
     public JDefinedClass _class(int mods, String name) throws JClassAlreadyExistsException {
         return _class(mods,name,ClassType.CLASS);
     }
@@ -139,38 +145,46 @@
      * @deprecated
      */
     @Deprecated
+    @Override
     public JDefinedClass _class( int mods, String name, boolean isInterface ) throws JClassAlreadyExistsException {
-    	return _class(mods,name, isInterface?ClassType.INTERFACE:ClassType.CLASS );
+        return _class(mods,name, isInterface?ClassType.INTERFACE:ClassType.CLASS );
     }
-    
+
+    @Override
     public JDefinedClass _class( int mods, String name, ClassType classTypeVal ) throws JClassAlreadyExistsException {
         if(classes.containsKey(name))
             throw new JClassAlreadyExistsException(classes.get(name));
         else {
             // XXX problems caught in the NC constructor
             JDefinedClass c = new JDefinedClass(this, mods, name, classTypeVal);
-            
+
             if( upperCaseClassMap!=null ) {
                 JDefinedClass dc = upperCaseClassMap.get(name.toUpperCase());
                 if(dc!=null)
                     throw new JClassAlreadyExistsException(dc);
                 upperCaseClassMap.put(name.toUpperCase(),c);
-            }            
+            }
             classes.put(name,c);
             return c;
         }
     }
 
-	/**
-	 * Adds a public class to this package.
-	 */
+    /**
+     * Adds a public class to this package.
+     *
+     * @param name
+     * @return
+     * @throws JClassAlreadyExistsException
+     */
+    @Override
     public JDefinedClass _class(String name) throws JClassAlreadyExistsException {
-		return _class( JMod.PUBLIC, name );
-	}
+        return _class(JMod.PUBLIC, name);
+    }
 
     /**
      * Gets a reference to the already created {@link JDefinedClass}.
-     * 
+     *
+     * @param name
      * @return null
      *      If the class is not yet created.
      */
@@ -183,7 +197,9 @@
 
     /**
      * Order is based on the lexicological order of the package name.
+     * @param that
      */
+    @Override
     public int compareTo(JPackage that) {
         return this.name.compareTo(that.name);
     }
@@ -198,18 +214,24 @@
      *        Name of interface to be added to this package
      *
      * @return Newly generated interface
+     * @throws JClassAlreadyExistsException
      */
+    @Override
     public JDefinedClass _interface(int mods, String name) throws JClassAlreadyExistsException {
         return _class(mods,name,ClassType.INTERFACE);
     }
 
     /**
      * Adds a public interface to this package.
+     * @param name
+     * @return
+     * @throws JClassAlreadyExistsException
      */
+    @Override
     public JDefinedClass _interface(String name) throws JClassAlreadyExistsException {
         return _interface(JMod.PUBLIC, name);
     }
-    
+
     /**
      * Add an annotationType Declaration to this package
      * @param name
@@ -218,12 +240,13 @@
      *      newly created Annotation Type Declaration
      * @exception JClassAlreadyExistsException
      *      When the specified class/interface was already created.
-     
+
      */
+    @Override
     public JDefinedClass _annotationTypeDeclaration(String name) throws JClassAlreadyExistsException {
     	return _class (JMod.PUBLIC,name,ClassType.ANNOTATION_TYPE_DECL);
     }
-	
+
     /**
      * Add a public enum to this package
      * @param name
@@ -232,21 +255,26 @@
      *      newly created Enum
      * @exception JClassAlreadyExistsException
      *      When the specified class/interface was already created.
-     
+
      */
+    @Override
     public JDefinedClass _enum (String name) throws JClassAlreadyExistsException {
     	return _class (JMod.PUBLIC,name,ClassType.ENUM);
     }
     /**
      * Adds a new resource file to this package.
+     * @param rsrc
+     * @return
      */
     public JResourceFile addResourceFile(JResourceFile rsrc) {
         resources.add(rsrc);
         return rsrc;
     }
-    
+
     /**
      * Checks if a resource of the given name exists.
+     * @param name
+     * @return
      */
     public boolean hasResourceFile(String name) {
         for (JResourceFile r : resources)
@@ -254,9 +282,10 @@
                 return true;
         return false;
     }
-    
+
     /**
      * Iterates all resource files in this package.
+     * @return
      */
     public Iterator<JResourceFile> propertyFiles() {
         return resources.iterator();
@@ -268,6 +297,7 @@
      *
      * @return JDocComment containing javadocs for this class
      */
+    @Override
     public JDocComment javadoc() {
         if (jdoc == null)
             jdoc = new JDocComment(owner());
@@ -276,6 +306,7 @@
 
     /**
      * Removes a class from this package.
+     * @param c
      */
     public void remove(JClass c) {
         if (c._package() != this)
@@ -288,9 +319,12 @@
         if (upperCaseClassMap != null)
             upperCaseClassMap.remove(c.name().toUpperCase());
     }
-	
+
     /**
      * Reference a class within this package.
+     * @param name
+     * @return
+     * @throws java.lang.ClassNotFoundException
      */
     public JClass ref(String name) throws ClassNotFoundException {
         if (name.indexOf('.') >= 0)
@@ -303,9 +337,11 @@
 
         return owner.ref(Class.forName(n));
     }
-    
+
     /**
      * Gets a reference to a sub package of this package.
+     * @param pkg
+     * @return
      */
     public JPackage subPackage( String pkg ) {
         if(isUnnamed())     return owner()._package(pkg);
@@ -315,11 +351,13 @@
     /**
      * Returns an iterator that walks the top-level classes defined in this
      * package.
+     * @return
      */
+    @Override
     public Iterator<JDefinedClass> classes() {
         return classes.values().iterator();
     }
-    
+
     /**
      * Checks if this package contains any classes.
      * @return {@code true} if this package contains any classes
@@ -331,6 +369,8 @@
 
     /**
      * Checks if a given name is already defined as a class/interface
+     * @param classLocalName
+     * @return
      */
     public boolean isDefined(String classLocalName) {
         Iterator<JDefinedClass> itr = classes();
@@ -344,6 +384,7 @@
 
     /**
      * Checks if this package is the root, unnamed package.
+     * @return
      */
     public final boolean isUnnamed() { return name.length() == 0; }
 
@@ -351,9 +392,9 @@
      * Get the name of this package
      *
      * @return
-     *		The name of this package, or the empty string if this is the
-     *		null package. For example, this method returns strings like
-     *		<code>"java.lang"</code>
+     *          The name of this package, or the empty string if this is the null
+     *          package. For example, this method returns strings like
+     *          <code>"java.lang"</code>
      */
     public String name() {
         return name;
@@ -361,35 +402,42 @@
 
     /**
      * Return the code model root object being used to create this package.
+     * @return
      */
+    @Override
     public final JCodeModel owner() { return owner; }
 
 
+    @Override
     public JAnnotationUse annotate(JClass clazz) {
         if(isUnnamed())
             throw new IllegalArgumentException("the root package cannot be annotated");
         if(annotations==null)
-           annotations = new ArrayList<JAnnotationUse>();
+           annotations = new ArrayList<>();
         JAnnotationUse a = new JAnnotationUse(clazz);
         annotations.add(a);
         return a;
     }
 
+    @Override
     public JAnnotationUse annotate(Class<? extends Annotation> clazz) {
         return annotate(owner.ref(clazz));
     }
 
+    @Override
     public <W extends JAnnotationWriter> W annotate2(Class<W> clazz) {
         return TypedAnnotationWriter.create(clazz,this);
     }
 
+    @Override
     public boolean removeAnnotation(JAnnotationUse annotation) {
         return this.annotations.remove(annotation);
     }
 
+    @Override
     public Collection<JAnnotationUse> annotations() {
         if (annotations == null)
-            annotations = new ArrayList<JAnnotationUse>();
+            annotations = new ArrayList<>();
         return Collections.unmodifiableList(annotations);
     }
 
@@ -401,11 +449,13 @@
         return new File(dir, name.replace('.', File.separatorChar));
     }
 
+    @Override
     public void declare(JFormatter f ) {
         if (name.length() != 0)
             f.p("package").p(name).p(';').nl();
     }
 
+    @Override
     public void generate(JFormatter f) {
         f.p(name);
     }
@@ -424,7 +474,7 @@
         }
 
         // write package annotations
-        if(annotations!=null || jdoc!=null) {
+        if ((annotations!=null && !annotations.isEmpty()) || jdoc != null) {
             JFormatter f = createJavaSourceFileWriter(src,"package-info");
 
             if (jdoc != null)
@@ -457,7 +507,7 @@
             r++;
         }
 
-        if(annotations!=null || jdoc!=null) {
+        if ((annotations!=null && !annotations.isEmpty()) || jdoc != null) {
             r++;
         }
 
diff --git a/jaxb-ri/codemodel/codemodel/src/test/java/com/sun/codemodel/tests/PackageAnnotationTest.java b/jaxb-ri/codemodel/codemodel/src/test/java/com/sun/codemodel/tests/PackageAnnotationTest.java
index 75356a5..7363951 100644
--- a/jaxb-ri/codemodel/codemodel/src/test/java/com/sun/codemodel/tests/PackageAnnotationTest.java
+++ b/jaxb-ri/codemodel/codemodel/src/test/java/com/sun/codemodel/tests/PackageAnnotationTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -7,7 +7,6 @@
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
-
 package com.sun.codemodel.tests;
 
 import java.io.IOException;
@@ -16,16 +15,33 @@
 import org.junit.Test;
 
 import com.sun.codemodel.JCodeModel;
+import com.sun.codemodel.JPackage;
 import com.sun.codemodel.writer.SingleStreamCodeWriter;
+import java.io.ByteArrayOutputStream;
+import org.junit.Assert;
 
 /**
  * @author Kohsuke Kawaguchi
  */
 public class PackageAnnotationTest {
-	@Test
-	public void main() throws IOException {
-		JCodeModel cm = new JCodeModel();
-		cm._package("foo").annotate(Inherited.class);
-		cm.build(new SingleStreamCodeWriter(System.out));
-	}
+
+    @Test
+    public void main() throws IOException {
+        JCodeModel cm = new JCodeModel();
+        cm._package("foo").annotate(Inherited.class);
+        cm.build(new SingleStreamCodeWriter(System.out));
+    }
+
+    @Test
+    public void noEmptyPInfo() throws IOException {
+        // https://github.com/eclipse-ee4j/jaxb-ri/issues/1039
+        JCodeModel cm = new JCodeModel();
+        JPackage pkg = cm._package("bar");
+        Assert.assertEquals(0, cm.countArtifacts());
+        Assert.assertNotNull(pkg.annotations());
+        Assert.assertEquals(0, cm.countArtifacts());
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        cm.build(new SingleStreamCodeWriter(baos));
+        Assert.assertTrue(baos.toString().trim().isEmpty());
+    }
 }