1 17 package org.alfresco.web.ui.common; 18 19 import java.io.BufferedReader ; 20 import java.io.IOException ; 21 import java.io.StringReader ; 22 import java.io.UnsupportedEncodingException ; 23 import java.net.URLEncoder ; 24 import java.text.DateFormat ; 25 import java.text.SimpleDateFormat ; 26 import java.util.Calendar ; 27 import java.util.Date ; 28 import java.util.HashMap ; 29 import java.util.Iterator ; 30 import java.util.List ; 31 import java.util.Map ; 32 33 import javax.faces.application.FacesMessage; 34 import javax.faces.component.NamingContainer; 35 import javax.faces.component.UIComponent; 36 import javax.faces.component.UIForm; 37 import javax.faces.context.FacesContext; 38 import javax.faces.context.ResponseWriter; 39 import javax.faces.el.EvaluationException; 40 import javax.faces.el.MethodBinding; 41 import javax.faces.event.AbortProcessingException; 42 import javax.faces.event.ActionEvent; 43 44 import org.alfresco.error.AlfrescoRuntimeException; 45 import org.alfresco.filesys.CIFSServer; 46 import org.alfresco.filesys.server.filesys.DiskSharedDevice; 47 import org.alfresco.filesys.smb.server.repo.ContentContext; 48 import org.alfresco.model.ContentModel; 49 import org.alfresco.repo.security.permissions.AccessDeniedException; 50 import org.alfresco.repo.webdav.WebDAVServlet; 51 import org.alfresco.service.cmr.dictionary.DictionaryService; 52 import org.alfresco.service.cmr.model.FileFolderService; 53 import org.alfresco.service.cmr.model.FileInfo; 54 import org.alfresco.service.cmr.model.FileNotFoundException; 55 import org.alfresco.service.cmr.repository.InvalidNodeRefException; 56 import org.alfresco.service.cmr.repository.NodeRef; 57 import org.alfresco.service.cmr.repository.NodeService; 58 import org.alfresco.service.cmr.repository.Path; 59 import org.alfresco.web.app.Application; 60 import org.alfresco.web.app.servlet.DownloadContentServlet; 61 import org.alfresco.web.app.servlet.ExternalAccessServlet; 62 import org.alfresco.web.bean.NavigationBean; 63 import org.alfresco.web.bean.repository.Node; 64 import org.alfresco.web.bean.repository.Repository; 65 import org.alfresco.web.bean.repository.User; 66 import org.alfresco.web.data.IDataContainer; 67 import org.alfresco.web.ui.common.component.UIStatusMessage; 68 import org.apache.commons.logging.Log; 69 import org.apache.commons.logging.LogFactory; 70 import org.apache.myfaces.renderkit.html.HtmlFormRendererBase; 71 import org.springframework.web.jsf.FacesContextUtils; 72 73 78 public final class Utils 79 { 80 private static final String MSG_TIME_PATTERN = "time_pattern"; 81 private static final String MSG_DATE_PATTERN = "date_pattern"; 82 private static final String MSG_DATE_TIME_PATTERN = "date_time_pattern"; 83 84 private static final String IMAGE_PREFIX16 = "/images/filetypes/"; 85 private static final String IMAGE_PREFIX32 = "/images/filetypes32/"; 86 private static final String IMAGE_POSTFIX = ".gif"; 87 private static final String DEFAULT_FILE_IMAGE16 = IMAGE_PREFIX16 + "_default" + IMAGE_POSTFIX; 88 private static final String DEFAULT_FILE_IMAGE32 = IMAGE_PREFIX32 + "_default" + IMAGE_POSTFIX; 89 90 private static final Map <String , String > s_fileExtensionMap = new HashMap <String , String >(89, 1.0f); 91 92 private static Log logger = LogFactory.getLog(Utils.class); 93 94 97 private Utils() 98 { 99 } 100 101 106 public static String encode(String string) 107 { 108 if (string == null) 109 { 110 return ""; 111 } 112 113 StringBuilder sb = null; String enc; 115 char c; 116 for (int i = 0; i < string.length(); i++) 117 { 118 enc = null; 119 c = string.charAt(i); 120 switch (c) 121 { 122 case '"': enc = """; break; case '&': enc = "&"; break; case '<': enc = "<"; break; case '>': enc = ">"; break; 127 case '\u00E4' : enc = "ä"; break; 129 case '\u00C4' : enc = "Ä"; break; 130 case '\u00F6' : enc = "ö"; break; 131 case '\u00D6' : enc = "Ö"; break; 132 case '\u00FC' : enc = "ü"; break; 133 case '\u00DC' : enc = "Ü"; break; 134 case '\u00DF' : enc = "ß"; break; 135 136 case '\u20AC': enc = "€"; break; 139 case '\u00AB': enc = "«"; break; 140 case '\u00BB': enc = "»"; break; 141 case '\u00A0': enc = " "; break; 142 143 default: 144 if (((int)c) >= 0x80) 145 { 146 enc = "&#" + ((int)c) + ";"; 148 } 149 break; 150 } 151 152 if (enc != null) 153 { 154 if (sb == null) 155 { 156 String soFar = string.substring(0, i); 157 sb = new StringBuilder (i + 8); 158 sb.append(soFar); 159 } 160 sb.append(enc); 161 } 162 else 163 { 164 if (sb != null) 165 { 166 sb.append(c); 167 } 168 } 169 } 170 171 if (sb == null) 172 { 173 return string; 174 } 175 else 176 { 177 return sb.toString(); 178 } 179 } 180 181 190 public static String cropEncode(String text) 191 { 192 return cropEncode(text, 32); 193 } 194 195 205 public static String cropEncode(String text, int length) 206 { 207 if (text.length() > length) 208 { 209 String label = text.substring(0, length - 3) + "..."; 210 StringBuilder buf = new StringBuilder (length + 32 + text.length()); 211 buf.append("<span title=\"") 212 .append(Utils.encode(text)) 213 .append("\">") 214 .append(Utils.encode(label)) 215 .append("</span>"); 216 return buf.toString(); 217 } 218 else 219 { 220 return Utils.encode(text); 221 } 222 } 223 224 233 public static String replace(String str, String repl, String with) 234 { 235 int lastindex = 0; 236 int pos = str.indexOf(repl); 237 238 if (pos < 0) 241 { 242 return str; 243 } 244 245 int len = repl.length(); 246 int lendiff = with.length() - repl.length(); 247 StringBuilder out = new StringBuilder ((lendiff <= 0) ? str.length() : (str.length() + (lendiff << 3))); 248 for (; pos >= 0; pos = str.indexOf(repl, lastindex = pos + len)) 249 { 250 out.append(str.substring(lastindex, pos)).append(with); 251 } 252 253 return out.append(str.substring(lastindex, str.length())).toString(); 254 } 255 256 264 public static String remove(String str, String match) 265 { 266 int lastindex = 0; 267 int pos = str.indexOf(match); 268 269 if (pos < 0) 272 { 273 return str; 274 } 275 276 int len = match.length(); 277 StringBuilder out = new StringBuilder (str.length()); 278 for (; pos >= 0; pos = str.indexOf(match, lastindex = pos + len)) 279 { 280 out.append(str.substring(lastindex, pos)); 281 } 282 283 return out.append(str.substring(lastindex, str.length())).toString(); 284 } 285 286 292 public static String replaceLineBreaks(String str) 293 { 294 String replaced = null; 295 296 if (str != null) 297 { 298 try 299 { 300 StringBuilder parsedContent = new StringBuilder (); 301 BufferedReader reader = new BufferedReader (new StringReader (str)); 302 String line = reader.readLine(); 303 while (line != null) 304 { 305 parsedContent.append(line).append("<br/>"); 306 line = reader.readLine(); 307 } 308 309 replaced = parsedContent.toString(); 310 } 311 catch (IOException ioe) 312 { 313 if (logger.isWarnEnabled()) 314 { 315 logger.warn("Failed to replace line breaks in string: " + str); 316 } 317 } 318 } 319 320 return replaced; 321 } 322 323 332 public static void outputAttribute(ResponseWriter out, Object attr, String mapping) 333 throws IOException 334 { 335 if (attr != null) 336 { 337 out.write(' '); 338 out.write(mapping); 339 out.write("=\""); 340 out.write(attr.toString()); 341 out.write('"'); 342 } 343 } 344 345 356 public static String getActionHiddenFieldName(FacesContext context, UIComponent component) 357 { 358 return Utils.getParentForm(context, component).getClientId(context) + NamingContainer.SEPARATOR_CHAR + "act"; 359 } 360 361 369 public static void encodeRecursive(FacesContext context, UIComponent component) 370 throws IOException 371 { 372 if (component.isRendered() == true) 373 { 374 component.encodeBegin(context); 375 376 if (component.getRendersChildren() == true) 378 { 379 component.encodeChildren(context); 380 } 381 else 382 { 383 if (component.getChildCount() != 0) 384 { 385 for (Iterator i=component.getChildren().iterator(); i.hasNext(); ) 386 { 387 encodeRecursive(context, (UIComponent)i.next()); 388 } 389 } 390 } 391 392 component.encodeEnd(context); 393 } 394 } 395 396 409 public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, String fieldValue) 410 { 411 return generateFormSubmit(context, component, fieldId, fieldValue, null); 412 } 413 414 428 public static String generateFormSubmit(FacesContext context, UIComponent component, String fieldId, String fieldValue, Map <String , String > params) 429 { 430 UIForm form = Utils.getParentForm(context, component); 431 if (form == null) 432 { 433 throw new IllegalStateException ("Must nest components inside UIForm to generate form submit!"); 434 } 435 436 String formClientId = form.getClientId(context); 437 438 StringBuilder buf = new StringBuilder (200); 439 buf.append("document.forms["); 440 buf.append("'"); 441 buf.append(formClientId); 442 buf.append("'"); 443 buf.append("]['"); 444 buf.append(fieldId); 445 buf.append("'].value='"); 446 buf.append(fieldValue); 447 buf.append("';"); 448 449 if (params != null) 450 { 451 for (String name : params.keySet()) 452 { 453 buf.append("document.forms["); 454 buf.append("'"); 455 buf.append(formClientId); 456 buf.append("'"); 457 buf.append("]['"); 458 buf.append(name); 459 buf.append("'].value='"); 460 buf.append(params.get(name)); 461 buf.append("';"); 462 463 HtmlFormRendererBase.addHiddenCommandParameter(form, name); 466 } 467 } 468 469 buf.append("document.forms["); 470 buf.append("'"); 471 buf.append(formClientId); 472 buf.append("'"); 473 buf.append("].submit()"); 474 475 buf.append(";return false;"); 476 477 HtmlFormRendererBase.addHiddenCommandParameter(form, fieldId); 480 481 return buf.toString(); 482 } 483 484 492 public static String generateFormSubmit(FacesContext context, UIComponent component) 493 { 494 UIForm form = Utils.getParentForm(context, component); 495 if (form == null) 496 { 497 throw new IllegalStateException ("Must nest components inside UIForm to generate form submit!"); 498 } 499 500 String formClientId = form.getClientId(context); 501 502 StringBuilder buf = new StringBuilder (48); 503 504 buf.append("document.forms["); 505 buf.append("'"); 506 buf.append(formClientId); 507 buf.append("'"); 508 buf.append("].submit()"); 509 510 buf.append(";return false;"); 511 512 return buf.toString(); 513 } 514 515 518 public enum URLMode {HTTP_DOWNLOAD, HTTP_INLINE, WEBDAV, CIFS, SHOW_DETAILS, FTP} 519 520 531 public static String generateURL(FacesContext context, Node node, URLMode usage) 532 { 533 String url = null; 534 535 switch (usage) 536 { 537 case WEBDAV: 538 { 539 FileFolderService fileFolderService = Repository.getServiceRegistry( 541 context).getFileFolderService(); 542 try 543 { 544 List <FileInfo> paths = fileFolderService.getNamePath(null, node.getNodeRef()); 545 User user = Application.getCurrentUser(context); 546 547 StringBuilder path = new StringBuilder ("/").append(WebDAVServlet.WEBDAV_PREFIX); 549 550 boolean homeSpaceFound = false; 552 for (int x = 1; x < paths.size(); x++) 553 { 554 path.append("/").append( 555 Utils.replace(URLEncoder.encode(paths.get(x).getName(), "UTF-8"), "+", "%20")); 556 } 557 url = path.toString(); 558 } 559 catch (AccessDeniedException e) 560 { 561 } 563 catch (FileNotFoundException nodeErr) 564 { 565 } 567 catch (UnsupportedEncodingException encErr) 568 { 569 if (logger.isWarnEnabled()) 570 logger.warn("Failed to calculate webdav url for node: " + node.getNodeRef(), encErr); 571 } 572 break; 573 } 574 575 case CIFS: 576 { 577 579 NodeService nodeService = Repository.getServiceRegistry(context).getNodeService(); 581 NavigationBean navBean = (NavigationBean)context.getExternalContext(). 582 getSessionMap().get("NavigationBean"); 583 CIFSServer cifsServer = (CIFSServer)FacesContextUtils.getRequiredWebApplicationContext( 584 context).getBean("cifsServer"); 585 586 if (nodeService != null && navBean != null && cifsServer != null) 587 { 588 DiskSharedDevice diskShare = cifsServer.getConfiguration().getPrimaryFilesystem(); 589 590 if (diskShare != null) 591 { 592 ContentContext contentCtx = (ContentContext) diskShare.getContext(); 593 NodeRef rootNode = contentCtx.getRootNode(); 594 try 595 { 596 Path path = nodeService.getPath(node.getNodeRef()); 597 url = Repository.getNamePath(nodeService, path, rootNode, "\\", 598 "file:///" + navBean.getCIFSServerPath(diskShare)); 599 } 600 catch (AccessDeniedException e) 601 { 602 } 604 catch (InvalidNodeRefException nodeErr) 605 { 606 } 608 } 609 } 610 break; 611 } 612 613 case HTTP_DOWNLOAD: 614 { 615 url = DownloadContentServlet.generateDownloadURL(node.getNodeRef(), node.getName()); 616 break; 617 } 618 619 case HTTP_INLINE: 620 { 621 url = DownloadContentServlet.generateBrowserURL(node.getNodeRef(), node.getName()); 622 break; 623 } 624 625 case SHOW_DETAILS: 626 { 627 DictionaryService dd = Repository.getServiceRegistry(context).getDictionaryService(); 628 629 String outcome = "showDocDetails"; 631 632 if (dd.isSubClass(node.getType(), ContentModel.TYPE_FOLDER)) 634 { 635 outcome = "showSpaceDetails"; 636 } 637 638 url = ExternalAccessServlet.generateExternalURL(outcome, 640 Repository.getStoreRef().getProtocol() + "/" + 641 Repository.getStoreRef().getIdentifier() + "/" + node.getId()); 642 break; 643 } 644 645 case FTP: 646 { 647 break; 649 } 650 } 651 652 return url; 653 } 654 655 668 public static String buildImageTag(FacesContext context, String image, int width, int height, String alt, String onclick) 669 { 670 return buildImageTag(context, image, width, height, alt, onclick, null); 671 } 672 673 687 public static String buildImageTag(FacesContext context, String image, int width, int height, String alt, String onclick, String align) 688 { 689 StringBuilder buf = new StringBuilder (200); 690 691 buf.append("<img SRC=\"") 692 .append(context.getExternalContext().getRequestContextPath()) 693 .append(image) 694 .append("\" width=") 695 .append(width) 696 .append(" height=") 697 .append(height) 698 .append(" border=0"); 699 700 if (alt != null) 701 { 702 alt = Utils.encode(alt); 703 buf.append(" alt=\"") 704 .append(alt) 705 .append("\" title=\"") 706 .append(alt) 707 .append('"'); 708 } 709 710 if (align != null) 711 { 712 buf.append(" align=") 713 .append(align); 714 } 715 716 if (onclick != null) 717 { 718 buf.append(" onclick=\"") 719 .append(onclick) 720 .append("\" style='cursor:pointer'"); 721 } 722 723 buf.append('>'); 724 725 return buf.toString(); 726 } 727 728 740 public static String buildImageTag(FacesContext context, String image, int width, int height, String alt) 741 { 742 return buildImageTag(context, image, width, height, alt, null); 743 } 744 745 755 public static String buildImageTag(FacesContext context, String image, String alt) 756 { 757 return buildImageTag(context, image, alt, null); 758 } 759 760 771 public static String buildImageTag(FacesContext context, String image, String alt, String align) 772 { 773 StringBuilder buf = new StringBuilder (128); 774 775 buf.append("<img SRC=\"") 776 .append(context.getExternalContext().getRequestContextPath()) 777 .append(image) 778 .append("\" border=0"); 779 780 if (alt != null) 781 { 782 alt = Utils.encode(alt); 783 buf.append(" alt=\"") 784 .append(alt) 785 .append("\" title=\"") 786 .append(alt) 787 .append('"'); 788 } 789 if (align != null) 790 { 791 buf.append(" align=") 792 .append(align); 793 } 794 795 buf.append('>'); 796 797 return buf.toString(); 798 } 799 800 808 public static UIForm getParentForm(FacesContext context, UIComponent component) 809 { 810 UIComponent parent = component.getParent(); 811 while (parent != null) 812 { 813 if (parent instanceof UIForm) 814 { 815 break; 816 } 817 parent = parent.getParent(); 818 } 819 return (UIForm)parent; 820 } 821 822 831 public static UIComponent getParentNamingContainer(FacesContext context, UIComponent component) 832 { 833 UIComponent parent = component.getParent(); 834 while (parent != null) 835 { 836 if (parent instanceof NamingContainer) 837 { 838 break; 839 } 840 parent = parent.getParent(); 841 } 842 return (UIComponent)parent; 843 } 844 845 854 public static IDataContainer getParentDataContainer(FacesContext context, UIComponent component) 855 { 856 UIComponent parent = component.getParent(); 857 while (parent != null) 858 { 859 if (parent instanceof IDataContainer) 860 { 861 break; 862 } 863 parent = parent.getParent(); 864 } 865 return (IDataContainer)parent; 866 } 867 868 874 public static boolean isComponentDisabledOrReadOnly(UIComponent component) 875 { 876 boolean disabled = false; 877 boolean readOnly = false; 878 879 Object disabledAttr = component.getAttributes().get("disabled"); 880 if (disabledAttr != null) 881 { 882 disabled = disabledAttr.equals(Boolean.TRUE); 883 } 884 885 if (disabled == false) 886 { 887 Object readOnlyAttr = component.getAttributes().get("disabled"); 888 if (readOnlyAttr != null) 889 { 890 readOnly = readOnlyAttr.equals(Boolean.TRUE); 891 } 892 } 893 894 return disabled || readOnly; 895 } 896 897 905 public static void processActionMethod(FacesContext context, MethodBinding method, ActionEvent event) 906 { 907 try 908 { 909 method.invoke(context, new Object [] {event}); 910 } 911 catch (EvaluationException e) 912 { 913 Throwable cause = e.getCause(); 914 if (cause instanceof AbortProcessingException) 915 { 916 throw (AbortProcessingException)cause; 917 } 918 else 919 { 920 throw e; 921 } 922 } 923 } 924 925 930 public static void addErrorMessage(String msg) 931 { 932 addErrorMessage(msg, null); 933 } 934 935 941 public static void addErrorMessage(String msg, Throwable err) 942 { 943 FacesContext context = FacesContext.getCurrentInstance( ); 944 FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_ERROR, msg, msg); 945 context.addMessage(null, facesMsg); 946 if (err != null) 947 { 948 if ((err instanceof InvalidNodeRefException == false && 949 err instanceof AccessDeniedException == false) || logger.isDebugEnabled()) 950 { 951 logger.error(msg, err); 952 } 953 } 954 } 955 956 962 public static void addStatusMessage(FacesMessage.Severity severity, String msg) 963 { 964 FacesContext fc = FacesContext.getCurrentInstance(); 965 String time = getTimeFormat(fc).format(new Date (System.currentTimeMillis())); 966 FacesMessage fm = new FacesMessage(severity, time, msg); 967 fc.addMessage(UIStatusMessage.STATUS_MESSAGE, fm); 968 } 969 970 973 public static DateFormat getTimeFormat(FacesContext fc) 974 { 975 return getDateFormatFromPattern(fc, Application.getMessage(fc, MSG_TIME_PATTERN)); 976 } 977 978 981 public static DateFormat getDateFormat(FacesContext fc) 982 { 983 return getDateFormatFromPattern(fc, Application.getMessage(fc, MSG_DATE_PATTERN)); 984 } 985 986 989 public static DateFormat getDateTimeFormat(FacesContext fc) 990 { 991 return getDateFormatFromPattern(fc, Application.getMessage(fc, MSG_DATE_TIME_PATTERN)); 992 } 993 994 997 private static DateFormat getDateFormatFromPattern(FacesContext fc, String pattern) 998 { 999 if (pattern == null) 1000 { 1001 throw new IllegalArgumentException ("DateTime pattern is mandatory."); 1002 } 1003 try 1004 { 1005 return new SimpleDateFormat (pattern, Application.getLanguage(fc)); 1006 } 1007 catch (IllegalArgumentException err) 1008 { 1009 throw new AlfrescoRuntimeException("Invalid DateTime pattern", err); 1010 } 1011 } 1012 1013 1018 public static Date parseXMLDateFormat(String isoDate) 1019 { 1020 Date parsed = null; 1021 1022 try 1023 { 1024 int offset = 0; 1025 1026 int year = Integer.parseInt(isoDate.substring(offset, offset += 4)); 1028 if (isoDate.charAt(offset) != '-') 1029 { 1030 throw new IndexOutOfBoundsException ("Expected - character but found " + isoDate.charAt(offset)); 1031 } 1032 1033 int month = Integer.parseInt(isoDate.substring(offset += 1, offset += 2)); 1035 if (isoDate.charAt(offset) != '-') 1036 { 1037 throw new IndexOutOfBoundsException ("Expected - character but found " + isoDate.charAt(offset)); 1038 } 1039 1040 int day = Integer.parseInt(isoDate.substring(offset += 1, offset += 2)); 1042 if (isoDate.charAt(offset) != 'T') 1043 { 1044 throw new IndexOutOfBoundsException ("Expected T character but found " + isoDate.charAt(offset)); 1045 } 1046 1047 int hour = Integer.parseInt(isoDate.substring(offset += 1, offset += 2)); 1049 if (isoDate.charAt(offset) != ':') 1050 { 1051 throw new IndexOutOfBoundsException ("Expected : character but found " + isoDate.charAt(offset)); 1052 } 1053 int minutes = Integer.parseInt(isoDate.substring(offset += 1, offset += 2)); 1054 if (isoDate.charAt(offset) != ':') 1055 { 1056 throw new IndexOutOfBoundsException ("Expected : character but found " + isoDate.charAt(offset)); 1057 } 1058 int seconds = Integer.parseInt(isoDate.substring(offset += 1 , offset += 2)); 1059 1060 Calendar calendar = Calendar.getInstance(); 1062 calendar.setLenient(false); 1063 calendar.set(Calendar.YEAR, year); 1064 calendar.set(Calendar.MONTH, month - 1); 1065 calendar.set(Calendar.DAY_OF_MONTH, day); 1066 calendar.set(Calendar.HOUR_OF_DAY, hour); 1067 calendar.set(Calendar.MINUTE, minutes); 1068 calendar.set(Calendar.SECOND, seconds); 1069 1070 parsed = calendar.getTime(); 1072 } 1073 catch(IndexOutOfBoundsException e) 1074 { 1075 } 1076 catch(NumberFormatException e) 1077 { 1078 } 1079 catch(IllegalArgumentException e) 1080 { 1081 } 1082 1083 return parsed; 1084 } 1085 1086 1094 public static String getFileTypeImage(String name, boolean small) 1095 { 1096 String image = (small ? DEFAULT_FILE_IMAGE16 : DEFAULT_FILE_IMAGE32); 1097 1098 int extIndex = name.lastIndexOf('.'); 1099 if (extIndex != -1 && name.length() > extIndex + 1) 1100 { 1101 String ext = name.substring(extIndex + 1).toLowerCase(); 1102 String key = ext + ' ' + (small ? "16" : "32"); 1103 1104 synchronized (s_fileExtensionMap) 1106 { 1107 image = s_fileExtensionMap.get(key); 1108 if (image == null) 1109 { 1110 image = (small ? IMAGE_PREFIX16 : IMAGE_PREFIX32) + ext + IMAGE_POSTFIX; 1112 1113 if (FacesContext.getCurrentInstance().getExternalContext().getResourceAsStream(image) != null) 1115 { 1116 s_fileExtensionMap.put(key, image); 1118 } 1119 else 1120 { 1121 image = (small ? DEFAULT_FILE_IMAGE16 : DEFAULT_FILE_IMAGE32); 1123 s_fileExtensionMap.put(key, image); 1124 } 1125 } 1126 } 1127 } 1128 1129 return image; 1130 } 1131} 1132 | Popular Tags |