package com.sora.util.akatsuki.analyzers; import java.util.Arrays; import java.util.HashMap; import java.util.Optional; import javax.lang.model.type.ArrayType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import com.sora.util.akatsuki.AndroidTypes; import com.sora.util.akatsuki.MustacheUtils; import com.sora.util.akatsuki.TransformationContext; import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer.Analysis; import com.sora.util.akatsuki.analyzers.Element.Builder.SetterMode; import com.sora.util.akatsuki.analyzers.PrimitiveTypeAnalyzer.Type; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.CodeBlock.Builder; public class ArrayTypeAnalyzer extends CascadingTypeAnalyzer<ArrayTypeAnalyzer, ArrayType, Analysis> { static final AndroidTypes[] SUPPORTED_ARRAY_TYPES = { AndroidTypes.Parcelable, AndroidTypes.CharSequence, AndroidTypes.String }; // for nested arrays (multidimensional arrays will have the same type) private final int depth; private static final String LOOP_EXPRESSION = "for (int $L = $L; $L < $L; $L++)"; @Override protected ArrayTypeAnalyzer createInstance(TransformationContext context) { return new ArrayTypeAnalyzer(context, depth); } public ArrayTypeAnalyzer(TransformationContext context) { super(context); this.depth = 0; } private ArrayTypeAnalyzer(TransformationContext context, int depth) { super(context); this.depth = depth; } @Override public Analysis createAnalysis(InvocationContext<ArrayType> context) throws UnknownTypeException { final TypeMirror component = context.field.refinedMirror().getComponentType(); // bundle supports all primitives if (utils().isPrimitive(component)) { return cascade(new PrimitiveTypeAnalyzer(this, Type.UNBOXED).suffix("Array"), context, component); } // bundle also supports some built in types final Optional<AndroidTypes> found = Arrays.stream(SUPPORTED_ARRAY_TYPES).sorted() .filter(t -> utils().isAssignable(component, utils().of(t.className), true)) .findFirst(); if (found.isPresent()) { return cascade(new ObjectTypeAnalyzer(this).suffix("Array") .target(types().getArrayType(found.get().asMirror(this))), context, component); } else { // everything else final Element<TypeMirror> refined = context.field.refine(component); CascadingTypeAnalyzer<?, ? extends TypeMirror, ? extends Analysis> resolved; if (component.getKind() == TypeKind.ARRAY) { throw new ConversionException( context.field + " is a multidimentional array, which is not supported " + "(open an issue on Github if you want it)"); // multidimensional array, we need to supply depth // resolved = new ArrayTypeAnalyzer(this, depth + 1); } else { // TODO, never really happens, handled by found type above resolved = resolve(refined).cast(TypeCastStrategy.NO_CAST); } // give up if (resolved == null) throw new UnknownTypeException(context.field); final String accessor = fieldAccessor(context); String currentCounter = counterName(context, depth); final Analysis cascade = cascade(resolved, context, f -> { return f.toBuilder().type(component) .keyName(SetterMode.APPEND, " + \"_\"+" + currentCounter) .accessor(SetterMode.APPEND, "[" + currentCounter + "]").build(); }); final HashMap<String, Object> scope = new HashMap<>(); scope.put("bundle", context.bundleContext.bundleObjectName()); scope.put("accessor", accessor + ".length"); scope.put("lengthKey", "\"length_" + currentCounter + "_r\"" + (depth == 0 ? "" : " + " + counterName(context, depth - 1))); scope.put("lengthVar", "length_" + currentCounter); final String lengthStatement = MustacheUtils.render(scope, context.type == InvocationType.SAVE ? "{{bundle}}.putInt({{lengthKey}}, {{accessor}})" : "int {{lengthVar}} = {{bundle}}.getInt({{lengthKey}})"); cascade.wrap(original -> { final Builder builder = CodeBlock.builder(); if (context.type == InvocationType.SAVE) { builder.beginControlFlow("if($L != null)", accessor) .addStatement(lengthStatement) .beginControlFlow(LOOP_EXPRESSION, currentCounter, 0, currentCounter, accessor + ".length", currentCounter) .add(original).endControlFlow().endControlFlow(); } else { builder.addStatement(lengthStatement) .beginControlFlow(LOOP_EXPRESSION, currentCounter, 0, currentCounter, scope.get("lengthVar"), currentCounter) .add(original).endControlFlow(); } return builder.build().toString(); }); return cascade; } } private String counterName(InvocationContext<?> context, int depth) { return context.field.uniqueName() + "_" + depth; } }