Home | History | Annotate | Download | only in api
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tools.lint.client.api;
     18 
     19 import com.android.annotations.NonNull;
     20 import com.android.tools.lint.detector.api.Detector;
     21 import com.android.tools.lint.detector.api.Detector.XmlScanner;
     22 import com.android.tools.lint.detector.api.LintUtils;
     23 import com.android.tools.lint.detector.api.XmlContext;
     24 import com.google.common.annotations.Beta;
     25 
     26 import org.w3c.dom.Attr;
     27 import org.w3c.dom.Element;
     28 import org.w3c.dom.NamedNodeMap;
     29 import org.w3c.dom.Node;
     30 import org.w3c.dom.NodeList;
     31 
     32 import java.io.File;
     33 import java.util.ArrayList;
     34 import java.util.Collection;
     35 import java.util.HashMap;
     36 import java.util.List;
     37 import java.util.Map;
     38 import java.util.RandomAccess;
     39 
     40 /**
     41  * Specialized visitor for running detectors on an XML document.
     42  * It operates in two phases:
     43  * <ol>
     44  *   <li> First, it computes a set of maps where it generates a map from each
     45  *        significant element name, and each significant attribute name, to a list
     46  *        of detectors to consult for that element or attribute name.
     47  *        The set of element names or attribute names (or both) that a detector
     48  *        is interested in is provided by the detectors themselves.
     49  *   <li> Second, it iterates over the document a single time. For each element and
     50  *        attribute it looks up the list of interested detectors, and runs them.
     51  * </ol>
     52  * It also notifies all the detectors before and after the document is processed
     53  * such that they can do pre- and post-processing.
     54  * <p>
     55  * <b>NOTE: This is not a public or final API; if you rely on this be prepared
     56  * to adjust your code for the next tools release.</b>
     57  */
     58 @Beta
     59 class XmlVisitor {
     60     private final Map<String, List<Detector.XmlScanner>> mElementToCheck =
     61             new HashMap<String, List<Detector.XmlScanner>>();
     62     private final Map<String, List<Detector.XmlScanner>> mAttributeToCheck =
     63             new HashMap<String, List<Detector.XmlScanner>>();
     64     private final List<Detector.XmlScanner> mDocumentDetectors =
     65             new ArrayList<Detector.XmlScanner>();
     66     private final List<Detector.XmlScanner> mAllElementDetectors =
     67             new ArrayList<Detector.XmlScanner>();
     68     private final List<Detector.XmlScanner> mAllAttributeDetectors =
     69             new ArrayList<Detector.XmlScanner>();
     70     private final List<? extends Detector> mAllDetectors;
     71     private final IDomParser mParser;
     72 
     73     // Really want this:
     74     //<T extends List<Detector> & Detector.XmlScanner> XmlVisitor(IDomParser parser,
     75     //    T xmlDetectors) {
     76     // but it makes client code tricky and ugly.
     77     XmlVisitor(@NonNull IDomParser parser, @NonNull List<? extends Detector> xmlDetectors) {
     78         mParser = parser;
     79         mAllDetectors = xmlDetectors;
     80 
     81         // TODO: Check appliesTo() for files, and find a quick way to enable/disable
     82         // rules when running through a full project!
     83         for (Detector detector : xmlDetectors) {
     84             Detector.XmlScanner xmlDetector = (XmlScanner) detector;
     85             Collection<String> attributes = xmlDetector.getApplicableAttributes();
     86             if (attributes == XmlScanner.ALL) {
     87                 mAllAttributeDetectors.add(xmlDetector);
     88             }  else if (attributes != null) {
     89                 for (String attribute : attributes) {
     90                     List<Detector.XmlScanner> list = mAttributeToCheck.get(attribute);
     91                     if (list == null) {
     92                         list = new ArrayList<Detector.XmlScanner>();
     93                         mAttributeToCheck.put(attribute, list);
     94                     }
     95                     list.add(xmlDetector);
     96                 }
     97             }
     98             Collection<String> elements = xmlDetector.getApplicableElements();
     99             if (elements == XmlScanner.ALL) {
    100                 mAllElementDetectors.add(xmlDetector);
    101             } else if (elements != null) {
    102                 for (String element : elements) {
    103                     List<Detector.XmlScanner> list = mElementToCheck.get(element);
    104                     if (list == null) {
    105                         list = new ArrayList<Detector.XmlScanner>();
    106                         mElementToCheck.put(element, list);
    107                     }
    108                     list.add(xmlDetector);
    109                 }
    110             }
    111 
    112             if ((attributes == null || (attributes.size() == 0
    113                     && attributes != XmlScanner.ALL))
    114                   && (elements == null || (elements.size() == 0
    115                   && elements != XmlScanner.ALL))) {
    116                 mDocumentDetectors.add(xmlDetector);
    117             }
    118         }
    119     }
    120 
    121     void visitFile(@NonNull XmlContext context,@NonNull  File file) {
    122         assert LintUtils.isXmlFile(file);
    123         context.parser = mParser;
    124 
    125         try {
    126             if (context.document == null) {
    127                 context.document = mParser.parseXml(context);
    128                 if (context.document == null) {
    129                     // No need to log this; the parser should be reporting
    130                     // a full warning (such as IssueRegistry#PARSER_ERROR)
    131                     // with details, location, etc.
    132                     return;
    133                 }
    134                 if (context.document.getDocumentElement() == null) {
    135                     // Ignore empty documents
    136                     return;
    137                 }
    138             }
    139 
    140             for (Detector check : mAllDetectors) {
    141                 check.beforeCheckFile(context);
    142             }
    143 
    144             for (Detector.XmlScanner check : mDocumentDetectors) {
    145                 check.visitDocument(context, context.document);
    146             }
    147 
    148             if (mElementToCheck.size() > 0 || mAttributeToCheck.size() > 0
    149                     || mAllAttributeDetectors.size() > 0 || mAllElementDetectors.size() > 0) {
    150                 visitElement(context, context.document.getDocumentElement());
    151             }
    152 
    153             for (Detector check : mAllDetectors) {
    154                 check.afterCheckFile(context);
    155             }
    156         } finally {
    157             if (context.document != null) {
    158                 mParser.dispose(context, context.document);
    159                 context.document = null;
    160             }
    161         }
    162     }
    163 
    164     private void visitElement(@NonNull XmlContext context, @NonNull Element element) {
    165         List<Detector.XmlScanner> elementChecks = mElementToCheck.get(element.getTagName());
    166         if (elementChecks != null) {
    167             assert elementChecks instanceof RandomAccess;
    168             for (int i = 0, n = elementChecks.size(); i < n; i++) {
    169                 Detector.XmlScanner check = elementChecks.get(i);
    170                 check.visitElement(context, element);
    171             }
    172         }
    173         if (mAllElementDetectors.size() > 0) {
    174             for (int i = 0, n = mAllElementDetectors.size(); i < n; i++) {
    175                 Detector.XmlScanner check = mAllElementDetectors.get(i);
    176                 check.visitElement(context, element);
    177             }
    178         }
    179 
    180         if (mAttributeToCheck.size() > 0 || mAllAttributeDetectors.size() > 0) {
    181             NamedNodeMap attributes = element.getAttributes();
    182             for (int i = 0, n = attributes.getLength(); i < n; i++) {
    183                 Attr attribute = (Attr) attributes.item(i);
    184                 String name = attribute.getLocalName();
    185                 if (name == null) {
    186                     name = attribute.getName();
    187                 }
    188                 List<Detector.XmlScanner> list = mAttributeToCheck.get(name);
    189                 if (list != null) {
    190                     for (int j = 0, max = list.size(); j < max; j++) {
    191                         Detector.XmlScanner check = list.get(j);
    192                         check.visitAttribute(context, attribute);
    193                     }
    194                 }
    195                 if (mAllAttributeDetectors.size() > 0) {
    196                     for (int j = 0, max = mAllAttributeDetectors.size(); j < max; j++) {
    197                         Detector.XmlScanner check = mAllAttributeDetectors.get(j);
    198                         check.visitAttribute(context, attribute);
    199                     }
    200                 }
    201             }
    202         }
    203 
    204         // Visit children
    205         NodeList childNodes = element.getChildNodes();
    206         for (int i = 0, n = childNodes.getLength(); i < n; i++) {
    207             Node child = childNodes.item(i);
    208             if (child.getNodeType() == Node.ELEMENT_NODE) {
    209                 visitElement(context, (Element) child);
    210             }
    211         }
    212 
    213         // Post hooks
    214         if (elementChecks != null) {
    215             for (int i = 0, n = elementChecks.size(); i < n; i++) {
    216                 Detector.XmlScanner check = elementChecks.get(i);
    217                 check.visitElementAfter(context, element);
    218             }
    219         }
    220         if (mAllElementDetectors.size() > 0) {
    221             for (int i = 0, n = mAllElementDetectors.size(); i < n; i++) {
    222                 Detector.XmlScanner check = mAllElementDetectors.get(i);
    223                 check.visitElementAfter(context, element);
    224             }
    225         }
    226     }
    227 }
    228