001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.commons.io.res.util;
020
021import java.io.ByteArrayInputStream;
022import java.io.Closeable;
023import java.io.IOException;
024import java.io.InputStream;
025import java.net.URL;
026import java.util.ArrayList;
027import java.util.zip.ZipEntry;
028import java.util.zip.ZipFile;
029
030import lucee.commons.io.IOUtil;
031import lucee.commons.io.res.Resource;
032import lucee.commons.lang.ExceptionUtil;
033import lucee.commons.lang.SizeOf;
034import lucee.runtime.type.Sizeable;
035
036public final class RCL extends ClassLoader implements Sizeable,Closeable {
037    
038    private final ClassLoader pcl;
039        private ZipFile[] zips;
040    
041    /**
042     * constructor of the class
043     * @param file
044     * @param parent
045     * @throws IOException
046     */
047    public RCL(Resource[] files, ClassLoader parent) throws IOException {
048        super(parent);
049        this.pcl=parent;
050        
051        ArrayList list=new ArrayList();
052        ZipFile zip;
053        for(int i=0;i<files.length;i++){
054                zip=toZip(files[i]);
055                if(zip!=null)
056                        list.add(zip);
057        }
058        zips=(ZipFile[]) list.toArray(new ZipFile[list.size()]);
059    }
060    
061    private ZipFile toZip(Resource file) {
062        if(!file.exists())
063            return null;
064        if(!file.isFile())
065            return null;
066        if(!file.isReadable())
067            return null;
068        try {
069                        return new ZipFile(FileWrapper.toFile(file));
070                } catch (Exception e) {
071                        return null;
072                }
073        }
074
075        /**
076     * Loads the class with the specified name. This method searches for 
077     * classes in the same manner as the {@link #loadClass(String, boolean)} 
078     * method. It is called by the Java virtual machine to resolve class 
079     * references. Calling this method is equivalent to calling 
080     * <code>loadClass(name, false)</code>.
081     *
082     * @param     name the name of the class
083     * @return    the resulting <code>Class</code> object
084     * @exception ClassNotFoundException if the class was not found
085     */
086   public Class loadClass(String name) throws ClassNotFoundException   {
087       return loadClass(name, false);
088   }
089
090    /**
091     * Loads the class with the specified name.  The default implementation of
092     * this method searches for classes in the following order:<p>
093     *
094     * <ol>
095     * <li> Call {@link #findLoadedClass(String)} to check if the class has
096     *      already been loaded. <p>
097     * <li> Call the <code>loadClass</code> method on the parent class
098     *      loader.  If the parent is <code>null</code> the class loader
099     *      built-in to the virtual machine is used, instead. <p>
100     * <li> Call the {@link #findClass(String)} method to find the class. <p>
101     * </ol>
102     *
103     * If the class was found using the above steps, and the
104     * <code>resolve</code> flag is true, this method will then call the
105     * {@link #resolveClass(Class)} method on the resulting class object.
106     * <p>
107     * From the Java 2 SDK, v1.2, subclasses of ClassLoader are 
108     * encouraged to override
109     * {@link #findClass(String)}, rather than this method.<p>
110     *
111     * @param     name the name of the class
112     * @param     resolve if <code>true</code> then resolve the class
113     * @return   the resulting <code>Class</code> object
114     * @exception ClassNotFoundException if the class could not be found
115     */
116    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
117        // First, check if the class has already been loaded
118        Class c = findLoadedClass(name);
119        if (c == null) {
120            c=findClassEL(name);
121            if(c==null) {
122                c =pcl.loadClass(name);
123            }
124        }
125        if (resolve) {
126            resolveClass(c);
127        }
128        return c;
129   }
130    
131    @Override
132    protected Class findClass(String name) throws ClassNotFoundException {
133        Class clazz=findClassEL(name);
134        if(clazz!=null) return clazz;
135        throw new ClassNotFoundException("class "+name+" not found");
136    }
137    
138    private Class findClassEL(String name) {
139        byte[] barr = getBytes(name.replace('.','/').concat(".class"));
140        if(barr!=null) {
141            try {
142                return defineClass(name,barr,0,barr.length);
143            }
144            catch(Throwable t) {
145                ExceptionUtil.rethrowIfNecessary(t);
146            }
147        }
148        return null;
149    }
150
151    @Override
152    public InputStream getResourceAsStream(String name) {
153        InputStream is = super.getResourceAsStream(name);
154        if(is!=null) return is;
155        
156        byte[] barr = getBytes(name);
157        if(barr!=null) return new ByteArrayInputStream(barr);
158        
159        return null;
160    }
161    
162    @Override 
163    public URL getResource(String name) { 
164        return null; 
165    } 
166
167    private byte[] getBytes(String name) {
168        ZipEntry entry=null;
169        for(int i=0;i<zips.length;i++){
170                entry= zips[i].getEntry(name);
171                if(entry!=null) {
172                        return getBytes(zips[i], entry);
173                }
174        }
175        return null;
176    }
177
178    private byte[] getBytes(ZipFile zip,ZipEntry entry) {
179        
180        if (entry == null)return null;
181        
182        int size= (int) entry.getSize();
183        InputStream is=null;
184        try {
185            is= zip.getInputStream(entry);
186            byte[] data= new byte[size];
187            int pos= 0;
188            while (pos < size) {
189                int n= is.read(data, pos, data.length - pos);
190                pos += n;
191            }
192            return data;
193        }
194        catch(IOException ioe) {}
195        finally {
196            IOUtil.closeEL(is);
197        }
198        return null;
199    }
200
201        @Override
202        public long sizeOf() {
203                return SizeOf.size(zips);
204        }
205
206        public void close() throws IOException {
207                for(int i=0;i<zips.length;i++){
208                        closeEL(zips[i]);
209                }
210        }
211
212        private static void closeEL(ZipFile zipFile) {
213                try {
214                        if(zipFile!=null)zipFile.close();
215                } catch (IOException e) {}
216        } 
217}