/* * Copyright 2016 Kejun Xia * * 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.shipdream.lib.android.mvc; import com.shipdream.lib.poke.Component; import com.shipdream.lib.poke.Provider; import com.shipdream.lib.poke.ProviderByClassType; import com.shipdream.lib.poke.exception.ProviderConflictException; import com.shipdream.lib.poke.exception.ProviderMissingException; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.annotation.Annotation; import java.lang.reflect.Modifier; /** * A component manages injectable objects. It is able to locate implementation class automatically by * <ul> * <ui>The injecting class is a concrete class and has empty constructor</ui> * <ui>The injecting class is an interface or abstract class and there is concrete class is named * with suffix "impl" and sitting in the subpackage "internal" which is at the * same level of the interface or abstract. For instance, the interface is * a.b.c.Car and there is an concrete class at a.b.c.internal.CarImpl</ui> * <ui>The injecting class is registered by {@link #register(Object)} or {@link #register(Provider)}</ui> * </ul> */ public class MvcComponent extends Component { private Logger logger = LoggerFactory.getLogger(getClass()); /** * Construct a MvcComponent with the give name with a cope cache so that providers registered * to this component will supply an injectable object with the same instance since its first * copy is created for injection until last instance is released. * * @param name Name of the component, can be null. But it's recommended to supply a name in order * to identify which component supplies an instance */ public MvcComponent(String name) { super(name); } /** * Construct a MvcComponent with the give name and specify whether this component cache instances * created by providers registered to this component. * * @param name Name of the component, can be null. But it's recommended to supply a name in order * to identify which component supplies an instance * @param enableCache If cache is enabled it will supply an * injectable object with the same instance since its first copy is created for injection until * last instance is released. Otherwise all providers registered by this component will always * generate new instances. */ public MvcComponent(String name, boolean enableCache) { super(name, enableCache); } @SuppressWarnings("unchecked") @Override public <T> Provider<T> findProvider(final Class<T> type, Annotation qualifier) throws ProviderMissingException { Provider<T> provider = null; try { provider = super.findProvider(type, qualifier); } catch (ProviderMissingException e) { //ignore since we will try to auto locate the impl class } if (provider == null) { Class<? extends T> impClass; if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { //Non concrete class needs to find its implementation class try { impClass = (Class<T>) Class.forName(getClassName(type)); } catch (ClassNotFoundException e) { String msg = String.format("Can't find implementation class for %s. Make sure class %s exists, or its implementation is registered to Mvc.graph().getRootComponent()", type.getName(), getClassName(type)); throw new ProviderMissingException(msg); } } else { //The type is a class then it's a construable by itself. impClass = type; } provider = new ProviderByClassType<>(type, impClass); if ((qualifier != null && !qualifier.equals(provider.getQualifier())) || provider.getQualifier() != null) { String msg; if (qualifier == null) { msg = String.format("Can't find implementation class for %s. Make sure class %s without qualifier %s exists, or its implementation is registered to graph's root component.", type.getName(), getClassName(type), provider.getQualifier().toString()); } else { msg = String.format("Can't find implementation class for %s. Make sure class %s with qualifier %s exists, or its implementation is registered to graph's root component.", type.getName(), getClassName(type), qualifier.toString()); } throw new ProviderMissingException(msg); } try { register(provider); } catch (ProviderConflictException e) { //Should not happen since otherwise it should have been found already e.printStackTrace(); } } return provider; } @Override public Component register(@NotNull Provider provider) throws ProviderConflictException { super.register(provider); provider.registerCreationListener(new Provider.CreationListener() { @Override public void onCreated(Provider provider, Object instance) { if (instance instanceof Bean) { final Bean bean = (Bean) instance; bean.onCreated(); logger.trace("+++Bean created - '{}'.", provider.type().getSimpleName()); } } }); return this; } private static String getClassName(Class type) { String pkg = type.getPackage().getName(); String implClassName = pkg + ".internal." + type.getSimpleName() + "Impl"; return implClassName; } }