Repository: assertj/assertj

Problem statement:
`hasNoPackage()` assertion for arrays and primitive types
**Describe the bug**

[`Class::getPackage`](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getPackage()) mentions:

> If this class represents an array type, a primitive type or void, this method returns `null`.

However, [`hasPackage(Package)`](https://www.javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/api/AbstractClassAssert.html#hasPackage(java.lang.Package)) does not allow a `null` parameter.

**Test case reproducing the bug**

```java
assertThat(int[].class.getPackage()).isNull();  // succeeds
assertThat(Integer.TYPE.getPackage()).isNull(); // succeeds
assertThat(Void.TYPE.getPackage()).isNull();    // succeeds

Package expected = null;

assertThat(int[].class).hasPackage(expected);  // fails with NPE
assertThat(Integer.TYPE).hasPackage(expected); // fails with NPE
assertThat(Void.TYPE).hasPackage(expected);    // fails with NPE
```


Hints:
Rather than changing the behavior of `hasPackage(Package)` and potentially `hasPackage(String)`, we should probably add a new `hasNoPackage()` assertion, in the style of [`hasNoSuperclass()`](https://www.javadoc.io/doc/org.assertj/assertj-core/latest/org/assertj/core/api/AbstractClassAssert.html#hasNoSuperclass()).

Patch:
diff --git a/assertj-core/src/main/java/org/assertj/core/api/AbstractClassAssert.java b/assertj-core/src/main/java/org/assertj/core/api/AbstractClassAssert.java
--- a/assertj-core/src/main/java/org/assertj/core/api/AbstractClassAssert.java
+++ b/assertj-core/src/main/java/org/assertj/core/api/AbstractClassAssert.java
@@ -28,6 +28,7 @@
 import static org.assertj.core.error.ShouldBeInterface.shouldNotBeInterface;
 import static org.assertj.core.error.ShouldBeRecord.shouldBeRecord;
 import static org.assertj.core.error.ShouldBeRecord.shouldNotBeRecord;
+import static org.assertj.core.error.ShouldHaveNoPackage.shouldHaveNoPackage;
 import static org.assertj.core.error.ShouldHaveNoSuperclass.shouldHaveNoSuperclass;
 import static org.assertj.core.error.ShouldHavePackage.shouldHavePackage;
 import static org.assertj.core.error.ShouldHaveRecordComponents.shouldHaveRecordComponents;
@@ -655,8 +656,8 @@ public SELF hasAnnotation(Class<? extends Annotation> annotation) {
   /**
    * Verifies that the actual {@code Class} has the given class as direct superclass (as in {@link Class#getSuperclass()}).
    * <p>
-   * The {@code superclass} should always be not {@code null}, use {@link #hasNoSuperclass()} to verify the absence of
-   * the superclass.
+   * The expected {@code superclass} should always be not {@code null}. To verify the absence of the superclass, use
+   * {@link #hasNoSuperclass()}.
    * <p>
    * Example:
    * <pre><code class='java'> // this assertion succeeds:
@@ -967,7 +968,9 @@ public SELF hasPublicMethods(String... methodNames) {
 
   /**
    * Verifies that the actual {@code Class} has the given package name (as in {@link Class#getPackage()}).
-   *
+   * <p>
+   * The expected package name should always be not {@code null}. To verify the absence of the package, use
+   * {@link #hasNoPackage()}. 
    * <p>
    * Example:
    * <pre><code class='java'> package one.two;
@@ -1005,7 +1008,9 @@ private void assertHasPackage(String packageName) {
 
   /**
    * Verifies that the actual {@code Class} has the given package (as in {@link Class#getPackage()}).
-   *
+   * <p>
+   * The expected package should always be not {@code null}. To verify the absence of the package, use
+   * {@link #hasNoPackage()}. 
    * <p>
    * Example:
    * <pre><code class='java'> package one.two;
@@ -1027,6 +1032,8 @@ private void assertHasPackage(String packageName) {
    * @throws AssertionError if the actual {@code Class} does not have the given package.
    *
    * @since 3.18.0
+   * @see #hasPackage(String)
+   * @see #hasNoPackage()
    */
   public SELF hasPackage(Package expected) {
     isNotNull();
@@ -1039,4 +1046,39 @@ private void assertHasPackage(Package expected) {
     if (!expected.equals(actual.getPackage())) throw assertionError(shouldHavePackage(actual, expected));
   }
 
+  /**
+   * Verifies that the actual {@code Class} has no package (as in {@link Class#getPackage()}, when {@code null}
+   * is returned).
+   * <p>
+   * Example:
+   * <pre><code class='java'> // this assertion succeeds as arrays have no package:
+   * assertThat(int[].class).hasNoPackage();
+   *
+   * // this assertion succeeds as primitive types have no package:
+   * assertThat(Integer.TYPE).hasNoPackage();
+   *
+   * // this assertion succeeds as void type has no package:
+   * assertThat(Void.TYPE).hasNoPackage();
+   *
+   * // this assertion fails as Object has java.lang as package:
+   * assertThat(Object.class).hasNoPackage();</code></pre>
+   *
+   * @return {@code this} assertions object
+   * @throws AssertionError if {@code actual} is {@code null}.
+   * @throws AssertionError if the actual {@code Class} has a package.
+   *
+   * @since 3.25.0
+   * @see #hasPackage(Package)
+   * @see #hasPackage(String)
+   */
+  public SELF hasNoPackage() {
+    isNotNull();
+    assertHasNoPackage();
+    return myself;
+  }
+
+  private void assertHasNoPackage() {
+    if (actual.getPackage() != null) throw assertionError(shouldHaveNoPackage(actual));
+  }
+
 }
diff --git a/assertj-core/src/main/java/org/assertj/core/error/ShouldHaveNoPackage.java b/assertj-core/src/main/java/org/assertj/core/error/ShouldHaveNoPackage.java
new file mode 100644
--- /dev/null
+++ b/assertj-core/src/main/java/org/assertj/core/error/ShouldHaveNoPackage.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2023 the original author or authors.
+ */
+package org.assertj.core.error;
+
+import java.util.StringJoiner;
+
+/**
+ * Creates an error message indicating that an assertion that verifies that a class has no package failed.
+ */
+public class ShouldHaveNoPackage extends BasicErrorMessageFactory {
+
+  private static final String SHOULD_HAVE_NO_PACKAGE = new StringJoiner("%n", "%n", "").add("Expecting")
+                                                                                       .add("  %s")
+                                                                                       .add("to have no package, but had:")
+                                                                                       .add("  %s")
+                                                                                       .toString();
+
+  /**
+   * Creates a new <code>{@link ShouldHaveNoPackage}</code>.
+   *
+   * @param actual the actual value in the failed assertion.
+   * @return the created {@code ErrorMessageFactory}.
+   */
+  public static ErrorMessageFactory shouldHaveNoPackage(Class<?> actual) {
+    return new ShouldHaveNoPackage(actual);
+  }
+
+  private ShouldHaveNoPackage(Class<?> actual) {
+    super(SHOULD_HAVE_NO_PACKAGE, actual, actual.getSuperclass());
+  }
+
+}


Test patch:
diff --git a/assertj-core/src/test/java/org/assertj/core/api/classes/ClassAssert_hasNoPackage_Test.java b/assertj-core/src/test/java/org/assertj/core/api/classes/ClassAssert_hasNoPackage_Test.java
new file mode 100644
--- /dev/null
+++ b/assertj-core/src/test/java/org/assertj/core/api/classes/ClassAssert_hasNoPackage_Test.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2023 the original author or authors.
+ */
+package org.assertj.core.api.classes;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.assertj.core.error.ShouldHaveNoPackage.shouldHaveNoPackage;
+import static org.assertj.core.util.AssertionsUtil.expectAssertionError;
+import static org.assertj.core.util.FailureMessages.actualIsNull;
+
+import java.util.stream.Stream;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class ClassAssert_hasNoPackage_Test {
+
+  @Test
+  void should_fail_if_actual_is_null() {
+    // GIVEN
+    Class<?> actual = null;
+    // WHEN
+    AssertionError assertionError = expectAssertionError(() -> assertThat(actual).hasNoPackage());
+    // THEN
+    then(assertionError).hasMessage(actualIsNull());
+  }
+
+  @Test
+  void should_fail_if_actual_has_a_package() {
+    // GIVEN
+    Class<?> actual = Object.class;
+    // WHEN
+    AssertionError assertionError = expectAssertionError(() -> assertThat(actual).hasNoPackage());
+    // THEN
+    then(assertionError).hasMessage(shouldHaveNoPackage(actual).create());
+  }
+
+  @ParameterizedTest
+  @MethodSource("nullPackageTypes")
+  void should_pass_if_actual_has_no_package(Class<?> actual) {
+    // WHEN/THEN
+    assertThat(actual).hasNoPackage();
+  }
+
+  private static Stream<Class<?>> nullPackageTypes() {
+    return Stream.of(int[].class, // any array
+                     Boolean.TYPE,
+                     Byte.TYPE,
+                     Character.TYPE,
+                     Double.TYPE,
+                     Float.TYPE,
+                     Integer.TYPE,
+                     Long.TYPE,
+                     Short.TYPE,
+                     Void.TYPE);
+  }
+
+}
diff --git a/assertj-core/src/test/java/org/assertj/core/error/ShouldHaveNoPackage_create_Test.java b/assertj-core/src/test/java/org/assertj/core/error/ShouldHaveNoPackage_create_Test.java
new file mode 100644
--- /dev/null
+++ b/assertj-core/src/test/java/org/assertj/core/error/ShouldHaveNoPackage_create_Test.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ *
+ * Copyright 2012-2023 the original author or authors.
+ */
+package org.assertj.core.error;
+
+import static java.lang.String.format;
+import static org.assertj.core.api.BDDAssertions.then;
+import static org.assertj.core.error.ShouldHaveNoPackage.shouldHaveNoPackage;
+import static org.assertj.core.presentation.StandardRepresentation.STANDARD_REPRESENTATION;
+
+import org.assertj.core.internal.TestDescription;
+import org.junit.jupiter.api.Test;
+
+class ShouldHaveNoPackage_create_Test {
+
+  @Test
+  void should_create_error_message() {
+    // WHEN
+    String message = shouldHaveNoPackage(String.class).create(new TestDescription("TEST"), STANDARD_REPRESENTATION);
+    // THEN
+    then(message).isEqualTo(format("[TEST] %n" +
+                                   "Expecting%n" +
+                                   "  java.lang.String%n" +
+                                   "to have no package, but had:%n" +
+                                   "  java.lang.Object"));
+  }
+
+}
diff --git a/assertj-core/src/test/java/org/assertj/core/error/ShouldHaveNoSuperclass_create_Test.java b/assertj-core/src/test/java/org/assertj/core/error/ShouldHaveNoSuperclass_create_Test.java
--- a/assertj-core/src/test/java/org/assertj/core/error/ShouldHaveNoSuperclass_create_Test.java
+++ b/assertj-core/src/test/java/org/assertj/core/error/ShouldHaveNoSuperclass_create_Test.java
@@ -17,18 +17,9 @@
 import static org.assertj.core.error.ShouldHaveNoSuperclass.shouldHaveNoSuperclass;
 import static org.assertj.core.presentation.StandardRepresentation.STANDARD_REPRESENTATION;
 
-import org.assertj.core.description.Description;
 import org.assertj.core.internal.TestDescription;
-import org.assertj.core.presentation.Representation;
-import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
 
-/**
- * Tests for <code>{@link ShouldHaveNoSuperclass#create(Description, Representation)}</code>.
- *
- * @author Stefano Cordio
- */
-@DisplayName("ShouldHaveNoSuperclass create")
 class ShouldHaveNoSuperclass_create_Test {
 
   @Test