/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.openejb.arquillian.openejb;

import org.apache.openejb.ClassLoaderUtil;
import org.apache.openejb.OpenEJBException;
import org.apache.openejb.OpenEJBRuntimeException;
import org.apache.openejb.cdi.CompositeBeans;
import org.apache.openejb.config.AppModule;
import org.apache.openejb.config.DeploymentLoader;
import org.apache.openejb.config.EjbModule;
import org.apache.openejb.config.FinderFactory;
import org.apache.openejb.config.ReadDescriptors;
import org.apache.openejb.config.WebModule;
import org.apache.openejb.config.WebappAggregatedArchive;
import org.apache.openejb.jee.Beans;
import org.apache.openejb.jee.EjbJar;
import org.apache.openejb.jee.ManagedBean;
import org.apache.openejb.jee.TransactionType;
import org.apache.openejb.jee.WebApp;
import org.apache.openejb.jee.WebApp$JAXB;
import org.apache.openejb.jee.oejb3.EjbDeployment;
import org.apache.openejb.jee.oejb3.OpenejbJar;
import org.apache.openejb.loader.IO;
import org.apache.openejb.sxc.Sxc;
import org.apache.openejb.util.classloader.URLClassLoaderFirst;
import org.apache.xbean.finder.archive.ClassesArchive;
import org.apache.xbean.finder.archive.CompositeArchive;
import org.apache.xbean.finder.archive.FilteredArchive;
import org.apache.xbean.finder.archive.JarArchive;
import org.jboss.arquillian.test.spi.TestClass;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ArchivePath;
import org.jboss.shrinkwrap.api.Node;
import org.jboss.shrinkwrap.api.asset.ArchiveAsset;
import org.jboss.shrinkwrap.api.asset.Asset;
import org.jboss.shrinkwrap.api.asset.ClassLoaderAsset;
import org.jboss.shrinkwrap.api.asset.FileAsset;
import org.jboss.shrinkwrap.api.asset.UrlAsset;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.impl.base.filter.IncludeRegExpPaths;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.util.Arrays.asList;
import static org.apache.openejb.arquillian.openejb.reflection.Assets.EMPTY_LOADER;
import static org.apache.openejb.arquillian.openejb.reflection.Assets.get;

// doesn't implement ApplicationArchiveProcessor anymore since in some cases
// (with some observers set for instance)
// it is not called before the deployment itself
// so it is like if it was skipped
public class OpenEJBArchiveProcessor {
    private static final Logger LOGGER = Logger.getLogger(OpenEJBArchiveProcessor.class.getName());

    private static final String META_INF = "META-INF/";
    private static final String WEB_INF = "WEB-INF/";
    private static final String EJB_JAR_XML = "ejb-jar.xml";

    private static final String BEANS_XML = "beans.xml";
    private static final String VALIDATION_XML = "validation.xml";
    private static final String RESOURCES_XML = "resources.xml";
    private static final String PERSISTENCE_XML = "persistence.xml";
    private static final String OPENEJB_JAR_XML = "openejb-jar.xml";
    private static final String ENV_ENTRIES_PROPERTIES = "env-entries.properties";
    private static final String WEB_INF_CLASSES = "/WEB-INF/classes/";

    public static AppModule createModule(final Archive<?> archive, final TestClass testClass, final Closeables closeables) {
        final Class<?> javaClass;
        if (testClass != null) {
            javaClass = testClass.getJavaClass();
        } else {
            javaClass = null;
        }

        final ClassLoader parent;
        if (javaClass == null) {
            parent = Thread.currentThread().getContextClassLoader();
        } else {
            parent = javaClass.getClassLoader();
        }

        final List<URL> additionalPaths = new ArrayList<>();
        CompositeArchive scannedArchive = null;
        final Map<URL, List<String>> earMap = new HashMap<>();
        final Map<String, Object> altDD = new HashMap<>();
        final List<Archive> earLibsArchives = new ArrayList<>();
        final CompositeBeans earBeans = new CompositeBeans();

        final boolean isEar = EnterpriseArchive.class.isInstance(archive);
        final boolean isWebApp = WebArchive.class.isInstance(archive);
        final String prefix = isWebApp ? WEB_INF : META_INF;
        if (isEar || isWebApp) {
            final Map<ArchivePath, Node> jars = archive.getContent(new IncludeRegExpPaths(isEar ? "/.*\\.jar" : "/WEB-INF/lib/.*\\.jar"));
            scannedArchive = analyzeLibs(parent, additionalPaths, earMap, earLibsArchives, earBeans, jars, altDD);
        }

        final URL[] urls = additionalPaths.toArray(new URL[additionalPaths.size()]);

        final URLClassLoaderFirst swParent = new URLClassLoaderFirst(urls, parent);
        closeables.add(swParent);

        if (!isEar) {
            earLibsArchives.add(archive);
        }
        final SWClassLoader loader = new SWClassLoader(swParent, earLibsArchives.toArray(new Archive<?>[earLibsArchives.size()]));
        closeables.add(loader);
        final URLClassLoader tempClassLoader = ClassLoaderUtil.createTempClassLoader(loader);
        closeables.add(tempClassLoader);

        final AppModule appModule = new AppModule(loader, archive.getName());
        if (WEB_INF.equals(prefix)) {
            appModule.setDelegateFirst(false);
            appModule.setStandloneWebModule();

            final WebModule webModule = new WebModule(createWebApp(archive), contextRoot(archive.getName()), loader, "", appModule.getModuleId());
            webModule.setUrls(additionalPaths);
            appModule.getWebModules().add(webModule);
        } else if (isEar) { // mainly for CDI TCKs
            final FinderFactory.OpenEJBAnnotationFinder earLibFinder = new FinderFactory.OpenEJBAnnotationFinder(new SimpleWebappAggregatedArchive(tempClassLoader, scannedArchive, earMap));
            appModule.setEarLibFinder(earLibFinder);

            final EjbModule earCdiModule = new EjbModule(appModule.getClassLoader(), DeploymentLoader.EAR_SCOPED_CDI_BEANS + appModule.getModuleId(), new EjbJar(), new OpenejbJar());
            earCdiModule.setBeans(earBeans);
            earCdiModule.setFinder(earLibFinder);

            final EjbJar ejbJar;
            final AssetSource ejbJarXml = AssetSource.class.isInstance(altDD.get(EJB_JAR_XML)) ? AssetSource.class.cast(altDD.get(EJB_JAR_XML)) : null;
            if (ejbJarXml != null) {
                try {
                    ejbJar = ReadDescriptors.readEjbJar(ejbJarXml.get());
                } catch (final Exception e) {
                    throw new OpenEJBRuntimeException(e);
                }
            } else {
                ejbJar = new EjbJar();
            }

            earCdiModule.setEjbJar(ejbJar); // EmptyEjbJar would prevent to add scanned EJBs but this is *here* an aggregator so we need to be able to do so
            appModule.getEjbModules().add(earCdiModule);

            for (final Map.Entry<ArchivePath, Node> node : archive.getContent(new IncludeRegExpPaths("/.*\\.war")).entrySet()) {
                final Asset asset = node.getValue().getAsset();
                if (ArchiveAsset.class.isInstance(asset)) {
                    final Archive<?> webArchive = ArchiveAsset.class.cast(asset).getArchive();
                    if (WebArchive.class.isInstance(webArchive)) {
                        final Map<String, Object> webAltDD = new HashMap<>();
                        final Node beansXml = findBeansXml(webArchive, WEB_INF);

                        final List<URL> webappAdditionalPaths = new LinkedList<>();
                        final CompositeBeans webAppBeansXml = new CompositeBeans();
                        final List<Archive> webAppArchives = new LinkedList<>();
                        final Map<URL, List<String>> webAppClassesByUrl = new HashMap<>();
                        final CompositeArchive webAppArchive = analyzeLibs(parent,
                                webappAdditionalPaths, webAppClassesByUrl,
                                webAppArchives, webAppBeansXml,
                                webArchive.getContent(new IncludeRegExpPaths("/WEB-INF/lib/.*\\.jar")),
                                webAltDD);

                        webAppArchives.add(webArchive);
                        final SWClassLoader webLoader = new SWClassLoader(parent, webAppArchives.toArray(new Archive<?>[webAppArchives.size()]));
                        closeables.add(webLoader);

                        final FinderFactory.OpenEJBAnnotationFinder finder = new FinderFactory.OpenEJBAnnotationFinder(
                                finderArchive(beansXml, webArchive, webLoader, webAppArchive, webAppClassesByUrl, webAppBeansXml));

                        final String contextRoot = contextRoot(webArchive.getName());
                        final WebModule webModule = new WebModule(createWebApp(webArchive), contextRoot, webLoader, "", appModule.getModuleId() + "_" + contextRoot);
                        webModule.setUrls(Collections.<URL>emptyList());
                        webModule.setScannableUrls(Collections.<URL>emptyList());
                        webModule.setFinder(finder);

                        final EjbJar webEjbJar = createEjbJar(prefix, webArchive);

                        final EjbModule ejbModule = new EjbModule(webLoader, webModule.getModuleId(), null, webEjbJar, new OpenejbJar());
                        ejbModule.setBeans(webAppBeansXml);
                        ejbModule.getAltDDs().putAll(webAltDD);
                        ejbModule.getAltDDs().put("beans.xml", webAppBeansXml);
                        ejbModule.setFinder(finder);
                        ejbModule.setClassLoader(webLoader);
                        ejbModule.setWebapp(true);

                        appModule.getEjbModules().add(ejbModule);
                        appModule.getWebModules().add(webModule);

                        addPersistenceXml(archive, WEB_INF, appModule);
                        addOpenEJbJarXml(archive, WEB_INF, ejbModule);
                        addValidationXml(archive, WEB_INF, new HashMap<>(), ejbModule);
                        addResourcesXml(archive, WEB_INF, ejbModule);
                        addEnvEntries(archive, WEB_INF, appModule, ejbModule);
                    }
                }
            }
        }

        if (isEar) { // adding the test class as lib class can break test if tested against the web part of the ear
            addTestClassAsManagedBean(javaClass, tempClassLoader, appModule);
            return appModule;
        }

        // add the test as a managed bean to be able to inject into it easily
        final Map<String, Object> testDD;
        if (javaClass != null) {
            final EjbModule testEjbModule = addTestClassAsManagedBean(javaClass, tempClassLoader, appModule);
            testDD = testEjbModule.getAltDDs();
        } else {
            testDD = new HashMap<>(); // ignore
        }

        final EjbJar ejbJar = createEjbJar(prefix, archive);

        if (ejbJar.getModuleName() == null) {
            final String name = archive.getName();
            if (name.endsWith("ar") && name.length() > 4) {
                ejbJar.setModuleName(name.substring(0, name.length() - ".jar".length()));
            } else {
                ejbJar.setModuleName(name);
            }
        }

        final EjbModule ejbModule = new EjbModule(ejbJar);
        ejbModule.setClassLoader(tempClassLoader);

        final Node beansXml = findBeansXml(archive, prefix);

        final FinderFactory.OpenEJBAnnotationFinder finder = new FinderFactory.OpenEJBAnnotationFinder(
                finderArchive(beansXml, archive, loader, scannedArchive, earMap, earBeans));
        ejbModule.setFinder(finder);
        ejbModule.setBeans(earBeans);
        ejbModule.getAltDDs().put("beans.xml", earBeans);

        if (appModule.isWebapp()) { // war
            appModule.getWebModules().iterator().next().setFinder(ejbModule.getFinder());
        }
        appModule.getEjbModules().add(ejbModule);

        addPersistenceXml(archive, prefix, appModule);
        addOpenEJbJarXml(archive, prefix, ejbModule);
        addValidationXml(archive, prefix, testDD, ejbModule);
        addResourcesXml(archive, prefix, ejbModule);
        addEnvEntries(archive, prefix, appModule, ejbModule);

        if (!appModule.isWebapp()) {
            appModule.getAdditionalLibraries().addAll(additionalPaths);
        }

        if (archive.getName().endsWith(".jar")) { // otherwise global naming will be broken
            appModule.setStandloneWebModule();
        }
        return appModule;
    }

    private static EjbJar createEjbJar(final String prefix, final Archive<?> webArchive) {
        final EjbJar webEjbJar;
        final Node webEjbJarXml = webArchive.get(prefix.concat(EJB_JAR_XML));
        if (webEjbJarXml != null) {
            try {
                webEjbJar = ReadDescriptors.readEjbJar(webEjbJarXml.getAsset().openStream());
            } catch (final OpenEJBException e) {
                throw new OpenEJBRuntimeException(e);
            }
        } else {
            webEjbJar = new EjbJar();
        }
        return webEjbJar;
    }

    private static EjbModule addTestClassAsManagedBean(Class<?> javaClass, URLClassLoader tempClassLoader, AppModule appModule) {
        final EjbJar ejbJar = new EjbJar();
        final OpenejbJar openejbJar = new OpenejbJar();
        final String ejbName = appModule.getModuleId() + "_" + javaClass.getName();
        final ManagedBean bean = ejbJar.addEnterpriseBean(new ManagedBean(ejbName, javaClass.getName(), true));
        bean.localBean();
        bean.setTransactionType(TransactionType.BEAN);
        final EjbDeployment ejbDeployment = openejbJar.addEjbDeployment(bean);
        ejbDeployment.setDeploymentId(ejbName);
        final EjbModule e = new EjbModule(ejbJar, openejbJar);
        e.getProperties().setProperty("openejb.cdi.activated", "false");
        e.getProperties().setProperty("openejb.test.module", "true");
        e.setBeans(new Beans());
        e.setClassLoader(tempClassLoader);
        appModule.getEjbModules().add(e);
        return e;
    }

    private static WebApp createWebApp(final Archive<?> archive) {
        WebApp webApp;
        final Node webXml = archive.get(WEB_INF + "web.xml");
        if (webXml == null) {
            webApp = new WebApp();
        } else {
            InputStream inputStream = null;
            try {
                inputStream = webXml.getAsset().openStream();
                webApp = Sxc.unmarshalJavaee(new WebApp$JAXB(), inputStream);
            } catch (final Exception e) {
                webApp = new WebApp();
            } finally {
                IO.close(inputStream);
            }
        }
        return webApp;
    }

    private static CompositeArchive analyzeLibs(final ClassLoader parent,
                                                final List<URL> additionalPaths, final Map<URL, List<String>> earMap,
                                                final List<Archive> earLibsArchives,
                                                final CompositeBeans earBeans,
                                                final Map<ArchivePath, Node> jars,
                                                final Map<String, Object> altDD) {
        final List<org.apache.xbean.finder.archive.Archive> archives = new ArrayList<>(jars.size());
        for (final Map.Entry<ArchivePath, Node> node : jars.entrySet()) {
            final Asset asset = node.getValue().getAsset();
            if (ArchiveAsset.class.isInstance(asset)) {
                final Archive<?> libArchive = ArchiveAsset.class.cast(asset).getArchive();
                if (!isExcluded(libArchive.getName())) {
                    earLibsArchives.add(libArchive);
                    final List<Class<?>> earClasses = new ArrayList<>();
                    final List<String> earClassNames = new ArrayList<>();
                    final Map<ArchivePath, Node> content = libArchive.getContent(new IncludeRegExpPaths(".*.class"));
                    for (final Map.Entry<ArchivePath, Node> classNode : content.entrySet()) {
                        final String classname = name(classNode.getKey().get());
                        try {
                            earClasses.add(parent.loadClass(classname));
                            earClassNames.add(classname);
                        } catch (final ClassNotFoundException e) {
                            LOGGER.fine("Can't load class " + classname);
                        } catch (final NoClassDefFoundError ncdfe) {
                            // no-op
                        }
                    }
                    try { // ends with !/META-INF/beans.xml to force it to be used as a cdi module *with bda*
                        final Node beansNode = libArchive.get(META_INF + BEANS_XML);
                        final URL arUrl = new URL("jar:file://!/lib/" + libArchive.getName() + (beansNode != null ? "!/META-INF/beans.xml" : ""));
                        if (beansNode != null) {
                            try {
                                DeploymentLoader.doMerge(arUrl, earBeans, ReadDescriptors.readBeans(beansNode.getAsset().openStream()));
                            } catch (final OpenEJBException e) {
                                throw new IllegalArgumentException(e);
                            }
                        }
                        earMap.put(arUrl, earClassNames);
                    } catch (final MalformedURLException e) {
                        // no-op
                    }
                    archives.add(new ClassesArchive(earClasses));
                }

                final Node ejbJarXml = libArchive.get(META_INF + EJB_JAR_XML);
                if (ejbJarXml != null) { // not super, we should merge them surely but ok for use cases we met until today
                    altDD.put("ejb-jar.xml", new AssetSource(ejbJarXml.getAsset(), null));
                }
            } if (UrlAsset.class.isInstance(asset) || FileAsset.class.isInstance(asset)) {
                try {
                    final URL url = UrlAsset.class.isInstance(asset) ? get(URL.class, "url", asset) : get(File.class, "file", asset).toURI().toURL();
                    final List<String> classes = new ArrayList<>();
                    archives.add(new FilteredArchive(
                            new JarArchive(parent, url),
                            new WebappAggregatedArchive.ScanXmlSaverFilter(false, null, classes, null)));
                    additionalPaths.add(url);

                    final URLClassLoader loader = new URLClassLoader(new URL[] { url }, EMPTY_LOADER);
                    for (final String beans : asList("META-INF/beans.xml", "/META-INF/beans.xml")) {
                        final URL u = loader.getResource(beans);
                        if (u != null) {
                            try {
                                DeploymentLoader.doMerge(u, earBeans, ReadDescriptors.readBeans(u.openStream()));
                            } catch (final OpenEJBException e) {
                                throw new IllegalArgumentException(e);
                            } catch (final IOException e) {
                                // no-op
                            }
                            earMap.put(u, classes);
                        }
                    }
                    try {
                        loader.close();
                    } catch (final IOException e) {
                        // no-op
                    }
                } catch (final MalformedURLException e) {
                    throw new IllegalArgumentException(e);
                }
            }
        }
        return new CompositeArchive(archives);
    }

    private static Node findBeansXml(final Archive<?> archive, final String prefix) {
        Node beansXml = archive.get(prefix.concat(BEANS_XML));
        if (beansXml == null && WEB_INF.equals(prefix)) {
            beansXml = archive.get(WEB_INF_CLASSES.concat(META_INF).concat(BEANS_XML));
        }
        return beansXml;
    }

    private static void addPersistenceXml(final Archive<?> archive, final String prefix, final AppModule appModule) {
        Node persistenceXml = archive.get(prefix.concat(PERSISTENCE_XML));
        if (persistenceXml == null && WEB_INF.equals(prefix)) {
            persistenceXml = archive.get(WEB_INF_CLASSES.concat(META_INF).concat(PERSISTENCE_XML));
        }
        if (persistenceXml != null) {
            final Asset asset = persistenceXml.getAsset();
            if (UrlAsset.class.isInstance(asset)) {
                appModule.getAltDDs().put(PERSISTENCE_XML, Arrays.asList(get(URL.class, "url", asset)));
            } else if (FileAsset.class.isInstance(asset)) {
                try {
                    appModule.getAltDDs().put(PERSISTENCE_XML, Arrays.asList(get(File.class, "file", asset).toURI().toURL()));
                } catch (final MalformedURLException e) {
                    appModule.getAltDDs().put(PERSISTENCE_XML, Arrays.asList(new AssetSource(persistenceXml.getAsset(), null)));
                }
            } else if (ClassLoaderAsset.class.isInstance(asset)) {
                final URL url = get(ClassLoader.class, "classLoader", asset).getResource(get(String.class, "resourceName", asset));
                if (url != null) {
                    appModule.getAltDDs().put(PERSISTENCE_XML, Arrays.asList(url));
                } else {
                    appModule.getAltDDs().put(PERSISTENCE_XML, Arrays.asList(new AssetSource(persistenceXml.getAsset(), null)));
                }
            } else {
                appModule.getAltDDs().put(PERSISTENCE_XML, Arrays.asList(new AssetSource(persistenceXml.getAsset(), null)));
            }
        }
    }

    private static void addOpenEJbJarXml(final Archive<?> archive, final String prefix, final EjbModule ejbModule) {
        final Node openejbJarXml = archive.get(prefix.concat(OPENEJB_JAR_XML));
        if (openejbJarXml != null) {
            ejbModule.getAltDDs().put(OPENEJB_JAR_XML, new AssetSource(openejbJarXml.getAsset(), null));
        }
    }

    private static void addValidationXml(final Archive<?> archive, final String prefix, final Map<String, Object> testDD, final EjbModule ejbModule) {
        Node validationXml = archive.get(prefix.concat(VALIDATION_XML));
        // bval tcks
        if (validationXml == null && WEB_INF == prefix) { // we can use == here
            validationXml = archive.get(WEB_INF_CLASSES.concat(META_INF).concat(VALIDATION_XML));
        }
        if (validationXml != null) {
            testDD.put(VALIDATION_XML, new AssetSource(validationXml.getAsset(), null)); // use same config otherwise behavior is weird
            ejbModule.getAltDDs().put(VALIDATION_XML, new AssetSource(validationXml.getAsset(), null));
        }
    }

    private static void addResourcesXml(final Archive<?> archive, final String prefix, final EjbModule ejbModule) {
        final Node resourcesXml = archive.get(prefix.concat(RESOURCES_XML));
        if (resourcesXml != null) {
            ejbModule.getAltDDs().put(RESOURCES_XML, new AssetSource(resourcesXml.getAsset(), null));
        }
    }

    private static void addEnvEntries(final Archive<?> archive, final String prefix, final AppModule appModule, final EjbModule ejbModule) {
        final Node envEntriesProperties = archive.get(prefix.concat(ENV_ENTRIES_PROPERTIES));
        if (envEntriesProperties != null) {
            InputStream is = null;
            final Properties properties = new Properties();
            try {
                is = envEntriesProperties.getAsset().openStream();
                properties.load(is);
                ejbModule.getAltDDs().put(ENV_ENTRIES_PROPERTIES, properties);

                // do it for test class too
                appModule.getEjbModules().iterator().next().getAltDDs().put(ENV_ENTRIES_PROPERTIES, properties);
            } catch (final Exception e) {
                LOGGER.log(Level.SEVERE, "can't read env-entries.properties", e);
            } finally {
                IO.close(is);
            }
        }
    }

    private static String contextRoot(final String name) {
        if (name.endsWith(".war")) {
            return name.substring(0, name.length() - ".war".length());
        }
        return name;
    }

    private static org.apache.xbean.finder.archive.Archive finderArchive(
            final Node beansXml, final Archive<?> archive,
            final ClassLoader cl, final CompositeArchive libs,
            final Map<URL, List<String>> webAppClassesByUrl,
            final CompositeBeans compositeBeans) {
        final List<Class<?>> classes = new ArrayList<>();
        final Map<ArchivePath, Node> content = archive.getContent(new IncludeRegExpPaths(".*.class"));
        for (final Map.Entry<ArchivePath, Node> node : content.entrySet()) {
            final String classname = name(node.getKey().get());
            try {
                classes.add(cl.loadClass(classname));
            } catch (final ClassNotFoundException e) {
                LOGGER.fine("Can't load class " + classname);
                if (LOGGER.isLoggable(Level.FINEST)) {
                    e.printStackTrace(System.err);
                }
            } catch (final NoClassDefFoundError ncdfe) {
                // no-op
            }
        }

        final List<org.apache.xbean.finder.archive.Archive> archives = new ArrayList<>();

        archives.add(new ClassesArchive(classes));
        if (beansXml != null) {
            try {
                final URL key = new URL("jar:file://!/WEB-INF/beans.xml"); // no host avoid host resolution in hashcode()
                try {
                    DeploymentLoader.doMerge(key, compositeBeans, ReadDescriptors.readBeans(beansXml.getAsset().openStream()));
                } catch (final OpenEJBException e) {
                    throw new IllegalArgumentException(e);
                }

                final List<String> mainClasses = new ArrayList<>();
                for (final Class<?> clazz : classes) {
                    mainClasses.add(clazz.getName());
                }

                webAppClassesByUrl.put(key, mainClasses);
            } catch (final MalformedURLException mue) {
                // no-op
            }
        }

        if (libs != null) {
            archives.add(libs);
        }

        return new SimpleWebappAggregatedArchive(cl, new CompositeArchive(archives), webAppClassesByUrl);
    }

    private static boolean isExcluded(final String archiveName) {
        return "arquillian-junit.jar".equals(archiveName) || "arquillian-protocol.jar".equals(archiveName)
                || "arquillian-core.jar".equals(archiveName);
    }

    private static String name(final String raw) {
        String name = raw;
        if (name.startsWith(WEB_INF_CLASSES)) {
            name = name.substring(WEB_INF_CLASSES.length() - 1);
        }
        name = name.replace('/', '.');
        return name.substring(1, name.length() - 6);
    }

    private static final class AssetSource extends ReadDescriptors.UrlSource {
        private Asset asset;

        private AssetSource(final Asset asset, final URL url) {
            super(url);
            this.asset = asset;
        }

        @Override
        public InputStream get() throws IOException {
            return asset.openStream();
        }

        @Override
        public String toString() {
            return "AssetSource{asset=" + asset + '}';
        }
    }

    // mainly extended to be sure to reuse our tip about scanning for CDI
    private static class SimpleWebappAggregatedArchive extends WebappAggregatedArchive {
        private final CompositeArchive delegate;
        private final Map<URL, List<String>> classesMap;

        public SimpleWebappAggregatedArchive(final ClassLoader cl, final CompositeArchive archive, final Map<URL, List<String>> map) {
            super(cl, new HashMap<>(), new ArrayList<>());

            delegate = archive;
            classesMap = map;
        }

        @Override
        public Map<URL, List<String>> getClassesMap() {
            return classesMap;
        }

        @Override
        public InputStream getBytecode(final String s) throws IOException, ClassNotFoundException {
            return delegate.getBytecode(s);
        }

        @Override
        public Class<?> loadClass(final String s) throws ClassNotFoundException {
            return delegate.loadClass(s);
        }

        @Override
        public Iterator<Entry> iterator() {
            return delegate.iterator();
        }
    }
}
