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