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