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