Home | History | Annotate | Download | only in processing
      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 
     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     }
     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 
     54     public static void exit() {
     55         ScopeEntry entry = sScopeItems.get();
     56         Preconditions.checkNotNull(entry, "Inconsistent scope exit");
     57         sScopeItems.set(entry.mParent);
     58     }
     59 
     60     public static void defer(ScopedException exception) {
     61         sDeferredExceptions.add(exception);
     62     }
     63 
     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      */
     93     public static void registerError(String msg, ScopeProvider... scopeProviders) {
     94         registerErrorInternal(msg, 0, scopeProviders);
     95     }
     96 
     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 
    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 
    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 
    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 
    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 
    205         public ScopeEntry(ScopeProvider scopeProvider, ScopeEntry parent) {
    206             mProvider = scopeProvider;
    207             mParent = parent;
    208         }
    209     }
    210 }