001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.io.res.util;
020
021import java.io.File;
022import java.io.FileNotFoundException;
023import java.io.IOException;
024import java.io.InputStream;
025import java.util.ArrayList;
026import java.util.HashMap;
027
028import lucee.commons.io.IOUtil;
029import lucee.commons.io.SystemUtil;
030import lucee.commons.io.res.ContentType;
031import lucee.commons.io.res.ContentTypeImpl;
032import lucee.commons.io.res.Resource;
033import lucee.commons.io.res.ResourceProvider;
034import lucee.commons.io.res.ResourceProviderPro;
035import lucee.commons.io.res.ResourcesImpl;
036import lucee.commons.io.res.filter.ExtensionResourceFilter;
037import lucee.commons.io.res.filter.IgnoreSystemFiles;
038import lucee.commons.io.res.filter.ResourceFilter;
039import lucee.commons.io.res.filter.ResourceNameFilter;
040import lucee.commons.io.res.type.http.HTTPResource;
041import lucee.commons.lang.ExceptionUtil;
042import lucee.commons.lang.StringUtil;
043import lucee.runtime.PageContext;
044import lucee.runtime.PageContextImpl;
045import lucee.runtime.PageSource;
046import lucee.runtime.PageSourceImpl;
047import lucee.runtime.config.Config;
048import lucee.runtime.config.ConfigWebImpl;
049import lucee.runtime.exp.ExpressionException;
050import lucee.runtime.exp.PageException;
051import lucee.runtime.type.util.ArrayUtil;
052import lucee.runtime.type.util.ListUtil;
053
054public final class ResourceUtil {
055
056        public static final int MIMETYPE_CHECK_EXTENSION=1;
057        public static final int MIMETYPE_CHECK_HEADER=2;
058        
059        
060        /**
061     * Field <code>FILE_SEPERATOR</code>
062     */
063    public static final char FILE_SEPERATOR=File.separatorChar; 
064    /**
065     * Field <code>FILE_ANTI_SEPERATOR</code>
066     */
067    public static final char FILE_ANTI_SEPERATOR=(FILE_SEPERATOR=='/')?'\\':'/';
068    
069    /**
070     * Field <code>TYPE_DIR</code>
071     */
072    public static final short TYPE_DIR=0;
073    
074    /**
075     * Field <code>TYPE_FILE</code>
076     */
077    public static final short TYPE_FILE=1;
078
079    /**
080     * Field <code>LEVEL_FILE</code>
081     */
082    public static final short LEVEL_FILE=0;
083    /**
084     * Field <code>LEVEL_PARENT_FILE</code>
085     */
086    public static final short LEVEL_PARENT_FILE=1;
087    /**
088     * Field <code>LEVEL_GRAND_PARENT_FILE</code>
089     */
090    public static final short LEVEL_GRAND_PARENT_FILE=2;
091    
092    
093    private static boolean isUnix=SystemUtil.isUnix();
094    
095    private static final HashMap<String, String> EXT_MT=new HashMap<String, String>();
096    static {
097        EXT_MT.put("ai","application/postscript");
098        EXT_MT.put("aif","audio/x-aiff");
099        EXT_MT.put("aifc","audio/x-aiff");
100        EXT_MT.put("aiff","audio/x-aiff");
101        EXT_MT.put("au","audio/basic");
102        EXT_MT.put("avi","video/x-msvideo");
103        EXT_MT.put("bin","application/octet-stream");
104        EXT_MT.put("bmp","image/x-ms-bmp");
105        EXT_MT.put("cgm","image/cgm");
106        EXT_MT.put("cmx","image/x-cmx");
107        EXT_MT.put("csh","application/x-csh");
108        EXT_MT.put("cfm","text/html");
109        EXT_MT.put("cfml","text/html");
110        EXT_MT.put("css","text/css");
111        EXT_MT.put("doc","application/msword");
112        EXT_MT.put("docx","application/msword");
113        EXT_MT.put("eps","application/postscript");
114        EXT_MT.put("exe","application/octet-stream");
115        EXT_MT.put("gif","image/gif");
116        EXT_MT.put("gtar","application/x-gtar");
117        EXT_MT.put("hqx","application/mac-binhex40");
118        EXT_MT.put("htm","text/html");
119        EXT_MT.put("html","text/html");
120        EXT_MT.put("jpe","image/jpeg");
121        EXT_MT.put("jpeg","image/jpeg");
122        EXT_MT.put("jpg","image/jpeg");
123        EXT_MT.put("js","text/javascript");
124        EXT_MT.put("mmid","x-music/x-midi");
125        EXT_MT.put("mov","video/quicktime");
126        EXT_MT.put("mp2a","audio/x-mpeg-2");
127        EXT_MT.put("mp2v","video/mpeg-2");
128        EXT_MT.put("mp3","audio/mpeg");
129        EXT_MT.put("mp4","video/mp4");
130        EXT_MT.put("mpa","audio/x-mpeg");
131        EXT_MT.put("mpa2","audio/x-mpeg-2");
132        EXT_MT.put("mpeg","video/mpeg");
133        EXT_MT.put("mpega","audio/x-mpeg");
134        EXT_MT.put("mpg","video/mpeg");
135        EXT_MT.put("mpv2","video/mpeg-2");
136        EXT_MT.put("pbm","image/x-portable-bitmap");
137        EXT_MT.put("pcd","image/x-photo-cd");
138        EXT_MT.put("pdf","application/pdf");
139        EXT_MT.put("pgm","image/x-portable-graymap");
140        EXT_MT.put("pict","image/x-pict");
141        EXT_MT.put("pl","application/x-perl");
142        EXT_MT.put("png","image/png");
143        EXT_MT.put("php","text/html");
144        EXT_MT.put("pnm","image/x-portable-anymap");
145        EXT_MT.put("ppm","image/x-portable-pixmap");
146        EXT_MT.put("ppt","application/vnd.ms-powerpoint");
147        EXT_MT.put("pptx","application/vnd.ms-powerpoint");
148        EXT_MT.put("ps","application/postscript");
149        EXT_MT.put("qt","video/quicktime");
150        EXT_MT.put("rgb","image/rgb");
151        EXT_MT.put("rtf","application/rtf");
152        EXT_MT.put("sh","application/x-sh");
153        EXT_MT.put("sit","application/x-stuffit");
154        EXT_MT.put("swf","application/x-shockwave-flash");
155        EXT_MT.put("tar","application/x-tar");
156        EXT_MT.put("tcl","application/x-tcl");
157        EXT_MT.put("tif","image/tiff");
158        EXT_MT.put("tiff","image/tiff");
159        EXT_MT.put("txt","text/plain");
160        EXT_MT.put("wav","audio/x-wav");
161        EXT_MT.put("wma","audio/x-ms-wma");
162        EXT_MT.put("wmv","video/x-ms-wmv");
163        EXT_MT.put("xbm","image/x-xbitmap");
164        EXT_MT.put("xhtml","application/xhtml+xml");
165        EXT_MT.put("xls","application/vnd.ms-excel");
166        EXT_MT.put("xlsx","application/vnd.ms-excel");
167        EXT_MT.put("xpm","image/x-xpixmap");
168        EXT_MT.put("zip","application/zip");
169        
170    }
171        
172
173    //private static Magic mimeTypeParser; 
174        
175    /**
176     * cast a String (argumet destination) to a File Object, 
177     * if destination is not a absolute, file object will be relative to current position (get from PageContext)
178     * file must exist otherwise throw exception
179     * @param pc Page Context to et actuell position in filesystem
180     * @param path relative or absolute path for file object
181     * @return file object from destination
182     * @throws ExpressionException
183     */
184    public static Resource toResourceExisting(PageContext pc ,String path) throws ExpressionException {
185        return toResourceExisting(pc, path,pc.getConfig().allowRealPath());
186    }
187    public static Resource toResourceExisting(PageContext pc ,String path,boolean allowRelpath) throws ExpressionException {
188        path=path.replace('\\','/');
189        Resource res = pc.getConfig().getResource(path);
190        
191        // not allow relpath
192        if(res.exists()) 
193                return res;
194        else if(!allowRelpath)
195                throw new ExpressionException("file or directory "+path+" not exist");
196        
197        
198        /*if(res.isAbsolute() && res.exists()) {
199            return res;
200        }*/
201        
202        //if(allowRelpath){
203                if(StringUtil.startsWith(path,'/')) {
204                        PageContextImpl pci=(PageContextImpl) pc;
205                        ConfigWebImpl cwi=(ConfigWebImpl) pc.getConfig();
206                        PageSource[] sources = cwi.getPageSources(pci, pc.getApplicationContext().getMappings(), path, false, pci.useSpecialMappings(), true);
207                        //Resource[] reses = cwi.getPhysicalResourcesX(pc,pc.getApplicationContext().getMappings(),path,false,pci.useSpecialMappings(),true);
208                        if(!ArrayUtil.isEmpty(sources)) {
209                                
210                                for(int i=0;i<sources.length;i++){
211                                        if(sources[i].exists()) return sources[i].getResource();
212                                }
213                        }
214                        //res = pc.getPhysical(path,true);
215                    //if(res!=null && res.exists()) return res;
216                }
217                res=getRealResource(pc, path, res);
218                if(res.exists()) return res;
219        //}
220        
221        throw new ExpressionException("file or directory "+path+" not exist");      
222    }
223    
224    public static Resource toResourceExisting(Config config ,String path) throws ExpressionException {
225        path=path.replace('\\','/');
226        Resource res = config.getResource(path);
227        
228        if(res.exists()) return res;
229        throw new ExpressionException("file or directory "+path+" not exist");   
230    }
231    
232    public static Resource toResourceNotExisting(Config config ,String path) {
233        Resource res;
234        path=path.replace('\\','/');  
235        res=config.getResource(path);
236        return res;
237    }
238    
239    
240
241    /**
242     * cast a String (argumet destination) to a File Object, 
243     * if destination is not a absolute, file object will be relative to current position (get from PageContext)
244     * at least parent must exist
245     * @param pc Page Context to et actuell position in filesystem
246     * @param destination relative or absolute path for file object
247     * @return file object from destination
248     * @throws ExpressionException
249     */
250
251    public static Resource toResourceExistingParent(PageContext pc ,String destination) throws ExpressionException {
252        return toResourceExistingParent(pc, destination, pc.getConfig().allowRealPath());
253    }
254    
255    public static Resource toResourceExistingParent(PageContext pc ,String destination, boolean allowRelpath) throws ExpressionException {
256        destination=destination.replace('\\','/');
257        Resource res=pc.getConfig().getResource(destination);
258        
259        // not allow relpath
260        if(!allowRelpath){
261                if(res.exists() || parentExists(res))
262                        return res;
263                throw new ExpressionException("parent directory "+res.getParent()+"  for file "+destination+" doesn't exist");
264            
265        }
266        
267        // allow relpath
268        if(res.isAbsolute() && (res.exists() || parentExists(res))) {
269                return res;
270        }
271        
272        if(StringUtil.startsWith(destination,'/')) {
273                PageContextImpl pci=(PageContextImpl) pc;
274                ConfigWebImpl cwi=(ConfigWebImpl) pc.getConfig();
275                PageSource[] sources = cwi.getPageSources(pci, pc.getApplicationContext().getMappings(), destination, 
276                                false, pci.useSpecialMappings(),true);
277                //Resource[] reses = cwi.getPhysicalResourcesX(pc,pc.getApplicationContext().getMappings(),destination,false,pci.useSpecialMappings(),true);
278                if(!ArrayUtil.isEmpty(sources)) {
279                        for(int i=0;i<sources.length;i++){
280                                if(sources[i].exists() || parentExists(sources[i])) {
281                                        res=sources[i].getResource();
282                                        if(res!=null) return res;
283                                }
284                        }
285                }
286        }
287        res=getRealResource(pc, destination, res);
288        if(res!=null && (res.exists() || parentExists(res))) return res;
289        
290        throw new ExpressionException("parent directory "+res.getParent()+"  for file "+destination+" doesn't exist");
291           
292    }
293    
294    /**
295     * cast a String (argument destination) to a File Object, 
296     * if destination is not a absolute, file object will be relative to current position (get from PageContext)
297     * existing file is prefered but dont must exist
298     * @param pc Page Context to et actuell position in filesystem
299     * @param destination relative or absolute path for file object
300     * @return file object from destination
301     */
302
303    public static Resource toResourceNotExisting(PageContext pc ,String destination) {
304        return toResourceNotExisting(pc ,destination,pc.getConfig().allowRealPath(),false);
305    }
306    
307    public static Resource toResourceNotExisting(PageContext pc ,String destination,boolean allowRelpath, boolean checkComponentMappings) {
308        destination=destination.replace('\\','/');
309        
310        Resource res=pc.getConfig().getResource(destination);
311                
312        if(!allowRelpath || res.exists()){
313                return res;
314        }
315        
316        boolean isUNC;
317        if(!(isUNC=isUNCPath(destination)) && StringUtil.startsWith(destination,'/')) {
318                PageContextImpl pci=(PageContextImpl) pc;
319                ConfigWebImpl cwi=(ConfigWebImpl) pc.getConfig();
320                PageSource[] sources = cwi.getPageSources(pci, pc.getApplicationContext().getMappings(), destination, false, 
321                                pci.useSpecialMappings(), SystemUtil.isWindows(),checkComponentMappings);
322                if(!ArrayUtil.isEmpty(sources)) {
323                        for(int i=0;i<sources.length;i++){
324                                res=sources[i].getResource();
325                                if(res!=null) return res;
326                        }
327                }
328                //Resource res2 = pc.getPhysical(destination,SystemUtil.isWindows());
329            //if(res2!=null) return res2;
330        }
331        if(isUNC) {
332                res=pc.getConfig().getResource(destination.replace('/','\\'));
333        }
334        else if(!destination.startsWith("..")) res=pc.getConfig().getResource(destination);
335        if(res!=null && res.isAbsolute()) return res;
336        
337        return getRealResource(pc,destination,res);
338    }
339
340    private static Resource getRealResource(PageContext pc, String destination, Resource defaultValue) {
341        PageSource ps = pc.getCurrentPageSource();
342        if(ps!=null) {
343                ps=ps.getRealPage(destination);
344                
345                if(ps!=null){
346                        Resource res = ps.getResource();
347                        if(res!=null)return getCanonicalResourceEL(res);
348                }
349                
350        }
351        return defaultValue;
352        }
353    
354        public static boolean isUNCPath(String path) {
355        return SystemUtil.isWindows() && ( path.startsWith("//") || path.startsWith( "\\\\" ) );
356        }
357    
358    /**
359     * transalte the path of the file to a existing file path by changing case of letters
360     * Works only on Linux, becasue 
361     * 
362     * Example Unix:
363     * we have a existing file with path "/usr/virtual/myFile.txt"
364     * now you call this method with path "/Usr/Virtual/myfile.txt"
365     * the result of the method will be "/usr/virtual/myFile.txt"
366     * 
367     * if there are more file with rhe same name but different cases
368     * Example:
369     *  /usr/virtual/myFile.txt
370     *  /usr/virtual/myfile.txt
371     *  /Usr/Virtual/myFile.txt
372     *  the nearest case wil returned
373     * 
374     * @param res
375     * @return file
376     */
377    public static Resource toExactResource(Resource res) {
378        res=getCanonicalResourceEL(res);
379        if(isUnix) {
380            if(res.exists()) return res;
381            return _check(res);
382            
383        }
384        return res;
385    }
386    private static Resource _check(Resource file) {
387        // todo cascade durch while ersetzten
388        Resource parent=file.getParentResource();
389        if(parent==null) return file;
390        
391        if(!parent.exists()) {
392            Resource op=parent;
393            parent=_check(parent);
394            if(op==parent) return file;
395            if((file = parent.getRealResource(file.getName())).exists()) return file;
396        }
397        
398        String[] files = parent.list();
399        if(files==null) return file;
400        String name=file.getName();
401        for(int i=0;i<files.length;i++) {
402            if(name.equalsIgnoreCase(files[i]))
403                return parent.getRealResource(files[i]);
404        }
405        return file;
406    }
407    
408    /**
409     * create a file if possible, return file if ok, otherwise return null 
410     * @param res file to touch 
411     * @param level touch also parent and grand parent
412     * @param type is file or directory
413     * @return file if exists, otherwise null
414     */
415    public static Resource createResource(Resource res, short level, short type) {
416        
417        boolean asDir=type==TYPE_DIR;
418        // File
419        if(level>=LEVEL_FILE && res.exists() && ((res.isDirectory() && asDir)||(res.isFile() && !asDir))) {
420            return getCanonicalResourceEL(res);
421        }
422        
423        // Parent
424        Resource parent=res.getParentResource();
425        if(level>=LEVEL_PARENT_FILE && parent!=null && parent.exists() && canRW(parent)) {
426            if(asDir) {
427                if(res.mkdirs()) return getCanonicalResourceEL(res);
428            }
429            else {
430                if(createNewResourceEL(res))return getCanonicalResourceEL(res);
431            }
432            return getCanonicalResourceEL(res);
433        }    
434        
435        // Grand Parent
436        if(level>=LEVEL_GRAND_PARENT_FILE && parent!=null) {
437            Resource gparent=parent.getParentResource();
438            if(gparent!=null && gparent.exists() && canRW(gparent)) {
439                if(asDir) {
440                    if(res.mkdirs())return getCanonicalResourceEL(res);
441                }
442                else {
443                    if(parent.mkdirs() && createNewResourceEL(res))
444                        return getCanonicalResourceEL(res);
445                }
446            }        
447        }
448        return null;
449    }
450    
451        public static void setAttribute(Resource res,String attributes) throws IOException {
452                /*if(res instanceof File && SystemUtil.isWindows()) {
453                        if(attributes.length()>0) {
454                                attributes=ResourceUtil.translateAttribute(attributes);
455                                Runtime.getRuntime().exec("attrib "+attributes+" " + res.getAbsolutePath());
456                }
457                }
458                else {*/
459                        short[] flags = strAttrToBooleanFlags(attributes);
460                        
461                        if(flags[READ_ONLY]==YES)res.setWritable(false);
462                        else if(flags[READ_ONLY]==NO)res.setWritable(true);
463                        
464                        if(flags[HIDDEN]==YES)          res.setAttribute(Resource.ATTRIBUTE_HIDDEN, true);//setHidden(true);
465                        else if(flags[HIDDEN]==NO)      res.setAttribute(Resource.ATTRIBUTE_HIDDEN, false);//res.setHidden(false);
466                        
467                        if(flags[ARCHIVE]==YES)         res.setAttribute(Resource.ATTRIBUTE_ARCHIVE, true);//res.setArchive(true);
468                        else if(flags[ARCHIVE]==NO)     res.setAttribute(Resource.ATTRIBUTE_ARCHIVE, false);//res.setArchive(false);
469                        
470                        if(flags[SYSTEM]==YES)          res.setAttribute(Resource.ATTRIBUTE_SYSTEM, true);//res.setSystem(true);
471                        else if(flags[SYSTEM]==NO)      res.setAttribute(Resource.ATTRIBUTE_SYSTEM, false);//res.setSystem(false);
472                        
473                //}
474        }
475
476        //private static final int NORMAL=0;
477        private static final int READ_ONLY=0;
478        private static final int HIDDEN=1;
479        private static final int ARCHIVE=2;
480        private static final int SYSTEM=3;
481
482        //private static final int IGNORE=0;
483        private static final int NO=1;
484        private static final int YES=2;
485        
486        
487
488    private static short[] strAttrToBooleanFlags(String attributes) throws IOException {
489        
490        String[] arr;
491                try {
492                        arr = ListUtil.toStringArray(ListUtil.listToArrayRemoveEmpty(attributes.toLowerCase(),','));
493                } 
494                catch (PageException e) {
495                        arr=new String[0];
496                }
497        
498        boolean hasNormal=false;
499        boolean hasReadOnly=false;
500        boolean hasHidden=false;
501        boolean hasArchive=false;
502        boolean hasSystem=false;
503        
504        for(int i=0;i<arr.length;i++) {
505           String str=arr[i].trim().toLowerCase();
506           if(str.equals("readonly") || str.equals("read-only") || str.equals("+r")) hasReadOnly=true;
507           else if(str.equals("normal") || str.equals("temporary")) hasNormal=true;
508           else if(str.equals("hidden") || str.equals("+h")) hasHidden=true;
509           else if(str.equals("system") || str.equals("+s")) hasSystem=true;
510           else if(str.equals("archive") || str.equals("+a")) hasArchive=true;
511           else throw new IOException("invalid attribute definition ["+str+"]");
512        }
513        
514        short[] flags=new short[4];
515        
516        if(hasReadOnly)flags[READ_ONLY]=YES;
517        else if(hasNormal)flags[READ_ONLY]=NO;
518        
519        if(hasHidden)flags[HIDDEN]=YES;
520        else if(hasNormal)flags[HIDDEN]=NO;
521        
522        if(hasSystem)flags[SYSTEM]=YES;
523        else if(hasNormal)flags[SYSTEM]=NO;
524        
525        if(hasArchive)flags[ARCHIVE]=YES;
526        else if(hasNormal)flags[ARCHIVE]=NO;
527        
528        return flags;
529    }
530        
531        
532        /**
533     * sets attributes of a file on Windows system
534     * @param res
535     * @param attributes
536     * @throws PageException
537     * @throws IOException
538     */
539    public static String translateAttribute(String attributes) throws IOException {
540        short[] flags = strAttrToBooleanFlags(attributes);
541       
542        StringBuilder sb=new StringBuilder();
543        if(flags[READ_ONLY]==YES)sb.append(" +R");
544        else if(flags[READ_ONLY]==NO)sb.append(" -R");
545        
546        if(flags[HIDDEN]==YES)sb.append(" +H");
547        else if(flags[HIDDEN]==NO)sb.append(" -H");
548        
549        if(flags[SYSTEM]==YES)sb.append(" +S");
550        else if(flags[SYSTEM]==NO)sb.append(" -S");
551        
552        if(flags[ARCHIVE]==YES)sb.append(" +A");
553        else if(flags[ARCHIVE]==NO)sb.append(" -A");
554
555        return sb.toString();
556    }
557
558        /* *
559         * transalte a path in a proper form
560         * example susi\petere -> /susi/peter
561         * @param path
562         * @return path
563         * /
564        public static String translatePath(String path) {
565                /*path=prettifyPath(path);
566                if(path.indexOf('/')!=0)path='/'+path;
567                int index=path.lastIndexOf('/');
568                // remove slash at the end
569                if(index==path.length()-1) path=path.substring(0,path.length()-1);
570                return path;* /
571                return translatePath(path, true, false);
572        }*/
573        
574        /* *
575         * transalte a path in a proper form
576         * example susi\petere -> susi/peter/
577         * @param path
578         * @return path
579         * /
580        public static String translatePath2x(String path) {
581                /*path=prettifyPath(path);
582                if(path.indexOf('/')==0)path=path.substring(1);
583                int index=path.lastIndexOf('/');
584                // remove slash at the end
585                if(index!=path.length()-1) path=path+'/';* /
586                return translatePath(path, false, true);
587        }*/
588        
589
590        public static String translatePath(String path, boolean slashAdBegin, boolean slashAddEnd) {
591                path=prettifyPath(path);
592                
593                // begin
594                if(slashAdBegin) {
595                        if(path.indexOf('/')!=0)path='/'+path;
596                }
597                else {
598                        if(path.indexOf('/')==0)path=path.substring(1);
599                }
600                
601                // end
602                int index=path.lastIndexOf('/');
603                if(slashAddEnd) {
604                        if(index!=path.length()-1) path=path+'/';
605                }
606                else {
607                        if(index==path.length()-1 && index>-1) path=path.substring(0,path.length()-1);
608                }
609                return path;
610        }
611        
612        
613        
614
615        /**
616         * transalte a path in a proper form and cut name away
617         * example susi\petere -> /susi/ and  peter
618         * @param path
619         * @return
620         */
621        public static String[] translatePathName(String path) {
622                path=prettifyPath(path);
623                if(path.indexOf('/')!=0)path='/'+path;
624                int index=path.lastIndexOf('/');
625                // remove slash at the end
626                if(index==path.length()-1) path=path.substring(0,path.length()-1);
627                
628                index=path.lastIndexOf('/');
629                String name;
630                if(index==-1) {
631                        name=path;
632                        path = "/";
633                }
634                else {
635                        name = path.substring(index+1);
636                        path = path.substring(0,index+1);
637                }
638                return new String[] {path,name};
639        }
640        
641        public static String prettifyPath(String path) {
642                if(path==null) return null;
643                path=path.replace('\\','/');
644                return StringUtil.replace(path, "//", "/", false);
645                // TODO /aaa/../bbb/
646        }
647
648        public static String removeScheme(String scheme, String path) {
649                if(path.indexOf("://")==scheme.length() && StringUtil.startsWithIgnoreCase(path,scheme)) path=path.substring(3+scheme.length());
650                return path;
651        }
652
653        /**
654         * merge to path parts to one
655         * @param parent
656         * @param child
657         * @return
658         */
659        public static String merge(String parent, String child) {
660                if(child.length()<=2) {
661                        if(child.length()==0) return parent;
662                        if(child.equals(".")) return parent;
663                        if(child.equals("..")) child="../";
664                }
665                
666                
667                
668                parent=translatePath(parent, true, false);
669                child=prettifyPath(child);//child.replace('\\', '/');
670                
671                if(child.startsWith("./"))child=child.substring(2);
672                if(StringUtil.startsWith(child, '/'))return parent.concat(child);
673                if(!StringUtil.startsWith(child, '.'))return parent.concat("/").concat(child);
674                
675                
676                while(child.startsWith("../")) {
677                        parent=pathRemoveLast(parent);
678                        child=child.substring(3);
679                }
680                if(StringUtil.startsWith(child, '/'))return parent.concat(child);
681                return parent.concat("/").concat(child);
682        }
683        
684        private static String pathRemoveLast(String path) {
685                if(path.length()==0) return "..";
686                
687                else if(path.endsWith("..")){
688                    return path.concat("/..");
689                }
690                return path.substring(0,path.lastIndexOf('/'));
691        }
692
693        /**
694     * Returns the canonical form of this abstract pathname.
695     * @param res file to get canoncial form from it
696     *
697     * @return  The canonical pathname string denoting the same file or
698     *          directory as this abstract pathname
699     *
700     * @throws  SecurityException
701     *          If a required system property value cannot be accessed.
702     */
703    public static String getCanonicalPathEL(Resource res) {
704        try {
705            return res.getCanonicalPath();
706        } catch (IOException e) {
707            return res.toString();
708        }
709    }
710    
711    
712    /**
713     * Returns the canonical form of this abstract pathname.
714     * @param res file to get canoncial form from it
715     *
716     * @return  The canonical pathname string denoting the same file or
717     *          directory as this abstract pathname
718     *
719     * @throws  SecurityException
720     *          If a required system property value cannot be accessed.
721     */
722    public static Resource getCanonicalResourceEL(Resource res) {
723        if(res==null) return res;
724        try {
725            return res.getCanonicalResource();
726        } catch (IOException e) {
727            return res.getAbsoluteResource();
728        }
729    }
730    
731    /**
732     * creates a new File
733     * @param res
734     * @return was successfull
735     */
736    public static boolean createNewResourceEL(Resource res) {
737        try {
738            res.createFile(false);
739            return true;
740        } catch (IOException e) {
741            return false;
742        }
743    }
744
745    public static boolean exists(Resource res) {
746        return res!=null && res.exists();
747    }
748    
749
750    /**
751     * check if file is read and writable
752     * @param res
753     * @return is or not
754     */
755    public static boolean canRW(Resource res) {
756        return res.isReadable() && res.isWriteable();
757    }
758    
759
760    /**
761     * similat to linux bash fuction toch, create file if not exist oherwise change last modified date
762     * @param res
763     * @throws IOException
764     */
765    public static void touch(Resource res) throws IOException {
766        if(res.exists()) {
767                res.setLastModified(System.currentTimeMillis());
768            }
769            else {
770                res.createFile(true);
771            }
772    }
773    
774    public static void clear(Resource res) throws IOException {
775        if(res.exists()) {
776                IOUtil.write(res, new byte[0]);
777            }
778            else {
779                res.createFile(true);
780            }
781    }
782        
783    
784
785    /**
786     * return the mime type of a file, dont check extension
787     * @param res
788     * @param defaultValue 
789     * @return mime type of the file
790     */
791    public static String getMimeType(Resource res, String defaultValue) {
792        return getMimeType(res, MIMETYPE_CHECK_HEADER,defaultValue);
793    }
794    
795    public static String getMimeType(Resource res, int checkingType, String defaultValue) {
796        
797        // check Extension
798        if((checkingType&MIMETYPE_CHECK_EXTENSION)!=0) {
799                String ext = getExtension(res, null);
800                        if(!StringUtil.isEmpty(ext)){
801                        String mt=EXT_MT.get(ext.trim().toLowerCase());
802                        if(mt!=null) return mt;
803                        }
804        }
805        
806        // check mimetype
807        if((checkingType&MIMETYPE_CHECK_HEADER)!=0) {
808                InputStream is=null;
809                try {
810                        is = res.getInputStream();
811                        return IOUtil.getMimeType(is, defaultValue);
812                        } 
813                catch (Throwable t) {
814                ExceptionUtil.rethrowIfNecessary(t);
815                                return defaultValue;
816                        }
817                finally {
818                        IOUtil.closeEL(is);
819                }
820        }
821        
822        return defaultValue;
823    }
824
825    
826    
827    
828        /**
829         * check if file is a child of given directory
830         * @param file file to search
831         * @param dir directory to search
832         * @return is inside or not
833         */
834        public static boolean isChildOf(Resource file, Resource dir) {
835                while(file!=null) {
836                        if(file.equals(dir)) return true;
837                        file=file.getParentResource();
838                }
839                return false;
840        }
841        /**
842         * return diffrents of one file to a other if first is child of second otherwise return null
843         * @param file file to search
844         * @param dir directory to search
845         */
846        public static String getPathToChild(Resource file, Resource dir) {
847                if(dir==null || !file.getResourceProvider().getScheme().equals(dir.getResourceProvider().getScheme())) return null;
848                boolean isFile=file.isFile();
849                String str="/";
850                while(file!=null) {
851                        if(file.equals(dir)) {
852                                if(isFile) return str.substring(0,str.length()-1);
853                                return str;
854                        }
855                        str="/"+file.getName()+str;
856                        file=file.getParentResource();
857                }
858                return null;
859        }
860        
861    /**
862     * get the Extension of a file
863     * @param res
864     * @return extension of file
865     */
866    public static String getExtension(Resource res, String defaultValue) {
867        return getExtension(res.getName(),defaultValue);
868    }
869
870    /**
871     * get the Extension of a file
872     * @param strFile
873     * @return extension of file
874     */
875    public static String getExtension(String strFile, String defaultValue) {
876        int pos=strFile.lastIndexOf('.');
877        if(pos==-1)return defaultValue;
878        return strFile.substring(pos+1);
879    }
880    
881    public static String getName(String strFileName) {
882        int pos=strFileName.lastIndexOf('.');
883        if(pos==-1)return strFileName;
884        return strFileName.substring(0,pos);
885    }
886    
887    /**
888     * split a FileName in Parts
889     * @param fileName
890     * @return new String[]{name[,extension]}
891     */
892    public static String[] splitFileName(String fileName) {
893        int pos=fileName.lastIndexOf('.');
894        if(pos==-1) {
895            return new String[]{fileName};
896        }
897        return new String[]{fileName.substring(0,pos),fileName.substring(pos+1)};
898    }
899    
900    /**
901     * change extesnion of file and return new file
902     * @param file
903     * @param newExtension
904     * @return  file with new Extension
905     */
906    public static Resource changeExtension(Resource file, String newExtension) {
907        String ext=getExtension(file,null);
908        if(ext==null) return file.getParentResource().getRealResource(file.getName()+'.'+newExtension);
909        //new File(file.getParentFile(),file.getName()+'.'+newExtension);
910        String name=file.getName();
911        return file.getParentResource().getRealResource(name.substring(0,name.length()-ext.length())+newExtension);
912        //new File(file.getParentFile(),name.substring(0,name.length()-ext.length())+newExtension);
913    }
914    
915    /**
916     * @param res delete the content of a directory
917     */
918
919    public static void deleteContent(Resource src,ResourceFilter filter) {
920        _deleteContent(src, filter,false);
921    }
922    public static void _deleteContent(Resource src,ResourceFilter filter,boolean deleteDirectories) {
923        if(src.isDirectory()) {
924                Resource[] files=filter==null?src.listResources():src.listResources(filter);
925            for(int i=0;i<files.length;i++) {
926                _deleteContent(files[i],filter,true);
927                if(deleteDirectories){
928                        try {
929                                                src.remove(false);
930                                        } catch (IOException e) {}
931                }
932            }
933            
934        }
935        else if(src.isFile()) {
936                src.delete();
937        }
938    }
939    
940
941    /**
942     * copy a file or directory recursive (with his content)
943     * @param res file or directory to delete
944     * @throws IOException 
945     * @throws FileNotFoundException 
946     */
947    public static void copyRecursive(Resource src,Resource trg) throws IOException {
948                copyRecursive(src, trg,null);
949        }
950    
951    
952    /**
953     * copy a file or directory recursive (with his content)
954     * @param src
955     * @param trg
956     * @param filter
957     * @throws IOException 
958     * @throws FileNotFoundException 
959     */
960    public static void copyRecursive(Resource src,Resource trg,ResourceFilter filter) throws IOException {
961        //print.out(src);
962        //print.out(trg);
963        if(!src.exists()) return ;
964        if(src.isDirectory()) {
965                if(!trg.exists())trg.createDirectory(true);
966                Resource[] files=filter==null?src.listResources():src.listResources(filter);
967            for(int i=0;i<files.length;i++) {
968                copyRecursive(files[i],trg.getRealResource(files[i].getName()),filter);
969            }
970        }
971        else if(src.isFile()) {
972                touch(trg);
973                IOUtil.copy(src,trg);
974        }
975    }
976    
977        public static void copy(Resource src, Resource trg) throws IOException {
978                if(src.equals(trg)) return;
979                ResourceUtil.checkCopyToOK(src,trg);
980                IOUtil.copy(src,trg);
981        }
982    
983    
984    /**
985     * return if parent file exists 
986     * @param res file to check
987     * @return parent exists?
988     */
989    private static boolean parentExists(Resource res) {
990        res=res.getParentResource();
991        return res!=null && res.exists();
992    }
993    private static boolean parentExists(PageSource ps) {
994        PageSource p = ((PageSourceImpl)ps).getParent();
995        return p!=null && p.exists();
996    }
997
998        public static void removeChildren(Resource res) throws IOException {
999                removeChildren(res, (ResourceFilter)null);
1000        }
1001
1002        public static void removeChildren(Resource res,ResourceNameFilter filter) throws IOException {
1003                Resource[] children = filter==null?res.listResources():res.listResources(filter);
1004                if(children==null) return;
1005                
1006                for(int i=0;i<children.length;i++) {
1007                        children[i].remove(true);
1008                }
1009        }
1010        
1011        public static void removeChildren(Resource res,ResourceFilter filter) throws IOException {
1012                Resource[] children = filter==null?res.listResources():res.listResources(filter);
1013                if(children==null) return;
1014                
1015                for(int i=0;i<children.length;i++) {
1016                        children[i].remove(true);
1017                }
1018        }
1019
1020        public static void removeChildrenEL(Resource res,ResourceNameFilter filter) {
1021                try {
1022                        removeChildren(res,filter);
1023                }
1024                catch(Throwable e) {
1025                ExceptionUtil.rethrowIfNecessary(e);
1026        }
1027        }
1028
1029        public static void removeChildrenEL(Resource res,ResourceFilter filter) {
1030                try {
1031                        removeChildren(res,filter);
1032                }
1033                catch(Throwable e) {
1034                ExceptionUtil.rethrowIfNecessary(e);
1035        }
1036        }
1037        
1038        public static void removeChildrenEL(Resource res) {
1039                try {
1040                        removeChildren(res);
1041                }
1042                catch(Throwable e) {
1043                ExceptionUtil.rethrowIfNecessary(e);
1044        }
1045        }
1046
1047        public static void removeEL(Resource res, boolean force) {
1048                try {
1049                        res.remove(force);
1050                } 
1051                catch (Throwable t) {
1052                ExceptionUtil.rethrowIfNecessary(t);
1053        }
1054        }
1055
1056        public static void createFileEL(Resource res, boolean force) {
1057                try {
1058                        res.createFile(force);
1059                } 
1060                catch (IOException e) {}
1061        }
1062
1063        public static void createDirectoryEL(Resource res, boolean force) {
1064                try {
1065                        res.createDirectory(force);
1066                } 
1067                catch (IOException e) {}
1068        }
1069
1070        public static ContentType getContentType(Resource resource) {
1071                // TODO make this part of a interface
1072                if(resource instanceof HTTPResource) {
1073                        try {
1074                                return ((HTTPResource)resource).getContentType();
1075                        } catch (IOException e) {}
1076                }
1077                InputStream is=null;
1078                try {
1079                        is = resource.getInputStream();
1080                        return new ContentTypeImpl(is);
1081                }
1082                catch(IOException e) {
1083                        return ContentTypeImpl.APPLICATION_UNKNOW;
1084                }
1085                finally {
1086                        IOUtil.closeEL(is);
1087                }
1088        }
1089        
1090        public static void moveTo(Resource src, Resource dest, boolean useResourceMethod) throws IOException {
1091                ResourceUtil.checkMoveToOK(src, dest);
1092                
1093                if(src.isFile()){
1094                        try{
1095                                if(useResourceMethod)src.moveTo(dest);
1096                        }
1097                        catch(IOException e){
1098                                useResourceMethod=false;
1099                        }
1100                        if(!useResourceMethod) {
1101                                if(!dest.exists()) dest.createFile(false);
1102                                IOUtil.copy(src,dest);
1103                                src.remove(false);
1104                        }
1105                }
1106                else {
1107                        if(!dest.exists()) dest.createDirectory(false);
1108                        Resource[] children = src.listResources();
1109                        for(int i=0;i<children.length;i++){
1110                                moveTo(children[i],dest.getRealResource(children[i].getName()),useResourceMethod);
1111                        }
1112                        src.remove(false);
1113                }
1114                dest.setLastModified(System.currentTimeMillis());
1115        }
1116
1117        /**
1118         * return the size of the Resource, other than method length of Resource this mthod return the size of all files in a directory
1119         * @param collectionDir
1120         * @return
1121         */
1122        public static long getRealSize(Resource res) {
1123                return getRealSize(res,null);
1124        }
1125        
1126        /**
1127         * return the size of the Resource, other than method length of Resource this mthod return the size of all files in a directory
1128         * @param collectionDir
1129         * @return
1130         */
1131        public static long getRealSize(Resource res, ResourceFilter filter) {
1132                if(res.isFile()) {
1133                        return res.length();
1134                }
1135                else if(res.isDirectory()) {
1136                        long size=0;
1137                        Resource[] children = filter==null?res.listResources():res.listResources(filter);
1138                        for(int i=0;i<children.length;i++) {
1139                                size+=getRealSize(children[i]);
1140                        }
1141                        return size;
1142                }
1143                
1144                return 0;
1145        }
1146
1147        public static int getChildCount(Resource res) {
1148                return getChildCount(res, null);
1149        }
1150        
1151        public static int getChildCount(Resource res, ResourceFilter filter) {
1152                if(res.isFile()) {
1153                        return 1;
1154                }
1155                else if(res.isDirectory()) {
1156                        int size=0;
1157                        Resource[] children = filter==null?res.listResources():res.listResources(filter);
1158                        for(int i=0;i<children.length;i++) {
1159                                size+=getChildCount(children[i]);
1160                        }
1161                        return size;
1162                }
1163                
1164                return 0;
1165        }
1166
1167
1168        /**
1169         * return if Resource is empty, means is directory and has no children or a empty file,
1170         * if not exist return false.
1171         * @param res
1172         * @return
1173         */
1174        public static boolean isEmpty(Resource res) {
1175                return isEmptyDirectory(res,null) || isEmptyFile(res);
1176        }
1177
1178        /**
1179         * return Boolean.True when directory is empty, Boolean.FALSE when directory s not empty and null if directory does not exists
1180         * @param res
1181         * @return
1182         */
1183        public static boolean isEmptyDirectory(Resource res, ResourceFilter filter) {
1184                if(res.isDirectory()) {
1185                        Resource[] children = filter==null? res.listResources(): res.listResources(filter);
1186                        if(children==null || children.length==0) return true;
1187                        
1188                        for(int i=0;i<children.length;i++){
1189                                if(children[i].isFile()) return false;
1190                                if(children[i].isDirectory() &&  !isEmptyDirectory(children[i], filter)) return false;
1191                        }
1192                        
1193                }
1194                return true;
1195        }
1196        
1197        public static boolean isEmptyFile(Resource res) {
1198                if(res.isFile()) {
1199                        return res.length()==0;
1200                }
1201                return false;
1202        }
1203
1204        public static Resource toResource(File file) {
1205                return ResourcesImpl.getFileResourceProvider().getResource(file.getPath());
1206        }
1207
1208
1209        /**
1210         * list childrn of all given resources
1211         * @param resources
1212         * @return
1213         */
1214        public static Resource[] listResources(Resource[] resources,ResourceFilter filter) {
1215                int count=0;
1216                Resource[] children;
1217                ArrayList<Resource[]> list=new ArrayList<Resource[]>();
1218                for(int i=0;i<resources.length;i++) {
1219                        children=filter==null?resources[i].listResources():resources[i].listResources(filter);
1220                        if(children!=null){
1221                                count+=children.length;
1222                                list.add(children);
1223                        }
1224                        else list.add(new Resource[0]);
1225                }
1226                Resource[] rtn=new Resource[count];
1227                int index=0;
1228                for(int i=0;i<resources.length;i++) {
1229                        children=list.get(i);
1230                        for(int y=0;y<children.length;y++) {
1231                                rtn[index++]=children[y];
1232                        }
1233                }
1234                //print.out(rtn);
1235                return rtn;
1236        }
1237
1238
1239        public static Resource[] listResources(Resource res,ResourceFilter filter) {
1240                return filter==null?res.listResources():res.listResources(filter);
1241        }
1242        
1243
1244        public static void deleteFileOlderThan(Resource res, long date, ExtensionResourceFilter filter) {
1245                if(res.isFile()) {
1246                        if(res.lastModified()<=date) res.delete();
1247                }
1248                else if(res.isDirectory()) {
1249                        Resource[] children = filter==null?res.listResources():res.listResources(filter);
1250                        for(int i=0;i<children.length;i++) {
1251                                deleteFileOlderThan(children[i],date,filter);
1252                        }
1253                }
1254        }
1255        
1256        /**
1257         * check if directory creation is ok with the rules for the Resource interface, to not change this rules.
1258         * @param resource
1259         * @param createParentWhenNotExists
1260         * @throws IOException
1261         */
1262        public static void checkCreateDirectoryOK(Resource resource, boolean createParentWhenNotExists) throws IOException {
1263                if(resource.exists()) {
1264                        if(resource.isFile()) 
1265                                throw new IOException("can't create directory ["+resource.getPath()+"], resource already exists as a file");
1266                        if(resource.isDirectory()) 
1267                                throw new IOException("can't create directory ["+resource.getPath()+"], directory already exists");
1268                }
1269                
1270                Resource parent = resource.getParentResource();
1271                // when there is a parent but the parent does not exist
1272                if(parent!=null) {
1273                        if(!parent.exists()) {
1274                                if(createParentWhenNotExists)parent.createDirectory(true);
1275                                else throw new IOException("can't create file ["+resource.getPath()+"], missing parent directory");
1276                        }
1277                        else if(parent.isFile()) {
1278                                throw new IOException("can't create directory ["+resource.getPath()+"], parent is a file");
1279                        }
1280                }
1281        }
1282
1283
1284        /**
1285         * check if file creating is ok with the rules for the Resource interface, to not change this rules.
1286         * @param resource
1287         * @param createParentWhenNotExists
1288         * @throws IOException
1289         */
1290        public static void checkCreateFileOK(Resource resource, boolean createParentWhenNotExists) throws IOException {
1291                if(resource.exists()) {
1292                        if(resource.isDirectory()) 
1293                                throw new IOException("can't create file ["+resource.getPath()+"], resource already exists as a directory");
1294                        if(resource.isFile()) 
1295                                throw new IOException("can't create file ["+resource.getPath()+"], file already exists");
1296                }
1297                
1298                Resource parent = resource.getParentResource();
1299                // when there is a parent but the parent does not exist
1300                if(parent!=null) {
1301                        if(!parent.exists()) {
1302                                if(createParentWhenNotExists)parent.createDirectory(true);
1303                                else throw new IOException("can't create file ["+resource.getPath()+"], missing parent directory");
1304                        }
1305                        else if(parent.isFile()) {
1306                                throw new IOException("can't create file ["+resource.getPath()+"], parent is a file");
1307                        }
1308                }
1309        }
1310
1311        /**
1312         * check if copying a file is ok with the rules for the Resource interface, to not change this rules.
1313         * @param source
1314         * @param target
1315         * @throws IOException
1316         */
1317        public static void checkCopyToOK(Resource source, Resource target) throws IOException {
1318                if(!source.isFile()) {
1319                        if(source.isDirectory())
1320                                throw new IOException("can't copy ["+source.getPath()+"] to ["+target.getPath()+"], source is a directory");
1321                        throw new IOException("can't copy ["+source.getPath()+"] to ["+target.getPath()+"], source file does not exist");
1322                }
1323                else if(target.isDirectory()) {
1324                        throw new IOException("can't copy ["+source.getPath()+"] to ["+target.getPath()+"], target is a directory");
1325                }
1326        }
1327
1328        /**
1329         * check if moveing a file is ok with the rules for the Resource interface, to not change this rules.
1330         * @param source
1331         * @param target
1332         * @throws IOException
1333         */
1334        public static void checkMoveToOK(Resource source, Resource target) throws IOException {
1335                if(!source.exists()) {
1336                        throw new IOException("can't move ["+source.getPath()+"] to ["+target.getPath()+"], source file does not exist");
1337                }
1338                if(source.isDirectory() && target.isFile())
1339                        throw new IOException("can't move ["+source.getPath()+"] directory to ["+target.getPath()+"], target is a file");
1340                if(source.isFile() && target.isDirectory())
1341                        throw new IOException("can't move ["+source.getPath()+"] file to ["+target.getPath()+"], target is a directory");
1342        }
1343
1344        /**
1345         * check if getting a inputstream of the file is ok with the rules for the Resource interface, to not change this rules.
1346         * @param resource
1347         * @throws IOException
1348         */
1349        public static void checkGetInputStreamOK(Resource resource) throws IOException {
1350                if(!resource.exists())
1351                        throw new IOException("file ["+resource.getPath()+"] does not exist");
1352                
1353                if(resource.isDirectory())
1354                        throw new IOException("can't read directory ["+resource.getPath()+"] as a file");
1355
1356        }
1357
1358        /**
1359         * check if getting a outputstream of the file is ok with the rules for the Resource interface, to not change this rules.
1360         * @param resource
1361         * @throws IOException
1362         */
1363        public static void checkGetOutputStreamOK(Resource resource) throws IOException {
1364                if(resource.exists() && !resource.isWriteable()) {
1365                        throw new IOException("can't write to file ["+resource.getPath()+"],file is readonly");
1366                }
1367                if(resource.isDirectory())
1368                        throw new IOException("can't write directory ["+resource.getPath()+"] as a file");
1369                if(!resource.getParentResource().exists())
1370                        throw new IOException("can't write file ["+resource.getPath()+"] as a file, missing parent directory ["+resource.getParent()+"]");
1371        }
1372
1373        /**
1374         * check if removing the file is ok with the rules for the Resource interface, to not change this rules.
1375         * @param resource
1376         * @throws IOException
1377         */
1378        public static void checkRemoveOK(Resource resource) throws IOException {
1379                if(!resource.exists())throw new IOException("can't delete resource "+resource+", resource does not exist");
1380                if(!resource.canWrite())throw new IOException("can't delete resource "+resource+", no access");
1381                
1382        }
1383        
1384        /**
1385     * if the pageSource is based on a archive, translate the source to a zip:// Resource
1386     * @return return the Resource matching this PageSource
1387     * @param pc the Page Context Object
1388     * @deprecated use instead <code>PageSource.getResourceTranslated(PageContext)</code>
1389     */
1390    public static Resource getResource(PageContext pc,PageSource ps) throws PageException {
1391                return ps.getResourceTranslated(pc);
1392        }
1393        
1394        public static Resource getResource(PageContext pc,PageSource ps, Resource defaultValue) {
1395                try {
1396                        return ps.getResourceTranslated(pc);
1397                } 
1398                catch (Throwable t) {
1399                ExceptionUtil.rethrowIfNecessary(t);
1400                        return defaultValue;
1401                }
1402        }
1403        
1404        public static int directrySize(Resource dir,ResourceFilter filter) {
1405                if(dir==null || !dir.isDirectory()) return 0;
1406                if(filter==null) return dir.list().length;
1407                return ArrayUtil.size(dir.list(filter));
1408        }
1409        
1410        public static int directrySize(Resource dir,ResourceNameFilter filter) {
1411                if(dir==null || !dir.isDirectory()) return 0;
1412                if(filter==null) return dir.list().length;
1413                return ArrayUtil.size(dir.list(filter));
1414        }
1415        
1416        public static String[] names(Resource[] resources) {
1417                String[] names=new String[resources.length];
1418                for(int i=0;i<names.length;i++){
1419                        names[i]=resources[i].getName();
1420                }
1421                return names;
1422        }
1423    
1424    public static Resource[] merge(Resource[] srcs, Resource[] trgs) {
1425        java.util.List<Resource> list=new ArrayList<Resource>();
1426
1427        if(srcs!=null){
1428                for(int i=0;i<srcs.length;i++){
1429                        list.add(srcs[i]);
1430                }
1431        }
1432        if(trgs!=null){
1433                for(int i=0;i<trgs.length;i++){
1434                        if(!list.contains(trgs[i]))list.add(trgs[i]);
1435                }
1436        }
1437        return list.toArray(new Resource[list.size()]);
1438        }
1439        
1440        /**
1441         * remove empty folder in given directory
1442         * @param dir directory to delete 
1443         * @param filter if set (not null), only delete directories that match the given filter
1444         * @throws IOException
1445         */
1446        public static void removeEmptyFolders(Resource dir,ResourceFilter filter) throws IOException {
1447                if(!dir.isDirectory()) return;
1448                
1449                
1450                Resource[] children = dir.listResources(IgnoreSystemFiles.INSTANCE);
1451                
1452                if(!ArrayUtil.isEmpty(children)) {
1453                        boolean hasFiles=false;
1454                        for(int i=0;i<children.length;i++){
1455                                if(children[i].isDirectory()) removeEmptyFolders(children[i],filter);
1456                                else if(children[i].isFile()) {
1457                                        hasFiles=true;
1458                                }
1459                        }
1460                        if(!hasFiles){
1461                                children = dir.listResources(IgnoreSystemFiles.INSTANCE);
1462                        }
1463                }
1464                if(ArrayUtil.isEmpty(children) && (filter==null || filter.accept(dir))) dir.remove(true);
1465        }
1466        
1467        
1468        public static char getSeparator(ResourceProvider rp) {
1469                if(rp instanceof ResourceProviderPro)
1470                        return ((ResourceProviderPro)rp).getSeparator();
1471                return '/';
1472        }
1473
1474}