1731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar/* 2731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * Copyright (C) 2015 The Android Open Source Project 3731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * 4731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * Licensed under the Apache License, Version 2.0 (the "License"); 5731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * you may not use this file except in compliance with the License. 6731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * You may obtain a copy of the License at 7731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * 8731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * http://www.apache.org/licenses/LICENSE-2.0 9731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * 10731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * Unless required by applicable law or agreed to in writing, software 11731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * distributed under the License is distributed on an "AS IS" BASIS, 12731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * See the License for the specific language governing permissions and 14731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * limitations under the License. 15731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar */ 16731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 17731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarpackage android.databinding.tool.processing; 18731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 19731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport android.databinding.tool.processing.scopes.FileScopeProvider; 20731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport android.databinding.tool.processing.scopes.LocationScopeProvider; 21731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport android.databinding.tool.processing.scopes.ScopeProvider; 22731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport android.databinding.tool.store.Location; 23731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport android.databinding.tool.util.Preconditions; 24731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 25731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport java.util.ArrayList; 26731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport java.util.Arrays; 27a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0George Mountimport java.util.HashSet; 28731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarimport java.util.List; 29731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 30731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar/** 31731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * Utility class to keep track of "logical" stack traces, which we can use to print better error 32731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar * reports. 33731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar */ 34731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyarpublic class Scope { 35731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 36731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar private static ThreadLocal<ScopeEntry> sScopeItems = new ThreadLocal<ScopeEntry>(); 37731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar static List<ScopedException> sDeferredExceptions = new ArrayList<>(); 38731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 39aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar public static void enter(final Location location) { 40aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar enter(new LocationScopeProvider() { 41aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar @Override 42aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar public List<Location> provideScopeLocation() { 43aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar return Arrays.asList(location); 44aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 45aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar }); 46aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 47731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar public static void enter(ScopeProvider scopeProvider) { 48731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeEntry peek = sScopeItems.get(); 49731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeEntry entry = new ScopeEntry(scopeProvider, peek); 50731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sScopeItems.set(entry); 51731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 52731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 53731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar public static void exit() { 54731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeEntry entry = sScopeItems.get(); 55731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Preconditions.checkNotNull(entry, "Inconsistent scope exit"); 56731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sScopeItems.set(entry.mParent); 57731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 58731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 59731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar public static void defer(ScopedException exception) { 60731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sDeferredExceptions.add(exception); 61731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 62731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 63aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar private static void registerErrorInternal(String msg, int scopeIndex, 64aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar ScopeProvider... scopeProviders) { 65aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar if (scopeProviders == null || scopeProviders.length <= scopeIndex) { 66aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar defer(new ScopedException(msg)); 67aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } else if (scopeProviders[scopeIndex] == null) { 68aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar registerErrorInternal(msg, scopeIndex + 1, scopeProviders); 69aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } else { 70aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar try { 71aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar Scope.enter(scopeProviders[scopeIndex]); 72aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar registerErrorInternal(msg, scopeIndex + 1, scopeProviders); 73aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } finally { 74aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar Scope.exit(); 75aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 76aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 77aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 78aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar 79aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar /** 80aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * Convenience method to add an error in a known list of scopes, w/o adding try catch flows. 81aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * <p> 82aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * This code actually starts entering the given scopes 1 by 1, starting from 0. When list is 83aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * consumed, it creates the exception and defers if possible then exits from the provided 84aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * scopes. 85aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * <p> 86aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * Note that these scopes are added on top of the already existing scopes. 87aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * 88aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * @param msg The exception message 89aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * @param scopeProviders The list of additional scope providers to enter. Null scopes are 90aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar * automatically ignored. 91aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar */ 92aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar public static void registerError(String msg, ScopeProvider... scopeProviders) { 93aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar registerErrorInternal(msg, 0, scopeProviders); 94aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar } 95aafbe5a2394ff9826201cca97d3298a9f355e311Yigit Boyar 96731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar public static void assertNoError() { 97731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (sDeferredExceptions.isEmpty()) { 98731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return; 99731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 100731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar StringBuilder sb = new StringBuilder(); 101a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0George Mount HashSet<String> messages = new HashSet<String>(); 102731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar for (ScopedException ex : sDeferredExceptions) { 103a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0George Mount final String message = ex.getMessage(); 104a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0George Mount if (!messages.contains(message)) { 105a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0George Mount sb.append(message).append("\n"); 106a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0George Mount messages.add(message); 107a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0George Mount } 108731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 109731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar throw new RuntimeException("Found data binding errors.\n" + sb.toString()); 110731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 111731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 112731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar static String produceScopeLog() { 113731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar StringBuilder sb = new StringBuilder(); 114731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sb.append("full scope log\n"); 115731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeEntry top = sScopeItems.get(); 116731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar while (top != null) { 117731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeProvider provider = top.mProvider; 118731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sb.append("---").append(provider).append("\n"); 119731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (provider instanceof FileScopeProvider) { 120731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sb.append("file:").append(((FileScopeProvider) provider).provideScopeFilePath()) 121731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar .append("\n"); 122731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 123731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (provider instanceof LocationScopeProvider) { 124731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar LocationScopeProvider loc = (LocationScopeProvider) provider; 125731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sb.append("loc:"); 126731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar List<Location> locations = loc.provideScopeLocation(); 127731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (locations == null) { 128731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sb.append("null\n"); 129731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } else { 130731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar for (Location location : locations) { 131731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sb.append(location).append("\n"); 132731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 133731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 134731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 135731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar top = top.mParent; 136731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 137731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar sb.append("---\n"); 138731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return sb.toString(); 139731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 140731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 141731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar static ScopedErrorReport createReport() { 142731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeEntry top = sScopeItems.get(); 143731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar String filePath = null; 144731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar List<Location> locations = null; 145731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar while (top != null && (filePath == null || locations == null)) { 146731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeProvider provider = top.mProvider; 147731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (locations == null && provider instanceof LocationScopeProvider) { 148731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar locations = findAbsoluteLocationFrom(top, (LocationScopeProvider) provider); 149731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 150731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (filePath == null && provider instanceof FileScopeProvider) { 151731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar filePath = ((FileScopeProvider) provider).provideScopeFilePath(); 152731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 153731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar top = top.mParent; 154731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 155731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return new ScopedErrorReport(filePath, locations); 156731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 157731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 158731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar private static List<Location> findAbsoluteLocationFrom(ScopeEntry entry, 159731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar LocationScopeProvider top) { 160731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar List<Location> locations = top.provideScopeLocation(); 161731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (locations == null || locations.isEmpty()) { 162731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return null; 163731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 164731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (locations.size() == 1) { 165731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return Arrays.asList(locations.get(0).toAbsoluteLocation()); 166731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 167731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar // We have more than 1 location. Depending on the scope, we may or may not want all of them 168731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar List<Location> chosen = new ArrayList<>(); 169731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar for (Location location : locations) { 170731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar Location absLocation = location.toAbsoluteLocation(); 171731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (validatedContained(entry.mParent, absLocation)) { 172731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar chosen.add(absLocation); 173731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 174731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 175731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return chosen.isEmpty() ? locations : chosen; 176731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 177731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 178731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar private static boolean validatedContained(ScopeEntry parent, Location absLocation) { 179731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (parent == null) { 180731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return true; 181731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 182731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeProvider provider = parent.mProvider; 183731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (!(provider instanceof LocationScopeProvider)) { 184731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return validatedContained(parent.mParent, absLocation); 185731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 186731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar List<Location> absoluteParents = findAbsoluteLocationFrom(parent, 187731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar (LocationScopeProvider) provider); 188731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar for (Location location : absoluteParents) { 189731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar if (location.contains(absLocation)) { 190731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return true; 191731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 192731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 193731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar return false; 194731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 195731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 196731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar private static class ScopeEntry { 197731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 198731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeProvider mProvider; 199731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 200731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar ScopeEntry mParent; 201731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar 202731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar public ScopeEntry(ScopeProvider scopeProvider, ScopeEntry parent) { 203731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar mProvider = scopeProvider; 204731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar mParent = parent; 205731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 206731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar } 207731b74f7f44e67312a1fc4161c4e0aae221b2417Yigit Boyar}