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