Scope.java revision aafbe5a2394ff9826201cca97d3298a9f355e311
1/* 2 * Copyright (C) 2015 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 17package android.databinding.tool.processing; 18 19import android.databinding.tool.processing.scopes.FileScopeProvider; 20import android.databinding.tool.processing.scopes.LocationScopeProvider; 21import android.databinding.tool.processing.scopes.ScopeProvider; 22import android.databinding.tool.store.Location; 23import android.databinding.tool.util.Preconditions; 24 25import java.util.ArrayList; 26import java.util.Arrays; 27import java.util.List; 28 29/** 30 * Utility class to keep track of "logical" stack traces, which we can use to print better error 31 * reports. 32 */ 33public class Scope { 34 35 private static ThreadLocal<ScopeEntry> sScopeItems = new ThreadLocal<ScopeEntry>(); 36 static List<ScopedException> sDeferredExceptions = new ArrayList<>(); 37 38 public static void enter(final Location location) { 39 enter(new LocationScopeProvider() { 40 @Override 41 public List<Location> provideScopeLocation() { 42 return Arrays.asList(location); 43 } 44 }); 45 } 46 public static void enter(ScopeProvider scopeProvider) { 47 ScopeEntry peek = sScopeItems.get(); 48 ScopeEntry entry = new ScopeEntry(scopeProvider, peek); 49 sScopeItems.set(entry); 50 } 51 52 public static void exit() { 53 ScopeEntry entry = sScopeItems.get(); 54 Preconditions.checkNotNull(entry, "Inconsistent scope exit"); 55 sScopeItems.set(entry.mParent); 56 } 57 58 public static void defer(ScopedException exception) { 59 sDeferredExceptions.add(exception); 60 } 61 62 private static void registerErrorInternal(String msg, int scopeIndex, 63 ScopeProvider... scopeProviders) { 64 if (scopeProviders == null || scopeProviders.length <= scopeIndex) { 65 defer(new ScopedException(msg)); 66 } else if (scopeProviders[scopeIndex] == null) { 67 registerErrorInternal(msg, scopeIndex + 1, scopeProviders); 68 } else { 69 try { 70 Scope.enter(scopeProviders[scopeIndex]); 71 registerErrorInternal(msg, scopeIndex + 1, scopeProviders); 72 } finally { 73 Scope.exit(); 74 } 75 } 76 } 77 78 /** 79 * Convenience method to add an error in a known list of scopes, w/o adding try catch flows. 80 * <p> 81 * This code actually starts entering the given scopes 1 by 1, starting from 0. When list is 82 * consumed, it creates the exception and defers if possible then exits from the provided 83 * scopes. 84 * <p> 85 * Note that these scopes are added on top of the already existing scopes. 86 * 87 * @param msg The exception message 88 * @param scopeProviders The list of additional scope providers to enter. Null scopes are 89 * automatically ignored. 90 */ 91 public static void registerError(String msg, ScopeProvider... scopeProviders) { 92 registerErrorInternal(msg, 0, scopeProviders); 93 } 94 95 public static void assertNoError() { 96 if (sDeferredExceptions.isEmpty()) { 97 return; 98 } 99 StringBuilder sb = new StringBuilder(); 100 for (ScopedException ex : sDeferredExceptions) { 101 sb.append(ex.getMessage()).append("\n"); 102 } 103 throw new RuntimeException("Found data binding errors.\n" + sb.toString()); 104 } 105 106 static String produceScopeLog() { 107 StringBuilder sb = new StringBuilder(); 108 sb.append("full scope log\n"); 109 ScopeEntry top = sScopeItems.get(); 110 while (top != null) { 111 ScopeProvider provider = top.mProvider; 112 sb.append("---").append(provider).append("\n"); 113 if (provider instanceof FileScopeProvider) { 114 sb.append("file:").append(((FileScopeProvider) provider).provideScopeFilePath()) 115 .append("\n"); 116 } 117 if (provider instanceof LocationScopeProvider) { 118 LocationScopeProvider loc = (LocationScopeProvider) provider; 119 sb.append("loc:"); 120 List<Location> locations = loc.provideScopeLocation(); 121 if (locations == null) { 122 sb.append("null\n"); 123 } else { 124 for (Location location : locations) { 125 sb.append(location).append("\n"); 126 } 127 } 128 } 129 top = top.mParent; 130 } 131 sb.append("---\n"); 132 return sb.toString(); 133 } 134 135 static ScopedErrorReport createReport() { 136 ScopeEntry top = sScopeItems.get(); 137 String filePath = null; 138 List<Location> locations = null; 139 while (top != null && (filePath == null || locations == null)) { 140 ScopeProvider provider = top.mProvider; 141 if (locations == null && provider instanceof LocationScopeProvider) { 142 locations = findAbsoluteLocationFrom(top, (LocationScopeProvider) provider); 143 } 144 if (filePath == null && provider instanceof FileScopeProvider) { 145 filePath = ((FileScopeProvider) provider).provideScopeFilePath(); 146 } 147 top = top.mParent; 148 } 149 return new ScopedErrorReport(filePath, locations); 150 } 151 152 private static List<Location> findAbsoluteLocationFrom(ScopeEntry entry, 153 LocationScopeProvider top) { 154 List<Location> locations = top.provideScopeLocation(); 155 if (locations == null || locations.isEmpty()) { 156 return null; 157 } 158 if (locations.size() == 1) { 159 return Arrays.asList(locations.get(0).toAbsoluteLocation()); 160 } 161 // We have more than 1 location. Depending on the scope, we may or may not want all of them 162 List<Location> chosen = new ArrayList<>(); 163 for (Location location : locations) { 164 Location absLocation = location.toAbsoluteLocation(); 165 if (validatedContained(entry.mParent, absLocation)) { 166 chosen.add(absLocation); 167 } 168 } 169 return chosen.isEmpty() ? locations : chosen; 170 } 171 172 private static boolean validatedContained(ScopeEntry parent, Location absLocation) { 173 if (parent == null) { 174 return true; 175 } 176 ScopeProvider provider = parent.mProvider; 177 if (!(provider instanceof LocationScopeProvider)) { 178 return validatedContained(parent.mParent, absLocation); 179 } 180 List<Location> absoluteParents = findAbsoluteLocationFrom(parent, 181 (LocationScopeProvider) provider); 182 for (Location location : absoluteParents) { 183 if (location.contains(absLocation)) { 184 return true; 185 } 186 } 187 return false; 188 } 189 190 private static class ScopeEntry { 191 192 ScopeProvider mProvider; 193 194 ScopeEntry mParent; 195 196 public ScopeEntry(ScopeProvider scopeProvider, ScopeEntry parent) { 197 mProvider = scopeProvider; 198 mParent = parent; 199 } 200 } 201}