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