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.runtime;
020
021import java.io.FileNotFoundException;
022import java.io.IOException;
023import java.io.InputStream;
024import java.lang.reflect.Constructor;
025import java.lang.reflect.InvocationTargetException;
026
027import lucee.commons.io.CharsetUtil;
028import lucee.commons.io.IOUtil;
029import lucee.commons.io.res.Resource;
030import lucee.commons.io.res.util.ResourceUtil;
031import lucee.commons.lang.ExceptionUtil;
032import lucee.commons.lang.SizeOf;
033import lucee.commons.lang.StringUtil;
034import lucee.commons.lang.types.RefBoolean;
035import lucee.commons.lang.types.RefBooleanImpl;
036import lucee.commons.lang.types.RefIntegerSync;
037import lucee.runtime.config.ConfigImpl;
038import lucee.runtime.config.ConfigWeb;
039import lucee.runtime.config.ConfigWebImpl;
040import lucee.runtime.engine.ThreadLocalPageContext;
041import lucee.runtime.engine.ThreadLocalPageSource;
042import lucee.runtime.exp.ExpressionException;
043import lucee.runtime.exp.MissingIncludeException;
044import lucee.runtime.exp.PageException;
045import lucee.runtime.exp.TemplateException;
046import lucee.runtime.functions.system.GetDirectoryFromPath;
047import lucee.runtime.op.Caster;
048import lucee.runtime.type.Sizeable;
049import lucee.runtime.type.util.ArrayUtil;
050import lucee.runtime.type.util.ListUtil;
051
052/**
053 * represent a cfml file on the runtime system
054 */
055public final class PageSourceImpl implements PageSource, Sizeable {
056
057        private static final long serialVersionUID = -7661676586215092539L;
058        //public static final byte LOAD_NONE=1;
059    public static final byte LOAD_ARCHIVE=2;
060    public static final byte LOAD_PHYSICAL=3;
061    
062    //private byte load=LOAD_NONE;
063
064        private final MappingImpl mapping;
065    private final String relPath;
066    
067    private boolean isOutSide;
068    
069    private String className;
070    private String packageName;
071    private String javaName;
072
073    private Resource physcalSource;
074    private Resource archiveSource;
075    private String fileName;
076    private String compName;
077    private Page page;
078        private long lastAccess;        
079        private RefIntegerSync accessCount=new RefIntegerSync(0);
080        private boolean flush=false;
081    //private boolean recompileAlways;
082    //private boolean recompileAfterStartUp;
083    
084    private PageSourceImpl() {
085        mapping=null;
086        relPath=null;
087    }
088    
089    
090    /**
091         * constructor of the class
092     * @param mapping
093     * @param relPath
094         */
095        PageSourceImpl(MappingImpl mapping,String relPath) {
096                this.mapping=mapping;
097        relPath=relPath.replace('\\','/');
098        if(relPath.indexOf("//")!=-1) {
099                //print.ds(relPath);
100                relPath=StringUtil.replace(relPath, "//", "/");
101        }
102        
103        
104                if(relPath.indexOf('/')!=0) {
105                    if(relPath.startsWith("../")) {
106                                isOutSide=true;
107                        }
108                        else if(relPath.startsWith("./")) {
109                                relPath=relPath.substring(1);
110                        }
111                        else {
112                                relPath="/"+relPath;
113                        }
114                }
115                this.relPath=relPath;
116            
117        }
118        
119        
120        
121        /**
122         * private constructor of the class
123         * @param mapping
124         * @param relPath
125         * @param isOutSide
126         */
127    PageSourceImpl(MappingImpl mapping, String relPath, boolean isOutSide) {
128        //recompileAlways=mapping.getConfig().getCompileType()==Config.RECOMPILE_ALWAYS;
129        //recompileAfterStartUp=mapping.getConfig().getCompileType()==Config.RECOMPILE_AFTER_STARTUP || recompileAlways;
130        this.mapping=mapping;
131            this.isOutSide=isOutSide;
132            if(relPath.indexOf("//")!=-1) {
133                //print.ds(relPath);
134                relPath=StringUtil.replace(relPath, "//", "/");
135        }
136                this.relPath=relPath;
137                
138        }
139        
140        /**
141         * return page when already loaded, otherwise null
142         * @param pc
143         * @param config
144         * @return
145         * @throws PageException
146         */
147        public Page getPage() {
148                return page;
149        }
150        
151        public PageSource getParent(){
152                if(relPath.equals("/")) return null;
153                if(StringUtil.endsWith(relPath, '/'))
154                        return new PageSourceImpl(mapping, GetDirectoryFromPath.invoke(relPath.substring(0, relPath.length()-1)));
155                return new PageSourceImpl(mapping, GetDirectoryFromPath.invoke(relPath));
156        }
157
158        
159        @Override
160        public Page loadPage(ConfigWeb config) throws PageException {
161                return loadPage(ThreadLocalPageContext.get());
162        }
163
164        @Override
165        public Page loadPage(ConfigWeb config, Page defaultValue) throws PageException {
166                return loadPage(ThreadLocalPageContext.get(), defaultValue);
167        }
168        
169
170        public Page loadPage(PageContext pc, boolean forceReload) throws PageException {
171                if(forceReload) page=null;
172                return loadPage(pc);
173        }
174        
175        public Page loadPage(PageContext pc) throws PageException {
176                Page page=this.page;
177                if(mapping.isPhysicalFirst()) {
178                        page=loadPhysical(pc,page);
179                        if(page==null) page=loadArchive(page); 
180                if(page!=null) return page;
181            }
182            else {
183                page=loadArchive(page);
184                if(page==null)page=loadPhysical(pc,page);
185                if(page!=null) return page;
186            }
187                throw new MissingIncludeException(this);
188            
189        }
190        
191        @Override
192        public Page loadPage(PageContext pc, Page defaultValue) throws PageException {
193                Page page=this.page;
194                if(mapping.isPhysicalFirst()) {
195                page=loadPhysical(pc,page);
196                if(page==null) page=loadArchive(page); 
197                if(page!=null) return page;
198            }
199            else {
200                page=loadArchive(page);
201                if(page==null)page=loadPhysical(pc,page);
202                if(page!=null) return page;
203            }
204            return defaultValue;
205        }
206        
207    private Page loadArchive(Page page) {
208        if(!mapping.hasArchive()) return null;
209                if(page!=null && page.getLoadType()==LOAD_ARCHIVE) return page;
210        
211        try {
212            Class clazz=mapping.getClassLoaderForArchive().loadClass(getClazz());
213            page=newInstance(clazz);
214            page.setPageSource(this);
215            page.setLoadType(LOAD_ARCHIVE);
216                this.page=page;
217            return page;
218        } 
219        catch (Exception e) {
220                return null;
221        }
222    }
223    
224
225    private Page loadPhysical(PageContext pc,Page page) throws PageException {
226        if(!mapping.hasPhysical()) return null;
227        
228        ConfigWeb config=pc.getConfig();
229        PageContextImpl pci=(PageContextImpl) pc;
230        if((mapping.getInspectTemplate()==ConfigImpl.INSPECT_NEVER || pci.isTrusted(page)) && isLoad(LOAD_PHYSICAL)) return page;
231        Resource srcFile = getPhyscalFile();
232        
233                long srcLastModified = srcFile.lastModified();
234        if(srcLastModified==0L) return null;
235        
236                // Page exists    
237                        if(page!=null) {
238                        //if(page!=null && !recompileAlways) {
239                                // java file is newer !mapping.isTrusted() && 
240                                if(srcLastModified!=page.getSourceLastModified()) {
241                                        this.page=page=compile(config,mapping.getClassRootDirectory(),Boolean.TRUE);
242                        page.setPageSource(this);
243                                        page.setLoadType(LOAD_PHYSICAL);
244                                }
245                        
246                        }
247                // page doesn't exist
248                        else {
249                    Resource classRootDir=mapping.getClassRootDirectory();
250                    Resource classFile=classRootDir.getRealResource(getJavaName()+".class");
251                    boolean isNew=false;
252                    // new class
253                    if(flush || !classFile.exists()) {
254                    //if(!classFile.exists() || recompileAfterStartUp) {
255                        this.page=page= compile(config,classRootDir,Boolean.FALSE);
256                        flush=false;
257                        isNew=true;
258                    }
259                    // load page
260                    else {
261                        try {
262                                                        this.page=page=newInstance(mapping.touchPCLCollection().getClass(this));
263                                                } catch (Throwable t) {
264                                                        ExceptionUtil.rethrowIfNecessary(t);
265                                                        t.printStackTrace();
266                                                        this.page=page=null;
267                                                }
268                        if(page==null) this.page=page=compile(config,classRootDir,Boolean.TRUE);     
269                    }
270                    
271                    // check if there is a newwer version
272                    if(!isNew && srcLastModified!=page.getSourceLastModified()) {
273                        isNew=true;
274                        this.page=page=compile(config,classRootDir,null);
275                                }
276                    
277                    // check version
278                    if(!isNew && page.getVersion()!=Info.getFullVersionInfo()) {
279                        isNew=true;
280                        this.page=page=compile(config,classRootDir,null);
281                    }
282                    
283                    page.setPageSource(this);
284                                page.setLoadType(LOAD_PHYSICAL);
285
286                        }
287                        pci.setPageUsed(page);
288                        return page;
289    }
290
291    private boolean isLoad(byte load) {
292                return page!=null && load==page.getLoadType();
293        }
294    
295    public void flush() {
296                page=null;
297                flush=true;
298        }
299    
300
301        private Page compile(ConfigWeb config,Resource classRootDir, Boolean resetCL) throws PageException {
302                try {
303                        return _compile(config, classRootDir, resetCL);
304        }
305                        catch(RuntimeException re) {re.printStackTrace();
306                String msg=StringUtil.emptyIfNull(re.getMessage());
307                if(StringUtil.indexOfIgnoreCase(msg, "Method code too large!")!=-1) {
308                        throw new TemplateException("There is too much code inside the template ["+getDisplayPath()+"], Lucee was not able to break it into pieces, move parts of your code to an include or a external component/function",msg);
309                }
310                throw re;
311            }
312        catch(ClassFormatError e) {
313                String msg=StringUtil.emptyIfNull(e.getMessage());
314                if(StringUtil.indexOfIgnoreCase(msg, "Invalid method Code length")!=-1) {
315                        throw new TemplateException("There is too much code inside the template ["+getDisplayPath()+"], Lucee was not able to break it into pieces, move parts of your code to an include or a external component/function",msg);
316                }
317                throw Caster.toPageException(e);
318        }
319        catch(Throwable t) {
320                throw Caster.toPageException(t);
321        }
322        }
323
324        private Page _compile(ConfigWeb config,Resource classRootDir, Boolean resetCL) throws IOException, SecurityException, IllegalArgumentException, PageException {
325        ConfigWebImpl cwi=(ConfigWebImpl) config;
326        
327        long now; // TODO reenable keywods, double check, inspect template, watch 
328        if((getPhyscalFile().lastModified()+60000)>(now=System.currentTimeMillis()))
329                cwi.getCompiler().watch(this,now);//SystemUtil.get
330        
331        
332        //synchronized (this) {
333                byte[] barr = cwi.getCompiler().
334                        compile(cwi,this,cwi.getTLDs(),cwi.getFLDs(),classRootDir,getJavaName());
335                Class<?> clazz = mapping.touchPCLCollection().loadClass(getClazz(), barr,isComponent());
336                try{
337                        return  newInstance(clazz);
338                }
339                catch(Throwable t){
340                        PageException pe = Caster.toPageException(t);
341                        pe.setExtendedInfo("failed to load template "+getDisplayPath());
342                        throw pe;
343                }
344                //}
345    }
346
347    private Page newInstance(Class clazz) throws SecurityException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
348        try{
349                        Constructor c = clazz.getConstructor(new Class[]{PageSource.class});
350                        return (Page) c.newInstance(new Object[]{this});
351                }
352        // this only happens with old code from ra files
353                catch(NoSuchMethodException e){
354                        ThreadLocalPageSource.register(this);
355                        try{
356                                return (Page) clazz.newInstance();
357                        }
358                        finally {
359                                ThreadLocalPageSource.release();
360                        }
361                        
362                        
363                }
364        }
365
366
367        /**
368     * return source path as String 
369     * @return source path as String
370     */
371    public String getDisplayPath() {
372        if(!mapping.hasArchive())       {
373                return StringUtil.toString(getPhyscalFile(), null);
374        }
375        else if(isLoad(LOAD_PHYSICAL))  {
376                return StringUtil.toString(getPhyscalFile(), null);
377        }
378        else if(isLoad(LOAD_ARCHIVE))   {
379                return StringUtil.toString(getArchiveSourcePath(), null);
380        }
381        else {
382            boolean pse = physcalExists();
383            boolean ase = archiveExists();
384            
385            if(mapping.isPhysicalFirst()) {
386                if(pse)return getPhyscalFile().toString();
387                else if(ase)return getArchiveSourcePath();
388                return getPhyscalFile().toString();
389            }
390            if(ase)return getArchiveSourcePath();
391            else if(pse)return getPhyscalFile().toString();
392            return getArchiveSourcePath();
393        }
394    }
395    
396    public boolean isComponent() {
397        return ResourceUtil.getExtension(getRealpath(), "").equalsIgnoreCase(mapping.getConfig().getCFCExtension());
398    }
399    
400    /**
401         * return file object, based on physical path and relpath
402         * @return file Object
403         */
404        private String getArchiveSourcePath() {
405            return "zip://"+mapping.getArchive().getAbsolutePath()+"!"+relPath; 
406        }
407
408    /**
409         * return file object, based on physical path and relpath
410         * @return file Object
411         */
412    public Resource getPhyscalFile() {
413        if(physcalSource==null) {
414            if(!mapping.hasPhysical()) {
415                return null;
416            }
417                        physcalSource=ResourceUtil.toExactResource(mapping.getPhysical().getRealResource(relPath));
418        }
419        return physcalSource;
420        }
421    
422    public Resource getArchiveFile() {
423        if(archiveSource==null) {
424                if(!mapping.hasArchive()) return null;
425                String path="zip://"+mapping.getArchive().getAbsolutePath()+"!"+relPath;
426                archiveSource = ThreadLocalPageContext.getConfig().getResource(path);
427        }
428        return archiveSource;
429        }
430    
431
432    /**
433         * merge to relpath to one
434         * @param mapping 
435         * @param parentRelPath 
436         * @param newRelPath
437         * @param isOutSide 
438         * @return merged relpath
439         */
440        private static String mergeRelPathes(Mapping mapping,String parentRelPath, String newRelPath, RefBoolean isOutSide) {
441                //print.e("---------- mergeRelPathes ------------");
442                
443                parentRelPath=pathRemoveLast(parentRelPath,isOutSide);
444                //print.e("->"+parentRelPath);
445                //print.e("->"+newRelPath);
446                
447                while(newRelPath.startsWith("../")) {
448                        parentRelPath=pathRemoveLast(parentRelPath,isOutSide);
449                        newRelPath=newRelPath.substring(3);
450                        //print.e("->"+parentRelPath);
451                        //print.e("->"+newRelPath);
452                }
453                
454                if(newRelPath.equals("..")) {
455                        parentRelPath=pathRemoveLast(parentRelPath,isOutSide);
456                        newRelPath="";
457                        //print.e("->"+parentRelPath);
458                        //print.e("->"+newRelPath);
459                }
460                
461                
462                // check if come back
463                String path=parentRelPath.concat("/").concat(newRelPath);
464                
465                if(path.startsWith("../")) {
466                        int count=0;
467                        do {
468                                count++;
469                                path=path.substring(3);
470                        }while(path.startsWith("../"));
471                        
472                        String strRoot=mapping.getPhysical().getAbsolutePath().replace('\\','/');
473                        if(!StringUtil.endsWith(strRoot,'/')) {
474                                strRoot+='/';
475                        }
476                        int rootLen=strRoot.length();
477                        String[] arr=ListUtil.toStringArray(ListUtil.listToArray(path,'/'),"");//path.split("/");
478                        int tmpLen;
479                        for(int i=count;i>0;i--) {
480                                if(arr.length>i) {
481                                        String tmp='/'+list(arr,0,i);
482                                        tmpLen=rootLen-tmp.length();
483                                        if(strRoot.lastIndexOf(tmp)==tmpLen && tmpLen>=0) {
484                                                StringBuffer rtn=new StringBuffer();
485                                                while(i<count-i) {
486                                                        count--;
487                                                        rtn.append("../");
488                                                }
489                                                isOutSide.setValue(rtn.length()!=0);
490                                                //print.e("2>"+(rtn.length()==0?"/":rtn.toString())+list(arr,i,arr.length));
491                                                return (rtn.length()==0?"/":rtn.toString())+list(arr,i,arr.length);
492                                        }
493                                }
494                        }
495                }
496                //print.e("3>"+(parentRelPath.concat("/").concat(newRelPath)));
497                return parentRelPath.concat("/").concat(newRelPath);
498        }
499
500        /**
501         * convert a String array to a string list, but only part of it 
502         * @param arr String Array
503         * @param from start from here
504         * @param len how many element
505         * @return String list
506         */
507        private static String list(String[] arr,int from, int len) {
508                StringBuffer sb=new StringBuffer();
509                for(int i=from;i<len;i++) {
510                        sb.append(arr[i]);
511                        if(i+1!=arr.length)sb.append('/');
512                }
513                return sb.toString();
514        }
515
516        
517        
518        /**
519         * remove the last elemtn of a path
520         * @param path path to remove last element from it
521         * @param isOutSide 
522         * @return path with removed element
523         */
524        private static String pathRemoveLast(String path, RefBoolean isOutSide) {
525                if(path.length()==0) {
526                        isOutSide.setValue(true);
527                        return "..";
528                }
529                else if(path.endsWith("..")){
530                    isOutSide.setValue(true);
531                        return path.concat("/..");//path+"/..";
532                }
533                path= path.substring(0,path.lastIndexOf('/'));
534                if(StringUtil.endsWith(path, '/'))
535                        path=path.substring(0,path.length()-1);
536                return path;
537        }
538        
539        @Override
540        public String getRealpath() {
541                return relPath;
542        }       
543        @Override
544        public String getFullRealpath() {
545                if(mapping.getVirtual().length()==1 || mapping.ignoreVirtual())
546                        return relPath;
547                return mapping.getVirtual()+relPath;
548        }
549        
550        @Override
551        public String getRealPathAsVariableString() {
552                return StringUtil.toIdentityVariableName(relPath);
553        }
554        
555        @Override
556        public String getClazz() {
557                if(className==null) createClassAndPackage();
558                if(packageName.length()>0) return packageName+'.'+className;
559                return className;
560        }
561        
562        /**
563         * @return returns the a classname matching to filename (Example: test_cfm)
564         */
565        public String getClassName() {
566                if(className==null) createClassAndPackage();
567                return className;
568        }
569
570    @Override
571    public String getFileName() {
572                if(fileName==null) createClassAndPackage();
573        return fileName;
574    }
575        
576        @Override
577        public String getJavaName() {
578                if(javaName==null) createClassAndPackage();
579                return javaName;
580        }
581
582        /**
583         * @return returns the a package matching to file (Example: lucee.web)
584         */
585        public String getPackageName() {
586                if(packageName==null) createClassAndPackage();
587                return packageName;
588        }
589        @Override
590        public String getComponentName() {
591                if(compName==null) createComponentName();
592                return compName;
593        }
594        
595        
596        private void createClassAndPackage() {
597                String str=relPath;
598                StringBuilder packageName=new StringBuilder();
599                StringBuilder javaName=new StringBuilder();
600                
601                String[] arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(str,'/'));
602                
603                String varName,className=null,fileName=null;
604                for(int i=0;i<arr.length;i++) {
605                        if(i==(arr.length-1)) {
606                                int index=arr[i].lastIndexOf('.');
607                                if(index!=-1){
608                                        String ext=arr[i].substring(index+1);
609                                        varName=StringUtil.toVariableName(arr[i].substring(0,index)+"_"+ext);
610                                }
611                                else varName=StringUtil.toVariableName(arr[i]);
612                                varName=varName+"$cf";
613                                className=varName.toLowerCase();
614                                fileName=arr[i];
615                        }
616                        else {
617                                varName=StringUtil.toVariableName(arr[i]);
618                                if(i!=0) {
619                                    packageName.append('.');
620                                }
621                                packageName.append(varName);
622                        }
623                        javaName.append('/');
624                        javaName.append(varName);
625                }
626                        this.fileName=fileName;
627                        this.className=className;
628                        this.packageName=packageName.toString().toLowerCase();
629                        this.javaName=javaName.toString().toLowerCase();
630                        
631                
632
633                
634                
635        }
636        
637        
638
639        private void createComponentName() {
640                Resource res = this.getPhyscalFile();
641                String relPath=this.relPath;
642            String str=null;
643                if(res!=null) {
644                        str=res.getAbsolutePath();
645                        if(str.length()>relPath.length() && !relPath.startsWith(".")) {// if the component is outside the mapping we have no problem
646                                str=str.substring(str.length()-relPath.length());
647                                if(!str.equalsIgnoreCase(relPath)) {
648                                        str=relPath;
649                                }
650                        }
651                        else str=relPath;
652                }
653                else str=relPath;
654            
655                StringBuilder compName=new StringBuilder();
656                String[] arr;
657                
658                // virtual part
659                if(!mapping.ignoreVirtual()) {
660                        arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(mapping.getVirtual(),"\\/"));
661                        for(int i=0;i<arr.length;i++) {
662                                if(compName.length()>0) compName.append('.');
663                                compName.append(arr[i]);
664                        }
665                }
666                
667                // physical part
668                arr=ListUtil.toStringArrayEL(ListUtil.listToArrayRemoveEmpty(str,'/')); 
669                for(int i=0;i<arr.length;i++) {
670                    if(compName.length()>0) compName.append('.');
671                        if(i==(arr.length-1)) {
672                            compName.append(arr[i].substring(0,arr[i].length()-4));
673                        }
674                        else compName.append(arr[i]);
675                }
676                this.compName=compName.toString();
677                
678        }
679
680    @Override
681    public Mapping getMapping() {
682        return mapping;
683    }
684
685    @Override
686    public boolean exists() {
687        if(mapping.isPhysicalFirst())
688                return physcalExists() || archiveExists();
689            return archiveExists() || physcalExists();
690    }
691
692    @Override
693    public boolean physcalExists() {
694        return ResourceUtil.exists(getPhyscalFile());
695    }
696    
697    private boolean archiveExists() {
698        if(!mapping.hasArchive())return false;
699        try {
700                String clazz = getClazz();
701                if(clazz==null) return getArchiveFile().exists();
702                mapping.getClassLoaderForArchive().loadClass(clazz);
703                return true;
704        } 
705        catch(ClassNotFoundException cnfe){
706                return false;
707        }
708        catch (Exception e) {
709            return getArchiveFile().exists();
710        }
711    }
712
713    /**
714     * return the inputstream of the source file
715     * @return return the inputstream for the source from ohysical or archive
716     * @throws FileNotFoundException
717     */
718    private InputStream getSourceAsInputStream() throws IOException {
719        if(!mapping.hasArchive())               return IOUtil.toBufferedInputStream(getPhyscalFile().getInputStream());
720        else if(isLoad(LOAD_PHYSICAL))  return IOUtil.toBufferedInputStream(getPhyscalFile().getInputStream());
721        else if(isLoad(LOAD_ARCHIVE))   {
722            StringBuffer name=new StringBuffer(getPackageName().replace('.','/'));
723            if(name.length()>0)name.append("/");
724            name.append(getFileName());
725            
726            return mapping.getClassLoaderForArchive().getResourceAsStream(name.toString());
727        }
728        else {
729            return null;
730        }
731    }
732    @Override
733    public String[] getSource() throws IOException {
734        //if(source!=null) return source;
735        InputStream is = getSourceAsInputStream();
736        if(is==null) return null;
737        try {
738                return IOUtil.toStringArray(IOUtil.getReader(is,CharsetUtil.toCharset(getMapping().getConfig().getTemplateCharset())));
739        }
740        finally {
741                IOUtil.closeEL(is);
742        }
743    }
744
745    @Override
746    public boolean equals(Object obj) {
747        if(this==obj) return true;  
748        if(!(obj instanceof PageSource)) return false;
749        return getClassName().equals(((PageSource)obj).getClassName());
750        //return equals((PageSource)obj);
751    }
752    
753    /**
754     * is given object equal to this
755     * @param other
756     * @return is same
757     */
758    public boolean equals(PageSource other) {
759        if(this==other) return true;  
760        return getClassName().equals(other.getClassName());
761    }
762
763        @Override
764        public PageSource getRealPage(String relPath) {
765                if(relPath.equals(".") || relPath.equals(".."))relPath+='/';
766            else relPath=relPath.replace('\\','/');
767            RefBoolean _isOutSide=new RefBooleanImpl(isOutSide);
768            
769            if(relPath.indexOf('/')==0) {
770                    _isOutSide.setValue(false);
771                }
772                else if(relPath.startsWith("./")) {
773                        relPath=mergeRelPathes(mapping,this.relPath, relPath.substring(2),_isOutSide);
774                }
775                else {
776                        relPath=mergeRelPathes(mapping,this.relPath, relPath,_isOutSide);
777                }
778                return mapping.getPageSource(relPath,_isOutSide.toBooleanValue());
779        }
780        
781        @Override
782        public final void setLastAccessTime(long lastAccess) {
783                this.lastAccess=lastAccess;
784        }       
785        
786        @Override
787        public final long getLastAccessTime() {
788                return lastAccess;
789        }
790
791        @Override
792        public final void setLastAccessTime() {
793                accessCount.plus(1);
794                this.lastAccess=System.currentTimeMillis();
795        }       
796        
797        @Override
798        public final int getAccessCount() {
799                return accessCount.toInt();
800        }
801
802    @Override
803    public Resource getResource() {
804        Resource p = getPhyscalFile();
805        Resource a = getArchiveFile();
806        if(mapping.isPhysicalFirst()){
807                if(a==null) return p;
808                if(p==null) return a;
809                
810                if(p.exists()) return p;
811                if(a.exists()) return a;
812                return p;
813        }
814        if(p==null) return a;
815        if(a==null) return p;
816        
817        if(a.exists()) return a;
818        if(p.exists()) return p;
819        return a;
820        
821        //return getArchiveFile();
822    }
823    
824    @Override
825    public Resource getResourceTranslated(PageContext pc) throws ExpressionException {
826        Resource res = null;
827        if(!isLoad(LOAD_ARCHIVE)) res=getPhyscalFile();
828        
829        // there is no physical resource
830                if(res==null){
831                String path=getDisplayPath();
832                if(path!=null){
833                        if(path.startsWith("ra://"))
834                                path="zip://"+path.substring(5);
835                        res=ResourceUtil.toResourceNotExisting(pc, path,false,false);
836                }
837        }
838                return res;
839    }
840
841
842    public void clear() {
843        if(page!=null){
844                page=null;
845        }
846    }
847    
848    /**
849     * clear page, but only when page use the same clasloader as provided
850     * @param cl
851     */
852    public void clear(ClassLoader cl) {
853        if(page!=null && page.getClass().getClassLoader().equals(cl)){
854                page=null;
855        }
856    }
857
858        @Override
859    public String getFullClassName() {
860        String s=_getFullClassName();
861        return s;
862    }
863    
864        public String _getFullClassName() {
865                String p=getPackageName();
866                if(p.length()==0) return getClassName();
867                return p.concat(".").concat(getClassName());
868        }
869        
870        public boolean isLoad() {
871                return page!=null;////load!=LOAD_NONE;
872        }
873
874        @Override
875        public String toString() {
876                return getDisplayPath();
877        }
878        
879        @Override
880        public long sizeOf() {
881                return SizeOf.size(page,0)+
882                SizeOf.size(className)+
883                SizeOf.size(packageName)+
884                SizeOf.size(javaName)+
885                SizeOf.size(fileName)+
886                SizeOf.size(compName)+
887                SizeOf.size(lastAccess)+
888                SizeOf.size(accessCount);
889        }
890
891        public static PageSource best(PageSource[] arr) {
892                if(ArrayUtil.isEmpty(arr)) return null;
893                if(arr.length==1)return arr[0];
894                for(int i=0;i<arr.length;i++) {
895                        if(pageExist(arr[i])) return arr[i];
896                }
897                return arr[0];
898        }
899
900        public static boolean pageExist(PageSource ps) {
901                return (ps.getMapping().isTrusted() && ((PageSourceImpl)ps).isLoad()) || ps.exists();
902        }
903
904        public static Page loadPage(PageContext pc,PageSource[] arr,Page defaultValue) throws PageException {
905                if(ArrayUtil.isEmpty(arr)) return null;
906                Page p;
907                for(int i=0;i<arr.length;i++) {
908                        p=arr[i].loadPage(pc,(Page)null);
909                        if(p!=null) return p;
910                }
911                return defaultValue;
912        }
913
914        public static Page loadPage(PageContext pc,PageSource[] arr) throws PageException {
915                if(ArrayUtil.isEmpty(arr)) return null;
916                
917                Page p;
918                for(int i=0;i<arr.length;i++) {
919                        p=arr[i].loadPage(pc,(Page)null);
920                        if(p!=null) return p;
921                }
922                throw new MissingIncludeException(arr[0]);
923        }
924        
925        
926        
927}