aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/RenderLogger.java
blob: 8548830bd675d49ffd267c2e64d59e306fb7a438 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Eclipse Public License, Version 1.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.eclipse.org/org/documents/epl-v10.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.ide.eclipse.adt.internal.editors.layout.gle2;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.ide.common.rendering.RenderSecurityManager;
import com.android.ide.common.rendering.api.LayoutLog;
import com.android.ide.eclipse.adt.AdtPlugin;

import org.eclipse.core.runtime.IStatus;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * A {@link LayoutLog} which records the problems it encounters and offers them as a
 * single summary at the end
 */
public class RenderLogger extends LayoutLog {
    static final String TAG_MISSING_DIMENSION = "missing.dimension";     //$NON-NLS-1$

    private final String mName;
    private List<String> mFidelityWarnings;
    private List<String> mWarnings;
    private List<String> mErrors;
    private boolean mHaveExceptions;
    private List<String> mTags;
    private List<Throwable> mTraces;
    private static Set<String> sIgnoredFidelityWarnings;
    private final Object mCredential;

    /** Construct a logger for the given named layout */
    RenderLogger(String name, Object credential) {
        mName = name;
        mCredential = credential;
    }

    /**
     * Are there any logged errors or warnings during the render?
     *
     * @return true if there were problems during the render
     */
    public boolean hasProblems() {
        return mFidelityWarnings != null || mErrors != null || mWarnings != null ||
            mHaveExceptions;
    }

    /**
     * Returns a list of traces encountered during rendering, or null if none
     *
     * @return a list of traces encountered during rendering, or null if none
     */
    @Nullable
    public List<Throwable> getFirstTrace() {
        return mTraces;
    }

    /**
     * Returns a (possibly multi-line) description of all the problems
     *
     * @param includeFidelityWarnings if true, include fidelity warnings in the problem
     *            summary
     * @return a string describing the rendering problems
     */
    @NonNull
    public String getProblems(boolean includeFidelityWarnings) {
        StringBuilder sb = new StringBuilder();

        if (mErrors != null) {
            for (String error : mErrors) {
                sb.append(error).append('\n');
            }
        }

        if (mWarnings != null) {
            for (String warning : mWarnings) {
                sb.append(warning).append('\n');
            }
        }

        if (includeFidelityWarnings && mFidelityWarnings != null) {
            sb.append("The graphics preview in the layout editor may not be accurate:\n");
            for (String warning : mFidelityWarnings) {
                sb.append("* ");
                sb.append(warning).append('\n');
            }
        }

        if (mHaveExceptions) {
            sb.append("Exception details are logged in Window > Show View > Error Log");
        }

        return sb.toString();
    }

    /**
     * Returns the fidelity warnings
     *
     * @return the fidelity warnings
     */
    @Nullable
    public List<String> getFidelityWarnings() {
        return mFidelityWarnings;
    }

    // ---- extends LayoutLog ----

    @Override
    public void error(String tag, String message, Object data) {
        String description = describe(message);

        appendToIdeLog(null, IStatus.ERROR, description);

        // Workaround: older layout libraries don't provide a tag for this error
        if (tag == null && message != null
                && message.startsWith("Failed to find style ")) { //$NON-NLS-1$
            tag = LayoutLog.TAG_RESOURCES_RESOLVE_THEME_ATTR;
        }

        addError(tag, description);
    }

    @Override
    public void error(String tag, String message, Throwable throwable, Object data) {
        String description = describe(message);
        appendToIdeLog(throwable, IStatus.ERROR, description);

        if (throwable != null) {
            if (throwable instanceof ClassNotFoundException) {
                // The project callback is given a chance to resolve classes,
                // and when it fails, it will record it in its own list which
                // is displayed in a special way (with action hyperlinks etc).
                // Therefore, include these messages in the visible render log,
                // especially since the user message from a ClassNotFoundException
                // is really not helpful (it just lists the class name without
                // even mentioning that it is a class-not-found exception.)
                return;
            }

            if (description.equals(throwable.getLocalizedMessage()) ||
                    description.equals(throwable.getMessage())) {
                description = "Exception raised during rendering: " + description;
            }
            recordThrowable(throwable);
            mHaveExceptions = true;
        }

        addError(tag, description);
    }

    /**
     * Record that the given exception was encountered during rendering
     *
     * @param throwable the exception that was raised
     */
    public void recordThrowable(@NonNull Throwable throwable) {
        if (mTraces == null) {
            mTraces = new ArrayList<Throwable>();
        }
        mTraces.add(throwable);
    }

    @Override
    public void warning(String tag, String message, Object data) {
        String description = describe(message);

        boolean log = true;
        if (TAG_RESOURCES_FORMAT.equals(tag)) {
            if (description.equals("You must supply a layout_width attribute.")       //$NON-NLS-1$
                || description.equals("You must supply a layout_height attribute.")) {//$NON-NLS-1$
                tag = TAG_MISSING_DIMENSION;
                log = false;
            }
        }

        if (log) {
            appendToIdeLog(null, IStatus.WARNING, description);
        }

        addWarning(tag, description);
    }

    @Override
    public void fidelityWarning(String tag, String message, Throwable throwable, Object data) {
        if (sIgnoredFidelityWarnings != null && sIgnoredFidelityWarnings.contains(message)) {
            return;
        }

        String description = describe(message);
        appendToIdeLog(throwable, IStatus.ERROR, description);

        if (throwable != null) {
            mHaveExceptions = true;
        }

        addFidelityWarning(tag, description);
    }

    /**
     * Ignore the given render fidelity warning for the current session
     *
     * @param message the message to be ignored for this session
     */
    public static void ignoreFidelityWarning(String message) {
        if (sIgnoredFidelityWarnings == null) {
            sIgnoredFidelityWarnings = new HashSet<String>();
        }
        sIgnoredFidelityWarnings.add(message);
    }

    @NonNull
    private String describe(@Nullable String message) {
        if (message == null) {
            return "";
        } else {
            return message;
        }
    }

    private void addWarning(String tag, String description) {
        if (mWarnings == null) {
            mWarnings = new ArrayList<String>();
        } else if (mWarnings.contains(description)) {
            // Avoid duplicates
            return;
        }
        mWarnings.add(description);
        addTag(tag);
    }

    private void addError(String tag, String description) {
        if (mErrors == null) {
            mErrors = new ArrayList<String>();
        } else if (mErrors.contains(description)) {
            // Avoid duplicates
            return;
        }
        mErrors.add(description);
        addTag(tag);
    }

    private void addFidelityWarning(String tag, String description) {
        if (mFidelityWarnings == null) {
            mFidelityWarnings = new ArrayList<String>();
        } else if (mFidelityWarnings.contains(description)) {
            // Avoid duplicates
            return;
        }
        mFidelityWarnings.add(description);
        addTag(tag);
    }

    // ---- Tags ----

    private void addTag(String tag) {
        if (tag != null) {
            if (mTags == null) {
                mTags = new ArrayList<String>();
            }
            mTags.add(tag);
        }
    }

    /**
     * Returns true if the given tag prefix has been seen
     *
     * @param prefix the tag prefix to look for
     * @return true iff any tags with the given prefix was seen during the render
     */
    public boolean seenTagPrefix(String prefix) {
        if (mTags != null) {
            for (String tag : mTags) {
                if (tag.startsWith(prefix)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Returns true if the given tag has been seen
     *
     * @param tag the tag to look for
     * @return true iff the tag was seen during the render
     */
    public boolean seenTag(String tag) {
        if (mTags != null) {
            return mTags.contains(tag);
        } else {
            return false;
        }
    }

    // Append the given message to the ADT log. Bypass the sandbox if necessary
    // such that we can write to the log file.
    private void appendToIdeLog(Throwable throwable, int severity, String description) {
        boolean token = RenderSecurityManager.enterSafeRegion(mCredential);
        try {
            if (throwable != null) {
                AdtPlugin.log(throwable, "%1$s: %2$s", mName, description);
            } else {
                AdtPlugin.log(severity, "%1$s: %2$s", mName, description);
            }
        } finally {
            RenderSecurityManager.exitSafeRegion(token);
        }
    }
}