package com.maxifier.guice.lifecycle;

import com.google.inject.*;
import com.google.inject.matcher.AbstractMatcher;
import com.google.inject.matcher.Matchers;
import com.google.inject.spi.InjectionListener;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;

/**
 * Project: Maxifier
 * Date: 10.09.2009
 * Time: 17:20:37
 * <p/>
 * Copyright (c) 1999-2009 Magenta Corporation Ltd. All Rights Reserved.
 * Magenta Technology proprietary and confidential.
 * Use is subject to license terms.
 *
 * @author Aleksey Didik
 */
public class LifecycleModule extends AbstractModule {

    public static void bind(Binder binder) {
        binder.install(new LifecycleModule());
    }


    @Override
    protected void configure() {
        //@PostConstruct
        bindListener(
                new AnnotatedMethodMatcher(PostConstruct.class),
                new PostConstructTypeListener(PostConstruct.class)
        );
        //@ShutdownSafe
        bindInterceptor(Matchers.any(), Matchers.annotatedWith(ShutdownSafe.class), new SafeShutdownInterceptor());
        //Destroy container during JVM shutdown
        bind(ShutdownHook.class).asEagerSingleton();
    }

    static class AnnotatedMethodMatcher extends AbstractMatcher<TypeLiteral<?>> {

        private final Class<? extends Annotation>[] annotations;

        public AnnotatedMethodMatcher(Class<? extends Annotation>... annotations) {
            this.annotations = annotations;
        }

        @Override
        public boolean matches(TypeLiteral<?> typeLiteral) {
            final Class<?> type = typeLiteral.getRawType();
            return isMatches(type);
        }

        private boolean isMatches(Class<?> type) {
            while (true) {
                if (type.equals(Object.class)) {
                    return false;
                }
                for (Method method : type.getDeclaredMethods()) {
                    for (Class<? extends Annotation> a : annotations) {
                        if (method.isAnnotationPresent(a)) {
                            return true;
                        }
                    }
                }
                type = type.getSuperclass();
            }
        }

        @Override
        public String toString() {
            return "AnnotatedMethodMatcher{" +
                    "annotations=" + (annotations == null ? null : Arrays.asList(annotations)) +
                    '}';
        }

    }

    private static class PostConstructTypeListener implements TypeListener {

        private final AnnotatedMethodCache cache;

        public PostConstructTypeListener(Class<? extends Annotation>... annotationClasses) {
            this.cache = new AnnotatedMethodCache(annotationClasses);
        }

        @Override
        public <I> void hear(TypeLiteral<I> type, TypeEncounter<I> encounter) {
            encounter.register(new InjectionListener<I>() {
                @Override
                public void afterInjection(I injectee) {
                    Method[] methods = cache.get(injectee.getClass());
                    for (Method method : methods) {
                        try {
                            method.invoke(injectee);
                        } catch (InvocationTargetException ie) {
                            String message = ie.getTargetException().getMessage();
                            throw new ProvisionException("Exception in @PostConstruct: " + message, ie.getTargetException());
                        } catch (IllegalAccessException e) {
                            throw new ProvisionException("Exception in @PostConstruct: " + e.getMessage(), e);
                        }
                    }
                }
            });
        }

    }

    static class ShutdownHook {

        @Inject
        void register(final Injector injector) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    Lifecycle.destroy(injector);
                }
            });
        }
    }
}
@@ -834,102 +834,177 @@
     
-public static void bind(Binder binder) %7B%0A        binder.install(new LifecycleModule());%0A    %7D%0A
+// Don't register multiple shutdown hooks on several configure() calls%0A    private final SafeShutdownInterceptor safeShutdownInterceptor = new SafeShutdownInterceptor();
 %0A%0A  
@@ -1346,21 +1346,17 @@
 class), 
-new S
+s
 afeShutd
@@ -1369,18 +1369,16 @@
 erceptor
-()
 );%0A     
@@ -4369,8 +4369,9 @@
 %0A    %7D%0A%7D
+%0A

Fix multiple shutdown hook registration