Issue #5127: Fixed NPE in JavadocPackageCheck when relative path is used to run checkstyle CLI
diff --git a/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheck.java b/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheck.java
index 18c22cd..e8190b7 100644
--- a/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheck.java
+++ b/src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheck.java
@@ -20,10 +20,12 @@
 package com.puppycrawl.tools.checkstyle.checks.javadoc;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
+import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
 import com.puppycrawl.tools.checkstyle.api.FileText;
 
 /**
@@ -67,9 +69,16 @@
     }
 
     @Override
-    protected void processFiltered(File file, FileText fileText) {
+    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
         // Check if already processed directory
-        final File dir = file.getParentFile();
+        final File dir;
+        try {
+            dir = file.getCanonicalFile().getParentFile();
+        }
+        catch (IOException ex) {
+            throw new CheckstyleException(
+                    "Exception while getting canonical path to file " + file.getPath(), ex);
+        }
         final boolean isDirChecked = !directoriesChecked.add(dir);
         if (!isDirChecked) {
             // Check for the preferred file.
diff --git a/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheckTest.java b/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheckTest.java
index 9a76336..ac88193 100644
--- a/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheckTest.java
+++ b/src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocPackageCheckTest.java
@@ -21,14 +21,21 @@
 
 import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck.MSG_LEGACY_PACKAGE_HTML;
 import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck.MSG_PACKAGE_INFO;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import java.io.File;
+import java.util.Collections;
 
 import org.junit.Test;
 
 import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
 import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
+import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
 import com.puppycrawl.tools.checkstyle.api.Configuration;
+import com.puppycrawl.tools.checkstyle.api.FileText;
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 
 public class JavadocPackageCheckTest
@@ -123,4 +130,48 @@
             getPath("annotation"
                     + File.separator + "package-info.java"), expected);
     }
+
+    /**
+     * Test require readable file with no parent to be used.
+     * Usage of Mockito.spy() is the only way to satisfy these requirements
+     * without the need to create new file in current working directory.
+     *
+     * @throws Exception if error occurs
+     */
+    @Test
+    public void testWithFileWithoutParent() throws Exception {
+        final DefaultConfiguration moduleConfig = createModuleConfig(JavadocPackageCheck.class);
+        final File fileWithoutParent = spy(new File(getPath("noparentfile"
+                    + File.separator + "package-info.java")));
+        when(fileWithoutParent.getParent()).thenReturn(null);
+        when(fileWithoutParent.getParentFile()).thenReturn(null);
+        final String[] expected = CommonUtils.EMPTY_STRING_ARRAY;
+        verify(createChecker(moduleConfig),
+                new File[] {fileWithoutParent},
+                getPath("annotation"
+                    + File.separator + "package-info.java"), expected);
+    }
+
+    /**
+     * Using direct call to check here because there is no other way
+     * to reproduce exception with invalid canonical path.
+     *
+     * @throws Exception if error occurs
+     */
+    @Test
+    public void testCheckstyleExceptionIfFailedToGetCanonicalPathToFile() throws Exception {
+        final JavadocPackageCheck check = new JavadocPackageCheck();
+        final File fileWithInvalidPath = new File("\u0000\u0000\u0000");
+        final FileText mockFileText = new FileText(fileWithInvalidPath, Collections.emptyList());
+        final String expectedExceptionMessage =
+                "Exception while getting canonical path to file " + fileWithInvalidPath.getPath();
+        try {
+            check.processFiltered(fileWithInvalidPath, mockFileText);
+            fail("CheckstyleException expected to be thrown");
+        }
+        catch (CheckstyleException ex) {
+            assertEquals("Invalid exception message. Expected: " + expectedExceptionMessage,
+                    expectedExceptionMessage, ex.getMessage());
+        }
+    }
 }
diff --git a/src/test/resources/com/puppycrawl/tools/checkstyle/checks/javadoc/javadocpackage/noparentfile/package-info.java b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/javadoc/javadocpackage/noparentfile/package-info.java
new file mode 100644
index 0000000..f7832c2
--- /dev/null
+++ b/src/test/resources/com/puppycrawl/tools/checkstyle/checks/javadoc/javadocpackage/noparentfile/package-info.java
@@ -0,0 +1 @@
+package com.puppycrawl.tools.checkstyle.checks.javadoc.javadocpackage.noparentfile;