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