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