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}