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" file.
052     */
053    public static final String MSG_LEGACY_PACKAGE_HTML = "javadoc.legacyPackageHtml";
054
055    /**
056     * A key is pointing to the warning message text in "messages.properties" file.
057     */
058    public static final String MSG_PACKAGE_INFO = "javadoc.packageInfo";
059
060    /** The directories checked. */
061    private final Set<File> directoriesChecked = new HashSet<>();
062
063    /** Allow legacy {@code package.html} file to be used. */
064    private boolean allowLegacy;
065
066    /**
067     * Creates a new instance.
068     */
069    public JavadocPackageCheck() {
070        // java, not html!
071        // The rule is: Every JAVA file should have a package.html sibling
072        setFileExtensions("java");
073    }
074
075    /**
076     * Setter to specify the file extensions of the files to process.
077     *
078     * @param extensions the set of file extensions. A missing
079     *         initial '.' character of an extension is automatically added.
080     * @throws IllegalArgumentException is argument is null
081     */
082    @Override
083    public final void setFileExtensions(String... extensions) {
084        super.setFileExtensions(extensions);
085    }
086
087    @Override
088    protected void processFiltered(File file, FileText fileText) throws CheckstyleException {
089        // module-info.java is not a package member.
090        if (!"module-info.java".equals(file.getName())) {
091            // Check if already processed directory
092            final File dir;
093            try {
094                dir = file.getCanonicalFile().getParentFile();
095            }
096            catch (IOException exc) {
097                throw new CheckstyleException(
098                        "Exception while getting canonical path to file " + file.getPath(), exc);
099            }
100            final boolean isDirChecked = !directoriesChecked.add(dir);
101            if (!isDirChecked) {
102                // Check for the preferred file.
103                final Path packageInfo = Path.of(dir.getPath(), "package-info.java");
104                final Path packageHtml = Path.of(dir.getPath(), "package.html");
105
106                if (Files.exists(packageInfo)) {
107                    if (Files.exists(packageHtml)) {
108                        log(1, MSG_LEGACY_PACKAGE_HTML);
109                    }
110                }
111                else if (!allowLegacy || !Files.exists(packageHtml)) {
112                    log(1, MSG_PACKAGE_INFO);
113                }
114            }
115        }
116    }
117
118    /**
119     * Setter to allow legacy {@code package.html} file to be used.
120     *
121     * @param allowLegacy whether to allow support.
122     * @since 5.0
123     */
124    public void setAllowLegacy(boolean allowLegacy) {
125        this.allowLegacy = allowLegacy;
126    }
127
128}