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 
ResourceBundle(String appPackage)55     public ResourceBundle(String appPackage) {
56         mAppPackage = appPackage;
57     }
58 
addLayoutBundle(LayoutFileBundle bundle)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 
getLayoutBundles()78     public HashMap<String, List<LayoutFileBundle>> getLayoutBundles() {
79         return mLayoutBundles;
80     }
81 
getAppPackage()82     public String getAppPackage() {
83         return mAppPackage;
84     }
85 
validateMultiResLayouts()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      */
validateAndMergeNameTypeLocations( List<LayoutFileBundle> bundles, String errorMessage, ValidateAndFilterCallback callback)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      */
validateAndGetSharedClassName(List<LayoutFileBundle> bundles)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
LayoutFileBundle()404         public LayoutFileBundle() {
405         }
406 
LayoutFileBundle(File file, String fileName, String directory, String modulePackage, boolean isMerge)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 
getClassNameLocationProvider()416         public LocationScopeProvider getClassNameLocationProvider() {
417             if (mClassNameLocationProvider == null && mClassNameLocation != null
418                     && mClassNameLocation.isValid()) {
419                 mClassNameLocationProvider = mClassNameLocation.createScope();
420             }
421             return mClassNameLocationProvider;
422         }
423 
addVariable(String name, String type, Location location)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 
addImport(String alias, String type, Location location)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 
createBindingTarget(String id, String viewName, boolean used, String tag, String originalTag, Location location)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 
isEmpty()444         public boolean isEmpty() {
445             return mVariables.isEmpty() && mImports.isEmpty() && mBindingTargetBundles.isEmpty();
446         }
447 
getBindingTargetById(String key)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 
getFileName()457         public String getFileName() {
458             return mFileName;
459         }
460 
getConfigName()461         public String getConfigName() {
462             return mConfigName;
463         }
464 
getDirectory()465         public String getDirectory() {
466             return mDirectory;
467         }
468 
hasVariations()469         public boolean hasVariations() {
470             return mHasVariations;
471         }
472 
getVariables()473         public List<NameTypeLocation> getVariables() {
474             return mVariables;
475         }
476 
getImports()477         public List<NameTypeLocation> getImports() {
478             return mImports;
479         }
480 
isMerge()481         public boolean isMerge() {
482             return mIsMerge;
483         }
484 
getBindingClassName()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 
setBindingClass(String bindingClass, Location location)494         public void setBindingClass(String bindingClass, Location location) {
495             mBindingClass = bindingClass;
496             mClassNameLocation = location;
497         }
498 
getBindingClassPackage()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 
getFullBindingClass()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 
getBindingTargetBundles()524         public List<BindingTargetBundle> getBindingTargetBundles() {
525             return mBindingTargetBundles;
526         }
527 
528         @Override
equals(Object o)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
hashCode()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
toString()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 
getModulePackage()574         public String getModulePackage() {
575             return mModulePackage;
576         }
577 
getAbsoluteFilePath()578         public String getAbsoluteFilePath() {
579             return mAbsoluteFilePath;
580         }
581 
582         @Override
provideScopeFilePath()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 
NameTypeLocation()599         public NameTypeLocation() {
600         }
601 
NameTypeLocation(String name, String type, Location location)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
toString()609         public String toString() {
610             return "{" +
611                     "type='" + type + '\'' +
612                     ", name='" + name + '\'' +
613                     ", location=" + location +
614                     '}';
615         }
616 
617         @Override
equals(Object o)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
hashCode()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 
contains(List<NameTypeLocation> list, String name)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
BindingTargetBundle()687         public BindingTargetBundle() {}
688 
BindingTargetBundle(String id, String viewName, boolean used, String tag, String originalTag, Location location)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 
addBinding(String name, String expr, Location location, Location valueLocation)699         public void addBinding(String name, String expr, Location location, Location valueLocation) {
700             mBindingBundleList.add(new BindingBundle(name, expr, location, valueLocation));
701         }
702 
setIncludedLayout(String includedLayout)703         public void setIncludedLayout(String includedLayout) {
704             mIncludedLayout = includedLayout;
705         }
706 
getIncludedLayout()707         public String getIncludedLayout() {
708             return mIncludedLayout;
709         }
710 
isBinder()711         public boolean isBinder() {
712             return mIncludedLayout != null;
713         }
714 
setInterfaceType(String interfaceType)715         public void setInterfaceType(String interfaceType) {
716             mInterfaceType = interfaceType;
717         }
718 
setLocation(Location location)719         public void setLocation(Location location) {
720             mLocation = location;
721         }
722 
getLocation()723         public Location getLocation() {
724             return mLocation;
725         }
726 
getId()727         public String getId() {
728             return mId;
729         }
730 
getTag()731         public String getTag() {
732             return mTag;
733         }
734 
getOriginalTag()735         public String getOriginalTag() {
736             return mOriginalTag;
737         }
738 
getFullClassName()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 
isUsed()762         public boolean isUsed() {
763             return mUsed;
764         }
765 
getBindingBundleList()766         public List<BindingBundle> getBindingBundleList() {
767             return mBindingBundleList;
768         }
769 
getInterfaceType()770         public String getInterfaceType() {
771             return mInterfaceType;
772         }
773 
774         @Override
provideScopeLocation()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 
BindingBundle()787             public BindingBundle() {}
788 
BindingBundle(String name, String expr, Location location, Location valueLocation)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)
getName()798             public String getName() {
799                 return mName;
800             }
801 
802             @XmlAttribute(name="text", required=true)
getExpr()803             public String getExpr() {
804                 return mExpr;
805             }
806 
setName(String name)807             public void setName(String name) {
808                 mName = name;
809             }
810 
setExpr(String expr)811             public void setExpr(String expr) {
812                 mExpr = expr;
813             }
814 
815             @XmlElement(name="Location")
getLocation()816             public Location getLocation() {
817                 return mLocation;
818             }
819 
setLocation(Location location)820             public void setLocation(Location location) {
821                 mLocation = location;
822             }
823 
824             @XmlElement(name="ValueLocation")
getValueLocation()825             public Location getValueLocation() {
826                 return mValueLocation;
827             }
828 
setValueLocation(Location valueLocation)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 {
get(LayoutFileBundle bundle)839         List<NameTypeLocation> get(LayoutFileBundle bundle);
840     }
841 }
842