/* * Copyright (c) 2009 - 2017 - Pierre-Laurent Coirer, Frank Hossfeld * * 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.mvp4g.rebind.config; import com.google.gwt.core.ext.BadPropertyValueException; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.TreeLogger.Type; import com.google.gwt.core.ext.typeinfo.*; import com.google.gwt.inject.client.GinModule; import com.mvp4g.client.Mvp4gLoader; import com.mvp4g.client.Mvp4gModule; import com.mvp4g.client.SingleSplitter; import com.mvp4g.client.annotation.*; import com.mvp4g.client.annotation.module.HistoryName; import com.mvp4g.client.annotation.module.Loader; import com.mvp4g.client.event.EventFilter; import com.mvp4g.client.event.EventHandlerInterface; import com.mvp4g.client.event.Mvp4gLogger; import com.mvp4g.client.history.ClearHistory; import com.mvp4g.client.history.HistoryConverter; import com.mvp4g.client.history.PlaceService; import com.mvp4g.client.presenter.NoStartPresenter; import com.mvp4g.client.presenter.PresenterInterface; import com.mvp4g.client.view.ReverseViewInterface; import com.mvp4g.rebind.config.element.*; import com.mvp4g.rebind.config.loader.annotation.*; import com.mvp4g.rebind.exception.*; import com.mvp4g.rebind.exception.loader.Mvp4gAnnotationException; import java.lang.annotation.Annotation; import java.util.*; /** * An in-memory representation of all elements in the configuration file and in the annotations. * * @author javier */ public class Mvp4gConfiguration { private static final String ROOT_MODULE_CLASS_NAME = Mvp4gModule.class.getCanonicalName(); private static final String NO_EVENT_BUS = "No Event Bus interface has been found for %s module."; private static final String REMOVE_OBJ = "%s %s: No instance of this class has been created since this class is not used."; private static final String MISSING_ATTRIBUTE = "%s: child module %s doesn't define any event to load its view."; private static final String NOT_EMPTY_EVENT_OBJ = "%s: %s event %s can't have any object associated with it."; private static final String WRONG_EVENT_OBJ = "%s: %s event %s can only be associated with one and only one object with type %s"; private static final String WRONG_NUMBER_ATT = "Event %s: event must have one and only one an object associated with it as it loads a child view."; private static final String WRONG_CHILD_LOAD_EVENT_OBJ = "Child Module %s: event %s can not load child module's start view. Can not convert %s to %s."; private static final String NO_PARENT_ERROR = "Event %s: Root module has no parent so you can't forward event to parent."; private static final String CHILD_MODULE_SAME_HISTORY_NAME = "Module %s: You can't have two child modules with the same history name \"%s\"."; private static final String ACTIVATE_DEACTIVATE_SAME_TIME = "Event %s: an event can't activate and deactivate the same handler: %s."; private static final String NAME_WITH_NO_CONVERTER = "Event %s: you defined an history name for this event but this event has no history converter."; private static final String TOKEN_WITH_NO_CONVERTER = "Event %s: you can't generate a token for this event if it has no history converter."; private static final String EMPTY_HISTORY_NAME_ROOT = "Event %s: An event of the Mvp4g Root module can't have an history name equal to empty string."; private static final String SAME_HISTORY_NAME = "Event %s: history name already used for another event: %s."; private static final String WRONG_HISTORY_NAME = "%s %s: history name can't start with '" + PlaceService.CRAWLABLE + "' or contain '" + PlaceService.MODULE_SEPARATOR + "'."; private static final String WRONG_FORWARD_EVENT = "You can't define a forward event for RootModule since no event from parent can be forwarded to it."; private static final String NO_START_PRESENTER = "Module %s: You must define a start presenter since this module has a parent module that uses the auto-displayed feature for this module."; private static final String NO_GIN_MODULE = "You need to define at least one GIN module. If you don't want to specify a GIN module, don't override the GIN modules option to use the default Mvp4g GIN module."; private static final String GIN_MODULE_UNKNOWN_PROPERTY = "Module %s: couldn't find a value for the GIN module property %s, %s."; private static final String ROOT_MODULE_NO_HISTORY_NAME = "Module %s can't have an history name since it's a root module."; private static final String BIND_AND_HANDLER_FOR_SAME_EVENT = "Event %s: the same handler %s is used in the bind and handlers attributes. If you need %s to handle this event, you should remove it from the bind attribute."; private static final String BIND_FOR_PASSIVE_EVENT = "Passive event can't bind any elements. Remove bind attribute from the %s event in order to keep it passive."; private static final String HISTORY_ONLY_FOR_ROOT = "Module %s: History configuration (init, not found event and history parameter separator) should be set only for root module (only module with no parent)"; private static final String HISTORY_NAME_MISSING = "Module %s: Child module that defines history converter must have a @HistoryName annotation."; private static final String HISTORY_INIT_MISSING = "You must define a History init event if you use history converters."; private static final String HISTORY_ON_START_ONLY_FOR_ROOT = "Eventbus %s: historyOnStart configuration should be set only for root module (only module with no parent)"; private static final String GENERATE_NOT_MULTIPLE = "Event %s: you can generate only multiple handlers. Did you forget to set the attribute multiple to true for %s?"; private static final String CHILD_MODULE_NOT_USED = "Module %s: the child module %s is not loaded by any of the event of this module. You should remove it if it is not used by another child module (ie used for sibling communication)."; private static final String UNKNOWN_MODULE = "Event %s: No instance of %s has been found. Is this module a child module, a parent module or a silbling module? If it's supposed to be a child module, have you forgotten to add it to @ChildModules of your event bus interface?"; private static final String ASYNC_START_PRESENTER = "Presenter %s: start presenter can't be loaded asynchronously. Async attribute must not be set."; private Set<PresenterElement> presenters = new HashSet<PresenterElement>(); private Set<EventHandlerElement> eventHandlers = new HashSet<EventHandlerElement>(); private Set<ViewElement> views = new HashSet<ViewElement>(); private Set<EventElement> events = new HashSet<EventElement>(); private Set<ServiceElement> services = new HashSet<ServiceElement>(); private Set<HistoryConverterElement> historyConverters = new HashSet<HistoryConverterElement>(); private Set<EventFilterElement> eventFilters = new HashSet<EventFilterElement>(); private Set<ChildModuleElement> childModules = new HashSet<ChildModuleElement>(); private Set<SplitterElement> splitters = new HashSet<SplitterElement>(); private Set<LoaderElement> loaders = new HashSet<LoaderElement>(); private StartElement start = null; private HistoryElement history = null; private HistoryProxyElement historyProxy = null; private EventBusElement eventBus = null; private JClassType module = null; private ChildModulesElement loadChildConfig = null; // associate a module class name with its event bus type private Map<String, JClassType> othersEventBusClassMap = new HashMap<String, JClassType>(); // associate a module class name with its parent private Map<String, ChildModuleElement> moduleParentEventBusClassMap = new HashMap<String, ChildModuleElement>(); private JClassType parentEventBus = null; private String historyName = null; private DebugElement debug = null; private GinModuleElement ginModule = null; private EventFiltersElement eventFilterConfiguration = null; private TreeLogger logger = null; private TypeOracle oracle = null; private PropertyOracle propertyOracle; private String suffix; /** * Contruct a Mvp4gConfiguration object * * @param logger logger of the GWT compiler * @param context context of the GWT compiler */ public Mvp4gConfiguration(TreeLogger logger, GeneratorContext context) { this.logger = logger; this.oracle = context.getTypeOracle(); this.propertyOracle = context.getPropertyOracle(); } /** * For test purpose only * * @param logger logger to set */ void setLogger(TreeLogger logger) { this.logger = logger; } /** * Loads all Mvp4g elements from an in-memory representation of the annotations.<br> * <br> * Configuration loading comprises up to three phases: * <br> * <ol> * <li>Phase 1, <i>Parsing all the annotations</i>: during this phase, all the Mvp4gElement * instances are constructed from their corresponding annotations. All possible validations are * performed.</li> * <li>Phase 2, <i>Validation of cross-element references and removal of useless elements</i>: * in this phase element identifiers are checked for global uniqueness; event handler references * and view references are checked for existence. Validity elements class is also checked.</li> * </ol> * * @param module module to load * @param scanResult Map of classes associated with an annotation * @return suffix from gin module * @throws InvalidMvp4gConfigurationException this exception is thrown where a configuration error occurs. */ public String load(JClassType module, Map<Class<? extends Annotation>, List<JClassType>> scanResult) throws InvalidMvp4gConfigurationException { this.module = module; // Phase 1: load information from annotations loadServices(scanResult.get(Service.class)); loadHistoryConverters(scanResult.get(History.class)); loadPresenters(scanResult.get(Presenter.class)); loadEventHandlers(scanResult.get(EventHandler.class)); loadEvents(scanResult.get(Events.class)); loadParentModule(); // Phase 2: perform cross-element validations if (eventBus == null) { throw new InvalidMvp4gConfigurationException(String.format(NO_EVENT_BUS, module.getSimpleSourceName())); } findChildModuleHistoryNameAndLoader(); checkUniquenessOfAllElements(); validateEventHandlers(); validateSplitters(); validateEventFilters(); validateHistoryConverters(); validateViews(); validateServices(); validateHistory(); validateChildModules(); validateEvents(); validateDebug(); suffix = getSuffix(validateGinModule()); validateStart(); return suffix; } public boolean isAsyncEnabled() { return (oracle.findType("com.google.gwt.core.client.RunAsyncCallback") != null); } /* * GETTERS & SETTERS */ /** * @return a set of Presenters loaded. */ public Set<PresenterElement> getPresenters() { return presenters; } /** * @return a set of Presenters loaded. */ public Set<EventHandlerElement> getEventHandlers() { return eventHandlers; } /** * @return a set of Views loaded. */ public Set<ViewElement> getViews() { return views; } /** * @return a set of History Converters loaded. */ public Set<HistoryConverterElement> getHistoryConverters() { return historyConverters; } /** * @return a set of Events loaded. */ public Set<EventElement> getEvents() { return events; } /** * @return a set of Services loaded. */ public Set<ServiceElement> getServices() { return services; } /** * @return a set of Event Filters loaded */ public Set<EventFilterElement> getEventFilters() { return eventFilters; } /** * @return a set of Child Modules loaded */ public Set<ChildModuleElement> getChildModules() { return childModules; } /** * @return the splitters */ public Set<SplitterElement> getSplitters() { return splitters; } /** * @return the loaders */ public Set<LoaderElement> getLoaders() { return loaders; } /** * @return the Start element loaded. */ public StartElement getStart() { return start; } /** * @param start the start to set */ public void setStart(StartElement start) { this.start = start; } /** * @return the History element loaded. */ public HistoryElement getHistory() { return history; } /** * @param history the history to set */ public void setHistory(HistoryElement history) { this.history = history; } /** * @return the HistoryProxy element loaded. */ public HistoryProxyElement getHistoryProxy() { return historyProxy; } /** * @param historyProxy the historyProxy to set */ public void setHistoryProxy(HistoryProxyElement historyProxy) { this.historyProxy = historyProxy; } /** * @return the Event Bus element loaded */ public EventBusElement getEventBus() { return eventBus; } /** * @param eventBus the eventBus to set */ public void setEventBus(EventBusElement eventBus) { this.eventBus = eventBus; } /** * @return the oracle */ public TypeOracle getOracle() { return oracle; } /** * @return the module */ public JClassType getModule() { return module; } /** * Should be used only for test * * @param module the module to set */ public void setModule(JClassType module) { this.module = module; } /** * @return the childEventBusClassMap */ public Map<String, JClassType> getOthersEventBusClassMap() { return othersEventBusClassMap; } /** * @return the moduleParentEventBusClassMap */ public Map<String, ChildModuleElement> getModuleParentEventBusClassMap() { return moduleParentEventBusClassMap; } /** * @return the loadChildConfig */ public ChildModulesElement getLoadChildConfig() { return loadChildConfig; } /** * @param loadChildConfig the loadChildConfig to set */ public void setLoadChildConfig(ChildModulesElement loadChildConfig) { this.loadChildConfig = loadChildConfig; } /** * @return the parentModule */ public boolean isRootModule() { return ROOT_MODULE_CLASS_NAME.equals(module.getQualifiedSourceName()) || (parentEventBus == null); } /** * @return the parentEventBus */ public JClassType getParentEventBus() { return parentEventBus; } /** * Should be used only for test * * @param parentEventBus the parentEventBus to set */ public void setParentEventBus(JClassType parentEventBus) { this.parentEventBus = parentEventBus; } /** * @return the historyName */ public String getHistoryName() { return historyName; } /** * Should be used only for test * * @param historyName the historyName to set */ public void setHistoryName(String historyName) { this.historyName = historyName; } /** * @return the debug */ public DebugElement getDebug() { return debug; } /** * @param debug the debug to set */ public void setDebug(DebugElement debug) { this.debug = debug; } /** * @return the ginModule */ public GinModuleElement getGinModule() { return ginModule; } /** * @param ginModule the ginModule to set */ public void setGinModule(GinModuleElement ginModule) { this.ginModule = ginModule; } /** * @return the filters */ public EventFiltersElement getEventFilterConfiguration() { return eventFilterConfiguration; } /** * @param eventFilterConfiguration the eventFilterConfiguration to set */ public void setEventFilterConfiguration(EventFiltersElement eventFilterConfiguration) { this.eventFilterConfiguration = eventFilterConfiguration; } public String getSuffix() { return suffix; } public void setSuffix(String suffix) { this.suffix = suffix; } /* * Validation */ /** * Checks that all injected views correspond to a configured mvp4g element. Remove views that * aren't injected into a presenter or loaded at start.<br> * * @throws UnknownConfigurationElementException if a view reference cannot be found among the configured elements. */ void validateViews() throws UnknownConfigurationElementException { Map<String, List<PresenterElement>> viewMap = new HashMap<String, List<PresenterElement>>(); List<PresenterElement> presenterList = null; String viewName = null; for (PresenterElement presenter : presenters) { viewName = presenter.getView(); presenterList = viewMap.get(viewName); if (presenterList == null) { presenterList = new ArrayList<PresenterElement>(); viewMap.put(viewName, presenterList); } presenterList.add(presenter); } Set<ViewElement> toRemove = new HashSet<ViewElement>(); for (ViewElement view : views) { viewName = view.getName(); if ((viewMap.remove(viewName) == null)) { // this object is not used, you can remove it toRemove.add(view); } } // Missing view if (!viewMap.isEmpty()) { String it = viewMap.keySet() .iterator() .next(); throw new UnknownConfigurationElementException(viewMap.get(it) .get(0), it); } removeUselessElements(views, toRemove); } /** * Checks that all service names injected on every presenter and history converter correspond to * a configured mvp4g element. Remove all services that aren't injected into a presenter or an * history converter.<br> * * @throws UnknownConfigurationElementException if a service cannot be found among the configured elements. */ void validateServices() throws UnknownConfigurationElementException { Map<String, List<Mvp4gWithServicesElement>> serviceMap = new HashMap<String, List<Mvp4gWithServicesElement>>(); Set<Mvp4gWithServicesElement> currentElements = new HashSet<Mvp4gWithServicesElement>(presenters); currentElements.addAll(eventHandlers); currentElements.addAll(historyConverters); List<Mvp4gWithServicesElement> elementList = null; for (Mvp4gWithServicesElement elementWithService : currentElements) { for (InjectedElement service : elementWithService.getInjectedServices()) { elementList = serviceMap.get(service.getElementName()); if (elementList == null) { elementList = new ArrayList<Mvp4gWithServicesElement>(); serviceMap.put(service.getElementName(), elementList); } elementList.add(elementWithService); } } Set<ServiceElement> toRemove = new HashSet<ServiceElement>(); for (ServiceElement service : services) { if (serviceMap.remove(service.getName()) == null) { // this object is not used, you can remove it toRemove.add(service); } } // Missing service if (!serviceMap.isEmpty()) { String it = serviceMap.keySet() .iterator() .next(); throw new UnknownConfigurationElementException(serviceMap.get(it) .get(0), it); } removeUselessElements(services, toRemove); } /** * Checks that all history converter names associated to each event is a configured mvp4g * element. Verify that these elements are valid. Remove all history converters that aren't used * by an event. * * @throws InvalidMvp4gConfigurationException */ void validateHistoryConverters() throws InvalidMvp4gConfigurationException { Map<String, List<EventElement>> historyConverterMap = new HashMap<String, List<EventElement>>(); List<EventElement> eventList = null; String hcName = null; String historyName; List<String> historyNames = new ArrayList<String>(); for (EventElement event : events) { if (event.hasHistory()) { hcName = event.getHistory(); eventList = historyConverterMap.get(hcName); if (eventList == null) { eventList = new ArrayList<EventElement>(); historyConverterMap.put(hcName, eventList); } eventList.add(event); historyName = event.getName(); if (isRootModule() && (historyName.length() == 0)) { throw new InvalidMvp4gConfigurationException(String.format(EMPTY_HISTORY_NAME_ROOT, event.getType())); } validateHistoryName(historyName, event); if (historyNames.contains(historyName)) { throw new InvalidMvp4gConfigurationException(String.format(SAME_HISTORY_NAME, event.getType(), event.getName(), historyName)); } historyNames.add(historyName); } else if (event.isWithTokenGeneration()) { if (isRootModule() || !checkIfParentEventReturnsString(event)) { throw new InvalidMvp4gConfigurationException(String.format(TOKEN_WITH_NO_CONVERTER, event.getType())); } else { event.setTokenGenerationFromParent(Boolean.toString(Boolean.TRUE)); } } else if (event.getName() != event.getType()) { throw new InvalidMvp4gConfigurationException(String.format(NAME_WITH_NO_CONVERTER, event.getType())); } } JGenericType hcGenType = getType(null, HistoryConverter.class.getCanonicalName()).isGenericType(); JClassType eventBusType = getType(null, eventBus.getInterfaceClassName()); String clearHistoryClassName = ClearHistory.class.getCanonicalName(); JClassType clearHistoryType = getType(null, clearHistoryClassName); JClassType hcType = null; JClassType eventBusParam = null; JMethod[] methods = null; JParameterizedType genHC = null; HistoryConverterElement clearHistoryToRemove = null; Set<HistoryConverterElement> toRemove = new HashSet<HistoryConverterElement>(); for (HistoryConverterElement history : historyConverters) { eventList = historyConverterMap.remove(history.getName()); if (eventList != null) { hcType = getType(history, history.getClassName()); // if historyConverter is a ClearHistory instance, no control // needed if (!clearHistoryType.equals(hcType)) { genHC = hcType.asParameterizationOf(hcGenType); if (genHC == null) { throw new InvalidClassException(history, HistoryConverter.class.getCanonicalName()); } methods = genHC.getMethods(); eventBusParam = (JClassType) methods[0].getParameters()[2].getType(); // Control if history converter event bus is compatible with // module event bus if (!eventBusType.isAssignableTo(eventBusParam)) { throw new InvalidTypeException(history, "Event Bus", eventBusParam.getQualifiedSourceName(), eventBus.getInterfaceClassName()); } } } else { if (!clearHistoryClassName.equals(history.getClassName())) { // this object is not used, you can remove it toRemove.add(history); } else { clearHistoryToRemove = history; } } } // Missing history converter if (!historyConverterMap.isEmpty()) { String it = historyConverterMap.keySet() .iterator() .next(); throw new UnknownConfigurationElementException(historyConverterMap.get(it) .get(0), it); } // remove clear history (you don't want this to appear on log) if (clearHistoryToRemove != null) { historyConverters.remove(clearHistoryToRemove); } removeUselessElements(historyConverters, toRemove); } /** * Checks that all event handler names correspond to a configured mvp4g element. Verify that * these elements are valid. Remove the ones that don't handle events or aren't associated with * the start view.<br> * * @throws InvalidMvp4gConfigurationException */ void validateEventHandlers() throws InvalidMvp4gConfigurationException { Map<String, List<EventElement>> presenterAndEventHandlerMap = new HashMap<String, List<EventElement>>(); Map<String, List<EventElement>> activateMap = new HashMap<String, List<EventElement>>(); Map<String, List<EventElement>> deactivateMap = new HashMap<String, List<EventElement>>(); Map<String, List<EventElement>> generateMap = new HashMap<String, List<EventElement>>(); // binds map validates in the same rules as presenters and event // handlers and added to the same map Map<JClassType, List<EventElement>> broadcastMap = new HashMap<JClassType, List<EventElement>>(); // Add presenter that handles event List<EventElement> eventList; List<String> activates, deactivates, handlers, binds, generates; String broadcast; JClassType type; for (EventElement event : events) { handlers = event.getHandlers(); activates = event.getActivate(); deactivates = event.getDeactivate(); broadcast = event.getBroadcastTo(); generates = event.getGenerate(); binds = event.getBinds(); if (handlers != null) { for (String handler : handlers) { // checking that we don't have the same handler in the binds // and in the handlers annotation if (binds != null && binds.contains(handler)) { throw new InvalidMvp4gConfigurationException(String.format(BIND_AND_HANDLER_FOR_SAME_EVENT, event.getType(), handler, handler)); } eventList = presenterAndEventHandlerMap.get(handler); if (eventList == null) { eventList = new ArrayList<EventElement>(); presenterAndEventHandlerMap.put(handler, eventList); } eventList.add(event); } } if (activates != null) { for (String activate : activates) { eventList = presenterAndEventHandlerMap.get(activate); if ((deactivates != null) && (deactivates.contains(activate))) { throw new InvalidMvp4gConfigurationException(String.format(ACTIVATE_DEACTIVATE_SAME_TIME, event, activate)); } if (eventList == null) { eventList = new ArrayList<EventElement>(); activateMap.put(activate, eventList); } eventList.add(event); } } if (deactivates != null) { for (String deactivate : deactivates) { eventList = presenterAndEventHandlerMap.get(deactivate); if (eventList == null) { eventList = new ArrayList<EventElement>(); deactivateMap.put(deactivate, eventList); } eventList.add(event); } } if (broadcast != null) { type = getType(event, broadcast); eventList = broadcastMap.get(type); if (eventList == null) { eventList = new ArrayList<EventElement>(); broadcastMap.put(type, eventList); } eventList.add(event); } if (generates != null) { for (String generate : generates) { eventList = generateMap.get(generate); if (eventList == null) { eventList = new ArrayList<EventElement>(); generateMap.put(generate, eventList); } eventList.add(event); } } // we are handling two exceptions with binds, if the event is // passive and it has binds elements if (event.isPassive() && (binds != null) && (binds.size() > 0)) { throw new InvalidMvp4gConfigurationException(String.format(BIND_FOR_PASSIVE_EVENT, event.getName())); } // create list of binds to ensure that presenter will not be deleted // later if (binds != null) { for (String bind : binds) { eventList = presenterAndEventHandlerMap.get(bind); if (eventList == null) { eventList = new ArrayList<EventElement>(); presenterAndEventHandlerMap.put(bind, eventList); } eventList.add(event); } } } boolean hasStartView = start.hasPresenter(); String startPresenter = start.getPresenter(); JGenericType presenterGenType = getType(null, PresenterInterface.class.getCanonicalName()).isGenericType(); JGenericType eventHandlerGenType = getType(null, EventHandlerInterface.class.getCanonicalName()).isGenericType(); JGenericType reverseViewGenType = getType(null, ReverseViewInterface.class.getCanonicalName()).isGenericType(); JClassType eventBusType = getType(null, eventBus.getInterfaceClassName()); JType[] noParam = new JType[0]; JClassType presenterType, viewParam, presenterParam, viewType; ViewElement view = null; JParameterizedType genPresenter, genView; boolean hasPossibleBroadcast = (broadcastMap.size() > 0); Set<PresenterElement> toRemove = new HashSet<PresenterElement>(); String viewName, name; boolean toKeep, notDirectHandler; List<EventElement> eventDeactivateList, eventActivateList, eventGenerateList; for (PresenterElement presenter : presenters) { name = presenter.getName(); eventList = presenterAndEventHandlerMap.remove(name);// binds presenters are checking as handlers eventDeactivateList = deactivateMap.remove(name); eventActivateList = activateMap.remove(name); eventGenerateList = generateMap.remove(name); viewName = presenter.getView(); toKeep = (eventList != null) || (hasStartView && name.equals(startPresenter)) || (eventGenerateList != null); notDirectHandler = !toKeep && (presenter.isMultiple() || hasPossibleBroadcast); if (toKeep || notDirectHandler) { toKeep = controlEventBus(presenter, eventHandlerGenType, eventBusType, toKeep); if (toKeep) { presenterType = getType(presenter, presenter.getClassName()); toKeep = findPossibleBroadcast(broadcastMap, presenter, presenterType) || !notDirectHandler || presenter.isMultiple(); if (toKeep) { genPresenter = presenterType.asParameterizationOf(presenterGenType); if (genPresenter == null) { throw new InvalidClassException(presenter, PresenterInterface.class.getCanonicalName()); } viewParam = (JClassType) genPresenter.findMethod("getView", noParam) .getReturnType(); // Control if view injected to the event bus is // compatible with // presenter view type view = getElement(viewName, views, presenter); viewType = getType(view, view.getClassName()); if (!viewType.isAssignableTo(viewParam)) { throw new InvalidTypeException(presenter, "View", view.getClassName(), viewParam.getQualifiedSourceName()); } if (viewType.isAssignableTo(reverseViewGenType)) { genView = viewType.asParameterizationOf(reverseViewGenType); presenterParam = (JClassType) genView.findMethod("getPresenter", noParam) .getReturnType(); if (!presenterType.isAssignableTo(presenterParam)) { throw new InvalidTypeException(view, "Presenter", presenter.getClassName(), presenterParam.getQualifiedSourceName()); } presenter.setInverseView(Boolean.TRUE.toString()); } if (!presenter.isMultiple() && !presenter.isAsync()) { view.setInstantiateAtStart(true); } } } } if (!toKeep) { removeFromActivateDeactivate(eventActivateList, eventDeactivateList, presenter); // this object is not used, you can remove it toRemove.add(presenter); } if ((eventGenerateList != null) && (!presenter.isMultiple())) { throw new InvalidMvp4gConfigurationException(String.format(GENERATE_NOT_MULTIPLE, eventGenerateList.get(0) .getType(), presenter.getName())); } } Set<EventHandlerElement> toRemoveEventHandlers = new HashSet<EventHandlerElement>(); for (EventHandlerElement eventHandler : eventHandlers) { name = eventHandler.getName(); eventList = presenterAndEventHandlerMap.remove(name); eventDeactivateList = deactivateMap.remove(name); eventActivateList = activateMap.remove(name); eventGenerateList = generateMap.remove(name); toKeep = (eventList != null) || (eventGenerateList != null); notDirectHandler = !toKeep && (eventHandler.isMultiple() || hasPossibleBroadcast); if (toKeep || notDirectHandler) { toKeep = controlEventBus(eventHandler, eventHandlerGenType, eventBusType, toKeep); if (toKeep) { toKeep = findPossibleBroadcast(broadcastMap, eventHandler, getType(eventHandler, eventHandler.getClassName())) || !notDirectHandler || eventHandler.isMultiple(); } } if (!toKeep) { removeFromActivateDeactivate(eventActivateList, eventDeactivateList, eventHandler); // this object is not used, you can remove it toRemoveEventHandlers.add(eventHandler); } if ((eventGenerateList != null) && (!eventHandler.isMultiple())) { throw new InvalidMvp4gConfigurationException(String.format(GENERATE_NOT_MULTIPLE, eventGenerateList.get(0) .getType(), eventHandler.getName())); } } // Missing handlers if (!presenterAndEventHandlerMap.isEmpty()) { String it = presenterAndEventHandlerMap.keySet() .iterator() .next(); throw new UnknownConfigurationElementException(presenterAndEventHandlerMap.get(it) .get(0), it); } if (!activateMap.isEmpty()) { String it = activateMap.keySet() .iterator() .next(); throw new UnknownConfigurationElementException(activateMap.get(it) .get(0), it); } if (!deactivateMap.isEmpty()) { String it = deactivateMap.keySet() .iterator() .next(); throw new UnknownConfigurationElementException(deactivateMap.get(it) .get(0), it); } if (!generateMap.isEmpty()) { String it = generateMap.keySet() .iterator() .next(); throw new UnknownConfigurationElementException(generateMap.get(it) .get(0), it); } removeUselessElements(presenters, toRemove); removeUselessElements(eventHandlers, toRemoveEventHandlers); } boolean findPossibleBroadcast(Map<JClassType, List<EventElement>> broadcastMap, EventHandlerElement eventHandler, JClassType handler) throws InvalidClassException, InvalidTypeException, NotFoundClassException { boolean keep = false; Iterator<JClassType> it = broadcastMap.keySet() .iterator(); JClassType type; String handlerName = eventHandler.getName(); while (it.hasNext()) { type = it.next(); if (handler.isAssignableTo(type)) { List<String> handlers; for (EventElement event : broadcastMap.get(type)) { handlers = event.getHandlers(); if (!handlers.contains(handlerName)) { handlers.add(handlerName); } } keep = true; } } return keep; } void validateSplitters() throws InvalidMvp4gConfigurationException { Map<String, EventAssociation<EventElement>> eventAssociationMap = new HashMap<String, EventAssociation<EventElement>>(); // Add presenter that handles event List<String> handlers, binds, activates, deactivates, generates; for (EventElement event : events) { handlers = event.getHandlers(); if (handlers != null) { for (String handler : handlers) { getEventAssociation(eventAssociationMap, handler).getHandlers() .add(event); } } binds = event.getBinds(); if (binds != null) { for (String bind : binds) { getEventAssociation(eventAssociationMap, bind).getBinds() .add(event); } } activates = event.getActivate(); if (activates != null) { for (String activate : activates) { getEventAssociation(eventAssociationMap, activate).getActivated() .add(event); } } deactivates = event.getDeactivate(); if (deactivates != null) { for (String deactivate : deactivates) { getEventAssociation(eventAssociationMap, deactivate).getDeactivated() .add(event); } } generates = event.getGenerate(); if (generates != null) { for (String generate : generates) { getEventAssociation(eventAssociationMap, generate).getGenerate() .add(event); } } } JClassType eventBusType = getType(null, eventBus.getInterfaceClassName()); Map<String, SplitterElement> splitterMap = new HashMap<String, SplitterElement>(); int singleIndex = 0; for (EventHandlerElement eventHandler : eventHandlers) { addSplitterHandler(eventHandler, singleIndex, eventAssociationMap.get(eventHandler.getName()), splitterMap, eventBusType); singleIndex++; } for (PresenterElement presenter : presenters) { addSplitterHandler(presenter, singleIndex, eventAssociationMap.get(presenter.getName()), splitterMap, eventBusType); singleIndex++; } splitters = new HashSet<>(splitterMap.values()); } private <K, V> EventAssociation<V> getEventAssociation(Map<K, EventAssociation<V>> eventAssociationMap, K handler) { EventAssociation<V> eventAssociation = eventAssociationMap.get(handler); if (eventAssociation == null) { eventAssociation = new EventAssociation<V>(); eventAssociationMap.put(handler, eventAssociation); } return eventAssociation; } private <T extends EventHandlerElement> void addSplitterHandler(T handler, int singleIndex, EventAssociation<EventElement> eventAssociation, Map<String, SplitterElement> splitterMap, JClassType eventBusType) throws InvalidMvp4gConfigurationException { String className = handler.getAsync(); if ((className != null) && (eventAssociation != null)) { if (handler.getName() .equals(start.getPresenter())) { throw new InvalidMvp4gConfigurationException(String.format(ASYNC_START_PRESENTER, handler.getName())); } boolean isSingleSplitter = SingleSplitter.class.getCanonicalName() .equals(className); if (isSingleSplitter) { className = className + singleIndex; } SplitterElement splitter = splitterMap.get(className); if (splitter == null) { splitter = new SplitterElement(); String name = className.replace(".", "_"); splitter.setName(name); splitter.setClassName(name); if (!isSingleSplitter) { splitter.setLoader(validateLoaderAndAdd(getType(splitter, className), eventBusType, splitter)); } splitterMap.put(className, splitter); } Set<EventHandlerElement> eventHandlers = splitter.getHandlers(); eventHandlers.add(handler); Map<EventElement, EventAssociation<String>> events = splitter.getEvents(); for (EventElement event : eventAssociation.getHandlers()) { setSplitterForEvent(handler.getName(), event.getSplitters(), splitter.getName(), getEventAssociation(events, event).getHandlers(), event.getHandlers()); } for (EventElement event : eventAssociation.getBinds()) { setSplitterForEvent(handler.getName(), event.getSplitters(), splitter.getName(), getEventAssociation(events, event).getBinds(), event.getBinds()); } for (EventElement event : eventAssociation.getActivated()) { setSplitterForEvent(handler.getName(), event.getSplitters(), splitter.getName(), getEventAssociation(events, event).getActivated(), event.getActivate()); } for (EventElement event : eventAssociation.getDeactivated()) { setSplitterForEvent(handler.getName(), event.getSplitters(), splitter.getName(), getEventAssociation(events, event).getDeactivated(), event.getDeactivate()); } for (EventElement event : eventAssociation.getGenerate()) { setSplitterForEvent(handler.getName(), event.getSplitters(), splitter.getName(), getEventAssociation(events, event).getGenerate(), event.getGenerate()); } } } private void setSplitterForEvent(String handlerName, List<String> splitters, String splitterName, List<String> splitterList, List<String> eventList) { splitterList.add(handlerName); eventList.remove(handlerName); if (!splitters.contains(splitterName)) { splitters.add(splitterName); } } /** * Verify that filters are valid. * * @throws InvalidMvp4gConfigurationException */ void validateEventFilters() throws InvalidMvp4gConfigurationException { JGenericType filterGenType = getType(null, EventFilter.class.getCanonicalName()).isGenericType(); JClassType eventBusType = getType(null, eventBus.getInterfaceClassName()); JClassType filterType, eventBusParam; JParameterizedType genEventFilter; for (EventFilterElement filter : eventFilters) { filterType = getType(filter, filter.getClassName()); genEventFilter = filterType.asParameterizationOf(filterGenType); if (genEventFilter == null) { throw new InvalidClassException(filter, EventFilter.class.getCanonicalName()); } eventBusParam = (JClassType) genEventFilter.getMethods()[0].getParameters()[2].getType(); // Control if filter event bus is compatible with module // event bus if (!eventBusType.isAssignableTo(eventBusParam)) { throw new InvalidTypeException(filter, "Event Bus", eventBus.getInterfaceClassName(), eventBusParam.getQualifiedSourceName()); } } } /** * Control if the event bus of the handler is compatible with the module event bus. * <br> * If the handler event bus is not compatible and is not multiple, then throw an exception. * <br> * If the handler is multiple, then it's an handler of another module, just ignore it by * returning false * * @param eventHandler * @param eventHandlerGenType * @param eventBusType * @return true if no error, false if the presenter has to be ignored. * @throws InvalidClassException throws if the handler event bus is not compatible * @throws InvalidTypeException throws if the handler event bus is not compatible * @throws NotFoundClassException throws if the handler event bus is not compatible */ private boolean controlEventBus(EventHandlerElement eventHandler, JGenericType eventHandlerGenType, JClassType eventBusType, boolean directHandler) throws InvalidClassException, InvalidTypeException, NotFoundClassException { JClassType eventHandlerType = getType(eventHandler, eventHandler.getClassName()); JParameterizedType genEventHandler = eventHandlerType.asParameterizationOf(eventHandlerGenType); if (genEventHandler == null) { if (directHandler) { throw new InvalidClassException(eventHandler, EventHandlerInterface.class.getCanonicalName()); } else { return false; } } JClassType eventBusParam = (JClassType) genEventHandler.findMethod("getEventBus", new JType[0]) .getReturnType(); // Control if presenter event bus is compatible with module // event bus if (!eventBusType.isAssignableTo(eventBusParam)) { if (directHandler) { throw new InvalidTypeException(eventHandler, "Event Bus", eventBus.getInterfaceClassName(), eventBusParam.getQualifiedSourceName()); } else { return false; } } return true; } private void removeFromActivateDeactivate(List<EventElement> eventActivateList, List<EventElement> eventDeactivateList, EventHandlerElement eventHandler) { if (eventActivateList != null) { for (EventElement event : eventActivateList) { event.getActivate() .remove(eventHandler.getName()); } } if (eventDeactivateList != null) { for (EventElement event : eventDeactivateList) { event.getDeactivate() .remove(eventHandler.getName()); } } } void validateChildModules() throws InvalidMvp4gConfigurationException { Map<String, List<EventElement>> childModuleMap = new HashMap<String, List<EventElement>>(); Map<JClassType, List<EventElement>> broadcastMap = new HashMap<JClassType, List<EventElement>>(); // Add presenter that handles event List<EventElement> eventList = null; List<String> modulesToLoad; String broadcast; JClassType type; for (EventElement event : events) { modulesToLoad = event.getForwardToModules(); broadcast = event.getBroadcastTo(); if (modulesToLoad != null) { for (String childModule : modulesToLoad) { eventList = childModuleMap.get(childModule); if (eventList == null) { eventList = new ArrayList<EventElement>(); childModuleMap.put(childModule, eventList); } eventList.add(event); } } if (broadcast != null) { type = getType(event, broadcast); eventList = broadcastMap.get(type); if (eventList == null) { eventList = new ArrayList<EventElement>(); broadcastMap.put(type, eventList); } eventList.add(event); } } JClassType moduleSuperClass = getType(null, Mvp4gModule.class.getCanonicalName()); JClassType moduleType = null; String eventName = null; EventElement eventElt = null; String[] eventObjClasses = null; String childModuleClass = null; JClassType childEventBus = null; JClassType startPresenterType = null; JClassType startViewType = null; String startPresenterClass = null; JGenericType presenterGenType = getType(null, PresenterInterface.class.getCanonicalName()).isGenericType(); JType[] noParam = new JType[0]; for (ChildModuleElement childModule : childModules) { eventList = childModuleMap.remove(childModule.getName()); childModuleClass = childModule.getClassName(); moduleType = getType(childModule, childModuleClass); if (findPossibleModuleBroadcast(broadcastMap, childModule, moduleType) || (eventList != null)) { if (!moduleType.isAssignableTo(moduleSuperClass)) { throw new InvalidClassException(childModule, Mvp4gModule.class.getCanonicalName()); } childEventBus = othersEventBusClassMap.get(childModuleClass); if (childEventBus != null && childEventBus.getAnnotation(Events.class) .historyOnStart()) { throw new InvalidMvp4gConfigurationException(String.format(Mvp4gConfiguration.HISTORY_ON_START_ONLY_FOR_ROOT, childEventBus.getName())); } if (childModule.isAutoDisplay()) { eventName = childModule.getEventToDisplayView(); if ((eventName == null) || (eventName.length() == 0)) { String error = String.format(MISSING_ATTRIBUTE, module.getQualifiedSourceName(), childModule.getClassName()); throw new InvalidMvp4gConfigurationException(error); } // verify event exists eventElt = getElement(eventName, events, childModule); eventObjClasses = eventElt.getEventObjectClass(); if ((eventObjClasses == null) || (eventObjClasses.length != 1)) { throw new InvalidMvp4gConfigurationException(String.format(WRONG_NUMBER_ATT, eventElt.getType())); } startPresenterClass = childEventBus.getAnnotation(Events.class) .startPresenter() .getCanonicalName(); if (startPresenterClass.equals(NoStartPresenter.class.getCanonicalName())) { throw new InvalidMvp4gConfigurationException(String.format(NO_START_PRESENTER, childModule.getClassName())); } startPresenterType = oracle.findType(startPresenterClass) .asParameterizationOf(presenterGenType); startViewType = (JClassType) startPresenterType.findMethod("getView", noParam) .getReturnType(); if (!startViewType.isAssignableTo(getType(eventElt, eventObjClasses[0]))) { throw new InvalidMvp4gConfigurationException(String.format(WRONG_CHILD_LOAD_EVENT_OBJ, childModule.getClassName(), eventElt.getType(), startViewType.getQualifiedSourceName(), eventObjClasses[0])); } } } else { // this object is not used, could be used by one of the child // module so display a warning logger.log(Type.WARN, String.format(CHILD_MODULE_NOT_USED, module.getName(), childModule.getName())); } } List<ChildModuleElement> siblings = getSiblings(); List<String> siblingsToLoad; String siblingClassName; for (ChildModuleElement sibling : siblings) { siblingClassName = sibling.getClassName(); eventList = childModuleMap.remove(siblingClassName); findPossibleSiblingBroadcast(broadcastMap, sibling); if (eventList != null) { for (EventElement event : eventList) { siblingsToLoad = event.getSiblingsToLoad(); modulesToLoad = event.getForwardToModules(); if (!siblingsToLoad.contains(siblingClassName)) { siblingsToLoad.add(siblingClassName); } modulesToLoad.remove(siblingClassName); } } } ChildModuleElement currentModule = moduleParentEventBusClassMap.get(module.getQualifiedSourceName()); if (currentModule != null) { String parentModuleClassName = currentModule.getParentModuleClass(); findPossibleParentBroadcast(broadcastMap, currentModule, parentModuleClassName); Iterator<String> it = childModuleMap.keySet() .iterator(); String trueStr = Boolean.TRUE.toString(); if (it.hasNext()) { String moduleName = it.next(); if (parentModuleClassName.equals(moduleName)) { eventList = childModuleMap.remove(moduleName); for (EventElement event : eventList) { event.getForwardToModules() .remove(parentModuleClassName); event.setForwardToParent(trueStr); } } } } // Missing child modules if (!childModuleMap.isEmpty()) { String next = childModuleMap.keySet() .iterator() .next(); throw new InvalidMvp4gConfigurationException(String.format(UNKNOWN_MODULE, childModuleMap.get(next) .get(0) .getName(), next)); } } List<ChildModuleElement> getSiblings() { List<ChildModuleElement> siblings = new ArrayList<ChildModuleElement>(); JClassType parentEventBus = findParentEventBus(module.getQualifiedSourceName()); if (parentEventBus != null) { Iterator<String> it = moduleParentEventBusClassMap.keySet() .iterator(); String currentModule = module.getQualifiedSourceName(); ChildModuleElement childModule; while (it.hasNext()) { childModule = moduleParentEventBusClassMap.get(it.next()); if (parentEventBus.equals(childModule.getParentEventBus()) && !currentModule.equals(childModule.getClassName())) { siblings.add(childModule); } } } return siblings; } boolean findPossibleModuleBroadcast(Map<JClassType, List<EventElement>> broadcastMap, ChildModuleElement childModuleElement, JClassType childModule) { boolean keep = false; Iterator<JClassType> it = broadcastMap.keySet() .iterator(); JClassType type; String moduleName = childModuleElement.getName(); while (it.hasNext()) { type = it.next(); if (childModule.isAssignableTo(type)) { List<String> modules; for (EventElement event : broadcastMap.get(type)) { modules = event.getForwardToModules(); if (!modules.contains(moduleName)) { modules.add(moduleName); } } keep = true; } } return keep; } boolean findPossibleSiblingBroadcast(Map<JClassType, List<EventElement>> broadcastMap, ChildModuleElement siblingElement) throws NotFoundClassException { boolean keep = false; Iterator<JClassType> it = broadcastMap.keySet() .iterator(); JClassType type; String siblingClass = siblingElement.getClassName(); JClassType siblingType = getType(siblingElement, siblingClass); List<String> siblings; while (it.hasNext()) { type = it.next(); if (siblingType.isAssignableTo(type)) { for (EventElement event : broadcastMap.get(type)) { siblings = event.getSiblingsToLoad(); if (!siblings.contains(siblingClass)) { siblings.add(siblingClass); } } keep = true; } } return keep; } boolean findPossibleParentBroadcast(Map<JClassType, List<EventElement>> broadcastMap, ChildModuleElement currentElement, String parentClass) throws NotFoundClassException { boolean keep = false; Iterator<JClassType> it = broadcastMap.keySet() .iterator(); JClassType type; JClassType siblingType = getType(currentElement, parentClass); String trueStr = Boolean.TRUE.toString(); while (it.hasNext()) { type = it.next(); if (siblingType.isAssignableTo(type)) { for (EventElement event : broadcastMap.get(type)) { event.setForwardToParent(trueStr); } keep = true; } } return keep; } /** * Checks that events dispatch at start or History correspond to a configured mvp4g element. * * @throws InvalidMvp4gConfigurationException */ void validateEvents() throws InvalidMvp4gConfigurationException { if (isRootModule()) { for (EventElement event : events) { if (event.hasForwardToParent()) { throw new InvalidMvp4gConfigurationException(String.format(NO_PARENT_ERROR, event.getType())); } } } String event; EventElement eventElt; String[] objClasses; if (start.hasForwardEventType()) { if (isRootModule()) { throw new InvalidMvp4gConfigurationException(WRONG_FORWARD_EVENT); } event = start.getForwardEventType(); eventElt = getElement(event, events, start); objClasses = eventElt.getEventObjectClass(); if ((objClasses != null) && (objClasses.length > 0)) { throw new InvalidMvp4gConfigurationException(String.format(NOT_EMPTY_EVENT_OBJ, "Forward", "Forward", eventElt.getType())); } } if ((history != null) && isRootModule()) { event = history.getInitEvent(); getElement(event, events, history); } if (loadChildConfig != null) { String eventName = loadChildConfig.getErrorEvent(); if ((eventName != null) && (eventName.length() > 0)) { eventElt = getElement(eventName, events, loadChildConfig); objClasses = eventElt.getEventObjectClass(); if ((objClasses != null) && ((objClasses.length > 1) || ((objClasses.length == 1) && (!Throwable.class.getName() .equals(objClasses[0]) ) ) )) { throw new InvalidMvp4gConfigurationException(String.format(WRONG_EVENT_OBJ, loadChildConfig.getTagName(), "Error", eventElt.getType(), Throwable.class.getName())); } } eventName = loadChildConfig.getAfterEvent(); if ((eventName != null) && (eventName.length() > 0)) { eventElt = getElement(eventName, events, loadChildConfig); objClasses = eventElt.getEventObjectClass(); if ((objClasses != null) && (objClasses.length > 0)) { throw new InvalidMvp4gConfigurationException(String.format(NOT_EMPTY_EVENT_OBJ, loadChildConfig.getTagName(), "After", eventElt.getType())); } } eventName = loadChildConfig.getBeforeEvent(); if ((eventName != null) && (eventName.length() > 0)) { eventElt = getElement(eventName, events, loadChildConfig); objClasses = eventElt.getEventObjectClass(); if ((objClasses != null) && (objClasses.length > 0)) { throw new InvalidMvp4gConfigurationException(String.format(NOT_EMPTY_EVENT_OBJ, loadChildConfig.getTagName(), "Before", eventElt.getType())); } } } } /** * Validate that if history converters are used, init history event is defined * * @throws InvalidMvp4gConfigurationException if history init event is not defined but history converters are used */ void validateHistory() throws InvalidMvp4gConfigurationException { if (historyConverters.size() > 0) { if (!isRootModule()) { if ((history != null) && ((history.getInitEvent() != null) || (history.getNotFoundEvent() != null) || (history.getPlaceServiceClass() != null))) { throw new InvalidMvp4gConfigurationException(String.format(HISTORY_ONLY_FOR_ROOT, module.getQualifiedSourceName())); } if ((historyName == null) || (historyName.length() == 0)) { throw new InvalidMvp4gConfigurationException(String.format(HISTORY_NAME_MISSING, module.getQualifiedSourceName())); } // make sure history is equal to null for the writer history = null; } else { if ((history == null) || (history.getInitEvent() == null) || (history.getInitEvent() .length() == 0 )) { throw new InvalidMvp4gConfigurationException(HISTORY_INIT_MISSING); } } } } /** * Validates that every mvp4g element has a globally unique identifier.<br> * * @throws NonUniqueIdentifierException if two or more elements have the same textual identifier. */ void checkUniquenessOfAllElements() throws NonUniqueIdentifierException { Set<String> allIds = new HashSet<String>(); checkUniquenessOf(historyConverters, allIds); checkUniquenessOf(presenters, allIds); checkUniquenessOf(eventHandlers, allIds); checkUniquenessOf(views, allIds); checkUniquenessOf(events, allIds); checkUniquenessOf(services, allIds); checkUniquenessOf(childModules, allIds); checkUniquenessOf(eventFilters, allIds); } /** * Validate start element * * @throws InvalidMvp4gConfigurationException thrown if no view to load at start has been defined. */ void validateStart() throws InvalidMvp4gConfigurationException { String startEvent = start.getEventType(); if ((startEvent != null) && (startEvent.length() > 0)) { EventElement eventElt = getElement(startEvent, events, start); String[] objClasses = eventElt.getEventObjectClass(); if ((objClasses != null) && (objClasses.length > 0)) { throw new InvalidMvp4gConfigurationException(String.format(NOT_EMPTY_EVENT_OBJ, "Start", "Start", eventElt.getType())); } } } void validateDebug() throws NotFoundClassException, InvalidTypeException { if (debug != null) { JClassType debugType = getType(debug, debug.getLogger()); if (!debugType.isAssignableTo(oracle.findType(Mvp4gLogger.class.getCanonicalName()))) { throw new InvalidTypeException(debug, "Logger", debug.getLogger(), Mvp4gLogger.class.getCanonicalName()); } } } String[] validateGinModule() throws InvalidMvp4gConfigurationException { String[] propertiesValues = getGinModulesByProperties(); List<String> modulesClassName = ginModule.getModules(); if ((modulesClassName == null) || (modulesClassName.size() == 0)) { throw new InvalidMvp4gConfigurationException(NO_GIN_MODULE); } JClassType ginType; JClassType ginModuleClass = getType(ginModule, GinModule.class.getName()); for (String module : modulesClassName) { ginType = getType(ginModule, module); if (!ginType.isAssignableTo(ginModuleClass)) { throw new InvalidTypeException(ginModule, "Logger", module, GinModule.class.getCanonicalName()); } } return propertiesValues; } private String[] getGinModulesByProperties() throws InvalidMvp4gConfigurationException { String[] properties = ginModule.getModuleProperties(); String[] propertiesValue; if (properties != null) { int size = properties.length; propertiesValue = new String[size]; String moduleClassName; List<String> modules = ginModule.getModules(); if (modules == null) { ginModule.setModules(new String[0]); modules = ginModule.getModules(); } String property; for (int i = 0; i < size; i++) { property = properties[i]; try { moduleClassName = propertyOracle.getSelectionProperty(logger, property) .getCurrentValue() .replace("$", "."); } catch (BadPropertyValueException e) { throw new InvalidMvp4gConfigurationException(String.format(GIN_MODULE_UNKNOWN_PROPERTY, module.getSimpleSourceName(), property, e.getMessage())); } modules.add(moduleClassName); propertiesValue[i] = moduleClassName; } } else { propertiesValue = null; } return propertiesValue; } /* * ANNOTATION LOAD */ /** * Pre-loads information contained in Presenter annotations. * * @param annotedClasses classes with the Presenter annotation * @throws Mvp4gAnnotationException thrown if presenter class associated with the annotation is not correct */ void loadPresenters(List<JClassType> annotedClasses) throws Mvp4gAnnotationException { PresenterAnnotationsLoader loader = new PresenterAnnotationsLoader(); loader.load(annotedClasses, this); } /** * Pre-loads information contained in EventHandler annotations. * * @param annotedClasses classes with the EventHandler annotation * @throws Mvp4gAnnotationException thrown if event handler class associated with the annotation is not correct */ void loadEventHandlers(List<JClassType> annotedClasses) throws Mvp4gAnnotationException { EventHandlerAnnotationsLoader loader = new EventHandlerAnnotationsLoader(); loader.load(annotedClasses, this); } /** * Pre-loads information contained in Service annotations. * * @param annotedClasses classes with the Service annotation * @throws Mvp4gAnnotationException thrown if service class associated with the annotation is not correct */ void loadServices(List<JClassType> annotedClasses) throws Mvp4gAnnotationException { ServiceAnnotationsLoader loader = new ServiceAnnotationsLoader(); loader.load(annotedClasses, this); } /** * Pre-loads all Events contained in the class of the Events annotation. Build eventBus element * if needed * * @param annotedClasses classes with the Events annotation * @throws Mvp4gAnnotationException thrown if events class associated with the annotation is not correct */ void loadEvents(List<JClassType> annotedClasses) throws Mvp4gAnnotationException { EventsAnnotationsLoader loader = new EventsAnnotationsLoader(); loader.load(annotedClasses, this); } /** * Pre-loads information contained in History annotations. * * @param annotedClasses classes with the History annotation * @throws Mvp4gAnnotationException thrown if history converter class associated with the annotation is not correct */ void loadHistoryConverters(List<JClassType> annotedClasses) throws Mvp4gAnnotationException { HistoryAnnotationsLoader loader = new HistoryAnnotationsLoader(); loader.load(annotedClasses, this); } void loadParentModule() throws InvalidMvp4gConfigurationException { if (!ROOT_MODULE_CLASS_NAME.equals(module.getQualifiedSourceName())) { parentEventBus = findParentEventBus(module.getQualifiedSourceName()); } HistoryName hName = module.getAnnotation(HistoryName.class); if (hName != null) { if (isRootModule()) { throw new InvalidMvp4gConfigurationException(String.format(ROOT_MODULE_NO_HISTORY_NAME, module.getQualifiedSourceName())); } else { historyName = hName.value(); } } } /** * Verify that id of elements contained in the set isn't already contained in the ids set. * * @param <E> type of elements contained in the set * @param subset set of elements to test * @param ids set containing the ids * @throws NonUniqueIdentifierException if id of an element is already contained in the ids set. */ private <E extends Mvp4gElement> void checkUniquenessOf(Set<E> subset, Set<String> ids) throws NonUniqueIdentifierException { for (E item : subset) { boolean unique = ids.add(item.getUniqueIdentifier()); if (!unique) { throw new NonUniqueIdentifierException(item.getUniqueIdentifier()); } } } /** * Retrieve an element exists in a set thanks to its unique identifier * * @param <T> type of the elements in the set * @param elementName value of the unique identifier of the element to find * @param elements set of elemets * @return element if found, null otherwise * @throws UnknownConfigurationElementException */ private <T extends Mvp4gElement> T getElement(String elementName, Set<T> elements, Mvp4gElement relatedElement) throws UnknownConfigurationElementException { T eFound = getElement(elementName, elements); if (eFound == null) { throw new UnknownConfigurationElementException(relatedElement, elementName); } return eFound; } private <T extends Mvp4gElement> T getElement(String elementName, Set<T> elements) { T eFound = null; for (T element : elements) { if (element.getUniqueIdentifier() .equals(elementName)) { eFound = element; break; } } return eFound; } /** * Remove all elements of the useless set from the set * * @param <T> type of the elements to remove * @param set set where elements need to be removed * @param toRemove set of elements to remove */ private <T extends Mvp4gElement> void removeUselessElements(Set<T> set, Set<T> toRemove) { for (T e : toRemove) { set.remove(e); logger.log(TreeLogger.DEBUG, String.format(REMOVE_OBJ, e.getTagName(), e.getUniqueIdentifier())); } } /** * Retrieve a type from the oracle. * * @param element element that asks to retrieve the class (can be null) * @param className name of the class to retrieve * @return type of the class * @throws NotFoundClassException if the class can not be found */ private JClassType getType(Mvp4gElement element, String className) throws NotFoundClassException { JClassType type = oracle.findType(className); if (type == null) { throw new NotFoundClassException(element, className); } return type; } private JClassType findParentEventBus(String moduleClassName) { ChildModuleElement child = moduleParentEventBusClassMap.get(moduleClassName); return (child == null) ? null : child.getParentEventBus(); } void findChildModuleHistoryNameAndLoader() throws InvalidMvp4gConfigurationException { JClassType childType; HistoryName hName; List<String> historyNames = new ArrayList<String>(); String moduleClassName; JClassType eventBusType = getType(null, eventBus.getInterfaceClassName()); for (ChildModuleElement childModule : childModules) { childType = getType(childModule, childModule.getClassName()); hName = childType.getAnnotation(HistoryName.class); if (hName != null) { String hNameStr = hName.value(); validateHistoryName(hNameStr, childModule); if (historyNames.contains(hNameStr)) { throw new InvalidMvp4gConfigurationException(String.format(CHILD_MODULE_SAME_HISTORY_NAME, module.getQualifiedSourceName(), hNameStr)); } historyNames.add(hNameStr); childModule.setHistoryName(hNameStr); } moduleClassName = childModule.getClassName(); childModule.setLoader(validateLoaderAndAdd(getType(childModule, moduleClassName), eventBusType, childModule)); } } void validateHistoryName(String historyName, Mvp4gElement element) throws InvalidMvp4gConfigurationException { if (historyName.startsWith(PlaceService.CRAWLABLE) || historyName.contains(PlaceService.MODULE_SEPARATOR)) { throw new InvalidMvp4gConfigurationException(String.format(WRONG_HISTORY_NAME, element.getTagName(), element.getUniqueIdentifier(), historyName)); } } String validateLoaderAndAdd(JClassType moduleType, JClassType eventBus, Mvp4gElement element) throws NotFoundClassException, InvalidTypeException { String name = null; Loader loaderAnnotation = moduleType.getAnnotation(Loader.class); if (loaderAnnotation != null) { JClassType loaderType = getType(element, loaderAnnotation.value() .getCanonicalName()); JGenericType loaderGenType = getType(null, Mvp4gLoader.class.getCanonicalName()).isGenericType(); JParameterizedType genLoader = loaderType.asParameterizationOf(loaderGenType); JClassType eventBusParam = (JClassType) genLoader.getMethods()[0].getParameters()[0].getType(); if (!eventBus.isAssignableTo(eventBusParam)) { throw new InvalidTypeException(element, "Loader, event bus not compatible", eventBusParam.getQualifiedSourceName(), eventBus.getQualifiedSourceName()); } name = "loader" + loaderType.getQualifiedSourceName() .replace(".", "_"); LoaderElement loader = getElement(name, loaders); if (loader == null) { loader = new LoaderElement(); loader.setName(name); loader.setClassName(loaderType.getQualifiedSourceName()); loaders.add(loader); } } return name; } boolean checkIfParentEventReturnsString(EventElement e) { if (parentEventBus != null) { try { String eventType = e.getType(); JClassType string = oracle.getType(String.class.getName()); for (JMethod m : parentEventBus.getOverridableMethods()) { if (eventType.equals(m.getName())) { return m.getReturnType() .equals(string); } } } catch (NotFoundException e1) { // nothing to do } } return false; } String getSuffix(String[] propertiesValues) { if ((propertiesValues == null) || (propertiesValues.length == 0)) { return ""; } else { StringBuilder builder = new StringBuilder(propertiesValues.length * 200); for (String propertyValue : propertiesValues) { builder.append(propertyValue); } //'-' is not a valid character for java class name return Integer.toString(builder.toString() .hashCode()) .replace("-", "A"); } } }