1 26 27 package net.sourceforge.groboutils.codecoverage.v2.ant; 28 29 import java.io.ByteArrayInputStream ; 30 import java.io.ByteArrayOutputStream ; 31 import java.io.File ; 32 import java.io.FileInputStream ; 33 import java.io.FileOutputStream ; 34 import java.io.IOException ; 35 import java.io.InputStream ; 36 import java.util.Enumeration ; 37 import java.util.Stack ; 38 import java.util.Vector ; 39 import java.util.zip.CRC32 ; 40 41 import org.apache.tools.ant.BuildException; 42 import org.apache.tools.ant.DirectoryScanner; 43 import org.apache.tools.ant.Project; 44 import org.apache.tools.ant.Task; 45 import org.apache.tools.ant.types.FileSet; 46 import org.apache.tools.ant.types.PatternSet; 47 import org.apache.tools.ant.util.FileUtils; 49 50 54 import net.sourceforge.groboutils.codecoverage.v2.ant.zip.ZipEntry; 56 import net.sourceforge.groboutils.codecoverage.v2.ant.zip.ZipOutputStream; 57 import net.sourceforge.groboutils.codecoverage.v2.ant.zip.ZipFile; 58 import net.sourceforge.groboutils.codecoverage.v2.ant.zip.ZipFileSet; 59 60 61 71 public class GroboReZipTask extends Task 72 { 73 private boolean failOnError = true; 74 private Vector zips = new Vector (); 75 76 private static final int BUFFER_SIZE = 8 * 1024; 77 private static final int DELETE_RETRY_SLEEP_MILLIS = 10; 78 79 private static final FileUtils FILEUTILS = FileUtils.newFileUtils(); 80 private static final long EMPTY_CRC = new CRC32 ().getValue (); 81 82 83 public static class ReZipFileSet extends ZipFileSet 84 { 85 private boolean replaceOnly = false; 86 87 public ReZipFileSet() 88 { 89 super(); 90 } 91 92 public ReZipFileSet( FileSet fs ) 93 { 94 super( fs ); 95 } 96 97 public ReZipFileSet( ZipFileSet zfs ) 98 { 99 super( zfs ); 100 } 101 102 public ReZipFileSet( ReZipFileSet rzfs ) 103 { 104 super( (ZipFileSet)rzfs ); 105 this.replaceOnly = rzfs.replaceOnly; 106 } 107 108 113 public void setReplaceOnly( boolean replace ) 114 { 115 this.replaceOnly = replace; 116 } 117 118 public boolean getReplaceOnly( Project p ) 119 { 120 if (isReference()) 121 { 122 return ((ReZipFileSet)getRef( p )).getReplaceOnly( p ); 123 } 124 return this.replaceOnly; 125 } 126 } 127 128 129 130 public static class AlterZip 131 { 132 private Vector subZip = new Vector (); 133 private Vector fileSets = new Vector (); 134 private Vector alter = new Vector (); 135 private String srcFile; 136 private String destFile; 137 String encoding; 138 boolean doCompress = true; 139 140 public void setSrc( String f ) 141 { 142 this.srcFile = f; 143 } 144 145 public void setDest( String f ) 146 { 147 this.destFile = f; 148 } 149 150 public void addFileSet( ReZipFileSet zfs ) 151 { 152 if (zfs != null) 153 { 154 this.fileSets.addElement( zfs ); 155 } 156 } 157 158 public void addZipFileSet( ReZipFileSet zfs ) 159 { 160 if (zfs != null) 161 { 162 this.fileSets.addElement( zfs ); 163 } 164 } 165 166 public void addAlterZip( AlterZip z ) 167 { 168 if (z != null) 169 { 170 this.subZip.addElement( z ); 171 } 172 } 173 174 public void addAlterWar( AlterWar z ) 175 { 176 if (z != null) 177 { 178 this.subZip.addElement( z ); 179 } 180 } 181 182 public void addAlterEar( AlterEar z ) 183 { 184 if (z != null) 185 { 186 this.subZip.addElement( z ); 187 } 188 } 189 190 public void addAlterJar( AlterJar z ) 191 { 192 if (z != null) 193 { 194 this.subZip.addElement( z ); 195 } 196 } 197 198 protected void _addModifyEntry( ModifyEntry me ) 199 { 200 if (me != null) 201 { 202 this.alter.addElement( me ); 203 } 204 } 205 206 public File getAbsoluteSrc( Project p ) 207 { 208 return p.resolveFile( this.srcFile ); 209 } 210 211 public File getAbsoluteDest( Project p ) 212 { 213 return p.resolveFile( this.destFile ); 214 } 215 216 public String getSrc() 217 { 218 return this.srcFile; 219 } 220 221 public String getDest() 222 { 223 return this.destFile; 224 } 225 226 public boolean hasDest() 227 { 228 return (this.destFile != null); 229 } 230 231 public AlterZip[] getSubZips() 232 { 233 AlterZip az[] = new AlterZip[ this.subZip.size() ]; 234 this.subZip.copyInto( az ); 235 return az; 236 } 237 238 public FileSet[] getFileSets() 239 { 240 FileSet fs[] = new FileSet[ this.fileSets.size() ]; 241 this.fileSets.copyInto( fs ); 242 return fs; 243 } 244 245 public ModifyEntry[] getModifyEntries() 246 { 247 Vector entries = new Vector (); 248 Enumeration e = this.alter.elements(); 249 while (e.hasMoreElements()) 250 { 251 entries.addElement( e.nextElement() ); 252 } 253 e = this.subZip.elements(); 254 while (e.hasMoreElements()) 255 { 256 AlterZip az = (AlterZip)e.nextElement(); 257 entries.addElement( new AlterZipModifyEntry( az ) ); 258 } 259 ModifyEntry me[] = new ModifyEntry[ entries.size() ]; 260 entries.copyInto( me ); 261 return me; 262 } 263 264 265 public ModifyEntry shouldModify( String r ) 266 { 267 ModifyEntry[] me = getModifyEntries(); 268 for (int i = 0; i < me.length; ++i) 269 { 270 if (me[i].shouldModify( r )) 271 { 272 return me[i]; 273 } 274 } 275 return null; 276 } 277 } 278 279 280 284 public static abstract class ModifyEntry 285 { 286 private String filename; 287 public ModifyEntry( String src ) 288 { 289 this.filename = src; 290 } 291 292 public boolean shouldModify( String r ) 293 { 294 return (this.filename.equals( r ) ); 295 } 296 297 public abstract void modify( GroboReZipTask grze, 298 InputStream in, File outfile ) 299 throws IOException ; 300 } 301 302 303 307 public static class AlterZipModifyEntry extends ModifyEntry 308 { 309 private AlterZip az; 310 public AlterZipModifyEntry( AlterZip az ) 311 { 312 super( az.srcFile ); 313 this.az = az; 314 } 315 316 public void modify( GroboReZipTask grze, 317 InputStream in, File outfile ) 318 throws IOException 319 { 320 File src = FILEUTILS.createTempFile( "z", ".zip", null ); 321 src.deleteOnExit(); 322 FileOutputStream fos = new FileOutputStream ( src ); 323 byte buff[] = new byte[ BUFFER_SIZE ]; 324 try 325 { 326 int size = in.read( buff, 0, BUFFER_SIZE ); 327 while (size > 0) 328 { 329 fos.write( buff, 0, size ); 330 size = in.read( buff, 0, BUFFER_SIZE ); 331 } 332 } 333 finally 334 { 335 fos.close(); 336 } 337 338 try 339 { 340 grze.processZip( this.az, src, outfile ); 341 } 342 finally 343 { 344 delete( src ); 345 } 346 } 347 } 348 349 350 public static class AlterEar extends AlterZip 351 { 352 } 356 357 358 public static class AlterJar extends AlterZip 359 { 360 } 365 366 367 public static class AlterWar extends AlterZip 368 { 369 public void addClasses( ReZipFileSet zfs ) 370 { 371 zfs.setPrefix( "WEB-INF/classes/" ); 372 zfs.setReplaceOnly( true ); 373 addZipFileSet( zfs ); 374 } 375 376 public void addLib( ReZipFileSet zfs ) 377 { 378 zfs.setPrefix( "WEB-INF/lib/" ); 379 addZipFileSet( zfs ); 380 } 381 } 382 383 384 385 387 public void addAlterZip( AlterZip z ) 388 { 389 if (z != null) 390 { 391 this.zips.addElement( z ); 392 } 393 } 394 395 public void addAlterWar( AlterWar z ) 396 { 397 if (z != null) 398 { 399 this.zips.addElement( z ); 400 } 401 } 402 403 public void addAlterEar( AlterEar z ) 404 { 405 if (z != null) 406 { 407 this.zips.addElement( z ); 408 } 409 } 410 411 public void addAlterJar( AlterJar z ) 412 { 413 if (z != null) 414 { 415 this.zips.addElement( z ); 416 } 417 } 418 419 420 422 public void execute() 423 throws BuildException 424 { 425 428 Enumeration e = this.zips.elements(); 429 while (e.hasMoreElements()) 430 { 431 AlterZip az = (AlterZip)e.nextElement(); 432 File src = az.getAbsoluteSrc( getProject() ); 433 if (src == null) 434 { 435 String msg = "No source specified for zip."; 436 if (this.failOnError) 437 { 438 throw new BuildException( msg ); 439 } 440 else 441 { 442 log( msg, Project.MSG_WARN ); 443 } 444 } 445 File dest = null; 446 boolean isTemp = false; 447 if (az.hasDest()) 448 { 449 dest = az.getAbsoluteDest( getProject() ); 450 if (dest.equals( src )) 451 { 452 dest = null; 454 } 455 } 456 if (dest == null) 457 { 458 isTemp = true; 459 dest = FILEUTILS.createTempFile( "z", ".zip", null ); 460 dest.deleteOnExit(); 461 } 462 463 log( "Altering ["+src.getAbsolutePath()+"] into ["+ 464 dest.getAbsolutePath()+"]", Project.MSG_INFO ); 465 466 try 467 { 468 processZip( az, src, dest ); 469 470 if (isTemp) 471 { 472 FILEUTILS.copyFile( dest, src ); 474 System.gc(); 475 System.runFinalization(); 476 delete( dest ); 477 } 478 } 479 catch (SecurityException se) 480 { 481 throw new BuildException( 482 "Not allowed to rename temporary file to old file (" 483 + src.getAbsolutePath() 484 + ")" ); 485 } 486 catch (IOException ioe) 487 { 488 String msg = "Problem processing zip file "+ 489 src.getAbsolutePath()+": "+ 490 ioe.getMessage(); 491 if (this.failOnError) 492 { 493 throw new BuildException( msg, ioe ); 494 } 495 else 496 { 497 log( msg, Project.MSG_WARN ); 498 } 499 } 500 catch (BuildException be) 501 { 502 if (this.failOnError) 503 { 504 throw be; 505 } 506 else 507 { 508 log( be.getMessage(), Project.MSG_WARN ); 509 } 510 } 511 } 512 } 513 514 515 517 518 public void processZip( AlterZip az, File src, File dest ) 519 throws BuildException, IOException 520 { 521 ZipFile inzip = new ZipFile( src ); 522 az.encoding = inzip.getEncoding(); 523 Vector originalFiles = new Vector (); 524 Enumeration entries = inzip.getEntries(); 525 while (entries.hasMoreElements()) 526 { 527 ZipEntry ze = (ZipEntry)entries.nextElement(); 528 originalFiles.addElement( ze.getName() ); 529 } 530 entries = null; 531 inzip.close(); 532 inzip = null; 533 534 ZipOutputStream zOut = new ZipOutputStream( 535 new FileOutputStream ( dest ) ); 536 zOut.setEncoding( az.encoding ); 537 538 Vector addedFiles = new Vector (); 539 addFiles( zOut, az.getFileSets(), az, addedFiles, originalFiles ); 540 541 log( "Adding contents of original zip ["+src.getAbsolutePath()+"]", 542 Project.MSG_VERBOSE ); 543 ReZipFileSet oldFiles = new ReZipFileSet(); 544 oldFiles.setProject( getProject() ); 545 oldFiles.setSrc( src ); 546 oldFiles.setReplaceOnly( false ); 547 555 addFiles( zOut, oldFiles, az, addedFiles, originalFiles ); 556 557 finalizeZipOutputStream(zOut); 558 } 559 560 561 private void addFiles( ZipOutputStream zOut, FileSet[] filesets, 562 AlterZip az, Vector addedFiles, Vector originalFiles ) 563 throws IOException , BuildException 564 { 565 for (int i = 0; i < filesets.length; ++i) 566 { 567 addFiles( zOut, filesets[i], az, addedFiles, originalFiles ); 568 } 569 } 570 571 572 private void addFiles( ZipOutputStream zOut, FileSet fileset, 573 AlterZip az, Vector addedFiles, Vector originalFiles ) 574 throws IOException , BuildException 575 { 576 DirectoryScanner ds = 577 fileset.getDirectoryScanner( getProject() ); 578 ds.scan(); 583 String [] f = ds.getIncludedFiles(); 584 if (f.length > 0) 585 { 586 addResources( az, fileset, f, false, zOut, addedFiles, originalFiles ); 587 } 588 589 String [] d = ds.getIncludedDirectories(); 590 if (d.length > 0) 591 { 592 addResources( az, fileset, d, true, zOut, addedFiles, originalFiles ); 593 } 594 } 595 596 597 607 protected final void addResources( AlterZip az, 608 FileSet fileset, String [] resources, boolean areDirs, 609 ZipOutputStream zOut, Vector addedFiles, Vector originalFiles ) 610 throws IOException 611 { 612 String prefix = ""; 613 String fullpath = ""; 614 int dirMode = ZipFileSet.DEFAULT_DIR_MODE; 615 int fileMode = ZipFileSet.DEFAULT_FILE_MODE; 616 boolean replaceOnly = false; 617 618 ZipFileSet zfs = null; 619 if (fileset instanceof ZipFileSet) 620 { 621 zfs = (ZipFileSet) fileset; 622 prefix = zfs.getPrefix(getProject()); 623 fullpath = zfs.getFullpath(getProject()); 624 dirMode = zfs.getDirMode(getProject()); 625 fileMode = zfs.getFileMode(getProject()); 626 if (fileset instanceof ReZipFileSet) 627 { 628 replaceOnly = ((ReZipFileSet)fileset).getReplaceOnly(getProject()); 629 } 630 log( "Processing resources from ZipFileSet: prefix=["+ 631 prefix+"]; fullpath=["+fullpath+"]; dirMode=["+ 632 dirMode+"]; fileMode=["+fileMode+"]; replaceOnly=["+ 633 replaceOnly+"]", Project.MSG_DEBUG ); 634 } 635 636 if (prefix.length() > 0 && fullpath.length() > 0) 637 { 638 throw new BuildException("Both prefix and fullpath attributes must" 639 + " not be set on the same fileset."); 640 } 641 642 if (resources.length != 1 && fullpath.length() > 0) 643 { 644 throw new BuildException("fullpath attribute may only be specified" 645 + " for filesets that specify a single" 646 + " file."); 647 } 648 649 if (prefix.length() > 0) 650 { 651 if (!prefix.endsWith("/") && !prefix.endsWith("\\")) 652 { 653 prefix += "/"; 654 } 655 addParentDirs( null, prefix, zOut, "", dirMode, addedFiles, 656 replaceOnly ); 657 } 658 659 ZipFile zf = null; 660 try 661 { 662 boolean dealingWithFiles = false; 663 File base = null; 664 if (zfs == null || zfs.getSrc( getProject() ) == null) 665 { 666 dealingWithFiles = true; 667 base = fileset.getDir( getProject() ); 668 log( "Dealing with files (base=["+base+"])", 669 Project.MSG_DEBUG ); 670 } 671 else 672 { 673 File src = zfs.getSrc( getProject() ); 674 zf = new ZipFile( src, az.encoding ); 675 log( "Dealing with zipFile (src=["+src.getAbsolutePath()+"])", 676 Project.MSG_DEBUG ); 677 } 678 679 for (int i = 0; i < resources.length; i++) 680 { 681 log("Processing resource ["+resources[i]+"]", 682 Project.MSG_DEBUG ); 683 String name = null; 684 if (fullpath.length() > 0) 685 { 686 name = fullpath; 687 } 688 else 689 { 690 name = resources[i]; 691 } 692 name = name.replace(File.separatorChar, '/'); 693 694 if ("".equals(name)) 695 { 696 log("Empty name - continuing.", 697 Project.MSG_DEBUG ); 698 continue; 699 } 700 if (areDirs && !name.endsWith("/")) 701 { 702 name = name + "/"; 703 } 704 String prefixName = prefix + name; 705 log("Output Zip entry name = ["+prefixName+"]", 706 Project.MSG_DEBUG ); 707 708 if (replaceOnly && !originalFiles.contains( prefixName )) 709 { 710 log( prefixName + " not in original zip, so skip it.", 712 Project.MSG_VERBOSE ); 713 continue; 714 } 715 716 if (!dealingWithFiles 717 && areDirs 718 && !zfs.hasDirModeBeenSet()) 719 { 720 log("Adding directory's path into zip", Project.MSG_DEBUG); 721 int nextToLastSlash = name.lastIndexOf( "/", 722 name.length() - 2 ); 723 if (nextToLastSlash != -1) 724 { 725 addParentDirs( base, name.substring( 0, 726 nextToLastSlash + 1 ), 727 zOut, prefix, dirMode, addedFiles, 728 replaceOnly ); 729 } 730 ZipEntry ze = zf.getEntry( resources[i] ); 731 addParentDirs( base, name, zOut, prefix, ze.getUnixMode(), 732 addedFiles, replaceOnly ); 733 } 734 else 735 { 736 log("Adding file's path into zip", Project.MSG_DEBUG); 737 addParentDirs( base, name, zOut, prefix, dirMode, 738 addedFiles, replaceOnly ); 739 } 740 741 if (!areDirs) 742 { 743 if (dealingWithFiles) 744 { 745 File f = FILEUTILS.resolveFile(base, 746 resources[i]); 747 zipFile( az, f, zOut, prefixName, fileMode, 750 addedFiles ); 751 } 752 else 753 { 754 ZipEntry ze = zf.getEntry( resources[i] ); 755 756 if (ze != null) 757 { 758 log("Inserting zip entry ["+resources[i]+ 759 "]", Project.MSG_DEBUG); 760 boolean oldCompress = az.doCompress; 761 az.doCompress = 762 (ze.getMethod() == ZipEntry.DEFLATED); 763 ModifyEntry me = az.shouldModify( resources[i] ); 764 try 765 { 766 zipFile( az, me, 767 zf.getInputStream(ze), zOut, prefixName, 768 ze.getTime(), zfs.getSrc( getProject() ), 769 zfs.hasFileModeBeenSet() ? fileMode 770 : ze.getUnixMode(), addedFiles ); 771 } 772 finally 773 { 774 az.doCompress = oldCompress; 775 } 776 } 777 } 778 } 779 } 780 } 781 finally 782 { 783 if (zf != null) 784 { 785 zf.close(); 786 } 787 } 788 } 789 790 791 792 799 protected final void addParentDirs( File baseDir, String entry, 800 ZipOutputStream zOut, String prefix, int dirMode, 801 Vector addedFiles, boolean replaceOnly ) 802 throws IOException 803 { 804 if (replaceOnly) 810 { 811 return; 812 } 813 814 815 Stack directories = new Stack (); 816 int slashPos = entry.length(); 817 818 while ((slashPos = entry.lastIndexOf( '/', slashPos - 1 )) != -1) 819 { 820 String dir = entry.substring( 0, slashPos + 1 ); 821 if (addedFiles.contains( prefix + dir )) { 822 break; 823 } 824 directories.push(dir); 825 } 826 827 while (!directories.isEmpty()) 828 { 829 String dir = (String ) directories.pop(); 830 File f = null; 831 if (baseDir != null) { 832 f = new File (baseDir, dir); 833 } else { 834 f = new File (dir); 835 } 836 837 zipDir( f, zOut, prefix + dir, dirMode, addedFiles ); 838 } 839 } 840 841 846 protected void zipDir( File dir, ZipOutputStream zOut, String vPath, 847 int mode, Vector addedFiles ) 848 throws IOException 849 { 850 if (addedFiles.contains(vPath)) 851 { 852 return; 855 } 856 857 log( "adding directory " + vPath, Project.MSG_VERBOSE ); 858 addedFiles.addElement( vPath ); 859 860 ZipEntry ze = new ZipEntry(vPath); 861 if (dir != null && dir.exists()) { 862 ze.setTime(dir.lastModified() + 1999); 864 } else { 865 ze.setTime(System.currentTimeMillis() + 1999); 867 } 868 ze.setSize( 0 ); 869 ze.setMethod( ZipEntry.STORED ); 870 ze.setCrc( EMPTY_CRC ); 872 ze.setUnixMode(mode); 873 874 zOut.putNextEntry( ze ); 875 } 876 877 878 891 protected void zipFile( AlterZip az, File file, 892 ZipOutputStream zOut, String vPath, int mode, Vector addedFiles ) 893 throws IOException 894 { 895 902 log("zipFile( vPath = ["+vPath+"]; file = ["+file.getAbsolutePath()+"] )", 903 Project.MSG_DEBUG ); 904 FileInputStream fIn = new FileInputStream (file); 905 try 906 { 907 zipFile( az, null, fIn, zOut, vPath, file.lastModified() + 1999, 909 null, mode, addedFiles ); 910 } 911 finally 912 { 913 fIn.close(); 914 } 915 } 916 917 918 933 protected void zipFile( AlterZip az, ModifyEntry me, InputStream in, 934 ZipOutputStream zOut, String vPath, 935 long lastModified, File fromArchive, int mode, Vector addedFiles ) 936 throws IOException 937 { 938 if (addedFiles.contains(vPath)) 939 { 940 log( vPath + " already added, skipping", Project.MSG_VERBOSE ); 941 return; 942 956 } 957 else 958 { 959 log( "adding entry " + vPath, Project.MSG_VERBOSE ); 960 } 961 962 ZipEntry ze = new ZipEntry( vPath ); 963 ze.setTime( lastModified ); 964 ze.setMethod( az.doCompress ? ZipEntry.DEFLATED : ZipEntry.STORED ); 965 966 File tmpFile = null; 968 try 969 { 970 if (me != null) 971 { 972 tmpFile = FILEUTILS.createTempFile( "z", ".tmp", null ); 973 tmpFile.deleteOnExit(); 974 975 me.modify( this, in, tmpFile ); 976 977 in = new FileInputStream ( tmpFile ); 978 } 979 980 987 if (!zOut.isSeekable() && !az.doCompress) 988 { 989 long size = 0; 990 CRC32 cal = new CRC32 (); 991 if (!in.markSupported()) 992 { 993 ByteArrayOutputStream bos = new ByteArrayOutputStream (); 995 996 byte[] buffer = new byte[8 * 1024]; 997 int count = 0; 998 do { 999 size += count; 1000 cal.update(buffer, 0, count); 1001 bos.write(buffer, 0, count); 1002 count = in.read(buffer, 0, buffer.length); 1003 } while (count != -1); 1004 in = new ByteArrayInputStream (bos.toByteArray()); 1005 } 1006 else 1007 { 1008 in.mark(Integer.MAX_VALUE); 1009 byte[] buffer = new byte[8 * 1024]; 1010 int count = 0; 1011 do 1012 { 1013 size += count; 1014 cal.update(buffer, 0, count); 1015 count = in.read(buffer, 0, buffer.length); 1016 } while (count != -1); 1017 in.reset(); 1018 } 1019 ze.setSize(size); 1020 ze.setCrc(cal.getValue()); 1021 } 1022 1023 ze.setUnixMode(mode); 1024 zOut.putNextEntry(ze); 1025 1026 byte[] buffer = new byte[BUFFER_SIZE]; 1027 int count = 0; 1028 do 1029 { 1030 if (count != 0) 1031 { 1032 zOut.write(buffer, 0, count); 1033 } 1034 count = in.read(buffer, 0, BUFFER_SIZE); 1035 } while (count != -1); 1036 1037 addedFiles.addElement(vPath); 1038 } 1039 finally 1040 { 1041 if (in != null) 1043 { 1044 in.close(); 1045 } 1046 1047 if (tmpFile != null) 1048 { 1049 delete( tmpFile ); 1050 } 1051 } 1052 } 1053 1054 1055 1056 1059 protected void initZipOutputStream(ZipOutputStream zOut) 1060 throws IOException , BuildException 1061 { 1062 } 1063 1064 1067 protected void finalizeZipOutputStream(ZipOutputStream zOut) 1068 throws IOException , BuildException 1069 { 1070 zOut.finish(); 1071 } 1072 1073 1079 private static boolean delete(File f) 1080 { 1081 if (!f.delete()) 1082 { 1083 try 1084 { 1085 Thread.sleep( DELETE_RETRY_SLEEP_MILLIS ); 1086 } 1087 catch (InterruptedException ex) 1088 { 1089 } 1091 return f.delete(); 1092 } 1093 return true; 1094 } 1095} 1096 1097 | Popular Tags |