|
| 1 | +package jtamaro.optics.processor; |
| 2 | + |
| 3 | +import java.util.List; |
| 4 | +import java.util.SequencedMap; |
| 5 | +import java.util.SequencedSet; |
| 6 | +import javax.lang.model.type.DeclaredType; |
| 7 | +import javax.lang.model.type.TypeMirror; |
| 8 | +import javax.lang.model.util.Types; |
| 9 | + |
| 10 | +final class ComponentTraversalsGenerator { |
| 11 | + |
| 12 | + private static final String METHOD_LENS_AT_INDEX = """ |
| 13 | + private static <T> Lens<Sequence<T>, Sequence<T>, T, T> lensAtIndex(int idx) { |
| 14 | + return new Lens<>() { |
| 15 | + @Override |
| 16 | + public Sequence<T> over(Function1<T, T> lift, Sequence<T> source) { |
| 17 | + return source.zipWithIndex() |
| 18 | + .map(p -> p.second() == idx |
| 19 | + ? lift.apply(p.first()) |
| 20 | + : p.first()); |
| 21 | + } |
| 22 | +
|
| 23 | + @Override |
| 24 | + public T view(Sequence<T> source) { |
| 25 | + return source.drop(idx).first(); |
| 26 | + } |
| 27 | + }; |
| 28 | + } |
| 29 | + """; // Lower indentation on purpose! |
| 30 | + |
| 31 | + private ComponentTraversalsGenerator() { |
| 32 | + } |
| 33 | + |
| 34 | + /** |
| 35 | + * Generate a traversal that allows to focus on a lens for each element of a |
| 36 | + * record component of type list. |
| 37 | + */ |
| 38 | + public static List<String> generate( |
| 39 | + Types types, |
| 40 | + TypeMirror targetRecordType, |
| 41 | + SequencedMap<String, TypeMirror> allComponents, |
| 42 | + SequencedSet<String> allComponentNames |
| 43 | + ) { |
| 44 | + return allComponents.sequencedEntrySet() |
| 45 | + .stream() |
| 46 | + .map(e -> traversalForComponent( |
| 47 | + types, |
| 48 | + targetRecordType, |
| 49 | + e.getKey(), |
| 50 | + getFirstTypeArgument(e.getValue()), |
| 51 | + allComponentNames |
| 52 | + )) |
| 53 | + .toList(); |
| 54 | + } |
| 55 | + |
| 56 | + /** |
| 57 | + * Generate a traversal that allows to focus on a lens for a single element of |
| 58 | + * a record component of type Sequence. |
| 59 | + */ |
| 60 | + private static String traversalForComponent( |
| 61 | + Types types, |
| 62 | + TypeMirror sourceType, |
| 63 | + String targetName, |
| 64 | + TypeMirror targetElementType, |
| 65 | + SequencedSet<String> allComponentNames |
| 66 | + ) { |
| 67 | + final String sourceTypeStr = Utils.formatType(types, sourceType); |
| 68 | + final String componentElementTypeStr = Utils.formatType(types, targetElementType); |
| 69 | + final String overImpl = Utils.newRecordInstanceExpr( |
| 70 | + sourceTypeStr, |
| 71 | + targetName, |
| 72 | + allComponentNames, |
| 73 | + ignored -> "newValue" |
| 74 | + ); |
| 75 | + return String.format(""" |
| 76 | + public static final Traversal<%1$s, %1$s, Lens<%1$s, %1$s, %2$s, %2$s>, %2$s> %3$sElements = new Traversal<>() { |
| 77 | + @Override |
| 78 | + public %1$s over(Function1<Lens<%1$s, %1$s, %2$s, %2$s>, %2$s> lift, |
| 79 | + %1$s source) { |
| 80 | + final Sequence<%2$s> newValue = source.%3$s().zipWithIndex() |
| 81 | + .map(p -> lift.apply(%3$s.then(lensAtIndex(p.second())))); |
| 82 | + return %4$s; |
| 83 | + } |
| 84 | +
|
| 85 | + @Override |
| 86 | + public <R> R foldMap(R neutralElement, |
| 87 | + Function2<R, R, R> reducer, |
| 88 | + Function1<Lens<%1$s, %1$s, %2$s, %2$s>, R> map, |
| 89 | + %1$s source) { |
| 90 | + return source.%3$s().zipWithIndex().foldLeft( |
| 91 | + neutralElement, |
| 92 | + (acc, p) -> reducer.apply( |
| 93 | + acc, |
| 94 | + map.apply(%3$s.then(lensAtIndex(p.second()))) |
| 95 | + ) |
| 96 | + ); |
| 97 | + } |
| 98 | + }; |
| 99 | + """, // Lower indentation on purpose! |
| 100 | + sourceTypeStr, // S, T |
| 101 | + componentElementTypeStr, // A, B |
| 102 | + targetName, // 3: target component name |
| 103 | + overImpl // 4: new instance in over |
| 104 | + ); |
| 105 | + } |
| 106 | + |
| 107 | + /** |
| 108 | + * Get the code that implements the method "lensAtIndex(int)". |
| 109 | + */ |
| 110 | + public static String getMethodLensAtIndexImpl() { |
| 111 | + return METHOD_LENS_AT_INDEX; |
| 112 | + } |
| 113 | + |
| 114 | + /** |
| 115 | + * Get the {@link TypeMirror} of the first type parameter of the given |
| 116 | + * {@link TypeMirror}. |
| 117 | + */ |
| 118 | + private static TypeMirror getFirstTypeArgument(TypeMirror typeMirror) { |
| 119 | + if (typeMirror instanceof DeclaredType declaredType) { |
| 120 | + final List<? extends TypeMirror> typeArgs = declaredType.getTypeArguments(); |
| 121 | + if (!typeArgs.isEmpty()) { |
| 122 | + return typeArgs.getFirst(); |
| 123 | + } |
| 124 | + } |
| 125 | + throw new IllegalArgumentException("Provided type " + typeMirror + " has no type parameter"); |
| 126 | + } |
| 127 | +} |
0 commit comments