001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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.ResourcesScanner;
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 =
076                new Reflections("com.puppycrawl.tools.checkstyle.meta",
077                        new ResourcesScanner()).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, new ResourcesScanner())
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 ModuleDetails moduleDetails = new ModuleDetails();
135            final Element module = getDirectChildsByTag(element, moduleType.getLabel()).get(0);
136            moduleDetails.setModuleType(moduleType);
137            result = createModule(module, moduleDetails);
138        }
139        return result;
140    }
141
142    /**
143     * Create the module detail object from XML metadata.
144     *
145     * @param mod root XML document element
146     * @param moduleDetails module detail object, which is to be updated
147     * @return module detail object containing all metadata
148     */
149    private static ModuleDetails createModule(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        return moduleDetails;
168    }
169
170    /**
171     * Create module property details from the XML metadata.
172     *
173     * @param properties parent document element which contains property's metadata
174     * @return list of property details object created
175     */
176    private static List<ModulePropertyDetails> createProperties(Element properties) {
177        final List<ModulePropertyDetails> result = new ArrayList<>();
178        final NodeList propertyList = properties.getElementsByTagName("property");
179        for (int i = 0; i < propertyList.getLength(); 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 List<String> listContent = new ArrayList<>();
215            for (int j = 0; j < nodeList.getLength(); j++) {
216                listContent.add(getAttributeValue((Element) nodeList.item(j), attribute));
217            }
218            result = listContent;
219        }
220        return result;
221    }
222
223    /**
224     * Utility to get the children of an element by tag name.
225     *
226     * @param element parent element
227     * @param sTagName tag name of children required
228     * @return list of elements retrieved
229     */
230    private static List<Element> getDirectChildsByTag(Element element, String sTagName) {
231        final NodeList children = element.getElementsByTagName(sTagName);
232        final List<Element> res = new ArrayList<>();
233        for (int i = 0; i < children.getLength(); i++) {
234            if (children.item(i).getParentNode().equals(element)) {
235                res.add((Element) children.item(i));
236            }
237        }
238        return res;
239    }
240
241    /**
242     * Utility to get attribute value of an element.
243     *
244     * @param element target element
245     * @param attribute attribute key
246     * @return attribute value
247     */
248    private static String getAttributeValue(Element element, String attribute) {
249        return element.getAttributes().getNamedItem(attribute).getNodeValue();
250    }
251
252}