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.meta;
021
022import java.io.IOException;
023import java.io.InputStream;
024import java.util.ArrayList;
025import java.util.HashSet;
026import java.util.List;
027import java.util.Set;
028import java.util.regex.Pattern;
029
030import javax.xml.XMLConstants;
031import javax.xml.parsers.DocumentBuilder;
032import javax.xml.parsers.DocumentBuilderFactory;
033import javax.xml.parsers.ParserConfigurationException;
034
035import org.reflections.Reflections;
036import org.reflections.scanners.Scanners;
037import org.w3c.dom.Document;
038import org.w3c.dom.Element;
039import org.w3c.dom.NodeList;
040import org.xml.sax.SAXException;
041
042/**
043 * Class having utilities required to read module details from an XML metadata file of a module.
044 * This class is used by plugins that need load of metadata from XML files.
045 */
046public final class XmlMetaReader {
047
048    /** Name tag of metadata XML files. */
049    private static final String XML_TAG_NAME = "name";
050
051    /** Description tag of metadata XML files. */
052    private static final String XML_TAG_DESCRIPTION = "description";
053
054    /**
055     * Do no allow {@code XmlMetaReader} instances to be created.
056     */
057    private XmlMetaReader() {
058    }
059
060    /**
061     * Utility to load all the metadata files present in the checkstyle JAR including third parties'
062     * module metadata files.
063     * checkstyle metadata files are grouped in a folder hierarchy similar to that of their
064     * corresponding source files.
065     * Third party(e.g. SevNTU Checks) metadata files are prefixed with {@code checkstylemeta-}
066     * to their file names.
067     *
068     * @param thirdPartyPackages fully qualified third party package names(can be only a
069     *                           hint, e.g. for SevNTU it can be com.github.sevntu / com.github)
070     * @return list of module details found in the classpath satisfying the above conditions
071     * @throws IllegalStateException if there was a problem reading the module metadata files
072     */
073    public static List<ModuleDetails> readAllModulesIncludingThirdPartyIfAny(
074            String... thirdPartyPackages) {
075        final Set<String> standardModuleFileNames = new Reflections(
076                "com.puppycrawl.tools.checkstyle.meta", Scanners.Resources)
077                .getResources(Pattern.compile(".*\\.xml"));
078        final Set<String> allMetadataSources = new HashSet<>(standardModuleFileNames);
079        for (String packageName : thirdPartyPackages) {
080            final Set<String> thirdPartyModuleFileNames =
081                    new Reflections(packageName, Scanners.Resources)
082                            .getResources(Pattern.compile(".*checkstylemeta-.*\\.xml"));
083            allMetadataSources.addAll(thirdPartyModuleFileNames);
084        }
085
086        final List<ModuleDetails> result = new ArrayList<>(allMetadataSources.size());
087        for (String fileName : allMetadataSources) {
088            final ModuleType moduleType;
089            if (fileName.endsWith("FileFilter.xml")) {
090                moduleType = ModuleType.FILEFILTER;
091            }
092            else if (fileName.endsWith("Filter.xml")) {
093                moduleType = ModuleType.FILTER;
094            }
095            else {
096                moduleType = ModuleType.CHECK;
097            }
098            final ModuleDetails moduleDetails;
099            try {
100                moduleDetails = read(XmlMetaReader.class.getResourceAsStream("/" + fileName),
101                        moduleType);
102            }
103            catch (ParserConfigurationException | IOException | SAXException exc) {
104                throw new IllegalStateException("Problem to read all modules including third "
105                        + "party if any. Problem detected at file: " + fileName, exc);
106            }
107            result.add(moduleDetails);
108        }
109
110        return result;
111    }
112
113    /**
114     * Read the module details from the supplied input stream of the module's XML metadata file.
115     *
116     * @param moduleMetadataStream input stream object of a module's metadata file
117     * @param moduleType type of module
118     * @return module detail object extracted from the XML metadata file
119     * @throws ParserConfigurationException if a parser configuration exception occurs
120     * @throws IOException if a IO exception occurs
121     * @throws SAXException if a SAX exception occurs during parsing the XML file
122     */
123    public static ModuleDetails read(InputStream moduleMetadataStream, ModuleType moduleType)
124            throws ParserConfigurationException, IOException, SAXException {
125        ModuleDetails result = null;
126        if (moduleType != null) {
127            final DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
128            factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
129            factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
130            final DocumentBuilder builder = factory.newDocumentBuilder();
131            final Document document = builder.parse(moduleMetadataStream);
132            final Element root = document.getDocumentElement();
133            final Element element = getDirectChildsByTag(root, "module").getFirst();
134            final Element module = getDirectChildsByTag(element, moduleType.getLabel()).getFirst();
135            result = new ModuleDetails();
136
137            result.setModuleType(moduleType);
138            populateModule(module, result);
139        }
140        return result;
141    }
142
143    /**
144     * Populate the module detail object from XML metadata.
145     *
146     * @param mod root XML document element
147     * @param moduleDetails module detail object, which is to be updated
148     */
149    private static void populateModule(Element mod, ModuleDetails moduleDetails) {
150        moduleDetails.setName(getAttributeValue(mod, XML_TAG_NAME));
151        moduleDetails.setFullQualifiedName(getAttributeValue(mod, "fully-qualified-name"));
152        moduleDetails.setParent(getAttributeValue(mod, "parent"));
153        moduleDetails.setDescription(getDirectChildsByTag(mod, XML_TAG_DESCRIPTION).getFirst()
154                .getFirstChild().getNodeValue());
155        final List<Element> properties = getDirectChildsByTag(mod, "properties");
156        if (!properties.isEmpty()) {
157            final List<ModulePropertyDetails> modulePropertyDetailsList =
158                    createProperties(properties.getFirst());
159            moduleDetails.addToProperties(modulePropertyDetailsList);
160        }
161        final List<String> messageKeys =
162                getListContentByAttribute(mod,
163                        "message-keys", "message-key", "key");
164        if (messageKeys != null) {
165            moduleDetails.addToViolationMessages(messageKeys);
166        }
167    }
168
169    /**
170     * Create module property details from the XML metadata.
171     *
172     * @param properties parent document element which contains property's metadata
173     * @return list of property details object created
174     */
175    private static List<ModulePropertyDetails> createProperties(Element properties) {
176        final NodeList propertyList = properties.getElementsByTagName("property");
177        final int propertyListLength = propertyList.getLength();
178        final List<ModulePropertyDetails> result = new ArrayList<>(propertyListLength);
179        for (int i = 0; i < propertyListLength; i++) {
180            final ModulePropertyDetails propertyDetails = new ModulePropertyDetails();
181            final Element prop = (Element) propertyList.item(i);
182            propertyDetails.setName(getAttributeValue(prop, XML_TAG_NAME));
183            propertyDetails.setType(getAttributeValue(prop, "type"));
184            final String defaultValueTag = "default-value";
185            if (prop.hasAttribute(defaultValueTag)) {
186                propertyDetails.setDefaultValue(getAttributeValue(prop, defaultValueTag));
187            }
188            final String validationTypeTag = "validation-type";
189            if (prop.hasAttribute(validationTypeTag)) {
190                propertyDetails.setValidationType(getAttributeValue(prop, validationTypeTag));
191            }
192            propertyDetails.setDescription(
193                    getDirectChildsByTag(prop, XML_TAG_DESCRIPTION)
194                            .getFirst().getFirstChild().getNodeValue());
195            result.add(propertyDetails);
196        }
197        return result;
198    }
199
200    /**
201     * Utility to get the list contents by the attribute specified.
202     *
203     * @param element doc element
204     * @param listParent parent element of list
205     * @param listOption child list element
206     * @param attribute attribute key
207     * @return list of strings containing the XML list data
208     */
209    private static List<String> getListContentByAttribute(Element element, String listParent,
210                                                         String listOption, String attribute) {
211        final List<Element> children = getDirectChildsByTag(element, listParent);
212        List<String> result = null;
213        if (!children.isEmpty()) {
214            final NodeList nodeList = children.getFirst().getElementsByTagName(listOption);
215            final int nodeListLength = nodeList.getLength();
216            final List<String> listContent = new ArrayList<>(nodeListLength);
217            for (int j = 0; j < nodeListLength; j++) {
218                listContent.add(getAttributeValue((Element) nodeList.item(j), attribute));
219            }
220            result = listContent;
221        }
222        return result;
223    }
224
225    /**
226     * Utility to get the children of an element by tag name.
227     *
228     * @param element parent element
229     * @param sTagName tag name of children required
230     * @return list of elements retrieved
231     */
232    private static List<Element> getDirectChildsByTag(Element element, String sTagName) {
233        final NodeList children = element.getElementsByTagName(sTagName);
234        final List<Element> res = new ArrayList<>();
235        for (int i = 0; i < children.getLength(); i++) {
236            if (children.item(i).getParentNode().equals(element)) {
237                res.add((Element) children.item(i));
238            }
239        }
240        return res;
241    }
242
243    /**
244     * Utility to get attribute value of an element.
245     *
246     * @param element target element
247     * @param attribute attribute key
248     * @return attribute value
249     */
250    private static String getAttributeValue(Element element, String attribute) {
251        return element.getAttributes().getNamedItem(attribute).getNodeValue();
252    }
253
254}