1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
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.ide.eclipse.adt.internal.editors.layout.gle2;
18 
19 import com.android.annotations.NonNull;
20 import com.android.annotations.Nullable;
21 import com.android.ide.common.rendering.RenderSecurityManager;
22 import com.android.ide.common.rendering.api.LayoutLog;
23 import com.android.ide.eclipse.adt.AdtPlugin;
24 
25 import org.eclipse.core.runtime.IStatus;
26 
27 import java.util.ArrayList;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Set;
31 
32 /**
33  * A {@link LayoutLog} which records the problems it encounters and offers them as a
34  * single summary at the end
35  */
36 public class RenderLogger extends LayoutLog {
37     static final String TAG_MISSING_DIMENSION = "missing.dimension";     //$NON-NLS-1$
38 
39     private final String mName;
40     private List<String> mFidelityWarnings;
41     private List<String> mWarnings;
42     private List<String> mErrors;
43     private boolean mHaveExceptions;
44     private List<String> mTags;
45     private List<Throwable> mTraces;
46     private static Set<String> sIgnoredFidelityWarnings;
47     private final Object mCredential;
48 
49     /** Construct a logger for the given named layout */
RenderLogger(String name, Object credential)50     RenderLogger(String name, Object credential) {
51         mName = name;
52         mCredential = credential;
53     }
54 
55     /**
56      * Are there any logged errors or warnings during the render?
57      *
58      * @return true if there were problems during the render
59      */
hasProblems()60     public boolean hasProblems() {
61         return mFidelityWarnings != null || mErrors != null || mWarnings != null ||
62             mHaveExceptions;
63     }
64 
65     /**
66      * Returns a list of traces encountered during rendering, or null if none
67      *
68      * @return a list of traces encountered during rendering, or null if none
69      */
70     @Nullable
getFirstTrace()71     public List<Throwable> getFirstTrace() {
72         return mTraces;
73     }
74 
75     /**
76      * Returns a (possibly multi-line) description of all the problems
77      *
78      * @param includeFidelityWarnings if true, include fidelity warnings in the problem
79      *            summary
80      * @return a string describing the rendering problems
81      */
82     @NonNull
getProblems(boolean includeFidelityWarnings)83     public String getProblems(boolean includeFidelityWarnings) {
84         StringBuilder sb = new StringBuilder();
85 
86         if (mErrors != null) {
87             for (String error : mErrors) {
88                 sb.append(error).append('\n');
89             }
90         }
91 
92         if (mWarnings != null) {
93             for (String warning : mWarnings) {
94                 sb.append(warning).append('\n');
95             }
96         }
97 
98         if (includeFidelityWarnings && mFidelityWarnings != null) {
99             sb.append("The graphics preview in the layout editor may not be accurate:\n");
100             for (String warning : mFidelityWarnings) {
101                 sb.append("* ");
102                 sb.append(warning).append('\n');
103             }
104         }
105 
106         if (mHaveExceptions) {
107             sb.append("Exception details are logged in Window > Show View > Error Log");
108         }
109 
110         return sb.toString();
111     }
112 
113     /**
114      * Returns the fidelity warnings
115      *
116      * @return the fidelity warnings
117      */
118     @Nullable
getFidelityWarnings()119     public List<String> getFidelityWarnings() {
120         return mFidelityWarnings;
121     }
122 
123     // ---- extends LayoutLog ----
124 
125     @Override
error(String tag, String message, Object data)126     public void error(String tag, String message, Object data) {
127         String description = describe(message);
128 
129         appendToIdeLog(null, IStatus.ERROR, description);
130 
131         // Workaround: older layout libraries don't provide a tag for this error
132         if (tag == null && message != null
133                 && message.startsWith("Failed to find style ")) { //$NON-NLS-1$
134             tag = LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR;
135         }
136 
137         addError(tag, description);
138     }
139 
140     @Override
error(String tag, String message, Throwable throwable, Object data)141     public void error(String tag, String message, Throwable throwable, Object data) {
142         String description = describe(message);
143         appendToIdeLog(throwable, IStatus.ERROR, description);
144 
145         if (throwable != null) {
146             if (throwable instanceof ClassNotFoundException) {
147                 // The project callback is given a chance to resolve classes,
148                 // and when it fails, it will record it in its own list which
149                 // is displayed in a special way (with action hyperlinks etc).
150                 // Therefore, include these messages in the visible render log,
151                 // especially since the user message from a ClassNotFoundException
152                 // is really not helpful (it just lists the class name without
153                 // even mentioning that it is a class-not-found exception.)
154                 return;
155             }
156 
157             if (description.equals(throwable.getLocalizedMessage()) ||
158                     description.equals(throwable.getMessage())) {
159                 description = "Exception raised during rendering: " + description;
160             }
161             recordThrowable(throwable);
162             mHaveExceptions = true;
163         }
164 
165         addError(tag, description);
166     }
167 
168     /**
169      * Record that the given exception was encountered during rendering
170      *
171      * @param throwable the exception that was raised
172      */
recordThrowable(@onNull Throwable throwable)173     public void recordThrowable(@NonNull Throwable throwable) {
174         if (mTraces == null) {
175             mTraces = new ArrayList<Throwable>();
176         }
177         mTraces.add(throwable);
178     }
179 
180     @Override
warning(String tag, String message, Object data)181     public void warning(String tag, String message, Object data) {
182         String description = describe(message);
183 
184         boolean log = true;
185         if (TAG_RESOURCES_FORMAT.equals(tag)) {
186             if (description.equals("You must supply a layout_width attribute.")       //$NON-NLS-1$
187                 || description.equals("You must supply a layout_height attribute.")) {//$NON-NLS-1$
188                 tag = TAG_MISSING_DIMENSION;
189                 log = false;
190             }
191         }
192 
193         if (log) {
194             appendToIdeLog(null, IStatus.WARNING, description);
195         }
196 
197         addWarning(tag, description);
198     }
199 
200     @Override
fidelityWarning(String tag, String message, Throwable throwable, Object data)201     public void fidelityWarning(String tag, String message, Throwable throwable, Object data) {
202         if (sIgnoredFidelityWarnings != null && sIgnoredFidelityWarnings.contains(message)) {
203             return;
204         }
205 
206         String description = describe(message);
207         appendToIdeLog(throwable, IStatus.ERROR, description);
208 
209         if (throwable != null) {
210             mHaveExceptions = true;
211         }
212 
213         addFidelityWarning(tag, description);
214     }
215 
216     /**
217      * Ignore the given render fidelity warning for the current session
218      *
219      * @param message the message to be ignored for this session
220      */
ignoreFidelityWarning(String message)221     public static void ignoreFidelityWarning(String message) {
222         if (sIgnoredFidelityWarnings == null) {
223             sIgnoredFidelityWarnings = new HashSet<String>();
224         }
225         sIgnoredFidelityWarnings.add(message);
226     }
227 
228     @NonNull
describe(@ullable String message)229     private String describe(@Nullable String message) {
230         if (message == null) {
231             return "";
232         } else {
233             return message;
234         }
235     }
236 
addWarning(String tag, String description)237     private void addWarning(String tag, String description) {
238         if (mWarnings == null) {
239             mWarnings = new ArrayList<String>();
240         } else if (mWarnings.contains(description)) {
241             // Avoid duplicates
242             return;
243         }
244         mWarnings.add(description);
245         addTag(tag);
246     }
247 
addError(String tag, String description)248     private void addError(String tag, String description) {
249         if (mErrors == null) {
250             mErrors = new ArrayList<String>();
251         } else if (mErrors.contains(description)) {
252             // Avoid duplicates
253             return;
254         }
255         mErrors.add(description);
256         addTag(tag);
257     }
258 
addFidelityWarning(String tag, String description)259     private void addFidelityWarning(String tag, String description) {
260         if (mFidelityWarnings == null) {
261             mFidelityWarnings = new ArrayList<String>();
262         } else if (mFidelityWarnings.contains(description)) {
263             // Avoid duplicates
264             return;
265         }
266         mFidelityWarnings.add(description);
267         addTag(tag);
268     }
269 
270     // ---- Tags ----
271 
addTag(String tag)272     private void addTag(String tag) {
273         if (tag != null) {
274             if (mTags == null) {
275                 mTags = new ArrayList<String>();
276             }
277             mTags.add(tag);
278         }
279     }
280 
281     /**
282      * Returns true if the given tag prefix has been seen
283      *
284      * @param prefix the tag prefix to look for
285      * @return true iff any tags with the given prefix was seen during the render
286      */
seenTagPrefix(String prefix)287     public boolean seenTagPrefix(String prefix) {
288         if (mTags != null) {
289             for (String tag : mTags) {
290                 if (tag.startsWith(prefix)) {
291                     return true;
292                 }
293             }
294         }
295 
296         return false;
297     }
298 
299     /**
300      * Returns true if the given tag has been seen
301      *
302      * @param tag the tag to look for
303      * @return true iff the tag was seen during the render
304      */
seenTag(String tag)305     public boolean seenTag(String tag) {
306         if (mTags != null) {
307             return mTags.contains(tag);
308         } else {
309             return false;
310         }
311     }
312 
313     // Append the given message to the ADT log. Bypass the sandbox if necessary
314     // such that we can write to the log file.
appendToIdeLog(Throwable throwable, int severity, String description)315     private void appendToIdeLog(Throwable throwable, int severity, String description) {
316         boolean token = RenderSecurityManager.enterSafeRegion(mCredential);
317         try {
318             if (throwable != null) {
319                 AdtPlugin.log(throwable, "%1$s: %2$s", mName, description);
320             } else {
321                 AdtPlugin.log(severity, "%1$s: %2$s", mName, description);
322             }
323         } finally {
324             RenderSecurityManager.exitSafeRegion(token);
325         }
326     }
327 }
328