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}