/*
 * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved.
 *
 * Licensed 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 com.hazelcast.test;

import com.google.common.collect.Sets;
import org.reflections.Configuration;
import org.reflections.ReflectionUtils;
import org.reflections.Reflections;
import org.reflections.adapters.JavaReflectionAdapter;
import org.reflections.scanners.AbstractScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;

import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * Initialize once org.reflections library to avoid duplicate work scanning the classpath from individual tests.
 */
public class ReflectionsHelper {

    public static final Reflections REFLECTIONS;

    static {
        // obtain all classpath URLs with com.hazelcast package classes
        Collection<URL> comHazelcastPackageURLs = ClasspathHelper.forPackage("com.hazelcast");
        // exclude hazelcast test artifacts from package URLs
        for (Iterator<URL> iterator = comHazelcastPackageURLs.iterator(); iterator.hasNext(); ) {
            URL url = iterator.next();
            // detect hazelcast-VERSION-tests.jar & $SOMEPATH/hazelcast/target/test-classes/ and exclude it from classpath
            // also exclude hazelcast-license-extractor artifact
            if (url.toString().contains("-tests.jar") || url.toString().contains("target/test-classes") ||
                    url.toString().contains("hazelcast-license-extractor")) {
                iterator.remove();
            }
        }
        HierarchyTraversingSubtypesScanner subtypesScanner = new HierarchyTraversingSubtypesScanner();
        subtypesScanner.setResultFilter(new FilterBuilder().exclude("java\\.lang\\.(Object|Enum)"));
        REFLECTIONS = new ReflectionsTransitive(new ConfigurationBuilder().addUrls(comHazelcastPackageURLs)
                .addScanners(subtypesScanner, new TypeAnnotationsScanner())
                .setMetadataAdapter(new JavaReflectionAdapter()));
    }

    // Overrides default implementation of getSubTypesOf to obtain also transitive subtypes of given class
    public static class ReflectionsTransitive extends Reflections {

        public ReflectionsTransitive(Configuration configuration) {
            super(configuration);
        }

        @Override
        public <T> Set<Class<? extends T>> getSubTypesOf(Class<T> type) {
            return Sets.newHashSet(ReflectionUtils.<T>forNames(
                    store.getAll(
                            HierarchyTraversingSubtypesScanner.class.getSimpleName(),
                            Arrays.asList(type.getName())),
                            configuration.getClassLoaders()));
        }
    }

    public static class HierarchyTraversingSubtypesScanner extends AbstractScanner{
        /**
         * creates new HierarchyTraversingSubtypesScanner. will exclude direct Object subtypes
         */
        public HierarchyTraversingSubtypesScanner() {
            this(true); //exclude direct Object subtypes by default
        }

        /**
         * creates new HierarchyTraversingSubtypesScanner.
         * @param excludeObjectClass if false, include direct {@link Object} subtypes in results.
         */
        public HierarchyTraversingSubtypesScanner(boolean excludeObjectClass) {
            if (excludeObjectClass) {
                filterResultsBy(new FilterBuilder().exclude(Object.class.getName())); //exclude direct Object subtypes
            }
        }

        // depending on Reflections configuration, cls is either a regular Class or a javassist ClassFile
        @SuppressWarnings({"unchecked"})
        public void scan(final Object cls) {
            String className = getMetadataAdapter().getClassName(cls);

            for (String anInterface : (List<String>) getMetadataAdapter().getInterfacesNames(cls)) {
                if (acceptResult(anInterface)) {
                    getStore().put(anInterface, className);
                }
            }

            // apart from this class' direct supertype and directly declared interfaces, also scan the class
            // hierarchy up until Object class
            Class superKlass = ((Class)cls).getSuperclass();
            while (superKlass != null) {
                scanClassAndInterfaces(superKlass, className);
                superKlass = superKlass.getSuperclass();
            }
        }

        private void scanClassAndInterfaces(Class klass, String className) {
            if (acceptResult(klass.getName())) {
                getStore().put(klass.getName(), className);
                for (String anInterface : (List<String>) getMetadataAdapter().getInterfacesNames(klass)) {
                    if (acceptResult(anInterface)) {
                        getStore().put(anInterface, className);
                    }
                }
            }
        }
    }
}

@@ -2466,16 +2466,127 @@
 %7CEnum)%22)
+%0A                                                           .exclude(%22com%5C%5C.hazelcast%5C%5C.com%5C%5C.eclipsesource.*%22)
 );%0A     

Exclude shaded com.eclipsesource.* classes from Reflections in tests