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