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>();
379784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar    static List<ScopedException> sDeferredExceptions = new ArrayList<ScopedException>();
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
1689784c9aaedeb863018f5fcaa0a598e8e2f8ed2f3Yigit Boyar        List<Location> chosen = new ArrayList<Location>();
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}