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