/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 2007-2015 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package org.glassfish.hk2.classmodel.reflect.util;

import org.glassfish.hk2.classmodel.reflect.Parser;

import java.net.URI;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Jar based archive abstraction
 */
public class JarArchive extends AbstractAdapter {

    private final Parser parser;
    private final JarFile jar;
    private final URI uri;

    /**
     * We need to maintain how many internal jars got opened so that
     * we don't close our jar archive until all the sub scanning
     * has been done successfully.
     */
    private final AtomicInteger releaseCount = new AtomicInteger(1);

    public JarArchive(Parser parser, URI uri) throws IOException
    {
        File f = new File(uri);
        this.uri = uri;
        this.jar = new JarFile(f);
        this.parser = parser;
    }

    @Override
    public URI getURI() {
        return uri;
    }

    @Override
     public void onSelectedEntries(Selector selector, EntryTask task, final Logger logger) throws IOException {
         Enumeration<JarEntry> enumEntries = jar.entries();
         while(enumEntries.hasMoreElements()) {
             JarEntry ja = enumEntries.nextElement();
             if (ja.getName().endsWith(".jar")) {
                 URI subURI = null;
                 try {
                     subURI = new URI("jar:"+uri+"!/"+ja.getName());
                 } catch (URISyntaxException e) {
                     try {
                         subURI = new URI(ja.getName());
                     } catch (URISyntaxException e1) {
                         logger.log(Level.FINE, "ignoring exception", e1);
                     }
                 }

                 final InputStreamArchiveAdapter subArchive = new InputStreamArchiveAdapter(this, subURI,
                         jar.getInputStream(jar.getEntry(ja.getName())));
                 releaseCount.incrementAndGet();
                 parser.parse(subArchive, new Runnable() {
                     @Override
                     public void run() {
                         try {
                             subArchive.close();
                         } catch (IOException e) {
                             logger.log(Level.SEVERE, "Cannot close sub archive {0}", subArchive.getURI());
                         }
                     }
                 });
                     subArchive.onSelectedEntries(selector, task, logger);
             }
             InputStream is = null;
             try {
                 Entry entry = new Entry(ja.getName(), ja.getSize());
                 if (!selector.isSelected(entry))
                    continue;
                 is = jar.getInputStream(ja);
                 try {
                     task.on(entry, is);
                 } catch (Exception e) {
                     logger.log(Level.SEVERE, "Exception while processing " + ja.getName()
                             + " inside " + jar.getName() + " of size " + ja.getSize(), e);
                 }
             } finally {
                 if (is!=null) {
                     is.close();
                 }
             }
         }

     }
    

    @Override
    public Manifest getManifest() throws IOException {
        return jar.getManifest();
    }

    @Override
    public void close() throws IOException {
        releaseCount();
    }

    void releaseCount() throws IOException {
        int release = releaseCount.decrementAndGet();
        if (release==0) {
            jar.close();
        }
    }
}
