package com.sora.util.akatsuki.analyzers; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import javax.lang.model.type.TypeMirror; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.escape.CharEscaperBuilder; import com.google.common.escape.Escaper; import com.sora.util.akatsuki.BundleContext; import com.sora.util.akatsuki.Log; import com.sora.util.akatsuki.MustacheUtils; import com.sora.util.akatsuki.ProcessorContext; import com.sora.util.akatsuki.TransformationContext; import com.sora.util.akatsuki.analyzers.CascadingTypeAnalyzer.Analysis; public abstract class CascadingTypeAnalyzer<S extends CascadingTypeAnalyzer<S, T, A>, T extends TypeMirror, A extends Analysis> extends TransformationContext { private int cascadeDepth; public enum InvocationType { SAVE, RESTORE } public static final Escaper ESCAPER = new CharEscaperBuilder() .addEscapes(new char[] { '[', ']', '.', '(', ')' }, "_").toEscaper(); protected TypeMirror targetMirror; protected TypeCastStrategy strategy = TypeCastStrategy.AUTO_CAST; protected String suffix = ""; S target(TypeMirror mirror) { final S instance = createInstance(this); cloneFields(instance); instance.targetMirror = mirror; return instance; } S cast(TypeCastStrategy strategy) { final S instance = createInstance(this); cloneFields(instance); instance.strategy = strategy; return instance; } S suffix(String suffix) { final S instance = createInstance(this); cloneFields(instance); instance.suffix = this.suffix + suffix; return instance; } private void cloneFields(CascadingTypeAnalyzer<?, ?, ?> target) { target.targetMirror = this.targetMirror; target.suffix = this.suffix; target.strategy = this.strategy; } protected abstract S createInstance(TransformationContext context); public enum TypeCastStrategy { NO_CAST((context, from, to) -> null), // FORCE_CAST((context, from, to) -> to.toString()), // AUTO_CAST((context, from, to) -> context.utils().isAssignable(from, to, true) ? null : to.toString());// private final ClassCastFunction function; TypeCastStrategy(ClassCastFunction function) { this.function = function; } public interface ClassCastFunction { String createCastExpression(ProcessorContext context, TypeMirror from, TypeMirror to); } } public CascadingTypeAnalyzer(TransformationContext context) { super(context); this.cascadeDepth = 0; } public static class InvocationContext<T extends TypeMirror> { public final BundleContext bundleContext; public final Element<T> field; public final InvocationType type; public InvocationContext(BundleContext bundleContext, Element<T> field, InvocationType type) { this.bundleContext = bundleContext; this.field = field; this.type = type; } } protected abstract A createAnalysis(InvocationContext<T> context) throws UnknownTypeException; protected String fieldAccessor(InvocationContext<?> context) { return context.field.accessor(fn -> { String objectName = context.bundleContext.sourceObjectName(); return Strings.isNullOrEmpty(objectName) ? fn : objectName + "." + fn; }); } @SuppressWarnings("unchecked") public A transform(BundleContext bundleContext, Element<?> element, InvocationType type) throws UnknownTypeException { Log.verbose(this, type + ">" + cascadeDepth + Strings.repeat(" ", cascadeDepth) + "\\Cascade:" + toString() + " -> " + element.toString() + " with " + bundleContext); return createAnalysis(new InvocationContext<>(bundleContext, (Element<T>) element, type)); } protected Analysis cascade(CascadingTypeAnalyzer<?, ?, ?> transformation, InvocationContext<?> context, TypeMirror mirror) throws UnknownTypeException { return cascade(transformation, context, f -> f.refine(mirror)); } protected Analysis cascade(CascadingTypeAnalyzer<?, ?, ?> transformation, InvocationContext<?> context, Function<Element<?>, Element<?>> elementTransformation) throws UnknownTypeException { transformation.cascadeDepth = cascadeDepth + 1; return transformation.transform(context.bundleContext, elementTransformation.apply(context.field), context.type); } protected TypeMirror targetOrElse(TypeMirror mirror) { return targetMirror != null ? targetMirror : mirror; } public interface Analysis { void prependOnce(Analysis analysis); void appendOnce(Analysis analysis); void prependOnce(String string); void appendOnce(String string); String emit(); void transform(CodeTransform transform); void wrap(CodeTransform transformation); String preEmitOnce(); String postEmitOnce(); } @Override public String toString() { return MoreObjects.toStringHelper(this).add("targetMirror", targetMirror) .add("strategy", strategy).add("suffix", suffix).toString(); } static abstract class ChainedAnalysis implements Analysis { private final List<String> preEmit = new ArrayList<>(); private final List<String> postEmit = new ArrayList<>(); protected final CodeTransforms transforms = new CodeTransforms(); @Override public void prependOnce(Analysis analysis) { appendAnalysis(preEmit, analysis); } @Override public void appendOnce(Analysis analysis) { appendAnalysis(postEmit, analysis); } @Override public void wrap(CodeTransform transform) { transforms.append(transform); } @Override public void prependOnce(String string) { preEmit.add(string); } @Override public void appendOnce(String string) { postEmit.add(string); } @Override public String preEmitOnce() { return preEmit.stream().collect(Collectors.joining()); } @Override public String postEmitOnce() { return postEmit.stream().collect(Collectors.joining()); } private void appendAnalysis(List<String> list, Analysis analysis) { list.add(analysis.preEmitOnce()); list.add(analysis.emit()); list.add(analysis.postEmitOnce()); } } static class DefaultAnalysis extends ChainedAnalysis { private final Map<String, Object> scope; private RawStatement expression; DefaultAnalysis(Map<String, Object> scope, RawStatement statement) { this.scope = scope; this.expression = statement; } @Override public String emit() { return preEmitOnce() + MustacheUtils.render(scope, transforms.apply(expression.render(scope))) + postEmitOnce(); } @Override public void transform(CodeTransform transform) { expression.transform(transform); } static <T extends TypeMirror> DefaultAnalysis of(CascadingTypeAnalyzer<?, T, ?> analyzer, RawStatement statement, InvocationContext<T> context, Map<String, Object> extraScope) { final HashMap<String, Object> scope = new HashMap<>(); scope.put("fieldName", analyzer.fieldAccessor(context)); scope.put("keyName", context.field.keyName()); scope.put("bundle", context.bundleContext.bundleObjectName()); if (extraScope != null) scope.putAll(extraScope); return new DefaultAnalysis(scope, statement); } static <T extends TypeMirror> DefaultAnalysis of(CascadingTypeAnalyzer<?, T, ?> analyzer, String methodName, InvocationContext<T> context) { final HashMap<String, Object> scope = new HashMap<>(); scope.put("methodName", methodName); RawStatement statement; if (context.type == InvocationType.SAVE) { // field mirror -> target mirror , we don't need to cast statement = new InvocationStatement( "{{bundle}}.put{{methodName}}({{keyName}}, {{fieldName}})"); } else { if (analyzer.targetMirror != null) { // when restore, target mirror is the return mirror so we // cast in opposite detection: target mirror -> field mirror final String castExpression = analyzer.strategy.function.createCastExpression( analyzer, analyzer.targetMirror, context.field.fieldMirror()); if (!Strings.isNullOrEmpty(castExpression)) { scope.put("cast", true); scope.put("castExpression", castExpression); } } statement = new InvocationAssignmentStatement("{{fieldName}}", "{{#cast}}({{castExpression}}){{/cast}}{{bundle}}.get{{methodName}}({{keyName}})"); } return of(analyzer, statement, context, scope); } } public static class ConversionException extends RuntimeException { public ConversionException(String message) { super(message); } public ConversionException(String message, Throwable cause) { super(message, cause); } } public static class UnknownTypeException extends ConversionException { // public final Field<?> field; public UnknownTypeException(Element<?> element) { super("unknown type " + element + "(" + element.refinedMirror().getClass() + ")"); } public UnknownTypeException(TypeMirror mirror) { super("unknown type " + mirror + "(" + mirror.getClass() + ")"); } } @FunctionalInterface public interface CodeTransform extends Function<String, String> { } public static class CodeTransforms { private final ArrayList<CodeTransform> transforms = new ArrayList<>(); public void append(CodeTransform transform) { this.transforms.add(transform); } public String apply(String source) { for (CodeTransform transform : transforms) { source = transform.apply(source); } return source; } } public interface RawStatement { String render(Object scope); void transform(CodeTransform transformation); } public static class InvocationStatement implements RawStatement { private String template; public InvocationStatement(String template) { this.template = template; } @Override public String render(Object scope) { return MustacheUtils.render(scope, template) + ";\n"; } @Override public void transform(CodeTransform transform) { template = transform.apply(template); } } public static class InvocationAssignmentStatement extends InvocationStatement { private final String variable; public InvocationAssignmentStatement(String variable, String template) { super(template); this.variable = variable; } @Override public String render(Object scope) { return MustacheUtils.render(scope, variable) + " = " + super.render(scope); } } }