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 17package com.android.tools.lint; 18 19import com.android.annotations.NonNull; 20import com.android.annotations.Nullable; 21import com.android.tools.lint.client.api.IDomParser; 22import com.android.tools.lint.client.api.IssueRegistry; 23import com.android.tools.lint.detector.api.Location; 24import com.android.tools.lint.detector.api.Location.Handle; 25import com.android.tools.lint.detector.api.XmlContext; 26import com.android.utils.PositionXmlParser; 27 28import org.w3c.dom.Document; 29import org.w3c.dom.Node; 30import org.xml.sax.SAXException; 31 32import java.io.File; 33import java.io.UnsupportedEncodingException; 34 35/** 36 * A customization of the {@link PositionXmlParser} which creates position 37 * objects that directly extend the lint 38 * {@link com.android.tools.lint.detector.api.Position} class. 39 * <p> 40 * It also catches and reports parser errors as lint errors. 41 */ 42public class LintCliXmlParser extends PositionXmlParser implements IDomParser { 43 @Override 44 public Document parseXml(@NonNull XmlContext context) { 45 try { 46 // Do we need to provide an input stream for encoding? 47 String xml = context.getContents(); 48 if (xml != null) { 49 return super.parse(xml); 50 } 51 } catch (UnsupportedEncodingException e) { 52 context.report( 53 // Must provide an issue since API guarantees that the issue parameter 54 // is valid 55 IssueRegistry.PARSER_ERROR, Location.create(context.file), 56 e.getCause() != null ? e.getCause().getLocalizedMessage() : 57 e.getLocalizedMessage(), 58 null); 59 } catch (SAXException e) { 60 context.report( 61 // Must provide an issue since API guarantees that the issue parameter 62 // is valid 63 IssueRegistry.PARSER_ERROR, Location.create(context.file), 64 e.getCause() != null ? e.getCause().getLocalizedMessage() : 65 e.getLocalizedMessage(), 66 null); 67 } catch (Throwable t) { 68 context.log(t, null); 69 } 70 return null; 71 } 72 73 @Override 74 public @NonNull Location getLocation(@NonNull XmlContext context, @NonNull Node node) { 75 OffsetPosition pos = (OffsetPosition) getPosition(node, -1, -1); 76 if (pos != null) { 77 return Location.create(context.file, pos, (OffsetPosition) pos.getEnd()); 78 } 79 80 return Location.create(context.file); 81 } 82 83 @Override 84 public @NonNull Location getLocation(@NonNull XmlContext context, @NonNull Node node, 85 int start, int end) { 86 OffsetPosition pos = (OffsetPosition) getPosition(node, start, end); 87 if (pos != null) { 88 return Location.create(context.file, pos, (OffsetPosition) pos.getEnd()); 89 } 90 91 return Location.create(context.file); 92 } 93 94 @Override 95 public @NonNull Handle createLocationHandle(@NonNull XmlContext context, @NonNull Node node) { 96 return new LocationHandle(context.file, node); 97 } 98 99 @Override 100 protected @NonNull OffsetPosition createPosition(int line, int column, int offset) { 101 return new OffsetPosition(line, column, offset); 102 } 103 104 private static class OffsetPosition extends com.android.tools.lint.detector.api.Position 105 implements PositionXmlParser.Position { 106 /** The line number (0-based where the first line is line 0) */ 107 private final int mLine; 108 109 /** 110 * The column number (where the first character on the line is 0), or -1 if 111 * unknown 112 */ 113 private final int mColumn; 114 115 /** The character offset */ 116 private final int mOffset; 117 118 /** 119 * Linked position: for a begin offset this will point to the end 120 * offset, and for an end offset this will be null 121 */ 122 private com.android.utils.PositionXmlParser.Position mEnd; 123 124 /** 125 * Creates a new {@link OffsetPosition} 126 * 127 * @param line the 0-based line number, or -1 if unknown 128 * @param column the 0-based column number, or -1 if unknown 129 * @param offset the offset, or -1 if unknown 130 */ 131 public OffsetPosition(int line, int column, int offset) { 132 mLine = line; 133 mColumn = column; 134 mOffset = offset; 135 } 136 137 @Override 138 public int getLine() { 139 return mLine; 140 } 141 142 @Override 143 public int getOffset() { 144 return mOffset; 145 } 146 147 @Override 148 public int getColumn() { 149 return mColumn; 150 } 151 152 @Override 153 public com.android.utils.PositionXmlParser.Position getEnd() { 154 return mEnd; 155 } 156 157 @Override 158 public void setEnd(@NonNull com.android.utils.PositionXmlParser.Position end) { 159 mEnd = end; 160 } 161 162 @Override 163 public String toString() { 164 return "OffsetPosition [line=" + mLine + ", column=" + mColumn + ", offset=" 165 + mOffset + ", end=" + mEnd + "]"; 166 } 167 } 168 169 @Override 170 public void dispose(@NonNull XmlContext context, @NonNull Document document) { 171 } 172 173 /* Handle for creating DOM positions cheaply and returning full fledged locations later */ 174 private class LocationHandle implements Handle { 175 private File mFile; 176 private Node mNode; 177 private Object mClientData; 178 179 public LocationHandle(File file, Node node) { 180 mFile = file; 181 mNode = node; 182 } 183 184 @Override 185 public @NonNull Location resolve() { 186 OffsetPosition pos = (OffsetPosition) getPosition(mNode); 187 if (pos != null) { 188 return Location.create(mFile, pos, (OffsetPosition) pos.getEnd()); 189 } 190 191 return Location.create(mFile); 192 } 193 194 @Override 195 public void setClientData(@Nullable Object clientData) { 196 mClientData = clientData; 197 } 198 199 @Override 200 @Nullable 201 public Object getClientData() { 202 return mClientData; 203 } 204 } 205} 206