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