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