1 5 package xdoclet.modules.ejb; 6 7 import java.util.*; 8 9 import org.apache.commons.logging.Log; 10 11 import xjavadoc.*; 12 13 import xdoclet.DocletContext; 14 import xdoclet.DocletSupport; 15 import xdoclet.DocletTask; 16 import xdoclet.SubTask; 17 import xdoclet.XDocletException; 18 import xdoclet.XDocletTagSupport; 19 import xdoclet.modules.ejb.entity.*; 20 import xdoclet.modules.ejb.home.HomeInterfaceSubTask; 21 import xdoclet.modules.ejb.home.LocalHomeInterfaceSubTask; 22 import xdoclet.modules.ejb.intf.LocalInterfaceSubTask; 23 import xdoclet.modules.ejb.intf.RemoteInterfaceSubTask; 24 import xdoclet.modules.ejb.mdb.MdbSubTask; 25 import xdoclet.modules.ejb.mdb.MdbTagsHandler; 26 import xdoclet.modules.ejb.session.SessionSubTask; 27 import xdoclet.modules.ejb.session.SessionTagsHandler; 28 import xdoclet.tagshandler.PackageTagsHandler; 29 import xdoclet.util.LogUtil; 30 import xdoclet.util.Translator; 31 import xdoclet.util.TypeConversionUtil; 32 33 40 public class EjbTagsHandler extends XDocletTagSupport 41 { 42 public final static String SERVICE_ENDPOINT = "service-endpoint"; 44 public final static String ALL = "all"; 45 public final static String SERVICE_ENDPOINT_SUFFIX = "Service"; 46 47 protected final static String LOCAL_SUFFIX = "Local"; 48 49 private XClass currentEjbClass; 50 51 58 public static boolean isAConcreteEJBean(XClass clazz) throws XDocletException 59 { 60 XTag beanTag = clazz.getDoc().getTag("ejb:bean"); 61 62 if (beanTag != null) { 63 String generateStr = beanTag.getAttributeValue("generate"); 64 65 if (generateStr != null) { 66 boolean generate = TypeConversionUtil.stringToBoolean(generateStr, true); 67 68 if (generate == true) { 70 return true; 71 } 72 else { 73 return false; 75 } 76 } 77 78 if (beanTag.getAttributeValue("name") != null) { 80 return true; 81 } 82 } 83 84 SubTask subtask = getSubTaskClassForClass(clazz); 87 88 if (clazz.isAbstract() == true) { 89 if (hasANonDocletGeneratedSubClass(clazz) == true) { 90 return false; 91 } 92 93 if (subtask == null) { 95 return false; 96 } 97 98 if (DocletContext.getInstance().isSubTaskDefined(subtask.getSubTaskName()) == true) { 100 return true; 102 } 103 else { 104 if (CmpTagsHandler.isEntityCmp(clazz) && CmpTagsHandler.isUsingCmp2Impl(clazz)) { 106 return true; 107 } 108 109 return false; 110 } 111 } 112 else { 113 if (subtask != null && DocletContext.getInstance().isSubTaskDefined(subtask.getSubTaskName())) { 115 if (subtask.getSubTaskName().equals(DocletTask.getSubTaskName(SessionSubTask.class))) { 116 return true; 117 } 118 119 String currentClassName = clazz.getQualifiedName(); 120 121 throw new XDocletException(Translator.getString("xdoclet.modules.ejb.Messages", "class_not_abstract", 122 new String []{currentClassName, DocletTask.getSubTaskName(SessionSubTask.class)})); 123 } 124 else { 125 return true; 126 } 127 } 128 } 129 130 139 public static String getEjbNameFor(XClass clazz) 140 { 141 XTag beanTag = clazz.getDoc().getTag("ejb:bean"); 142 String paramValue = null; 143 144 if (beanTag != null) { 145 paramValue = beanTag.getAttributeValue("name"); 146 } 147 148 if (paramValue == null) { 149 String className = clazz.getName(); 150 151 String suffixlist = (String ) getDocletContext().getConfigParam("ejbClassNameSuffix"); 153 StringTokenizer st = new StringTokenizer(suffixlist, ","); 154 155 while (st.hasMoreTokens()) { 156 String suffix = st.nextToken(); 157 158 if (className.endsWith(suffix)) { 159 int index = className.lastIndexOf(suffix); 160 161 className = className.substring(0, index); 162 break; 163 } 164 } 165 166 return className; 167 } 168 169 return paramValue; 170 } 171 172 179 public static String getShortEjbNameFor(XClass clazz) 180 { 181 Log log = LogUtil.getLog(EjbTagsHandler.class, "shortEjbName"); 182 183 StringTokenizer ejbNameTokens = new StringTokenizer(getEjbNameFor(clazz), ":./\\-"); 185 String name; 186 187 do { 188 name = ejbNameTokens.nextToken(); 189 } while (ejbNameTokens.hasMoreTokens()); 190 191 if (log.isDebugEnabled()) { 192 log.debug("Name=" + name); 193 } 194 195 return name; 196 } 197 198 202 public static String getEjbIdFor(XClass clazz) 203 { 204 return getEjbNameFor(clazz).replace('/', '_'); 205 } 206 207 212 public static String getEjbSpec() 213 { 214 return (String ) getDocletContext().getConfigParam("EjbSpec"); 215 } 216 217 public static boolean isLocalEjb(XClass clazz) throws XDocletException 218 { 219 return isViewtypeEjb(clazz, "local"); 220 } 221 222 public static boolean isRemoteEjb(XClass clazz) throws XDocletException 223 { 224 return isViewtypeEjb(clazz, "remote"); 225 } 226 227 228 public static boolean isServiceEndpointEjb(XClass clazz) throws XDocletException 229 { 230 return isViewtypeEjb(clazz, SERVICE_ENDPOINT); 231 } 232 233 240 public static boolean isOnlyLocalEjb(XClass clazz) throws XDocletException 241 { 242 return isLocalEjb(clazz) && !isRemoteEjb(clazz) && !isServiceEndpointEjb(clazz); 243 } 244 245 252 public static boolean isOnlyRemoteEjb(XClass clazz) throws XDocletException 253 { 254 return isRemoteEjb(clazz) && !isLocalEjb(clazz) && !isServiceEndpointEjb(clazz); 255 } 256 257 264 public static boolean isOnlyServiceEndpointEjb(XClass clazz) throws XDocletException 265 { 266 return isServiceEndpointEjb(clazz) && !isRemoteEjb(clazz) && !isLocalEjb(clazz); 267 } 268 269 276 public static XClass getEjb(String name) throws XDocletException 277 { 278 Collection classes = getXJavaDoc().getSourceClasses(); 279 280 for (Iterator i = classes.iterator(); i.hasNext(); ) { 281 XClass clazz = (XClass) i.next(); 282 283 if (name.equals(getEjbNameFor(clazz))) { 284 return clazz; 285 } 286 } 287 return null; 288 } 289 290 297 public static boolean isEjb(XClass clazz) throws XDocletException 298 { 299 return clazz.isA("javax.ejb.SessionBean") 300 || clazz.isA("javax.ejb.EntityBean") 301 || clazz.isA("javax.ejb.MessageDrivenBean"); 302 } 303 304 315 public static String choosePackage(String packageName, String packagePattern, String subtask) 316 { 317 Log log = LogUtil.getLog(EjbTagsHandler.class, "choosePackage"); 318 319 ArrayList packageSubstitutions = PackageTagsHandler.getPackageSubstitutions(subtask); 320 321 if (log.isDebugEnabled()) { 322 log.debug("Package name=" + packageName + " - Pattern=" + packagePattern); 323 } 324 325 if (packagePattern != null) { 326 return packagePattern; 328 } 329 else { 330 for (int i = 0; i < packageSubstitutions.size(); i++) { 331 PackageTagsHandler.PackageSubstitution ps = (PackageTagsHandler.PackageSubstitution) packageSubstitutions.get(i); 332 StringTokenizer st = new StringTokenizer(ps.getPackages(), ",", false); 333 334 if (ps.getUseFirst() == false) { 335 while (st.hasMoreTokens()) { 336 String packages = st.nextToken(); 337 String suffix = "." + packages; 338 339 if (packageName.endsWith(suffix)) { 340 packageName = packageName.substring(0, packageName.length() - suffix.length()) + '.' + ps.getSubstituteWith(); 341 break; 342 } 343 } 344 } 345 else { 346 packageName = PackageTagsHandler.replaceInline(packageName, ps.getPackages(), ps.getSubstituteWith()); 347 } 348 } 349 } 350 351 if (log.isDebugEnabled()) { 352 log.debug("packageName=" + packageName); 353 } 354 355 return packageName; 356 } 357 358 365 public static String ejbRefName() throws XDocletException 366 { 367 return ejbRefName(getCurrentClassTag(), getCurrentClass()); 368 } 369 370 379 public static String ejbRefName(XTag tag, XClass clazz) throws XDocletException 380 { 381 String ejbRefName = null; 382 383 String refName = tag.getAttributeValue("ref-name"); 384 385 if (refName != null) { 386 ejbRefName = refName; 387 } 388 else { 389 ejbRefName = prefixWithEjbSlash(getEjbNameFor(clazz)); 390 391 String type = tag.getAttributeValue("view-type"); 392 393 if (type != null) { 394 if (type.equals("local") && isLocalEjb(clazz)) { 395 ejbRefName = ejbRefName + LOCAL_SUFFIX; 396 } 397 } 398 else { 399 if (isLocalEjb(getCurrentClass()) && !isRemoteEjb(clazz)) { 400 ejbRefName = ejbRefName + LOCAL_SUFFIX; 401 } 402 } 403 } 404 405 return ejbRefName; 406 } 407 408 414 protected static String prefixWithEjbSlash(String ejbName) 415 { 416 ejbName = ejbName.replace('.', '/'); 417 if (ejbName.startsWith("ejb/")) { 418 return ejbName; 419 } 420 else { 421 return "ejb/" + ejbName; 422 } 423 } 424 425 private static boolean isViewtypeEjb(XClass clazz, String viewtype) throws XDocletException 426 { 427 if (getEjbSpec().equals("1.1")) { 429 return "remote".equals(viewtype); 430 } 431 432 String value = getTagValue( 433 FOR_CLASS, 434 clazz.getDoc(), 435 "ejb:bean", 436 "view-type", 437 "remote,local,both," + SERVICE_ENDPOINT + ",local-" + SERVICE_ENDPOINT + ",remote-" + SERVICE_ENDPOINT + "," + ALL, 438 null, 439 true, 440 false 441 ); 442 443 if (value == null) { 444 if (!viewtype.equals(SERVICE_ENDPOINT)) { 446 return true; 447 } 448 else { 449 return false; 450 } 451 } 452 else { 453 if (value.indexOf(viewtype) != -1) { 454 return true; 455 } 456 else { 457 if (value.indexOf(ALL) != -1) { 458 return true; 459 } 460 else if (!viewtype.equals(SERVICE_ENDPOINT) && value.indexOf("both") != -1) { 461 return true; 462 } 463 else { 464 return false; 465 } 466 } 467 } 468 } 469 470 471 478 private static SubTask getSubTaskClassForClass(XClass clazz) throws XDocletException 479 { 480 if (CmpTagsHandler.isEntityCmp(clazz)) { 481 return DocletContext.getInstance().getSubTaskBy(DocletTask.getSubTaskName(EntityCmpSubTask.class)); 482 } 483 484 else if (MdbTagsHandler.isMessageDriven(clazz)) { 485 return DocletContext.getInstance().getSubTaskBy(DocletTask.getSubTaskName(MdbSubTask.class)); 486 } 487 else if (BmpTagsHandler.isEntityBmp(clazz)) { 488 return DocletContext.getInstance().getSubTaskBy(DocletTask.getSubTaskName(EntityBmpSubTask.class)); 489 } 490 else if (SessionTagsHandler.isSession(clazz)) { 491 return DocletContext.getInstance().getSubTaskBy(DocletTask.getSubTaskName(SessionSubTask.class)); 492 } 493 else { 494 return null; 495 } 496 } 497 498 504 private static boolean hasANonDocletGeneratedSubClass(XClass currentClass) 505 { 506 String fullClassName = currentClass.getQualifiedName(); 508 Collection classes = getXJavaDoc().getSourceClasses(); 509 510 for (Iterator i = classes.iterator(); i.hasNext(); ) { 511 XClass clazz = (XClass) i.next(); 512 513 if (fullClassName.equals(clazz.getQualifiedName()) == false && 514 !clazz.getDoc().hasTag("xdoclet-generated") && 515 clazz.isA(fullClassName)) { 516 return true; 517 } 518 } 519 520 return false; 521 } 522 523 534 public String ejbName(Properties attributes) throws XDocletException 535 { 536 String prefixWithEjbSlashStr = attributes.getProperty("prefixWithEjbSlash"); 537 boolean prefixWithEjbSlash = TypeConversionUtil.stringToBoolean(prefixWithEjbSlashStr, false); 538 String ejbName = getEjbNameFor(currentEjbClass == null ? getCurrentClass() : currentEjbClass); 539 540 if (prefixWithEjbSlash == true) { 541 return prefixWithEjbSlash(ejbName); 542 } 543 else { 544 return ejbName; 545 } 546 } 547 548 555 public String ejbExternalRefName() throws XDocletException 556 { 557 String ejbRefName = null; 558 String refName = getCurrentClassTag().getAttributeValue("ref-name"); 559 560 if (refName != null) { 561 ejbRefName = refName; 562 } 563 else { 564 ejbRefName = prefixWithEjbSlash(getCurrentClassTag().getAttributeValue("ejb-name")); 565 } 566 567 return ejbRefName; 568 } 569 570 578 public String symbolicClassName() throws XDocletException 579 { 580 return shortEjbName(); 581 } 582 583 591 public String shortEjbName() throws XDocletException 592 { 593 return getShortEjbNameFor(getCurrentClass()); 594 } 595 596 607 public void forAllBeans(String template) throws XDocletException 608 { 609 Collection classes = getXJavaDoc().getSourceClasses(); 610 611 for (Iterator i = classes.iterator(); i.hasNext(); ) { 612 XClass clazz = (XClass) i.next(); 613 614 setCurrentClass(clazz); 615 616 if (DocletSupport.isDocletGenerated(getCurrentClass())) { 617 continue; 618 } 619 620 if (EntityTagsHandler.isEntity(getCurrentClass()) || SessionTagsHandler.isSession(getCurrentClass()) || 621 MdbTagsHandler.isMessageDriven(getCurrentClass())) { 622 currentEjbClass = getCurrentClass(); 623 generate(template); 624 currentEjbClass = null; 625 } 626 } 627 } 628 629 639 public void ifIsAConcreteEJBean(String template, Properties attributes) throws XDocletException 640 { 641 if (isAConcreteEJBean(getCurrentClass()) == true) { 642 generate(template); 643 } 644 } 645 646 656 public String beanType() throws XDocletException 657 { 658 if (EntityTagsHandler.isEntity(getCurrentClass())) { 659 return "Entity"; 660 } 661 else if (SessionTagsHandler.isSession(getCurrentClass())) { 662 return "Session"; 663 } 664 else if (MdbTagsHandler.isMessageDriven(getCurrentClass())) { 665 return "Message Driven"; 666 } 667 else { 668 return "Unknown"; 669 } 670 } 671 672 684 public String concreteFullClassName() throws XDocletException 685 { 686 XTag beanTag = getCurrentClass().getDoc().getTag("ejb.bean"); 687 String paramValue = null; 688 689 if (beanTag != null) { 690 paramValue = beanTag.getAttributeValue("impl-class-name"); 691 } 692 693 if (SessionTagsHandler.isSession(getCurrentClass())) { 694 if (DocletContext.getInstance().isSubTaskDefined(DocletTask.getSubTaskName(SessionSubTask.class))) { 695 return SessionTagsHandler.getSessionClassFor(getCurrentClass()); 696 } 697 else { 698 return (paramValue != null) ? paramValue : getCurrentClass().getQualifiedName(); 699 } 700 } 701 else if (BmpTagsHandler.isEntityBmp(getCurrentClass())) { 702 if (DocletContext.getInstance().isSubTaskDefined(DocletTask.getSubTaskName(EntityBmpSubTask.class))) { 703 return BmpTagsHandler.getEntityBmpClassFor(getCurrentClass()); 704 } 705 else { 706 return (paramValue != null) ? paramValue : getCurrentClass().getQualifiedName(); 707 } 708 } 709 710 else if (CmpTagsHandler.isEntityCmp(getCurrentClass())) { 711 if (DocletContext.getInstance().isSubTaskDefined(DocletTask.getSubTaskName(EntityCmpSubTask.class))) { 712 return CmpTagsHandler.getEntityCmpClassFor(getCurrentClass()); 713 } 714 else { 715 return (paramValue != null) ? paramValue : getCurrentClass().getQualifiedName(); 716 } 717 } 718 else if (MdbTagsHandler.isMessageDriven(getCurrentClass())) { 719 720 if (DocletContext.getInstance().isSubTaskDefined(DocletTask.getSubTaskName(MdbSubTask.class))) { 721 return MdbTagsHandler.getMessageDrivenClassFor(getCurrentClass()); 722 } 723 else { 724 return (paramValue != null) ? paramValue : getCurrentClass().getQualifiedName(); 725 } 726 } 727 else { 728 return null; 729 } 730 } 731 732 739 public String id() throws XDocletException 740 { 741 return getEjbIdFor(getCurrentClass()); 742 } 743 744 749 public void ifLocalEjb(String template) throws XDocletException 750 { 751 if (isLocalEjb(getCurrentClass())) { 752 generate(template); 753 } 754 } 755 756 761 public void ifRemoteEjb(String template) throws XDocletException 762 { 763 if (isRemoteEjb(getCurrentClass())) { 764 generate(template); 765 } 766 } 767 768 773 public void ifServiceEndpointEjb(String template) throws XDocletException 774 { 775 if (isServiceEndpointEjb(getCurrentClass())) { 776 generate(template); 777 } 778 } 779 780 781 786 public void ifNotLocalEjb(String template) throws XDocletException 787 { 788 if (!isLocalEjb(getCurrentClass())) { 789 generate(template); 790 } 791 } 792 793 798 public void ifNotRemoteEjb(String template) throws XDocletException 799 { 800 if (!isRemoteEjb(getCurrentClass())) { 801 generate(template); 802 } 803 } 804 805 810 public void ifNotServiceEndpointEjb(String template) throws XDocletException 811 { 812 if (!isServiceEndpointEjb(getCurrentClass())) { 813 generate(template); 814 } 815 } 816 817 825 protected String getDependentClassFor(XClass clazz, String type) throws XDocletException 826 { 827 return null; 828 } 829 830 835 protected String getDependentClassTagName() 836 { 837 if (getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(DataObjectSubTask.class))) { 839 return "ejb:data-object"; 840 } 841 else if (getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(EntityBmpSubTask.class)) || 842 getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(EntityCmpSubTask.class))) { 843 return "ejb:bean"; 844 } 845 else if (getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(RemoteInterfaceSubTask.class)) || 846 getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(LocalInterfaceSubTask.class))) { 847 return "ejb:interface"; 848 } 849 else if (getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(HomeInterfaceSubTask.class)) || 850 getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(LocalHomeInterfaceSubTask.class))) { 851 return "ejb:interface"; 852 } 853 else if (getDocletContext().getActiveSubTask().getSubTaskName().equals(DocletTask.getSubTaskName(EntityPkSubTask.class))) { 854 return "ejb:pk"; 855 } 856 else { 857 return null; 858 } 859 } 860 861 868 protected boolean hasTransaction(XDoc doc) throws XDocletException 869 { 870 return doc.hasTag("ejb:transaction"); 871 } 872 873 884 protected String extendsFromFor(XClass clazz, String tagName, String type, String extendsParamName, String defaultBaseClassName) throws XDocletException 885 { 886 Log log = LogUtil.getLog(EjbTagsHandler.class, "extendsFromFor"); 887 888 log.debug("Looking " + type + " extendsFrom for class " + clazz.getName()); 889 890 XClass superclass = clazz.getSuperclass(); 892 893 boolean generateSuper; 894 895 if (superclass.getDoc().hasTag(tagName)) { 896 String generateSuperStr = getTagValue( 897 FOR_CLASS, 898 superclass.getDoc(), 899 tagName, 900 "generate", 901 null, 902 null, 903 false, 904 false 905 ); 906 907 generateSuper = TypeConversionUtil.stringToBoolean(generateSuperStr, true); 908 } 909 else { 910 generateSuper = false; 912 913 Collection interfaces = clazz.getSuperclass().getInterfaces(); 914 915 for (Iterator i = interfaces.iterator(); i.hasNext(); ) { 916 XClass intf = (XClass) i.next(); 917 918 if (intf.getQualifiedName().equals("javax.ejb.EntityBean") || 921 intf.getQualifiedName().equals("javax.ejb.SessionBean") || 922 intf.getQualifiedName().equals("javax.ejb.MessageDrivenBean")) { 923 generateSuper = true; 925 } 926 } 927 } 928 929 log.debug(clazz.getName() + " superclass is generatable? " + generateSuper); 930 931 932 String extendsValue = getTagValue( 935 FOR_CLASS, 936 clazz.getDoc(), 937 tagName, 938 extendsParamName, 939 null, 940 null, 941 !generateSuper, 942 false 943 ); 944 945 if (extendsValue != null) { 947 log.debug(clazz.getName() + " contains an explicit extends. Returning " + extendsValue); 948 return extendsValue; 949 } 950 else { 951 log.debug(clazz.getName() + " does not contains an explicit extends. Trying to guess it"); 954 955 if (superclass.getSuperclass() == null) { 957 log.debug("Top of class hierachy reached. Returning default extends: " + defaultBaseClassName); 958 return defaultBaseClassName; 959 } 960 else if (generateSuper == true) { 962 String className = getDependentClassFor(superclass, type); 963 964 if (className != null) { 965 log.debug("Superclass " + superclass.getName() + " has a dependent class: " + className); 966 return className; 967 } 968 else { 969 log.debug("No dependent class for " + superclass.getName() + ". Returning default extends: " + defaultBaseClassName); 970 return defaultBaseClassName; 971 } 972 } 973 else { 974 log.debug("Can't guess now. Going deeper into class hierarchy"); 976 return extendsFromFor(superclass, tagName, type, extendsParamName, defaultBaseClassName); 977 } 978 } 979 } 980 981 989 protected boolean shouldTraverseSuperclassForDependentClass(XClass clazz, String tagName) throws XDocletException 990 { 991 Log log = LogUtil.getLog(EjbTagsHandler.class, "shouldTraverseSuperclassForDependentClass"); 992 993 if (clazz.getQualifiedName().equals("java.lang.Object")) { 994 log.debug("clazz = java.lang.Object"); 995 996 return true; 997 } 998 999 if (!clazz.isA("javax.ejb.EntityBean") 1000 && !clazz.isA("javax.ejb.SessionBean")) { 1001 log.debug(clazz.getQualifiedName() + " is _not_ of type javax.ejb.EntityBean,javax.ejb.SessionBean"); 1002 1003 return true; 1004 } 1005 else { 1006 log.debug(clazz.getQualifiedName() + " _is_ of type javax.ejb.EntityBean,javax.ejb.SessionBean"); 1007 } 1008 1009 String generateBeanStr = getTagValue( 1011 FOR_CLASS, 1012 clazz.getDoc(), 1013 "ejb:bean", 1014 "generate", 1015 null, 1016 "true", 1017 false, 1018 false 1019 ); 1020 1021 boolean generateBean = TypeConversionUtil.stringToBoolean(generateBeanStr, true); 1022 1023 if (generateBean == false) { 1024 log.debug("generateBean == false"); 1025 1026 return true; 1027 } 1028 1029 boolean generate = false; 1030 1031 if (tagName != null) { 1032 String generateStr = getTagValue( 1034 FOR_CLASS, 1035 clazz.getDoc(), 1036 tagName, 1037 "generate", 1038 null, 1039 "true", 1040 false, 1041 false 1042 ); 1043 1044 generate = TypeConversionUtil.stringToBoolean(generateStr, true); 1045 } 1046 1047 if (generate == false) { 1048 log.debug("generate == false"); 1049 1050 return true; 1051 } 1052 else { 1053 log.debug("generate == true"); 1054 1055 return false; 1056 } 1057 } 1058} 1059 | Popular Tags |