001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.lang;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.HashMap;
024import java.util.LinkedList;
025import java.util.Map;
026
027import lucee.commons.io.res.Resource;
028import lucee.runtime.MappingImpl;
029import lucee.runtime.PageSourceImpl;
030import lucee.runtime.instrumentation.InstrumentationUtil;
031import lucee.runtime.type.util.StructUtil;
032
033/**
034 * Directory ClassLoader
035 */
036public final class PCLCollection {
037    
038    private final Resource directory;
039    private final ClassLoader resourceCL;
040
041        private final int maxBlockSize;
042        private final MappingImpl mapping;
043        private final LinkedList<PCLBlock> cfcs=new LinkedList<PCLBlock>();
044        private LinkedList<PCLBlock> cfms=new LinkedList<PCLBlock>();
045        private PCLBlock cfc;
046        private PCLBlock cfm;
047        private Map<String,PCLBlock> index=new HashMap<String, PCLBlock>();
048
049    /**
050     * Constructor of the class
051     * @param directory
052     * @param parent
053     * @throws IOException
054     */
055    public PCLCollection(MappingImpl mapping,Resource directory, ClassLoader resourceCL, int maxBlockSize) throws IOException {
056        // check directory
057        if(!directory.exists())
058            directory.mkdirs();
059        
060        if(!directory.isDirectory())
061            throw new IOException("resource "+directory+" is not a directory");
062        if(!directory.canRead())
063            throw new IOException("no access to "+directory+" directory");
064        
065        this.directory=directory;
066        this.mapping=mapping;
067        //this.pcl=systemCL;
068        this.resourceCL=resourceCL;
069        cfc=new PCLBlock(directory, resourceCL);
070        cfcs.add(cfc);
071        cfm=new PCLBlock(directory, resourceCL);
072        cfms.add(cfm);
073        this.maxBlockSize=100;//maxBlockSize;
074    }
075    
076
077    private PCLBlock current(boolean isCFC) {
078        if((isCFC?cfc.count():cfm.count())>=maxBlockSize) {
079                synchronized (isCFC?cfcs:cfms) {
080                        if(isCFC) {
081                                cfc=new PCLBlock(directory, resourceCL);
082                                cfcs.add(cfc);
083                        }
084                        else {
085                                cfm=new PCLBlock(directory, resourceCL);
086                                cfms.add(cfm);
087                        }
088                        }
089        }
090                return isCFC?cfc:cfm;
091        }
092    
093    
094
095    public synchronized Class<?> loadClass(String name, byte[] barr, boolean isCFC)   {
096        // if class is already loaded flush the classloader and do new classloader
097        PCLBlock cl = index.get(name);
098        if(cl!=null) {
099                // if can update class
100                if(InstrumentationUtil.isSupported()){
101                        try{
102                                Class<?> old = cl.loadClass(name);
103                        InstrumentationUtil.redefineClass(old, barr);
104                        return old;
105                        }
106                        catch(Throwable t){
107                        ExceptionUtil.rethrowIfNecessary(t);
108                                t.printStackTrace();
109                        }
110                }
111                
112                // flush classloader when update is not possible
113                mapping.clearPages(cl);
114                StructUtil.removeValue(index,cl);
115                if(isCFC){
116                cfcs.remove(cl);
117                        if(cl==cfc) cfc=new PCLBlock(directory, resourceCL);
118                }
119                else {
120                cfms.remove(cl);
121                        if(cl==cfm) cfm=new PCLBlock(directory, resourceCL);
122                }
123        }
124        
125        // load class from byte array
126        PCLBlock c = current(isCFC);
127        index.put(name, c);
128        return c.loadClass(name, barr);
129    }
130    
131    /**
132     * load existing class
133     * @param name
134     * @return
135     * @throws ClassNotFoundException 
136     */
137    public synchronized Class<?> loadClass(String className) throws ClassNotFoundException {
138        // if class is already loaded flush the classloader and do new classloader
139        PCLBlock cl = index.get(className);
140        if(cl!=null) {
141                return cl.loadClass(className);
142        }
143        throw new ClassNotFoundException("class "+className+" not found");
144    }
145
146    public synchronized Class<?> getClass(PageSourceImpl ps) throws ClassNotFoundException {
147        String name=ps.getClazz();
148        PCLBlock cl = index.get(name);
149        if(cl==null) {
150                cl=current(ps.isComponent());
151                Class<?> clazz = cl.loadClass(name);
152                index.put(name, cl);
153                return clazz;
154        }
155        return cl.loadClass(name);      
156    }
157
158    public synchronized InputStream getResourceAsStream(String name) {
159        return current(false).getResourceAsStream(name);
160    }
161
162        public long count() {
163                return index.size();
164        }
165        
166        /**
167         * shrink the classloader elements
168         * @return how many page have removed from classloaders
169         */
170
171        public synchronized int shrink(boolean force){
172                int before=index.size();
173                
174                // CFM
175                int flushCFM=0;
176                while(cfms.size()>1) {
177                        flush(cfms.poll());
178                        flushCFM++;
179                }
180        
181                // CFC
182                if(force && flushCFM<2 && cfcs.size()>1) {
183                        flush(oldest(cfcs));
184                        if(cfcs.size()>1)flush(cfcs.poll());
185                }
186                //print.o("shrink("+mapping.getVirtual()+"):"+(before-index.size())+">"+force+";"+(flushCFM));
187        return before-index.size();
188        }
189
190        private static PCLBlock oldest(LinkedList<PCLBlock> queue) {
191                int index=NumberUtil.randomRange(0,queue.size()-2);
192                return queue.remove(index);
193                //return queue.poll();
194        }
195
196
197        private void flush(PCLBlock cl) {
198                mapping.clearPages(cl);
199                StructUtil.removeValue(index,cl);
200                //System.gc(); gc is in Controller call, to make sure gc is only called once 
201        }
202}