Java tutorial
/** * Copyright 2011 ArcBees Inc. * * 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.gwtplatform.mvp.rebind; import java.util.ArrayList; import java.util.List; import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.JField; import com.google.gwt.core.ext.typeinfo.JMethod; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.user.rebind.SourceWriter; import com.gwtplatform.mvp.client.annotations.ContentSlot; import com.gwtplatform.mvp.client.annotations.CustomProvider; import com.gwtplatform.mvp.client.annotations.ProxyCodeSplit; import com.gwtplatform.mvp.client.annotations.ProxyCodeSplitBundle; import com.gwtplatform.mvp.client.annotations.ProxyCodeSplitBundle.NoOpProviderBundle; import com.gwtplatform.mvp.client.annotations.ProxyEvent; import com.gwtplatform.mvp.client.annotations.ProxyStandard; import com.gwtplatform.mvp.client.annotations.TabInfo; import com.gwtplatform.mvp.client.annotations.TitleFunction; /** * A class used to inspect the presenter, the methods and inner interfaces it contains. * You must call {@link #init(JClassType)} before any other method can be called. */ public class PresenterInspector { private final TypeOracle oracle; private final TreeLogger logger; private final ClassCollection classCollection; private final GinjectorInspector ginjectorInspector; private final List<JField> contentSlots = new ArrayList<JField>(); private JClassType presenterClass; private String presenterClassName; private ClassInspector classInspector; private ProxyStandard proxyStandardAnnotation; private ProxyCodeSplit proxyCodeSplitAnnotation; private ProxyCodeSplitBundle proxyCodeSplitBundleAnnotation; private CustomProvider customProviderAnnotation; private String getPresenterMethodName; private String bundleClassName; public PresenterInspector(TypeOracle oracle, TreeLogger logger, ClassCollection classCollection, GinjectorInspector ginjectorInspector) { this.oracle = oracle; this.logger = logger; this.classCollection = classCollection; this.ginjectorInspector = ginjectorInspector; } /** * Initializes the presenter inspector given the annotation present on a proxy interface. The * possible annotations are {@link ProxyStandard}, {@link ProxyCodeSplit} or * {@link ProxyCodeSplitBundle}. If none are present the method returns {@code false} and no code * should be generated. * * @param proxyInterface The annotated interface inheriting from proxy and that should be * annotated. * @return {@code true} if the presenter provider was built, {@code false} if the interface wasn't * annotated indicating that no proxy should be generated. * @throws UnableToCompleteException When more than one annotation is present on the proxy * interface. */ public boolean init(JClassType proxyInterface) throws UnableToCompleteException { findPresenterClass(logger, proxyInterface); presenterClassName = presenterClass.getName(); classInspector = new ClassInspector(logger, presenterClass); proxyStandardAnnotation = proxyInterface.getAnnotation(ProxyStandard.class); proxyCodeSplitAnnotation = proxyInterface.getAnnotation(ProxyCodeSplit.class); proxyCodeSplitBundleAnnotation = proxyInterface.getAnnotation(ProxyCodeSplitBundle.class); customProviderAnnotation = proxyInterface.getAnnotation(CustomProvider.class); if (!shouldGenerate()) { return false; } findGetPresenterMethodName(); classInspector.collectStaticAnnotatedFields(classCollection.typeClass, classCollection.revealContentHandlerClass, ContentSlot.class, contentSlots); return true; } private void findGetPresenterMethodName() throws UnableToCompleteException { if (proxyStandardAnnotation != null) { getPresenterMethodName = ginjectorInspector.findGetMethod(classCollection.providerClass, presenterClass); failIfNoProviderError(getPresenterMethodName, "Provider", ProxyStandard.class.getSimpleName()); } else if (proxyCodeSplitAnnotation != null) { getPresenterMethodName = ginjectorInspector.findGetMethod(classCollection.asyncProviderClass, presenterClass); failIfNoProviderError(getPresenterMethodName, "AsyncProvider", ProxyCodeSplit.class.getSimpleName()); } else { verifyManualCodeSplitBundleConfiguration(); JClassType bundleClass = findBundleClass(); getPresenterMethodName = ginjectorInspector.findGetMethod(classCollection.asyncProviderClass, bundleClass); failIfNoProviderError(getPresenterMethodName, "AsyncProvider", bundleClassName, ProxyCodeSplitBundle.class.getSimpleName()); } } private void verifyManualCodeSplitBundleConfiguration() throws UnableToCompleteException { if (ginjectorInspector.isGenerated()) { return; } if (!proxyCodeSplitBundleAnnotation.value().isEmpty()) { logger.log(TreeLogger.WARN, "Bundle value used with @" + ProxyCodeSplitBundle.class.getSimpleName() + " on presenter '" + presenterClassName + "' is " + "ignored"); } if (proxyCodeSplitBundleAnnotation.id() == -1 || proxyCodeSplitBundleAnnotation.bundleClass().equals(NoOpProviderBundle.class)) { logger.log(TreeLogger.ERROR, "ID and bundleClass must be specified with @" + ProxyCodeSplitBundle.class.getSimpleName() + " on presenter '" + presenterClassName + "'."); throw new UnableToCompleteException(); } } private JClassType findBundleClass() throws UnableToCompleteException { assert proxyCodeSplitBundleAnnotation != null; if (ginjectorInspector.isGenerated()) { bundleClassName = GinjectorGenerator.DEFAULT_PACKAGE + "." + proxyCodeSplitBundleAnnotation.value() + ProviderBundleGenerator.SUFFIX; } else { bundleClassName = proxyCodeSplitBundleAnnotation.bundleClass().getName(); } JClassType bundleClass = oracle.findType(bundleClassName); if (bundleClass == null) { logger.log(TreeLogger.ERROR, "Cannot find the bundle class '" + bundleClassName + ", used with @" + ProxyCodeSplitBundle.class.getSimpleName() + " on presenter '" + presenterClassName + "'."); throw new UnableToCompleteException(); } return bundleClass; } private void failIfNoProviderError(String providerMethodName, String providerClassName, String providedClassName, String annotationClassName) throws UnableToCompleteException { if (providerMethodName != null) { return; } String actualProvidedClassName = providedClassName; String extraString = " See presenter '" + presenterClassName + "'."; if (providedClassName == null) { actualProvidedClassName = presenterClassName; extraString = ""; } logger.log(TreeLogger.ERROR, "The Ginjector '" + ginjectorInspector.getGinjectorClassName() + "' does not have a get() method returning '" + providerClassName + "<" + actualProvidedClassName + ">'. This is required when using @" + annotationClassName + "." + extraString); throw new UnableToCompleteException(); } private void failIfNoProviderError(String providerMethodName, String providerClassName, String annotationClassName) throws UnableToCompleteException { failIfNoProviderError(providerMethodName, providerClassName, null, annotationClassName); } /** * @return The class of the presenter that this {@link PresenterInspector} provides. */ public JClassType getPresenterClass() { return presenterClass; } /** * @return The name of the class of the presenter that this {@link PresenterInspector} provides. */ public String getPresenterClassName() { return presenterClassName; } /** * Writes the assignation into the {@code provider} field of * {@link com.gwtplatform.mvp.client.proxy.ProxyImpl ProxyImpl}. */ public void writeProviderAssignation(SourceWriter writer) { if (customProviderAnnotation != null) { JClassType customProvider = oracle.findType(customProviderAnnotation.value().getName()); writer.println("presenter = new " + customProvider.getQualifiedSourceName() + "( ginjector." + getPresenterMethodName + "() );"); } else if (proxyStandardAnnotation != null) { writer.println("presenter = new StandardProvider<" + presenterClassName + ">( ginjector." + getPresenterMethodName + "() );"); } else if (proxyCodeSplitAnnotation != null) { writer.println("presenter = new CodeSplitProvider<" + presenterClassName + ">( ginjector." + getPresenterMethodName + "() );"); } else { assert proxyCodeSplitBundleAnnotation != null; writer.print("presenter = new CodeSplitBundleProvider<" + presenterClassName + ", " + bundleClassName + ">(ginjector." + getPresenterMethodName + "(), "); if (ginjectorInspector.isGenerated()) { writer.print(bundleClassName + "." + presenterClass.getSimpleSourceName().toUpperCase() + ");"); } else { writer.print(proxyCodeSplitBundleAnnotation.id() + ");"); } } } /** * Register a {@link com.gwtplatform.mvp.client.proxy.RevealContentHandler RevealContentHandler} * for each {@code @ContentSlot} defined in the presenter. */ public void writeContentSlotHandlerRegistration(SourceWriter writer) { if (contentSlots.size() == 0) { return; } writer.println(); writer.println( "RevealContentHandler<" + presenterClassName + "> revealContentHandler = new RevealContentHandler<" + presenterClassName + ">( eventBus, this );"); for (JField field : contentSlots) { writer.println("getEventBus().addHandler( " + presenterClassName + "." + field.getName() + ", revealContentHandler );"); } } /** * Look in the presenter and any superclass for a method annotated with {@link TitleFunction}. */ public PresenterTitleMethod findPresenterTitleMethod() throws UnableToCompleteException { // Look for the title function in the parent presenter JMethod method = classInspector.findAnnotatedMethod(TitleFunction.class); if (method == null) { return null; } PresenterTitleMethod result = new PresenterTitleMethod(logger, classCollection, ginjectorInspector, this); result.init(method); return result; } /** * Collect all the {@link ProxyEventMethod} of methods annotated with * {@literal @}{@link ProxyEvent} and contained in the presenter or its super classes. * * @param proxyEventMethods The list into which to collect the proxy events. * @throws UnableToCompleteException If something goes wrong. An error will be logged. */ public void collectProxyEvents(List<ProxyEventMethod> proxyEventMethods) throws UnableToCompleteException { // Look for @ProxyEvent methods in the parent presenter List<JMethod> collectedMethods = new ArrayList<JMethod>(); classInspector.collectAnnotatedMethods(ProxyEvent.class, collectedMethods); for (JMethod method : collectedMethods) { ProxyEventMethod proxyEventMethod = new ProxyEventMethod(logger, classCollection, this); proxyEventMethod.init(method); // Make sure that handler method name is not already used for (ProxyEventMethod previousMethod : proxyEventMethods) { proxyEventMethod.ensureNoClashWith(previousMethod); } proxyEventMethods.add(proxyEventMethod); } } /** * Retrieves the static {@link TabInfoMethod} defined in the presenter. * * @return The {@link TabInfoMethod}, or {@code null} if none is found. * @throws UnableToCompleteException If something goes wrong. An error will be logged. */ public TabInfoMethod findTabInfoMethod() throws UnableToCompleteException { JMethod method = classInspector.findAnnotatedMethod(TabInfo.class); if (method == null) { return null; } TabInfoMethod result = new TabInfoMethod(logger, classCollection, ginjectorInspector, this); result.init(method); return result; } /** * Identify the presenter class containing the proxy as an inner interface. */ private void findPresenterClass(TreeLogger logger, JClassType proxyInterface) throws UnableToCompleteException { presenterClass = proxyInterface.getEnclosingType(); if (presenterClass == null || !presenterClass.isAssignableTo(classCollection.basePresenterClass)) { presenterClass = null; logger.log(TreeLogger.ERROR, "Proxy must be enclosed in a class derived from '" + ClassCollection.basePresenterClassName + "'"); throw new UnableToCompleteException(); } } private boolean shouldGenerate() throws UnableToCompleteException { int nbNonNullTags = 0; if (proxyStandardAnnotation != null) { nbNonNullTags++; } if (proxyCodeSplitAnnotation != null) { nbNonNullTags++; } if (proxyCodeSplitBundleAnnotation != null) { nbNonNullTags++; } // If there is no annotations, don't use generator. if (nbNonNullTags == 0) { return false; } // Fail if there is more than one annotation. if (nbNonNullTags > 1) { logger.log(TreeLogger.ERROR, "Proxy for '" + presenterClassName + "' has more than one @Proxy annotation."); throw new UnableToCompleteException(); } return true; } }