• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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;
18 
19 import static com.android.tools.lint.detector.api.LintConstants.DOT_9PNG;
20 import static com.android.tools.lint.detector.api.LintConstants.DOT_PNG;
21 import static com.android.tools.lint.detector.api.LintUtils.endsWith;
22 
23 import com.google.common.annotations.Beta;
24 import com.google.common.io.ByteStreams;
25 import com.google.common.io.Closeables;
26 import com.google.common.io.Files;
27 
28 import java.io.File;
29 import java.io.FileOutputStream;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.UnsupportedEncodingException;
33 import java.net.URL;
34 import java.net.URLEncoder;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.Map;
38 
39 /** A reporter is an output generator for lint warnings
40  * <p>
41  * <b>NOTE: This is not a public or final API; if you rely on this be prepared
42  * to adjust your code for the next tools release.</b>
43  */
44 @Beta
45 public abstract class Reporter {
46     protected final Main mClient;
47     protected final File mOutput;
48     protected String mTitle = "Lint Report";
49     protected boolean mSimpleFormat;
50     protected boolean mBundleResources;
51     protected Map<String, String> mUrlMap;
52     protected File mResources;
53     protected Map<File, String> mResourceUrl = new HashMap<File, String>();
54     protected Map<String, File> mNameToFile = new HashMap<String, File>();
55 
56     /**
57      * Write the given warnings into the report
58      *
59      * @param errorCount the number of errors
60      * @param warningCount the number of warnings
61      * @param issues the issues to be reported
62      * @throws IOException if an error occurs
63      */
write(int errorCount, int warningCount, List<Warning> issues)64     public abstract void write(int errorCount, int warningCount, List<Warning> issues)
65             throws IOException;
66 
Reporter(Main client, File output)67     protected Reporter(Main client, File output) {
68         mClient = client;
69         mOutput = output;
70     }
71 
72     /**
73      * Sets the report title
74      *
75      * @param title the title of the report
76      */
setTitle(String title)77     public void setTitle(String title) {
78         mTitle = title;
79     }
80 
81     /** @return the title of the report */
getTitle()82     public String getTitle() {
83         return mTitle;
84     }
85 
86     /**
87      * Sets whether the report should bundle up resources along with the HTML report.
88      * This implies a non-simple format (see {@link #setSimpleFormat(boolean)}).
89      *
90      * @param bundleResources if true, copy images into a directory relative to
91      *            the report
92      */
setBundleResources(boolean bundleResources)93     public void setBundleResources(boolean bundleResources) {
94         mBundleResources = bundleResources;
95         mSimpleFormat = false;
96     }
97 
98     /**
99      * Sets whether the report should use simple formatting (meaning no JavaScript,
100      * embedded images, etc).
101      *
102      * @param simpleFormat whether the formatting should be simple
103      */
setSimpleFormat(boolean simpleFormat)104     public void setSimpleFormat(boolean simpleFormat) {
105         mSimpleFormat = simpleFormat;
106     }
107 
108     /**
109      * Returns whether the report should use simple formatting (meaning no JavaScript,
110      * embedded images, etc).
111      *
112      * @return whether the report should use simple formatting
113      */
isSimpleFormat()114     public boolean isSimpleFormat() {
115         return mSimpleFormat;
116     }
117 
118 
getUrl(File file)119     String getUrl(File file) {
120         if (mBundleResources && !mSimpleFormat) {
121             String url = getRelativeResourceUrl(file);
122             if (url != null) {
123                 return url;
124             }
125         }
126 
127         if (mUrlMap != null) {
128             String path = file.getAbsolutePath();
129             try {
130                 // Perform the comparison using URLs such that we properly escape spaces etc.
131                 String pathUrl = URLEncoder.encode(path, "UTF-8");         //$NON-NLS-1$
132                 for (Map.Entry<String, String> entry : mUrlMap.entrySet()) {
133                     String prefix = entry.getKey();
134                     String prefixUrl = URLEncoder.encode(prefix, "UTF-8"); //$NON-NLS-1$
135                     if (pathUrl.startsWith(prefixUrl)) {
136                         String relative = pathUrl.substring(prefixUrl.length());
137                         return entry.getValue()
138                                 + relative.replace("%2F", "/"); //$NON-NLS-1$ //$NON-NLS-2$
139                     }
140                 }
141             } catch (UnsupportedEncodingException e) {
142                 // This shouldn't happen for UTF-8
143                 System.err.println("Invalid URL map specification - " + e.getLocalizedMessage());
144             }
145         }
146 
147         return null;
148     }
149 
150     /** Encodes the given String as a safe URL substring, escaping spaces etc */
encodeUrl(String url)151     static String encodeUrl(String url) {
152         try {
153             return URLEncoder.encode(url, "UTF-8");         //$NON-NLS-1$
154         } catch (UnsupportedEncodingException e) {
155             // This shouldn't happen for UTF-8
156             System.err.println("Invalid string " + e.getLocalizedMessage());
157             return url;
158         }
159     }
160 
161     /** Set mapping of path prefixes to corresponding URLs in the HTML report */
setUrlMap(Map<String, String> urlMap)162     void setUrlMap(Map<String, String> urlMap) {
163         mUrlMap = urlMap;
164     }
165 
166     /** Gets a pointer to the local resource directory, if any */
getResourceDir()167     File getResourceDir() {
168         if (mResources == null && mBundleResources) {
169             mResources = computeResourceDir();
170             if (mResources == null) {
171                 mBundleResources = false;
172             }
173         }
174 
175         return mResources;
176     }
177 
178     /** Finds/creates the local resource directory, if possible */
computeResourceDir()179     File computeResourceDir() {
180         String fileName = mOutput.getName();
181         int dot = fileName.indexOf('.');
182         if (dot != -1) {
183             fileName = fileName.substring(0, dot);
184         }
185 
186         File resources = new File(mOutput.getParentFile(), fileName + "_files"); //$NON-NLS-1$
187         if (!resources.exists() && !resources.mkdir()) {
188             resources = null;
189         }
190 
191         return resources;
192     }
193 
194     /** Returns a URL to a local copy of the given file, or null */
getRelativeResourceUrl(File file)195     protected String getRelativeResourceUrl(File file) {
196         String resource = mResourceUrl.get(file);
197         if (resource != null) {
198             return resource;
199         }
200 
201         String name = file.getName();
202         if (!endsWith(name, DOT_PNG) || endsWith(name, DOT_9PNG)) {
203             return null;
204         }
205 
206         // Attempt to make local copy
207         File resourceDir = getResourceDir();
208         if (resourceDir != null) {
209             String base = file.getName();
210 
211             File path = mNameToFile.get(base);
212             if (path != null && !path.equals(file)) {
213                 // That filename already exists and is associated with a different path:
214                 // make a new unique version
215                 for (int i = 0; i < 100; i++) {
216                     base = '_' + base;
217                     path = mNameToFile.get(base);
218                     if (path == null || path.equals(file)) {
219                         break;
220                     }
221                 }
222             }
223 
224             File target = new File(resourceDir, base);
225             try {
226                 Files.copy(file, target);
227             } catch (IOException e) {
228                 return null;
229             }
230             return resourceDir.getName() + '/' + encodeUrl(base);
231         }
232         return null;
233     }
234 
235     /** Returns a URL to a local copy of the given resource, or null. There is
236      * no filename conflict resolution. */
addLocalResources(URL url)237     protected String addLocalResources(URL url) {
238         // Attempt to make local copy
239         File resourceDir = computeResourceDir();
240         if (resourceDir != null) {
241             String base = url.getFile();
242             base = base.substring(base.lastIndexOf('/') + 1);
243             mNameToFile.put(base, new File(url.toExternalForm()));
244 
245             File target = new File(resourceDir, base);
246             try {
247                 FileOutputStream output = new FileOutputStream(target);
248                 InputStream input = url.openStream();
249                 ByteStreams.copy(input, output);
250                 Closeables.closeQuietly(output);
251                 Closeables.closeQuietly(input);
252             } catch (IOException e) {
253                 return null;
254             }
255             return resourceDir.getName() + '/' + encodeUrl(base);
256         }
257         return null;
258     }
259 }