001/** 002 * Copyright 2013 John Ericksen 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.androidtransfuse.gen; 017 018import com.google.common.collect.ImmutableMap; 019import com.sun.codemodel.*; 020import org.androidtransfuse.TransfuseAnalysisException; 021import org.androidtransfuse.adapter.ASTPrimitiveType; 022import org.androidtransfuse.adapter.ASTType; 023import org.androidtransfuse.adapter.ASTUtils; 024import org.androidtransfuse.adapter.PackageClass; 025import org.androidtransfuse.adapter.classes.ASTClassFactory; 026import org.androidtransfuse.analysis.astAnalyzer.IntentFactoryExtraAspect; 027import org.androidtransfuse.experiment.ComponentBuilder; 028import org.androidtransfuse.experiment.ComponentDescriptor; 029import org.androidtransfuse.experiment.ComponentPartGenerator; 030import org.androidtransfuse.experiment.Generation; 031import org.androidtransfuse.intentFactory.AbstractIntentFactoryStrategy; 032import org.androidtransfuse.intentFactory.ActivityIntentFactoryStrategy; 033import org.androidtransfuse.model.InjectionNode; 034import org.androidtransfuse.model.TypedExpression; 035import org.androidtransfuse.util.AndroidLiterals; 036 037import javax.inject.Inject; 038import java.io.Serializable; 039import java.util.*; 040 041 042/** 043 * @author John Ericksen 044 */ 045public class IntentFactoryStrategyGenerator implements Generation { 046 047 private static final PackageClass ATPARCEL_NAME = new PackageClass("org.parceler", "Parcel"); 048 private static final PackageClass PARCELS_NAME = new PackageClass("org.parceler", "Parcels"); 049 public static final String WRAP_METHOD = "wrap"; 050 051 private static final String STRATEGY_EXT = "Strategy"; 052 053 private final Class<? extends AbstractIntentFactoryStrategy> factoryStrategyClass; 054 private final ASTClassFactory astClassFactory; 055 private final ClassGenerationUtil generationUtil; 056 private final ImmutableMap<ASTPrimitiveType, String> methodMapping; 057 private final UniqueVariableNamer namer; 058 059 @Inject 060 public IntentFactoryStrategyGenerator(/*@Assisted*/ Class factoryStrategyClass, 061 ASTClassFactory astClassFactory, 062 ClassGenerationUtil generationUtil, 063 UniqueVariableNamer namer) { 064 this.factoryStrategyClass = factoryStrategyClass; 065 this.astClassFactory = astClassFactory; 066 this.generationUtil = generationUtil; 067 this.namer = namer; 068 069 ImmutableMap.Builder<ASTPrimitiveType, String> methodMappingBuilder = ImmutableMap.builder(); 070 methodMappingBuilder.put(ASTPrimitiveType.BOOLEAN, "putBoolean"); 071 methodMappingBuilder.put(ASTPrimitiveType.BYTE, "putByte"); 072 methodMappingBuilder.put(ASTPrimitiveType.CHAR, "putChar"); 073 methodMappingBuilder.put(ASTPrimitiveType.DOUBLE, "putDouble"); 074 methodMappingBuilder.put(ASTPrimitiveType.FLOAT, "putFloat"); 075 methodMappingBuilder.put(ASTPrimitiveType.INT, "putInt"); 076 methodMappingBuilder.put(ASTPrimitiveType.LONG, "putLong"); 077 methodMappingBuilder.put(ASTPrimitiveType.SHORT, "putShort"); 078 079 this.methodMapping = methodMappingBuilder.build(); 080 } 081 082 @Override 083 public void schedule(final ComponentBuilder builder, ComponentDescriptor descriptor) { 084 builder.add(new ComponentPartGenerator() { 085 @Override 086 public void generate(org.androidtransfuse.experiment.ComponentDescriptor descriptor) { 087 try { 088 JDefinedClass strategyClass = generationUtil.defineClass(descriptor.getPackageClass().append(STRATEGY_EXT)); 089 strategyClass.annotate(SuppressWarnings.class).param("value", "unchecked"); 090 091 strategyClass._extends(factoryStrategyClass); 092 093 JInvocation getExtrasMethod = JExpr.invoke(ActivityIntentFactoryStrategy.GET_EXTRAS_METHOD); 094 095 List<IntentFactoryExtraAspect> extras = getExtras(builder.getExpressionMap()); 096 097 //constructor, with required extras 098 JMethod constructor = strategyClass.constructor(JMod.PUBLIC); 099 JBlock constructorBody = constructor.body(); 100 JDocComment javadocComments = constructor.javadoc(); 101 javadocComments.append("Strategy Class for generating Intent for " + descriptor.getPackageClass().getClassName()); 102 103 constructorBody.add(JExpr.invoke("super") 104 .arg(generationUtil.ref(descriptor.getPackageClass()).dotclass()) 105 .arg(JExpr._new(generationUtil.ref(AndroidLiterals.BUNDLE))) 106 ); 107 108 //addFlags method 109 JMethod addFlags = strategyClass.method(JMod.PUBLIC, strategyClass, "addFlags"); 110 JVar flagParam = addFlags.param(int.class, namer.generateName(int.class)); 111 JBlock addFlagsBody = addFlags.body(); 112 addFlagsBody.invoke("internalAddFlags").arg(flagParam); 113 addFlagsBody._return(JExpr._this()); 114 115 //addCategories method 116 JMethod addCategory = strategyClass.method(JMod.PUBLIC, strategyClass, "addCategory"); 117 JVar categoryParam = addCategory.param(String.class, namer.generateName(String.class)); 118 JBlock addCategoryBody = addCategory.body(); 119 addCategoryBody.invoke("internalAddCategory").arg(categoryParam); 120 addCategoryBody._return(JExpr._this()); 121 122 for (IntentFactoryExtraAspect extra : extras) { 123 if (extra.isRequired()) { 124 JVar extraParam = constructor.param(generationUtil.ref(extra.getType()), extra.getName()); 125 126 constructorBody.add(buildBundleMethod(getExtrasMethod, extra.getType(), extra.getName(), extraParam)); 127 128 javadocComments.addParam(extraParam); 129 } else { 130 //setter for non-required extra 131 JMethod setterMethod = strategyClass.method(JMod.PUBLIC, strategyClass, "set" + upperFirst(extra.getName())); 132 JVar extraParam = setterMethod.param(generationUtil.ref(extra.getType()), extra.getName()); 133 134 JBlock setterBody = setterMethod.body(); 135 setterBody.add(buildBundleMethod(getExtrasMethod, extra.getType(), extra.getName(), extraParam)); 136 setterMethod.javadoc().append("Optional Extra parameter"); 137 setterMethod.javadoc().addParam(extraParam); 138 139 setterBody._return(JExpr._this()); 140 141 } 142 } 143 144 } catch (JClassAlreadyExistsException e) { 145 throw new TransfuseAnalysisException("Class already defined while trying to define IntentFactoryStrategy", e); 146 } 147 148 } 149 }); 150 } 151 152 private String upperFirst(String name) { 153 return name.substring(0, 1).toUpperCase(Locale.ENGLISH) + name.substring(1); 154 } 155 156 private JStatement buildBundleMethod(JInvocation extras, ASTType type, String name, JVar extraParam) { 157 158 //autoboxable (Long, Integer, etc) 159 ASTPrimitiveType primitiveType = ASTPrimitiveType.getAutoboxType(type.getName()); 160 161 if (type instanceof ASTPrimitiveType) { 162 primitiveType = (ASTPrimitiveType) type; 163 } 164 165 if (primitiveType != null) { 166 return extras.invoke(methodMapping.get(primitiveType)).arg(name).arg(extraParam); 167 } else if (type.getName().equals(String.class.getName())) { 168 return extras.invoke("putString").arg(name).arg(extraParam); 169 } else if (type.implementsFrom(astClassFactory.getType(Serializable.class))) { 170 return extras.invoke("putSerializable").arg(name).arg(extraParam); 171 } 172 if (type.inheritsFrom(AndroidLiterals.PARCELABLE)) { 173 return extras.invoke("putParcelable").arg(name).arg(extraParam); 174 } 175 if ( ASTUtils.getInstance().isAnnotated(type, ATPARCEL_NAME.getCanonicalName())) { 176 JInvocation wrappedParcel = generationUtil.ref(PARCELS_NAME) 177 .staticInvoke(WRAP_METHOD).arg(extraParam); 178 179 return extras.invoke("putParcelable").arg(name).arg(wrappedParcel); 180 } 181 182 throw new TransfuseAnalysisException("Unable to find appropriate type to build intent factory strategy: " + type.getName()); 183 } 184 185 private List<IntentFactoryExtraAspect> getExtras(Map<InjectionNode, TypedExpression> expressionMap) { 186 Set<IntentFactoryExtraAspect> uniqueExtras = new HashSet<IntentFactoryExtraAspect>(); 187 List<IntentFactoryExtraAspect> extras = new ArrayList<IntentFactoryExtraAspect>(); 188 for (InjectionNode injectionNode : expressionMap.keySet()) { 189 IntentFactoryExtraAspect intentFactoryExtra = injectionNode.getAspect(IntentFactoryExtraAspect.class); 190 if (intentFactoryExtra != null && !uniqueExtras.contains(intentFactoryExtra)) { 191 uniqueExtras.add(intentFactoryExtra); 192 extras.add(intentFactoryExtra); 193 } 194 } 195 Collections.sort(extras); 196 return extras; 197 } 198}