Home | History | Annotate | Download | only in store
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  * Licensed under the Apache License, Version 2.0 (the "License");
      4  * you may not use this file except in compliance with the License.
      5  * You may obtain a copy of the License at
      6  *      http://www.apache.org/licenses/LICENSE-2.0
      7  * Unless required by applicable law or agreed to in writing, software
      8  * distributed under the License is distributed on an "AS IS" BASIS,
      9  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     10  * See the License for the specific language governing permissions and
     11  * limitations under the License.
     12  */
     13 
     14 package android.databinding.tool.store;
     15 
     16 import org.apache.commons.lang3.ArrayUtils;
     17 
     18 import android.databinding.tool.processing.ErrorMessages;
     19 import android.databinding.tool.processing.Scope;
     20 import android.databinding.tool.processing.ScopedException;
     21 import android.databinding.tool.processing.scopes.FileScopeProvider;
     22 import android.databinding.tool.processing.scopes.LocationScopeProvider;
     23 import android.databinding.tool.util.L;
     24 import android.databinding.tool.util.ParserHelper;
     25 import android.databinding.tool.util.Preconditions;
     26 
     27 import java.io.File;
     28 import java.io.Serializable;
     29 import java.util.ArrayList;
     30 import java.util.Arrays;
     31 import java.util.HashMap;
     32 import java.util.HashSet;
     33 import java.util.List;
     34 import java.util.Map;
     35 import java.util.Set;
     36 
     37 import javax.xml.bind.annotation.XmlAccessType;
     38 import javax.xml.bind.annotation.XmlAccessorType;
     39 import javax.xml.bind.annotation.XmlAttribute;
     40 import javax.xml.bind.annotation.XmlElement;
     41 import javax.xml.bind.annotation.XmlElementWrapper;
     42 import javax.xml.bind.annotation.XmlRootElement;
     43 
     44 /**
     45  * This is a serializable class that can keep the result of parsing layout files.
     46  */
     47 public class ResourceBundle implements Serializable {
     48     private static final String[] ANDROID_VIEW_PACKAGE_VIEWS = new String[]
     49             {"View", "ViewGroup", "ViewStub", "TextureView", "SurfaceView"};
     50     private String mAppPackage;
     51 
     52     private HashMap<String, List<LayoutFileBundle>> mLayoutBundles
     53             = new HashMap<String, List<LayoutFileBundle>>();
     54 
     55     public ResourceBundle(String appPackage) {
     56         mAppPackage = appPackage;
     57     }
     58 
     59     public void addLayoutBundle(LayoutFileBundle bundle) {
     60         if (bundle.mFileName == null) {
     61             L.e("File bundle must have a name. %s does not have one.", bundle);
     62             return;
     63         }
     64         if (!mLayoutBundles.containsKey(bundle.mFileName)) {
     65             mLayoutBundles.put(bundle.mFileName, new ArrayList<LayoutFileBundle>());
     66         }
     67         final List<LayoutFileBundle> bundles = mLayoutBundles.get(bundle.mFileName);
     68         for (LayoutFileBundle existing : bundles) {
     69             if (existing.equals(bundle)) {
     70                 L.d("skipping layout bundle %s because it already exists.", bundle);
     71                 return;
     72             }
     73         }
     74         L.d("adding bundle %s", bundle);
     75         bundles.add(bundle);
     76     }
     77 
     78     public HashMap<String, List<LayoutFileBundle>> getLayoutBundles() {
     79         return mLayoutBundles;
     80     }
     81 
     82     public String getAppPackage() {
     83         return mAppPackage;
     84     }
     85 
     86     public void validateMultiResLayouts() {
     87         for (List<LayoutFileBundle> layoutFileBundles : mLayoutBundles.values()) {
     88             for (LayoutFileBundle layoutFileBundle : layoutFileBundles) {
     89                 for (BindingTargetBundle target : layoutFileBundle.getBindingTargetBundles()) {
     90                     if (target.isBinder()) {
     91                         List<LayoutFileBundle> boundTo =
     92                                 mLayoutBundles.get(target.getIncludedLayout());
     93                         if (boundTo == null || boundTo.isEmpty()) {
     94                             L.e("There is no binding for %s", target.getIncludedLayout());
     95                         } else {
     96                             String binding = boundTo.get(0).getFullBindingClass();
     97                             target.setInterfaceType(binding);
     98                         }
     99                     }
    100                 }
    101             }
    102         }
    103 
    104         for (Map.Entry<String, List<LayoutFileBundle>> bundles : mLayoutBundles.entrySet()) {
    105             if (bundles.getValue().size() < 2) {
    106                 continue;
    107             }
    108 
    109             // validate all ids are in correct view types
    110             // and all variables have the same name
    111             for (LayoutFileBundle bundle : bundles.getValue()) {
    112                 bundle.mHasVariations = true;
    113             }
    114             String bindingClass = validateAndGetSharedClassName(bundles.getValue());
    115             Map<String, NameTypeLocation> variableTypes = validateAndMergeNameTypeLocations(
    116                     bundles.getValue(), ErrorMessages.MULTI_CONFIG_VARIABLE_TYPE_MISMATCH,
    117                     new ValidateAndFilterCallback() {
    118                         @Override
    119                         public List<NameTypeLocation> get(LayoutFileBundle bundle) {
    120                             return bundle.mVariables;
    121                         }
    122                     });
    123 
    124             Map<String, NameTypeLocation> importTypes = validateAndMergeNameTypeLocations(
    125                     bundles.getValue(), ErrorMessages.MULTI_CONFIG_IMPORT_TYPE_MISMATCH,
    126                     new ValidateAndFilterCallback() {
    127                         @Override
    128                         public List<NameTypeLocation> get(LayoutFileBundle bundle) {
    129                             return bundle.mImports;
    130                         }
    131                     });
    132 
    133             for (LayoutFileBundle bundle : bundles.getValue()) {
    134                 // now add missing ones to each to ensure they can be referenced
    135                 L.d("checking for missing variables in %s / %s", bundle.mFileName,
    136                         bundle.mConfigName);
    137                 for (Map.Entry<String, NameTypeLocation> variable : variableTypes.entrySet()) {
    138                     if (!NameTypeLocation.contains(bundle.mVariables, variable.getKey())) {
    139                         bundle.mVariables.add(variable.getValue());
    140                         L.d("adding missing variable %s to %s / %s", variable.getKey(),
    141                                 bundle.mFileName, bundle.mConfigName);
    142                     }
    143                 }
    144                 for (Map.Entry<String, NameTypeLocation> userImport : importTypes.entrySet()) {
    145                     if (!NameTypeLocation.contains(bundle.mImports, userImport.getKey())) {
    146                         bundle.mImports.add(userImport.getValue());
    147                         L.d("adding missing import %s to %s / %s", userImport.getKey(),
    148                                 bundle.mFileName, bundle.mConfigName);
    149                     }
    150                 }
    151             }
    152 
    153             Set<String> includeBindingIds = new HashSet<String>();
    154             Set<String> viewBindingIds = new HashSet<String>();
    155             Map<String, String> viewTypes = new HashMap<String, String>();
    156             Map<String, String> includes = new HashMap<String, String>();
    157             L.d("validating ids for %s", bundles.getKey());
    158             Set<String> conflictingIds = new HashSet<>();
    159             for (LayoutFileBundle bundle : bundles.getValue()) {
    160                 try {
    161                     Scope.enter(bundle);
    162                     for (BindingTargetBundle target : bundle.mBindingTargetBundles) {
    163                         try {
    164                             Scope.enter(target);
    165                             L.d("checking %s %s %s", target.getId(), target.getFullClassName(),
    166                                     target.isBinder());
    167                             if (target.mId != null) {
    168                                 if (target.isBinder()) {
    169                                     if (viewBindingIds.contains(target.mId)) {
    170                                         L.d("%s is conflicting", target.mId);
    171                                         conflictingIds.add(target.mId);
    172                                         continue;
    173                                     }
    174                                     includeBindingIds.add(target.mId);
    175                                 } else {
    176                                     if (includeBindingIds.contains(target.mId)) {
    177                                         L.d("%s is conflicting", target.mId);
    178                                         conflictingIds.add(target.mId);
    179                                         continue;
    180                                     }
    181                                     viewBindingIds.add(target.mId);
    182                                 }
    183                                 String existingType = viewTypes.get(target.mId);
    184                                 if (existingType == null) {
    185                                     L.d("assigning %s as %s", target.getId(),
    186                                             target.getFullClassName());
    187                                             viewTypes.put(target.mId, target.getFullClassName());
    188                                     if (target.isBinder()) {
    189                                         includes.put(target.mId, target.getIncludedLayout());
    190                                     }
    191                                 } else if (!existingType.equals(target.getFullClassName())) {
    192                                     if (target.isBinder()) {
    193                                         L.d("overriding %s as base binder", target.getId());
    194                                         viewTypes.put(target.mId,
    195                                                 "android.databinding.ViewDataBinding");
    196                                         includes.put(target.mId, target.getIncludedLayout());
    197                                     } else {
    198                                         L.d("overriding %s as base view", target.getId());
    199                                         viewTypes.put(target.mId, "android.view.View");
    200                                     }
    201                                 }
    202                             }
    203                         } catch (ScopedException ex) {
    204                             Scope.defer(ex);
    205                         } finally {
    206                             Scope.exit();
    207                         }
    208                     }
    209                 } finally {
    210                     Scope.exit();
    211                 }
    212             }
    213 
    214             if (!conflictingIds.isEmpty()) {
    215                 for (LayoutFileBundle bundle : bundles.getValue()) {
    216                     for (BindingTargetBundle target : bundle.mBindingTargetBundles) {
    217                         if (conflictingIds.contains(target.mId)) {
    218                             Scope.registerError(String.format(
    219                                             ErrorMessages.MULTI_CONFIG_ID_USED_AS_IMPORT,
    220                                             target.mId), bundle, target);
    221                         }
    222                     }
    223                 }
    224             }
    225 
    226             for (LayoutFileBundle bundle : bundles.getValue()) {
    227                 try {
    228                     Scope.enter(bundle);
    229                     for (Map.Entry<String, String> viewType : viewTypes.entrySet()) {
    230                         BindingTargetBundle target = bundle.getBindingTargetById(viewType.getKey());
    231                         if (target == null) {
    232                             String include = includes.get(viewType.getKey());
    233                             if (include == null) {
    234                                 bundle.createBindingTarget(viewType.getKey(), viewType.getValue(),
    235                                         false, null, null, null);
    236                             } else {
    237                                 BindingTargetBundle bindingTargetBundle = bundle
    238                                         .createBindingTarget(
    239                                                 viewType.getKey(), null, false, null, null, null);
    240                                 bindingTargetBundle
    241                                         .setIncludedLayout(includes.get(viewType.getKey()));
    242                                 bindingTargetBundle.setInterfaceType(viewType.getValue());
    243                             }
    244                         } else {
    245                             L.d("setting interface type on %s (%s) as %s", target.mId,
    246                                     target.getFullClassName(), viewType.getValue());
    247                             target.setInterfaceType(viewType.getValue());
    248                         }
    249                     }
    250                 } catch (ScopedException ex) {
    251                     Scope.defer(ex);
    252                 } finally {
    253                     Scope.exit();
    254                 }
    255             }
    256         }
    257         // assign class names to each
    258         for (Map.Entry<String, List<LayoutFileBundle>> entry : mLayoutBundles.entrySet()) {
    259             for (LayoutFileBundle bundle : entry.getValue()) {
    260                 final String configName;
    261                 if (bundle.hasVariations()) {
    262                     // append configuration specifiers.
    263                     final String parentFileName = bundle.mDirectory;
    264                     L.d("parent file for %s is %s", bundle.getFileName(), parentFileName);
    265                     if ("layout".equals(parentFileName)) {
    266                         configName = "";
    267                     } else {
    268                         configName = ParserHelper.toClassName(parentFileName.substring("layout-".length()));
    269                     }
    270                 } else {
    271                     configName = "";
    272                 }
    273                 bundle.mConfigName = configName;
    274             }
    275         }
    276     }
    277 
    278     /**
    279      * Receives a list of bundles which are representations of the same layout file in different
    280      * configurations.
    281      * @param bundles
    282      * @return The map for variables and their types
    283      */
    284     private Map<String, NameTypeLocation> validateAndMergeNameTypeLocations(
    285             List<LayoutFileBundle> bundles, String errorMessage,
    286             ValidateAndFilterCallback callback) {
    287         Map<String, NameTypeLocation> result = new HashMap<>();
    288         Set<String> mismatched = new HashSet<>();
    289         for (LayoutFileBundle bundle : bundles) {
    290             for (NameTypeLocation item : callback.get(bundle)) {
    291                 NameTypeLocation existing = result.get(item.name);
    292                 if (existing != null && !existing.type.equals(item.type)) {
    293                     mismatched.add(item.name);
    294                     continue;
    295                 }
    296                 result.put(item.name, item);
    297             }
    298         }
    299         if (mismatched.isEmpty()) {
    300             return result;
    301         }
    302         // create exceptions. We could get more clever and find the outlier but for now, listing
    303         // each file w/ locations seems enough
    304         for (String mismatch : mismatched) {
    305             for (LayoutFileBundle bundle : bundles) {
    306                 NameTypeLocation found = null;
    307                 for (NameTypeLocation item : callback.get(bundle)) {
    308                     if (mismatch.equals(item.name)) {
    309                         found = item;
    310                         break;
    311                     }
    312                 }
    313                 if (found == null) {
    314                     // variable is not defined in this layout, continue
    315                     continue;
    316                 }
    317                 Scope.registerError(String.format(
    318                                 errorMessage, found.name, found.type,
    319                                 bundle.mDirectory + "/" + bundle.getFileName()), bundle,
    320                         found.location.createScope());
    321             }
    322         }
    323         return result;
    324     }
    325 
    326     /**
    327      * Receives a list of bundles which are representations of the same layout file in different
    328      * configurations.
    329      * @param bundles
    330      * @return The shared class name for these bundles
    331      */
    332     private String validateAndGetSharedClassName(List<LayoutFileBundle> bundles) {
    333         String sharedClassName = null;
    334         boolean hasMismatch = false;
    335         for (LayoutFileBundle bundle : bundles) {
    336             bundle.mHasVariations = true;
    337             String fullBindingClass = bundle.getFullBindingClass();
    338             if (sharedClassName == null) {
    339                 sharedClassName = fullBindingClass;
    340             } else if (!sharedClassName.equals(fullBindingClass)) {
    341                 hasMismatch = true;
    342                 break;
    343             }
    344         }
    345         if (!hasMismatch) {
    346             return sharedClassName;
    347         }
    348         // generate proper exceptions for each
    349         for (LayoutFileBundle bundle : bundles) {
    350             Scope.registerError(String.format(ErrorMessages.MULTI_CONFIG_LAYOUT_CLASS_NAME_MISMATCH,
    351                     bundle.getFullBindingClass(), bundle.mDirectory + "/" + bundle.getFileName()),
    352                     bundle, bundle.getClassNameLocationProvider());
    353         }
    354         return sharedClassName;
    355     }
    356 
    357     @XmlAccessorType(XmlAccessType.NONE)
    358     @XmlRootElement(name="Layout")
    359     public static class LayoutFileBundle implements Serializable, FileScopeProvider {
    360         @XmlAttribute(name="layout", required = true)
    361         public String mFileName;
    362         @XmlAttribute(name="modulePackage", required = true)
    363         public String mModulePackage;
    364         @XmlAttribute(name="absoluteFilePath", required = true)
    365         public String mAbsoluteFilePath;
    366         private String mConfigName;
    367 
    368         // The binding class as given by the user
    369         @XmlAttribute(name="bindingClass", required = false)
    370         public String mBindingClass;
    371 
    372         // The location of the name of the generated class, optional
    373         @XmlElement(name = "ClassNameLocation", required = false)
    374         private Location mClassNameLocation;
    375         // The full package and class name as determined from mBindingClass and mModulePackage
    376         private String mFullBindingClass;
    377 
    378         // The simple binding class name as determined from mBindingClass and mModulePackage
    379         private String mBindingClassName;
    380 
    381         // The package of the binding class as determined from mBindingClass and mModulePackage
    382         private String mBindingPackage;
    383 
    384         @XmlAttribute(name="directory", required = true)
    385         public String mDirectory;
    386         public boolean mHasVariations;
    387 
    388         @XmlElement(name="Variables")
    389         public List<NameTypeLocation> mVariables = new ArrayList<>();
    390 
    391         @XmlElement(name="Imports")
    392         public List<NameTypeLocation> mImports = new ArrayList<>();
    393 
    394         @XmlElementWrapper(name="Targets")
    395         @XmlElement(name="Target")
    396         public List<BindingTargetBundle> mBindingTargetBundles = new ArrayList<BindingTargetBundle>();
    397 
    398         @XmlAttribute(name="isMerge", required = true)
    399         private boolean mIsMerge;
    400 
    401         private LocationScopeProvider mClassNameLocationProvider;
    402 
    403         // for XML binding
    404         public LayoutFileBundle() {
    405         }
    406 
    407         public LayoutFileBundle(File file, String fileName, String directory,
    408                 String modulePackage, boolean isMerge) {
    409             mFileName = fileName;
    410             mDirectory = directory;
    411             mModulePackage = modulePackage;
    412             mIsMerge = isMerge;
    413             mAbsoluteFilePath = file.getAbsolutePath();
    414         }
    415 
    416         public LocationScopeProvider getClassNameLocationProvider() {
    417             if (mClassNameLocationProvider == null && mClassNameLocation != null
    418                     && mClassNameLocation.isValid()) {
    419                 mClassNameLocationProvider = mClassNameLocation.createScope();
    420             }
    421             return mClassNameLocationProvider;
    422         }
    423 
    424         public void addVariable(String name, String type, Location location) {
    425             Preconditions.check(!NameTypeLocation.contains(mVariables, name),
    426                     "Cannot use same variable name twice. %s in %s", name, location);
    427             mVariables.add(new NameTypeLocation(name, type, location));
    428         }
    429 
    430         public void addImport(String alias, String type, Location location) {
    431             Preconditions.check(!NameTypeLocation.contains(mImports, alias),
    432                     "Cannot import same alias twice. %s in %s", alias, location);
    433             mImports.add(new NameTypeLocation(alias, type, location));
    434         }
    435 
    436         public BindingTargetBundle createBindingTarget(String id, String viewName,
    437                 boolean used, String tag, String originalTag, Location location) {
    438             BindingTargetBundle target = new BindingTargetBundle(id, viewName, used, tag,
    439                     originalTag, location);
    440             mBindingTargetBundles.add(target);
    441             return target;
    442         }
    443 
    444         public boolean isEmpty() {
    445             return mVariables.isEmpty() && mImports.isEmpty() && mBindingTargetBundles.isEmpty();
    446         }
    447 
    448         public BindingTargetBundle getBindingTargetById(String key) {
    449             for (BindingTargetBundle target : mBindingTargetBundles) {
    450                 if (key.equals(target.mId)) {
    451                     return target;
    452                 }
    453             }
    454             return null;
    455         }
    456 
    457         public String getFileName() {
    458             return mFileName;
    459         }
    460 
    461         public String getConfigName() {
    462             return mConfigName;
    463         }
    464 
    465         public String getDirectory() {
    466             return mDirectory;
    467         }
    468 
    469         public boolean hasVariations() {
    470             return mHasVariations;
    471         }
    472 
    473         public List<NameTypeLocation> getVariables() {
    474             return mVariables;
    475         }
    476 
    477         public List<NameTypeLocation> getImports() {
    478             return mImports;
    479         }
    480 
    481         public boolean isMerge() {
    482             return mIsMerge;
    483         }
    484 
    485         public String getBindingClassName() {
    486             if (mBindingClassName == null) {
    487                 String fullClass = getFullBindingClass();
    488                 int dotIndex = fullClass.lastIndexOf('.');
    489                 mBindingClassName = fullClass.substring(dotIndex + 1);
    490             }
    491             return mBindingClassName;
    492         }
    493 
    494         public void setBindingClass(String bindingClass, Location location) {
    495             mBindingClass = bindingClass;
    496             mClassNameLocation = location;
    497         }
    498 
    499         public String getBindingClassPackage() {
    500             if (mBindingPackage == null) {
    501                 String fullClass = getFullBindingClass();
    502                 int dotIndex = fullClass.lastIndexOf('.');
    503                 mBindingPackage = fullClass.substring(0, dotIndex);
    504             }
    505             return mBindingPackage;
    506         }
    507 
    508         private String getFullBindingClass() {
    509             if (mFullBindingClass == null) {
    510                 if (mBindingClass == null) {
    511                     mFullBindingClass = getModulePackage() + ".databinding." +
    512                             ParserHelper.toClassName(getFileName()) + "Binding";
    513                 } else if (mBindingClass.startsWith(".")) {
    514                     mFullBindingClass = getModulePackage() + mBindingClass;
    515                 } else if (mBindingClass.indexOf('.') < 0) {
    516                     mFullBindingClass = getModulePackage() + ".databinding." + mBindingClass;
    517                 } else {
    518                     mFullBindingClass = mBindingClass;
    519                 }
    520             }
    521             return mFullBindingClass;
    522         }
    523 
    524         public List<BindingTargetBundle> getBindingTargetBundles() {
    525             return mBindingTargetBundles;
    526         }
    527 
    528         @Override
    529         public boolean equals(Object o) {
    530             if (this == o) {
    531                 return true;
    532             }
    533             if (o == null || getClass() != o.getClass()) {
    534                 return false;
    535             }
    536 
    537             LayoutFileBundle bundle = (LayoutFileBundle) o;
    538 
    539             if (mConfigName != null ? !mConfigName.equals(bundle.mConfigName)
    540                     : bundle.mConfigName != null) {
    541                 return false;
    542             }
    543             if (mDirectory != null ? !mDirectory.equals(bundle.mDirectory)
    544                     : bundle.mDirectory != null) {
    545                 return false;
    546             }
    547             if (mFileName != null ? !mFileName.equals(bundle.mFileName)
    548                     : bundle.mFileName != null) {
    549                 return false;
    550             }
    551 
    552             return true;
    553         }
    554 
    555         @Override
    556         public int hashCode() {
    557             int result = mFileName != null ? mFileName.hashCode() : 0;
    558             result = 31 * result + (mConfigName != null ? mConfigName.hashCode() : 0);
    559             result = 31 * result + (mDirectory != null ? mDirectory.hashCode() : 0);
    560             return result;
    561         }
    562 
    563         @Override
    564         public String toString() {
    565             return "LayoutFileBundle{" +
    566                     "mHasVariations=" + mHasVariations +
    567                     ", mDirectory='" + mDirectory + '\'' +
    568                     ", mConfigName='" + mConfigName + '\'' +
    569                     ", mModulePackage='" + mModulePackage + '\'' +
    570                     ", mFileName='" + mFileName + '\'' +
    571                     '}';
    572         }
    573 
    574         public String getModulePackage() {
    575             return mModulePackage;
    576         }
    577 
    578         public String getAbsoluteFilePath() {
    579             return mAbsoluteFilePath;
    580         }
    581 
    582         @Override
    583         public String provideScopeFilePath() {
    584             return mAbsoluteFilePath;
    585         }
    586     }
    587 
    588     @XmlAccessorType(XmlAccessType.NONE)
    589     public static class NameTypeLocation {
    590         @XmlAttribute(name="type", required = true)
    591         public String type;
    592 
    593         @XmlAttribute(name="name", required = true)
    594         public String name;
    595 
    596         @XmlElement(name="location", required = false)
    597         public Location location;
    598 
    599         public NameTypeLocation() {
    600         }
    601 
    602         public NameTypeLocation(String name, String type, Location location) {
    603             this.type = type;
    604             this.name = name;
    605             this.location = location;
    606         }
    607 
    608         @Override
    609         public String toString() {
    610             return "{" +
    611                     "type='" + type + '\'' +
    612                     ", name='" + name + '\'' +
    613                     ", location=" + location +
    614                     '}';
    615         }
    616 
    617         @Override
    618         public boolean equals(Object o) {
    619             if (this == o) {
    620                 return true;
    621             }
    622             if (o == null || getClass() != o.getClass()) {
    623                 return false;
    624             }
    625 
    626             NameTypeLocation that = (NameTypeLocation) o;
    627 
    628             if (location != null ? !location.equals(that.location) : that.location != null) {
    629                 return false;
    630             }
    631             if (!name.equals(that.name)) {
    632                 return false;
    633             }
    634             if (!type.equals(that.type)) {
    635                 return false;
    636             }
    637 
    638             return true;
    639         }
    640 
    641         @Override
    642         public int hashCode() {
    643             int result = type.hashCode();
    644             result = 31 * result + name.hashCode();
    645             result = 31 * result + (location != null ? location.hashCode() : 0);
    646             return result;
    647         }
    648 
    649         public static boolean contains(List<NameTypeLocation> list, String name) {
    650             for (NameTypeLocation ntl : list) {
    651                 if (name.equals(ntl.name)) {
    652                     return true;
    653                 }
    654             }
    655             return false;
    656         }
    657     }
    658 
    659     public static class MarshalledMapType {
    660         public List<NameTypeLocation> entries;
    661     }
    662 
    663     @XmlAccessorType(XmlAccessType.NONE)
    664     public static class BindingTargetBundle implements Serializable, LocationScopeProvider {
    665         // public for XML serialization
    666 
    667         @XmlAttribute(name="id")
    668         public String mId;
    669         @XmlAttribute(name="tag", required = true)
    670         public String mTag;
    671         @XmlAttribute(name="originalTag")
    672         public String mOriginalTag;
    673         @XmlAttribute(name="view", required = false)
    674         public String mViewName;
    675         private String mFullClassName;
    676         public boolean mUsed = true;
    677         @XmlElementWrapper(name="Expressions")
    678         @XmlElement(name="Expression")
    679         public List<BindingBundle> mBindingBundleList = new ArrayList<BindingBundle>();
    680         @XmlAttribute(name="include")
    681         public String mIncludedLayout;
    682         @XmlElement(name="location")
    683         public Location mLocation;
    684         private String mInterfaceType;
    685 
    686         // For XML serialization
    687         public BindingTargetBundle() {}
    688 
    689         public BindingTargetBundle(String id, String viewName, boolean used,
    690                 String tag, String originalTag, Location location) {
    691             mId = id;
    692             mViewName = viewName;
    693             mUsed = used;
    694             mTag = tag;
    695             mOriginalTag = originalTag;
    696             mLocation = location;
    697         }
    698 
    699         public void addBinding(String name, String expr, Location location, Location valueLocation) {
    700             mBindingBundleList.add(new BindingBundle(name, expr, location, valueLocation));
    701         }
    702 
    703         public void setIncludedLayout(String includedLayout) {
    704             mIncludedLayout = includedLayout;
    705         }
    706 
    707         public String getIncludedLayout() {
    708             return mIncludedLayout;
    709         }
    710 
    711         public boolean isBinder() {
    712             return mIncludedLayout != null;
    713         }
    714 
    715         public void setInterfaceType(String interfaceType) {
    716             mInterfaceType = interfaceType;
    717         }
    718 
    719         public void setLocation(Location location) {
    720             mLocation = location;
    721         }
    722 
    723         public Location getLocation() {
    724             return mLocation;
    725         }
    726 
    727         public String getId() {
    728             return mId;
    729         }
    730 
    731         public String getTag() {
    732             return mTag;
    733         }
    734 
    735         public String getOriginalTag() {
    736             return mOriginalTag;
    737         }
    738 
    739         public String getFullClassName() {
    740             if (mFullClassName == null) {
    741                 if (isBinder()) {
    742                     mFullClassName = mInterfaceType;
    743                 } else if (mViewName.indexOf('.') == -1) {
    744                     if (ArrayUtils.contains(ANDROID_VIEW_PACKAGE_VIEWS, mViewName)) {
    745                         mFullClassName = "android.view." + mViewName;
    746                     } else if("WebView".equals(mViewName)) {
    747                         mFullClassName = "android.webkit." + mViewName;
    748                     } else {
    749                         mFullClassName = "android.widget." + mViewName;
    750                     }
    751                 } else {
    752                     mFullClassName = mViewName;
    753                 }
    754             }
    755             if (mFullClassName == null) {
    756                 L.e("Unexpected full class name = null. view = %s, interface = %s, layout = %s",
    757                         mViewName, mInterfaceType, mIncludedLayout);
    758             }
    759             return mFullClassName;
    760         }
    761 
    762         public boolean isUsed() {
    763             return mUsed;
    764         }
    765 
    766         public List<BindingBundle> getBindingBundleList() {
    767             return mBindingBundleList;
    768         }
    769 
    770         public String getInterfaceType() {
    771             return mInterfaceType;
    772         }
    773 
    774         @Override
    775         public List<Location> provideScopeLocation() {
    776             return mLocation == null ? null : Arrays.asList(mLocation);
    777         }
    778 
    779         @XmlAccessorType(XmlAccessType.NONE)
    780         public static class BindingBundle implements Serializable {
    781 
    782             private String mName;
    783             private String mExpr;
    784             private Location mLocation;
    785             private Location mValueLocation;
    786 
    787             public BindingBundle() {}
    788 
    789             public BindingBundle(String name, String expr, Location location,
    790                     Location valueLocation) {
    791                 mName = name;
    792                 mExpr = expr;
    793                 mLocation = location;
    794                 mValueLocation = valueLocation;
    795             }
    796 
    797             @XmlAttribute(name="attribute", required=true)
    798             public String getName() {
    799                 return mName;
    800             }
    801 
    802             @XmlAttribute(name="text", required=true)
    803             public String getExpr() {
    804                 return mExpr;
    805             }
    806 
    807             public void setName(String name) {
    808                 mName = name;
    809             }
    810 
    811             public void setExpr(String expr) {
    812                 mExpr = expr;
    813             }
    814 
    815             @XmlElement(name="Location")
    816             public Location getLocation() {
    817                 return mLocation;
    818             }
    819 
    820             public void setLocation(Location location) {
    821                 mLocation = location;
    822             }
    823 
    824             @XmlElement(name="ValueLocation")
    825             public Location getValueLocation() {
    826                 return mValueLocation;
    827             }
    828 
    829             public void setValueLocation(Location valueLocation) {
    830                 mValueLocation = valueLocation;
    831             }
    832         }
    833     }
    834 
    835     /**
    836      * Just an inner callback class to process imports and variables w/ the same code.
    837      */
    838     private interface ValidateAndFilterCallback {
    839         List<NameTypeLocation> get(LayoutFileBundle bundle);
    840     }
    841 }
    842