1 /*
2  * Copyright (C) 2020 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.build.config;
18 
19 import java.lang.reflect.Field;
20 import java.io.PrintStream;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26 
27 /**
28  * Base class for reporting errors.
29  */
30 public class ErrorReporter {
31     /**
32      * List of Entries that have occurred.
33      */
34     // Also used as the lock for this object.
35     private final ArrayList<Entry> mEntries = new ArrayList();
36 
37     /**
38      * The categories that are for this Errors object.
39      */
40     private Map<Integer, Category> mCategories;
41 
42     /**
43      * Whether there has been a warning or an error yet.
44      */
45     private boolean mHadWarningOrError;
46 
47     /**
48      * Whether there has been an error yet.
49      */
50     private boolean mHadError;
51 
52     public static class FatalException extends RuntimeException {
FatalException(String message)53         FatalException(String message) {
54             super(message);
55         }
56 
FatalException(String message, Throwable chain)57         FatalException(String message, Throwable chain) {
58             super(message, chain);
59         }
60     }
61 
62     /**
63      * Whether errors are errors, warnings or hidden.
64      */
65     public static enum Level {
66         HIDDEN("hidden"),
67         WARNING("warning"),
68         ERROR("error");
69 
70         private final String mLabel;
71 
Level(String label)72         Level(String label) {
73             mLabel = label;
74         }
75 
getLabel()76         String getLabel() {
77             return mLabel;
78         }
79     }
80 
81     /**
82      * The available error codes.
83      */
84     public class Category {
85         private final int mCode;
86         private boolean mIsLevelSettable;
87         private Level mLevel;
88         private String mHelp;
89 
90         /**
91          * Construct a Category object.
92          */
Category(int code, boolean isLevelSettable, Level level, String help)93         public Category(int code, boolean isLevelSettable, Level level, String help) {
94             if (!isLevelSettable && level != Level.ERROR) {
95                 throw new RuntimeException("Don't have WARNING or HIDDEN without isLevelSettable");
96             }
97             mCode = code;
98             mIsLevelSettable = isLevelSettable;
99             mLevel = level;
100             mHelp = help;
101         }
102 
103         /**
104          * Get the numeric code for the Category, which can be used to set the level.
105          */
getCode()106         public int getCode() {
107             return mCode;
108         }
109 
110         /**
111          * Get whether the level of this Category can be changed.
112          */
isLevelSettable()113         public boolean isLevelSettable() {
114             return mIsLevelSettable;
115         }
116 
117         /**
118          * Set the level of this category.
119          */
setLevel(Level level)120         public void setLevel(Level level) {
121             if (!mIsLevelSettable) {
122                 throw new RuntimeException("Can't set level for error " + mCode);
123             }
124             mLevel = level;
125         }
126 
127         /**
128          * Return the level, including any overrides.
129          */
getLevel()130         public Level getLevel() {
131             return mLevel;
132         }
133 
134         /**
135          * Return the category's help text.
136          */
getHelp()137         public String getHelp() {
138             return mHelp;
139         }
140 
141         /**
142          * Add an error with no source position.
143          */
add(String message)144         public void add(String message) {
145             ErrorReporter.this.add(this, false, new Position(), message);
146         }
147 
148         /**
149          * Add an error.
150          */
add(Position pos, String message)151         public void add(Position pos, String message) {
152             ErrorReporter.this.add(this, false, pos, message);
153         }
154 
155         /**
156          * Add an error with no source position, and throw a FatalException, stopping processing
157          * immediately.
158          */
fatal(String message)159         public void fatal(String message) {
160             ErrorReporter.this.add(this, true, new Position(), message);
161         }
162 
163         /**
164          * Add an error, and throw a FatalException, stopping processing immediately.
165          */
fatal(Position pos, String message)166         public void fatal(Position pos, String message) {
167             ErrorReporter.this.add(this, true, pos, message);
168         }
169     }
170 
171     /**
172      * An instance of an error happening.
173      */
174     public static class Entry {
175         private final Category mCategory;
176         private final Position mPosition;
177         private final String mMessage;
178 
Entry(Category category, Position position, String message)179         Entry(Category category, Position position, String message) {
180             mCategory = category;
181             mPosition = position;
182             mMessage = message;
183         }
184 
getCategory()185         public Category getCategory() {
186             return mCategory;
187         }
188 
getPosition()189         public Position getPosition() {
190             return mPosition;
191         }
192 
getMessage()193         public String getMessage() {
194             return mMessage;
195         }
196 
197         @Override
toString()198         public String toString() {
199             return mPosition
200                     + "[" + mCategory.getLevel().getLabel() + " " + mCategory.getCode() + "] "
201                     + mMessage;
202         }
203     }
204 
initLocked()205     private void initLocked() {
206         if (mCategories == null) {
207             HashMap<Integer, Category> categories = new HashMap();
208             for (Field field: getClass().getFields()) {
209                 if (Category.class.isAssignableFrom(field.getType())) {
210                     Category category = null;
211                     try {
212                         category = (Category)field.get(this);
213                     } catch (IllegalAccessException ex) {
214                         // Wrap and rethrow, this is always on this class, so it's
215                         // our programming error if this happens.
216                         throw new RuntimeException("Categories on Errors should be public.", ex);
217                     }
218                     Category prev = categories.put(category.getCode(), category);
219                     if (prev != null) {
220                         throw new RuntimeException("Duplicate categories with code "
221                                 + category.getCode());
222                     }
223                 }
224             }
225             mCategories = Collections.unmodifiableMap(categories);
226         }
227     }
228 
229     /**
230      * Returns a map of the category codes to the categories.
231      */
getCategories()232     public Map<Integer, Category> getCategories() {
233         synchronized (mEntries) {
234             initLocked();
235             return mCategories;
236         }
237     }
238 
239     /**
240      * Add an error.
241      */
add(Category category, boolean fatal, Position pos, String message)242     private void add(Category category, boolean fatal, Position pos, String message) {
243         synchronized (mEntries) {
244             initLocked();
245             if (mCategories.get(category.getCode()) != category) {
246                 throw new RuntimeException("Errors.Category used from the wrong Errors object.");
247             }
248             final Entry entry = new Entry(category, pos, message);
249             mEntries.add(entry);
250             final Level level = category.getLevel();
251             if (level == Level.WARNING || level == Level.ERROR) {
252                 mHadWarningOrError = true;
253             }
254             if (level == Level.ERROR) {
255                 mHadError = true;
256             }
257             if (fatal) {
258                 throw new FatalException(entry.toString());
259             }
260         }
261     }
262 
263     /**
264      * Returns whether there has been a warning or an error yet.
265      */
hadWarningOrError()266     public boolean hadWarningOrError() {
267         synchronized (mEntries) {
268             return mHadWarningOrError;
269         }
270     }
271 
272     /**
273      * Returns whether there has been an error yet.
274      */
hadError()275     public boolean hadError() {
276         synchronized (mEntries) {
277             return mHadError;
278         }
279     }
280 
281     /**
282      * Returns a list of all entries that were added.
283      */
getEntries()284     public List<Entry> getEntries() {
285         synchronized (mEntries) {
286             return new ArrayList<Entry>(mEntries);
287         }
288     }
289 
290     /**
291      * Prints the errors.
292      */
printErrors(PrintStream out)293     public void printErrors(PrintStream out) {
294         synchronized (mEntries) {
295             for (Entry entry: mEntries) {
296                 if (entry.getCategory().getLevel() == Level.HIDDEN) {
297                     continue;
298                 }
299                 out.println(entry.toString());
300             }
301         }
302     }
303 }
304