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