package railo.commons.lang; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; import railo.commons.io.res.Resource; import railo.runtime.MappingImpl; import railo.runtime.PageSourceImpl; import railo.runtime.instrumentation.InstrumentationUtil; import railo.runtime.type.util.StructUtil; /** * Directory ClassLoader */ public final class PCLCollection { private final Resource directory; private final ClassLoader resourceCL; private final int maxBlockSize; private final MappingImpl mapping; private final LinkedList<PCLBlock> cfcs=new LinkedList<PCLBlock>(); private LinkedList<PCLBlock> cfms=new LinkedList<PCLBlock>(); private PCLBlock cfc; private PCLBlock cfm; private Map<String,PCLBlock> index=new HashMap<String, PCLBlock>(); /** * Constructor of the class * @param directory * @param parent * @throws IOException */ public PCLCollection(MappingImpl mapping,Resource directory, ClassLoader resourceCL, int maxBlockSize) throws IOException { // check directory if(!directory.exists()) directory.mkdirs(); if(!directory.isDirectory()) throw new IOException("resource "+directory+" is not a directory"); if(!directory.canRead()) throw new IOException("no access to "+directory+" directory"); this.directory=directory; this.mapping=mapping; //this.pcl=systemCL; this.resourceCL=resourceCL; cfc=new PCLBlock(directory, resourceCL); cfcs.add(cfc); cfm=new PCLBlock(directory, resourceCL); cfms.add(cfm); this.maxBlockSize=100;//maxBlockSize; } private PCLBlock current(boolean isCFC) { if((isCFC?cfc.count():cfm.count())>=maxBlockSize) { synchronized (isCFC?cfcs:cfms) { if(isCFC) { cfc=new PCLBlock(directory, resourceCL); cfcs.add(cfc); } else { cfm=new PCLBlock(directory, resourceCL); cfms.add(cfm); } } } return isCFC?cfc:cfm; } public synchronized Class<?> loadClass(String name, byte[] barr, boolean isCFC) { // if class is already loaded flush the classloader and do new classloader PCLBlock cl = index.get(name); if(cl!=null) { // if can upate class if(InstrumentationUtil.isSupported()){ try{ Class<?> old = cl.loadClass(name); InstrumentationUtil.redefineClass(old, barr); return old; } catch(Throwable t){ t.printStackTrace(); } } // flush classloader when update is not possible mapping.clearPages(cl); StructUtil.removeValue(index,cl); if(isCFC){ cfcs.remove(cl); if(cl==cfc) cfc=new PCLBlock(directory, resourceCL); } else { cfms.remove(cl); if(cl==cfm) cfm=new PCLBlock(directory, resourceCL); } } // load class from byte array PCLBlock c = current(isCFC); index.put(name, c); return c.loadClass(name, barr); } public synchronized Class<?> getClass(PageSourceImpl ps) throws ClassNotFoundException { String name=ps.getClazz(); PCLBlock cl = index.get(name); if(cl==null) { cl=current(ps.isComponent()); Class<?> clazz = cl.loadClass(name); index.put(name, cl); return clazz; } return cl.loadClass(name); } public synchronized InputStream getResourceAsStream(String name) { return current(false).getResourceAsStream(name); } public long count() { return index.size(); } /** * shrink the classloader elements * @return how many page have removed from classloaders */ public synchronized int shrink(boolean force){ int before=index.size(); // CFM int flushCFM=0; while(cfms.size()>1) { flush(cfms.poll()); flushCFM++; } // CFC if(force && flushCFM<2 && cfcs.size()>1) { flush(oldest(cfcs)); if(cfcs.size()>1)flush(cfcs.poll()); } //print.o("shrink("+mapping.getVirtual()+"):"+(before-index.size())+">"+force+";"+(flushCFM)); return before-index.size(); } private static PCLBlock oldest(LinkedList<PCLBlock> queue) { int index=NumberUtil.randomRange(0,queue.size()-2); return queue.remove(index); //return queue.poll(); } private void flush(PCLBlock cl) { mapping.clearPages(cl); StructUtil.removeValue(index,cl); //System.gc(); gc is in Controller call, to make sure gc is only called once } }