001/*
002 * Copyright 2013 Atteo.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.atteo.moonshine.services.internal;
017
018import java.lang.reflect.InvocationHandler;
019import java.lang.reflect.Method;
020import java.lang.reflect.Proxy;
021import java.util.HashSet;
022import java.util.Set;
023
024import com.google.inject.Binder;
025import com.google.inject.Module;
026import com.google.inject.PrivateBinder;
027import com.google.inject.PrivateModule;
028import com.google.inject.internal.ProviderMethodsModule;
029
030/**
031 * Allows to enhance the module with the logic to skip duplicate modules installation.
032 */
033public class DuplicateDetectionWrapper {
034    private final Set<Module> modules = new HashSet<>();
035
036    /**
037     * Wraps the module into another one which skips any modules which are already installed.
038     */
039    public Module wrap(final Module module) {
040        if (module instanceof PrivateModule) {
041            return new PrivateModule() {
042                @Override
043                protected void configure() {
044                    if (modules.add(module)) {
045                        module.configure(createForwardingBinder(binder()));
046                        install(ProviderMethodsModule.forModule(module));
047                    }
048                }
049            };
050        }
051        return new Module() {
052            @Override
053            public void configure(Binder binder) {
054                if (modules.add(module)) {
055                    module.configure(createForwardingBinder(binder));
056                    binder.install(ProviderMethodsModule.forModule(module));
057                }
058            }
059        };
060    }
061
062    private Binder createForwardingBinder(final Binder binder) {
063        if (binder instanceof PrivateBinder) {
064            return (Binder) Proxy.newProxyInstance(DuplicateDetectionWrapper.class.getClassLoader(),
065                    new Class<?>[] { PrivateBinder.class }, new ForwardingBinderInvocationHandler(binder));
066        }
067        return (Binder) Proxy.newProxyInstance(DuplicateDetectionWrapper.class.getClassLoader(),
068                new Class<?>[] { Binder.class }, new ForwardingBinderInvocationHandler(binder));
069    }
070
071    private class ForwardingBinderInvocationHandler implements InvocationHandler {
072
073        private final Binder binder;
074
075        public ForwardingBinderInvocationHandler(Binder binder) {
076            this.binder = binder;
077        }
078
079        @Override
080        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
081            switch (method.getName()) {
082                case "install":
083                    Module module = (Module) args[0];
084                    binder.install(wrap(module));
085                    return null;
086                case "withSource":
087                    Binder withSourceBinder = binder.withSource(args[0]);
088                    return createForwardingBinder(withSourceBinder);
089                case "skipSources":
090                    Binder skipSourcesBinder = binder.skipSources((Class[]) args[0]);
091                    return createForwardingBinder(skipSourcesBinder);
092                case "newPrivateBinder":
093                    PrivateBinder privateBinder = binder.newPrivateBinder();
094                    return createForwardingBinder(privateBinder);
095            }
096            return method.invoke(binder, args);
097        }
098    }
099}