001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.javadoc;
021
022import java.io.File;
023import java.io.IOException;
024import java.nio.file.Files;
025import java.nio.file.Path;
026import java.util.HashSet;
027import java.util.Set;
028
029import com.puppycrawl.tools.checkstyle.GlobalStatefulCheck;
030import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
031import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
032import com.puppycrawl.tools.checkstyle.api.FileText;
033
034/**
035 * <div>
036 * Checks that each Java package has a Javadoc file used for commenting.
037 * By default, it only allows a {@code package-info.java} file,
038 * but can be configured to allow a {@code package.html} file.
039 * </div>
040 *
041 * <p>
042 * A violation will be reported if both files exist as this is not allowed by the Javadoc tool.
043 * </p>
044 *
045 * @since 5.0
046 */
047@GlobalStatefulCheck
048public class JavadocPackageCheck extends AbstractFileSetCheck {
049
050    /**
051     * A key is pointing to the warning message text in "messages.properties"
052     * file.
053     */
054    public static final String MSG_LEGACY_PACKAGE_HTML = "javadoc.legacyPackageHtml";
055
056    /**
057     * A key is pointing to the warning message text in "messages.properties"
058     * file.
059     */
060    public static final String MSG_PACKAGE_INFO = "javadoc.packageInfo";
061
062    /** The directories checked. */
063    private final Set<File> directoriesChecked = new HashSet<>();
064
065    /** Allow legacy {@code package.html} file to be used. */
066    private boolean allowLegacy;
067
068    /**
069     * Creates a new instance.
070     */
071    public JavadocPackageCheck() {
072        // java, not html!
073        // The rule is: Every JAVA file should have a package.html sibling
074        setFileExtensions("java");
075    }
076
077    /**
078     * Setter to specify the file extensions of the files to process.
079     *
080     * @param extensions the set of file extensions. A missing
081     *         initial '.' character of an extension is automatically added.
082     * @throws IllegalArgumentException is argument is null
083     */
084    @Override
085    public final void setFileExtensions(String... extensions) {
086        super.setFileExtensions(extensions);
087    }
088
089    @Override
090    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
091        // module-info.java is not a package member.
092        if (!"module-info.java".equals(file.getName())) {
093            // Check if already processed directory
094            final File dir;
095            try {
096                dir = file.getCanonicalFile().getParentFile();
097            }
098            catch (IOException exc) {
099                throw new CheckstyleException(
100                        "Exception while getting canonical path to file " + file.getPath(), exc);
101            }
102            final boolean isDirChecked = !directoriesChecked.add(dir);
103            if (!isDirChecked) {
104                // Check for the preferred file.
105                final Path packageInfo = Path.of(dir.getPath(), "package-info.java");
106                final Path packageHtml = Path.of(dir.getPath(), "package.html");
107
108                if (Files.exists(packageInfo)) {
109                    if (Files.exists(packageHtml)) {
110                        log(1, MSG_LEGACY_PACKAGE_HTML);
111                    }
112                }
113                else if (!allowLegacy || !Files.exists(packageHtml)) {
114                    log(1, MSG_PACKAGE_INFO);
115                }
116            }
117        }
118    }
119
120    /**
121     * Setter to allow legacy {@code package.html} file to be used.
122     *
123     * @param allowLegacy whether to allow support.
124     * @since 5.0
125     */
126    public void setAllowLegacy(boolean allowLegacy) {
127        this.allowLegacy = allowLegacy;
128    }
129
130}