package com.sora.util.akatsuki.analyzers;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import com.sora.util.akatsuki.AndroidTypes;
import com.sora.util.akatsuki.TransformationContext;
import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer.Analysis;
import com.sora.util.akatsuki.analyzers.PrimitiveTypeAnalyzer.Type;
import com.squareup.javapoet.CodeBlock;
public class CollectionTypeAnalyzer
extends CascadingTypeAnalyzer<CollectionTypeAnalyzer, DeclaredType, Analysis> {
static final AndroidTypes[] SUPPORTED_ARRAY_LIST_TYPES = { AndroidTypes.CharSequence,
AndroidTypes.String };
static final InstantiationStatement COPY_CONSTRUCTOR = (analyzer, type, source) -> CodeBlock
.builder().add("new $L($L)", type.toString().replace("<E>", "<>"), source).build()
.toString();
interface InstantiationStatement {
String createStatement(CollectionTypeAnalyzer analyzer, TypeMirror type, String source);
}
public CollectionTypeAnalyzer(TransformationContext context) {
super(context);
}
@Override
protected CollectionTypeAnalyzer createInstance(TransformationContext context) {
return new CollectionTypeAnalyzer(context);
}
@Override
public Analysis createAnalysis(InvocationContext<DeclaredType> context)
throws UnknownTypeException {
// ArrayList is supported
final Optional<TypeMirror> arrayListType = getSupportedArrayListType(context.field);
final DeclaredType rawTypeMirror = context.field.refinedMirror();
if (arrayListType.isPresent()) {
final TypeMirror mirror = arrayListType.get();
final Analysis analysis;
Function<Element<?>, Element<?>> ARRAY_LIST_WRAPPER = (e) -> {
// if our list is not an ArrayList, wrap it in one
if (context.type == InvocationType.SAVE
&& !utils().isSameType(rawTypeMirror, true, utils().of(ArrayList.class))) {
e = e.toBuilder().fieldNameTransforms(o -> COPY_CONSTRUCTOR
.createStatement(this, utils().of(ArrayList.class), o)).build();
}
return e;
};
CascadingTypeAnalyzer<?, ?, ?> analyzer;
if (utils().isPrimitive(mirror)) {
analyzer = new PrimitiveTypeAnalyzer(this, Type.BOXED).suffix("ArrayList");
} else {
final DeclaredType methodMirror = utils()
.getDeclaredType((DeclaredType) utils().of(ArrayList.class), mirror);
analyzer = new ObjectTypeAnalyzer(this).suffix("ArrayList").target(methodMirror);
}
analysis = cascade(analyzer.cast(TypeCastStrategy.NO_CAST), context,
e -> ARRAY_LIST_WRAPPER.apply(e.refine(mirror)));
if (context.type == InvocationType.RESTORE) {
analysis.transform(original -> {
if (utils().isSameType(rawTypeMirror, true, utils().of(ArrayList.class))) {
// ArrayList is natively supported; we can't possibly
// know
// the type of the List at compile time :(
// TODO do some runtime checking for the interface List
// ?
return original;
}
if (!utils().isSameType(rawTypeMirror, true, utils().of(List.class))) {
if (utils().isSameType(rawTypeMirror, true, utils().of(LinkedList.class),
utils().of(CopyOnWriteArrayList.class))) {
return COPY_CONSTRUCTOR.createStatement(this, rawTypeMirror, original);
}
throw new UnknownTypeException(context.field);
}
return original;
});
}
return analysis;
} else {
final List<? extends TypeMirror> mirror = rawTypeMirror.getTypeArguments();
if (mirror.size() != 1) {
// collections only have one generic parameter
throw new UnknownTypeException(context.field);
}
final TypeMirror typeMirror = mirror.get(0);
final Analysis analysis = cascade(resolve(context.field.refine(typeMirror)), context,
typeMirror);
if(analysis == null)
throw new UnknownTypeException(context.field);
throw new UnsupportedOperationException("not implemented(yet?), use Parceler instead");
// return new Invocation() {
// @Override
// public String create() {
// return CodeBlock.builder()
// .beginControlFlow("for ()", from, to)
// .addStatement(invocation.create()).endControlFlow().build().toString();
// }
// };
}
}
private Optional<TypeMirror> getSupportedArrayListType(Element<DeclaredType> field) {
if (utils().isAssignable(field.refinedMirror(), utils().of(List.class), true)) {
final TypeMirror mirror = field.refinedMirror().getTypeArguments().get(0);
final TypeMirror integerMirror = utils().of(Integer.class);
final DeclaredType parcelableType = AndroidTypes.Parcelable.asMirror(this);
// bundle supports array list of some types and primitive of integer
// ONLY... why?
if (utils().isSameType(mirror, integerMirror, true)) {
// ArrayList<Integer>
return Optional.of(integerMirror);
// ArrayList<? extends Parcelable>
} else if (utils().isAssignable(mirror, parcelableType, true)) {
return Optional.of(parcelableType);
} else {
// ArrayList<SUPPORTED_TYPES>
return Arrays.stream(SUPPORTED_ARRAY_LIST_TYPES).sorted()
.filter(t -> utils().isSameType(mirror, utils().of(t.className), true))
.findFirst().map(t -> t.asMirror(this));
}
}
return Optional.empty();
}
}