/* * Copyright 2013 Jake Wharton * Copyright 2014 Prateek Srivastava (@f2prateek) * * 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.f2prateek.dart.processor; import com.f2prateek.dart.Dart; import com.f2prateek.dart.common.Binding; import com.f2prateek.dart.common.ExtraInjection; import com.f2prateek.dart.common.FieldBinding; import com.f2prateek.dart.common.BaseGenerator; import com.f2prateek.dart.common.InjectionTarget; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import java.util.Collection; import java.util.List; import javax.lang.model.element.Modifier; import javax.lang.model.type.TypeMirror; /** * Creates Java code to inject extras into an activity * or a {@link android.os.Bundle}. * @see {@link Dart} to use this code at runtime. */ public class ExtraInjectionGenerator extends BaseGenerator { private final InjectionTarget target; public ExtraInjectionGenerator(InjectionTarget target) { this.target = target; } @Override public String brewJava() { TypeSpec.Builder injectorTypeSpec = TypeSpec.classBuilder(target.className + Dart.INJECTOR_SUFFIX) .addModifiers(Modifier.PUBLIC); emitInject(injectorTypeSpec); JavaFile javaFile = JavaFile.builder(target.classPackage, injectorTypeSpec.build()) .addFileComment("Generated code from Dart. Do not modify!") .build(); return javaFile.toString(); } @Override public String getFqcn() { return target.getFqcn() + Dart.INJECTOR_SUFFIX; } private void emitInject(TypeSpec.Builder builder) { MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("inject") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addParameter(ClassName.get(Dart.Finder.class), "finder") .addParameter(ClassName.bestGuess(target.targetClass), "target") .addParameter(ClassName.get(Object.class), "source"); if (target.parentTarget != null) { // Emit a call to the superclass injector, if any. injectBuilder.addStatement("$T.inject(finder, target, source)", ClassName.bestGuess(target.parentTarget + Dart.INJECTOR_SUFFIX)); } // Local variable in which all extras will be temporarily stored. injectBuilder.addStatement("Object object"); // Loop over each extras injection and emit it. for (ExtraInjection injection : target.injectionMap.values()) { emitExtraInjection(injectBuilder, injection); } builder.addMethod(injectBuilder.build()); } private void emitExtraInjection(MethodSpec.Builder builder, ExtraInjection injection) { builder.addStatement("object = finder.getExtra(source, $S)", injection.getKey()); List<Binding> requiredBindings = injection.getRequiredBindings(); if (!requiredBindings.isEmpty()) { builder.beginControlFlow("if (object == null)") .addStatement("throw new IllegalStateException(\"Required extra with key '$L' for $L " + "was not found. If this extra is optional add '@Nullable' annotation.\")", injection.getKey(), emitHumanDescription(requiredBindings)) .endControlFlow(); emitFieldBindings(builder, injection); } else { // an optional extra, wrap it in a check to keep original value, if any builder.beginControlFlow("if (object != null)"); emitFieldBindings(builder, injection); builder.endControlFlow(); } } private void emitFieldBindings(MethodSpec.Builder builder, ExtraInjection injection) { Collection<FieldBinding> fieldBindings = injection.getFieldBindings(); if (fieldBindings.isEmpty()) { return; } for (FieldBinding fieldBinding : fieldBindings) { builder.addCode("target.$L = ", fieldBinding.getName()); if (fieldBinding.isParcel()) { builder.addCode("org.parceler.Parcels.unwrap((android.os.Parcelable) object);\n"); } else { emitCast(builder, fieldBinding.getType()); builder.addCode("object;\n"); } } } private void emitCast(MethodSpec.Builder builder, TypeMirror fieldType) { builder.addCode("($T) ", TypeName.get(fieldType)); } //TODO add android annotations dependency to get that annotation plus others. /** Visible for testing*/ String emitHumanDescription(List<Binding> bindings) { StringBuilder builder = new StringBuilder(); switch (bindings.size()) { case 1: builder.append(bindings.get(0).getDescription()); break; case 2: builder.append(bindings.get(0).getDescription()) .append(" and ") .append(bindings.get(1).getDescription()); break; default: for (int i = 0, count = bindings.size(); i < count; i++) { Binding requiredField = bindings.get(i); if (i != 0) { builder.append(", "); } if (i == count - 1) { builder.append("and "); } builder.append(requiredField.getDescription()); } break; } return builder.toString(); } }