001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2022 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 list of 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<>();
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 List<ModulePropertyDetails> result = new ArrayList<>();
177        final NodeList propertyList = properties.getElementsByTagName("property");
178        for (int i = 0; i < propertyList.getLength(); i++) {
179            final ModulePropertyDetails propertyDetails = new ModulePropertyDetails();
180            final Element prop = (Element) propertyList.item(i);
181            propertyDetails.setName(getAttributeValue(prop, XML_TAG_NAME));
182            propertyDetails.setType(getAttributeValue(prop, "type"));
183            final String defaultValueTag = "default-value";
184            if (prop.hasAttribute(defaultValueTag)) {
185                propertyDetails.setDefaultValue(getAttributeValue(prop, defaultValueTag));
186            }
187            final String validationTypeTag = "validation-type";
188            if (prop.hasAttribute(validationTypeTag)) {
189                propertyDetails.setValidationType(getAttributeValue(prop, validationTypeTag));
190            }
191            propertyDetails.setDescription(getDirectChildsByTag(prop, XML_TAG_DESCRIPTION)
192                    .get(0).getFirstChild().getNodeValue());
193            result.add(propertyDetails);
194        }
195        return result;
196    }
197
198    /**
199     * Utility to get the list contents by the attribute specified.
200     *
201     * @param element doc element
202     * @param listParent parent element of list
203     * @param listOption child list element
204     * @param attribute attribute key
205     * @return list of strings containing the XML list data
206     */
207    private static List<String> getListContentByAttribute(Element element, String listParent,
208                                                         String listOption, String attribute) {
209        final List<Element> children = getDirectChildsByTag(element, listParent);
210        List<String> result = null;
211        if (!children.isEmpty()) {
212            final NodeList nodeList = children.get(0).getElementsByTagName(listOption);
213            final List<String> listContent = new ArrayList<>();
214            for (int j = 0; j < nodeList.getLength(); j++) {
215                listContent.add(getAttributeValue((Element) nodeList.item(j), attribute));
216            }
217            result = listContent;
218        }
219        return result;
220    }
221
222    /**
223     * Utility to get the children of an element by tag name.
224     *
225     * @param element parent element
226     * @param sTagName tag name of children required
227     * @return list of elements retrieved
228     */
229    private static List<Element> getDirectChildsByTag(Element element, String sTagName) {
230        final NodeList children = element.getElementsByTagName(sTagName);
231        final List<Element> res = new ArrayList<>();
232        for (int i = 0; i < children.getLength(); i++) {
233            if (children.item(i).getParentNode().equals(element)) {
234                res.add((Element) children.item(i));
235            }
236        }
237        return res;
238    }
239
240    /**
241     * Utility to get attribute value of an element.
242     *
243     * @param element target element
244     * @param attribute attribute key
245     * @return attribute value
246     */
247    private static String getAttributeValue(Element element, String attribute) {
248        return element.getAttributes().getNamedItem(attribute).getNodeValue();
249    }
250
251}