/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 
 * 
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License("CDDL") (the "License").  You may not use this file
 * except in compliance with the License. 
 * 
 * You can obtain a copy of the License at:
 *     https://jersey.dev.java.net/license.txt
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * When distributing the Covered Code, include this CDDL Header Notice in each
 * file and include the License file at:
 *     https://jersey.dev.java.net/license.txt
 * If applicable, add the following below this CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 *     "Portions Copyrighted [year] [name of copyright owner]"
 */

package com.sun.ws.rest.deployer;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;

/**
 * ASM ResourceClassScanner Implementation
 * @author Frank D. Martinez. fmartinez@asimovt.com
 */
public class ASMResourceClassScanner implements ResourceClassScanner {
    
    private List<Class> classes;
    
    private Set<String> annotations;

    boolean resourceClassFound = false;
    
    public List<Class> scan(File[] paths, Class[] annotations) {
        try {
            
            // 1. Init classes
            this.classes = new LinkedList<Class>();
            
            // 2. Init annotations
            this.annotations = new HashSet<String>();
            for (Class cls : annotations) {
                this.annotations.add(
                        "L" + cls.getName().replaceAll("\\.", "/") + ";");
            }
            
            // 3. Search
            for (File file : paths) {
                index(file);
            }
            
            // 4. Return
            return classes;
            
        }
        catch (Throwable ex) {
            throw new RuntimeException(ex);
        }
    }
    
    private void analyze(URL file) 
            throws IOException, ClassNotFoundException {
        
        resourceClassFound = false;
        ClassReader reader = new ClassReader(file.openStream());
        reader.accept(CLASS_VISITOR, 0);
        if (resourceClassFound) {
            classes.add(Class.forName(
                    reader.getClassName().replaceAll("/", ".")));
        }
        
    }
    
    private void index(File file) 
            throws MalformedURLException, IOException, ClassNotFoundException {
        
        if (file.isDirectory()) {
            indexDir(file);
        }
        else {
            indexJar(file);
        }
        
    }
    
    private void indexDir(File root) 
            throws MalformedURLException, IOException, ClassNotFoundException {
        
        File[] children = root.listFiles();
        for (File child : children) {
            if (child.isDirectory()) {
                indexDir(child);
            }
            else if (child.getName().endsWith(".jar")) {
                indexJar(child);
            }
            else if (child.getName().endsWith(".class")) {
                analyze(child.toURI().toURL());
            }
        }
        
    }
    
    private void indexJar(File file) 
            throws IOException, ClassNotFoundException {
        
        final JarFile jar = new JarFile(file);
        final Enumeration<JarEntry> entries = jar.entries();
        final String jarBase = "jar:" + file.toURI().toURL() + "!/";
        while (entries.hasMoreElements()) {
            JarEntry e = entries.nextElement();
            if (e.getName().endsWith(".class")) {
                analyze(new URL(jarBase + e.getName()));
            }
        }
        
    }

    private final ClassVisitor CLASS_VISITOR = new ClassVisitorAdapter() {
        
        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            resourceClassFound |= annotations.contains(desc);
            return null;
        }

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, 
                String signature, String[] exceptions) {
            return METHOD_VISITOR;
        }
        
    };
    
    private final MethodVisitor METHOD_VISITOR = new MethodVisitorAdapter() {
        
        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            resourceClassFound |= annotations.contains(desc);
            return null;
        }
        
    };

}
