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