Scope.java revision a649c6ce3f6eb4882221e77a1d27e0d28c4c13a0
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.HashSet;
28import 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 */
34public class Scope {
35
36    private static ThreadLocal<ScopeEntry> sScopeItems = new ThreadLocal<ScopeEntry>();
37    static List<ScopedException> sDeferredExceptions = new ArrayList<>();
38
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    }
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
53    public static void exit() {
54        ScopeEntry entry = sScopeItems.get();
55        Preconditions.checkNotNull(entry, "Inconsistent scope exit");
56        sScopeItems.set(entry.mParent);
57    }
58
59    public static void defer(ScopedException exception) {
60        sDeferredExceptions.add(exception);
61    }
62
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     */
92    public static void registerError(String msg, ScopeProvider... scopeProviders) {
93        registerErrorInternal(msg, 0, scopeProviders);
94    }
95
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
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
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
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
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
202        public ScopeEntry(ScopeProvider scopeProvider, ScopeEntry parent) {
203            mProvider = scopeProvider;
204            mParent = parent;
205        }
206    }
207}