001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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 ex) {
104                throw new IllegalStateException("Problem to read all modules including third "
105                        + "party if any. Problem detected at file: " + fileName, ex);
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").get(0);
134            final Element module = getDirectChildsByTag(element, moduleType.getLabel()).get(0);
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).get(0)
154                .getFirstChild().getNodeValue());
155        final List<Element> properties = getDirectChildsByTag(mod, "properties");
156        if (!properties.isEmpty()) {
157            final List<ModulePropertyDetails> modulePropertyDetailsList =
158                    createProperties(properties.get(0));
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(getDirectChildsByTag(prop, XML_TAG_DESCRIPTION)
193                    .get(0).getFirstChild().getNodeValue());
194            result.add(propertyDetails);
195        }
196        return result;
197    }
198
199    /**
200     * Utility to get the list contents by the attribute specified.
201     *
202     * @param element doc element
203     * @param listParent parent element of list
204     * @param listOption child list element
205     * @param attribute attribute key
206     * @return list of strings containing the XML list data
207     */
208    private static List<String> getListContentByAttribute(Element element, String listParent,
209                                                         String listOption, String attribute) {
210        final List<Element> children = getDirectChildsByTag(element, listParent);
211        List<String> result = null;
212        if (!children.isEmpty()) {
213            final NodeList nodeList = children.get(0).getElementsByTagName(listOption);
214            final int nodeListLength = nodeList.getLength();
215            final List<String> listContent = new ArrayList<>(nodeListLength);
216            for (int j = 0; j < nodeListLength; j++) {
217                listContent.add(getAttributeValue((Element) nodeList.item(j), attribute));
218            }
219            result = listContent;
220        }
221        return result;
222    }
223
224    /**
225     * Utility to get the children of an element by tag name.
226     *
227     * @param element parent element
228     * @param sTagName tag name of children required
229     * @return list of elements retrieved
230     */
231    private static List<Element> getDirectChildsByTag(Element element, String sTagName) {
232        final NodeList children = element.getElementsByTagName(sTagName);
233        final List<Element> res = new ArrayList<>();
234        for (int i = 0; i < children.getLength(); i++) {
235            if (children.item(i).getParentNode().equals(element)) {
236                res.add((Element) children.item(i));
237            }
238        }
239        return res;
240    }
241
242    /**
243     * Utility to get attribute value of an element.
244     *
245     * @param element target element
246     * @param attribute attribute key
247     * @return attribute value
248     */
249    private static String getAttributeValue(Element element, String attribute) {
250        return element.getAttributes().getNamedItem(attribute).getNodeValue();
251    }
252
253}