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.detector.api; 18 19 import com.android.annotations.NonNull; 20 import com.android.annotations.Nullable; 21 import com.android.resources.ResourceFolderType; 22 import com.android.tools.lint.client.api.IDomParser; 23 import com.android.tools.lint.client.api.LintDriver; 24 import com.google.common.annotations.Beta; 25 26 import org.w3c.dom.Document; 27 import org.w3c.dom.Node; 28 29 import java.io.File; 30 import java.util.regex.Matcher; 31 import java.util.regex.Pattern; 32 33 /** 34 * A {@link Context} used when checking XML files. 35 * <p/> 36 * <b>NOTE: This is not a public or final API; if you rely on this be prepared 37 * to adjust your code for the next tools release.</b> 38 */ 39 @Beta 40 public class XmlContext extends Context { 41 /** The XML parser */ 42 public IDomParser parser; 43 /** The XML document */ 44 public Document document; 45 private final ResourceFolderType mFolderType; 46 47 /** 48 * Construct a new {@link XmlContext} 49 * 50 * @param driver the driver running through the checks 51 * @param project the project containing the file being checked 52 * @param main the main project if this project is a library project, or 53 * null if this is not a library project. The main project is 54 * the root project of all library projects, not necessarily the 55 * directly including project. 56 * @param file the file being checked 57 * @param folderType the {@link ResourceFolderType} of this file, if any 58 */ XmlContext( @onNull LintDriver driver, @NonNull Project project, @Nullable Project main, @NonNull File file, @Nullable ResourceFolderType folderType)59 public XmlContext( 60 @NonNull LintDriver driver, 61 @NonNull Project project, 62 @Nullable Project main, 63 @NonNull File file, 64 @Nullable ResourceFolderType folderType) { 65 super(driver, project, main, file); 66 mFolderType = folderType; 67 } 68 69 /** 70 * Returns the location for the given node, which may be an element or an attribute. 71 * 72 * @param node the node to look up the location for 73 * @return the location for the node 74 */ 75 @NonNull getLocation(@onNull Node node)76 public Location getLocation(@NonNull Node node) { 77 if (parser != null) { 78 return parser.getLocation(this, node); 79 } 80 81 return Location.create(file); 82 } 83 84 /** 85 * Reports an issue applicable to a given DOM node. The DOM node is used as the 86 * scope to check for suppress lint annotations. 87 * 88 * @param issue the issue to report 89 * @param scope the DOM node scope the error applies to. The lint infrastructure 90 * will check whether there are suppress directives on this node (or its enclosing 91 * nodes) and if so suppress the warning without involving the client. 92 * @param location the location of the issue, or null if not known 93 * @param message the message for this warning 94 * @param data any associated data, or null 95 */ report( @onNull Issue issue, @Nullable Node scope, @Nullable Location location, @NonNull String message, @Nullable Object data)96 public void report( 97 @NonNull Issue issue, 98 @Nullable Node scope, 99 @Nullable Location location, 100 @NonNull String message, 101 @Nullable Object data) { 102 if (scope != null && mDriver.isSuppressed(issue, scope)) { 103 return; 104 } 105 super.report(issue, location, message, data); 106 } 107 108 @Override report( @onNull Issue issue, @Nullable Location location, @NonNull String message, @Nullable Object data)109 public void report( 110 @NonNull Issue issue, 111 @Nullable Location location, 112 @NonNull String message, 113 @Nullable Object data) { 114 // Warn if clients use the non-scoped form? No, there are cases where an 115 // XML detector's error isn't applicable to one particular location (or it's 116 // not feasible to compute it cheaply) 117 //mDriver.getClient().log(null, "Warning: Issue " + issue 118 // + " was reported without a scope node: Can't be suppressed."); 119 120 // For now just check the document root itself 121 if (document != null && mDriver.isSuppressed(issue, document)) { 122 return; 123 } 124 125 super.report(issue, location, message, data); 126 } 127 128 /** 129 * Returns the resource folder type of this XML file, if any. 130 * 131 * @return the resource folder type or null 132 */ 133 @Nullable getResourceFolderType()134 public ResourceFolderType getResourceFolderType() { 135 return mFolderType; 136 } 137 138 139 private final static Pattern sVersionPattern = Pattern.compile("^v(\\d+)$");//$NON-NLS-1$ 140 141 private static File sCachedFolder = null; 142 private static int sCachedFolderVersion = -1; 143 144 /** 145 * Returns the folder version. For example, for the file values-v14/foo.xml, 146 * it returns 14. 147 * 148 * @return the folder version, or -1 if no specific version was specified 149 */ getFolderVersion()150 public int getFolderVersion() { 151 File parent = file.getParentFile(); 152 if (parent.equals(sCachedFolder)) { 153 return sCachedFolderVersion; 154 } 155 156 sCachedFolder = parent; 157 sCachedFolderVersion = -1; 158 159 String[] qualifiers = parent.getName().split("-"); //$NON-NLS-1$ 160 for (String qualifier : qualifiers) { 161 Matcher matcher = sVersionPattern.matcher(qualifier); 162 if (matcher.matches()) { 163 sCachedFolderVersion = Integer.parseInt(matcher.group(1)); 164 break; 165 } 166 } 167 168 return sCachedFolderVersion; 169 } 170 } 171