42 | * If different bounds are required for different types, you can use
43 | * multiple {@link TypeConstraint}s together
44 | *
45 | */
46 | Bound bound() default Bound.EXACTLY;
47 |
48 | /**
49 | * Class matching bounds
50 | */
51 | enum Bound {
52 | /**
53 | * Has the same meaning of {@code A.class.equals(B.class)}. If used on
54 | * annotations,the bound matches classes that is being annotated
55 | * directly.
56 | */
57 | EXACTLY,
58 |
59 | /**
60 | * Has the same meaning of {@code A.class.isAssignableFrom(B.class)}. If
61 | * used on annotations, the bound matches classes that inherits from a
62 | * class that contains the annotation.
63 | */
64 |
65 | EXTENDS,
66 |
67 | /**
68 | * Has the same meaning of
69 | * {@code A.class.equals(B.class.getSuperclass())} where the inheritance
70 | * hierarchy is transversed and checked for equality all the way up to
71 | * {@link Object}. If used on annotations, the bound matches classes
72 | * that inherits from a class that contains the annotation.
73 | */
74 | SUPER
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/akatsuki-compiler/src/test/java/com/sora/util/akatsuki/IntegrationTestBase.java:
--------------------------------------------------------------------------------
1 | package com.sora.util.akatsuki;
2 |
3 | import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
4 |
5 | import java.lang.annotation.Annotation;
6 | import java.util.Collections;
7 | import java.util.concurrent.atomic.AtomicLong;
8 |
9 | import javax.annotation.processing.Processor;
10 | import javax.lang.model.element.Modifier;
11 | import javax.tools.JavaFileObject;
12 |
13 | import com.google.common.truth.Truth;
14 | import com.google.testing.compile.CompileTester;
15 | import com.squareup.javapoet.AnnotationSpec;
16 | import com.squareup.javapoet.ClassName;
17 | import com.squareup.javapoet.FieldSpec;
18 | import com.squareup.javapoet.FieldSpec.Builder;
19 | import com.squareup.javapoet.TypeName;
20 |
21 | // this class should not contain any static inner classes because @RunWith(Enclosed.class)
22 | // with discover them and treat them as test classes
23 | public abstract class IntegrationTestBase {
24 |
25 | public static final String TEST_PACKAGE = "test";
26 | public static final String TEST_CLASS = "TestClass";
27 | public static TypeName STRING_TYPE = ClassName.get(String.class);
28 | private static AtomicLong classIdentifier = new AtomicLong();
29 |
30 | // creates a field spec
31 | public static FieldSpec field(TypeName typeName, String name,
32 | Class extends Annotation> annotation, Modifier... modifiers) {
33 | return field(typeName, name, annotation, null, modifiers);
34 |
35 | }
36 |
37 | // allows custom annotation spec
38 | public static FieldSpec field(TypeName typeName, String name, AnnotationSpec spec,
39 | Modifier... modifiers) {
40 | final Builder builder = FieldSpec.builder(typeName, name, modifiers);
41 | if (spec != null)
42 | builder.addAnnotation(spec);
43 | return builder.build();
44 | }
45 |
46 | // allows custom initializer
47 | public static FieldSpec field(TypeName typeName, String name,
48 | Class extends Annotation> annotation, String initializer, Modifier... modifiers) {
49 | final Builder builder = FieldSpec.builder(typeName, name, modifiers);
50 | if (annotation != null)
51 | builder.addAnnotation(annotation);
52 | if (initializer != null)
53 | builder.initializer(initializer);
54 | return builder.build();
55 | }
56 |
57 | public static String generateClassName() {
58 | return TEST_CLASS + classIdentifier.incrementAndGet();
59 | }
60 |
61 | public Iterable
30 | * Provided variables:
31 | *
55 | * Provided variables:
56 | * , Map super Object, ? extends List extends
59 | // CharSequence>>>
60 | // spacialMap3;
61 |
62 | // static class Generic
32 | *
36 | * Sample usage:
37 | *
38 | *
39 | * {@code {{bundle}}.putInt(\"{{fieldName}}\", {{fieldName}});\n}
40 | *
41 | *
42 | * Assuming {{fieldName}} is myInt, the template above will produce
43 | *
44 | *
45 | * {@code bundle.putInt("myInt", myInt);}
46 | *
47 | *
48 | */
49 | StatementTemplate save();
50 |
51 | /**
52 | * The restore template. See {@link #save()} for explanation on how
53 | * templates work.
54 | *
57 | *
61 | */
62 | StatementTemplate restore();
63 |
64 | /**
65 | * Defines how types are matched, see {@link TypeFilter} for more details on
66 | * how to use
67 | *
68 | */
69 | TypeFilter[]filters();
70 |
71 | /**
72 | * Execution time of the custom template. Setting this to "BEFORE" while
73 | * overriding any of the built-in types could cause unforeseen issues
74 | *
75 | */
76 | Execution execution() default Execution.BEFORE;
77 |
78 | /**
79 | * Execution times for the template
80 | */
81 | enum Execution {
82 | /**
83 | * The template is applied before any of the built-in type converter is
84 | * invoked
85 | */
86 | BEFORE,
87 |
88 | /**
89 | * The template is ignored and not applied at all. This is useful for
90 | * temporarily disabling misbehaving templates while debugging
91 | */
92 | NEVER
93 | }
94 |
95 | @interface StatementTemplate {
96 |
97 | enum Type {
98 | INVOCATION, ASSIGNMENT
99 | }
100 |
101 | Type type() default Type.INVOCATION;
102 |
103 | String value();
104 |
105 | String variable() default "";
106 |
107 | }
108 |
109 | }
110 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 | 0.2.0 *(2015/10/23)*
3 | ----------------------------
4 |
5 | Major refactor, cleaned up most of the configuration annotations.
6 |
7 | Additions
8 |
9 | - @AkatsukiConfig for global configuration
10 | - @RetainConfig is now a type annotation
11 | - Flexible compiler flags and optimisation flags
12 | - Better verbose messages
13 | - Tests now covers the public facing API (such as the `Akatsuki` class)
14 |
15 | Removed
16 |
17 | - `Optimisation` is removed in favour of flags, this allows more flexibility in configuring
18 | - Multidimentional arrays is no longer supported in ETS, it wasn't properly implemented anyway
19 | - Arrays of types that take generic arguments are no longer supported due to non existing use case
20 |
21 | Changes
22 |
23 | - Over half of the tests are reviewed and refactored if required
24 | - All `Test` suffix changed to `IntegrationTest` to better reflect the fact that all of them are integration tests
25 |
26 |
27 | If you want multidimentional arrays and arrays of generic type back, please open a issue (and send a PR if you want it very badly)
28 |
29 |
30 |
31 |
32 |
33 | 0.1.0 *(2015/10/18)*
34 | ----------------------------
35 | Akatsuki now takes care of the entire IPC, that includes the already supported state restoration plus argument passing!
36 |
37 | Additions
38 |
39 | - Argument passing for any object (Fragment, Activity, Service, etc)
40 | - Compile-time checked builders
41 | - Free `BundleBuilder`
42 | - Proguard rules added (in README)
43 |
44 | Changes
45 |
46 | - `TypeConverter` is moved into a new annotation `@With` to accommodate `@Arg`
47 |
48 | Check out the Wiki for more information on how to pass arguments using `@Arg` and the builder
49 |
50 |
51 | 0.0.3 *(2015/08/27)*
52 | ----------------------------
53 |
54 |
55 | Additions
56 |
57 | - Compiler arguments added (use `-Aakatsuki.
55 | * Additional classes will be generated when this is enabled
56 | */
57 | CLASS_LUT,
58 |
59 | /**
60 | * Eliminate runtime traverse of the inheritance hierarchy. To be used
61 | * in conjunction with {@link #CLASS_LUT} only, this flag adds
62 | * additional entries to the table
63 | */
64 | VECTORIZE_INHERITANCE,
65 |
66 | /**
67 | * Caches created instances in memory; instances are weakly referenced
68 | * to prevent memory leak
69 | */
70 | CACHE_INSTANCES,
71 |
72 | /**
73 | * Compacts the source by using inheritance(following the original
74 | * inheritance tree)
75 | */
76 | TREE_COMPACTION
77 |
78 | }
79 |
80 | /**
81 | * Normal flags that changes the behavior of the compiler
82 | */
83 | enum Flags {
84 |
85 | /**
86 | * Warnings issued will be treated as error and stop the compilation
87 | */
88 | WARNING_AS_ERROR,
89 |
90 | /**
91 | * Performs a dry run by not writing any of the generated codes
92 | */
93 | ANALYSE_ONLY,
94 |
95 | /**
96 | * Disables the entire compiler, all annotations will be ignored. This
97 | * is useful for debugging
98 | */
99 | DISABLE_COMPILER
100 |
101 | }
102 |
103 | /**
104 | * Compile time and runtime logging options
105 | */
106 | LoggingLevel loggingLevel() default LoggingLevel.ERROR_ONLY;
107 |
108 | /**
109 | * Fields with the {@code transient} keyword will be ignored by default
110 | */
111 | boolean allowTransient() default false;
112 |
113 | /**
114 | * Fields with the {@code volatile} keyword will be ignored by default
115 | */
116 | boolean allowVolatile() default false;
117 |
118 | /**
119 | * Sets the global default for {@link ArgConfig}
120 | */
121 | ArgConfig argConfig() default @ArgConfig
122 | ;
123 |
124 | /**
125 | * Sets the global default for {@link RetainConfig}
126 | */
127 | RetainConfig retainConfig() default @RetainConfig
128 | ;
129 |
130 | /**
131 | * Optimisation flags for the compiler, see
132 | * {@link com.sora.util.akatsuki.AkatsukiConfig.OptFlags} for details. You
133 | * can specify multiple flags, repeated flags will throw a warning
134 | */
135 | OptFlags[]optFlags() default { OptFlags.CLASS_LUT, OptFlags.VECTORIZE_INHERITANCE,
136 | OptFlags.CACHE_INSTANCES, OptFlags.TREE_COMPACTION };
137 |
138 | /**
139 | * Flags for the compiler, see
140 | * {@link com.sora.util.akatsuki.AkatsukiConfig.Flags} for details. You can
141 | * specify multiple flags, repeated flags will throw a warning
142 | */
143 | Flags[]flags() default {};
144 |
145 | }
146 |
--------------------------------------------------------------------------------
/akatsuki-compiler/src/main/java/com/sora/util/akatsuki/ArgumentBuildersModel.java:
--------------------------------------------------------------------------------
1 | package com.sora.util.akatsuki;
2 |
3 | import static com.sora.util.akatsuki.SourceUtils.var;
4 |
5 | import java.io.IOException;
6 | import java.util.HashMap;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Map.Entry;
10 | import java.util.Optional;
11 | import java.util.stream.Collectors;
12 |
13 | import javax.annotation.processing.Filer;
14 | import javax.lang.model.element.Modifier;
15 |
16 | import com.sora.util.akatsuki.models.ClassInfo;
17 | import com.sora.util.akatsuki.models.SourceClassModel;
18 | import com.sora.util.akatsuki.models.SourceCollectingModel;
19 | import com.squareup.javapoet.ClassName;
20 | import com.squareup.javapoet.JavaFile;
21 | import com.squareup.javapoet.MethodSpec;
22 | import com.squareup.javapoet.ParameterizedTypeName;
23 | import com.squareup.javapoet.TypeSpec;
24 |
25 | class ArgumentBuildersModel extends SourceCollectingModel
22 | * EX:
23 | * {@code a(1).b(2).c(3)}, where {@code a()} returns a class with only
24 | * {@code b()} visible
25 | * This builder is the safe without runtime check as it implicitly
26 | * forbid you from omitting mandatory args
27 | *
28 | */
29 | // TODO for next release
30 | // FLUENT(ReturnType.SUBCLASSED,
31 | // Check.NONE), /**
32 | // * Builder returns itself for chaining, no
33 | // * ordering is enforced, checked is done at
34 | // * runtime
35 | // * EX:
36 | // * {@code }a(2).b(2).a(1).c(3) } , notice that
37 | // * repeated args and unordered invocation are
38 | // * allowed
39 | // * When a mandatory field is omitted, an
40 | // * {@link IllegalArgumentException} is thrown
41 | // */
42 | CHAINED_CHECKED(ReturnType.CHAINED,
43 | Check.RUNTIME), /**
44 | * Builder setters returns itself, checking is
45 | * done at runtime
46 | */
47 | CHAINED_UNCHECKED(ReturnType.CHAINED,
48 | Check.NONE), /**
49 | * Builder setters return void, behaves like
50 | * {@link #CHAINED_CHECKED} but without chaining
51 | */
52 | CHECKED(ReturnType.VOID,
53 | Check.RUNTIME), /**
54 | * Builder returns void, behaves like
55 | * {@link #CHAINED_UNCHECKED} but without
56 | * chaining
57 | */
58 | UNCHECKED(ReturnType.VOID, Check.NONE);
59 |
60 | final ReturnType returnType;
61 | final Check check;
62 |
63 | BuilderType(ReturnType returnType, Check check) {
64 | this.returnType = returnType;
65 | this.check = check;
66 | }
67 |
68 | enum ReturnType {
69 | CHAINED, SUBCLASSED, VOID
70 | }
71 |
72 | enum Check {
73 | RUNTIME, NONE
74 | // TODO compile time maybe? I guess we can get caller(traverse the
75 | // entire source tree) and then check for incomplete calls.... but
76 | // that's writing half a compiler ....
77 | }
78 | }
79 |
80 | /**
81 | * Specifies what type of builder will be genrerated, see
82 | * {@link ArgConfig.BuilderType} for all supported types
83 | */
84 | BuilderType type() default BuilderType.CHAINED_CHECKED;
85 |
86 | @interface BuilderNamingRules {
87 |
88 | enum Type {
89 | ANDROID, UNDERSCORE, SIMPLE, TEMPLATE
90 | }
91 |
92 | Type value() default Type.SIMPLE;
93 |
94 | String splitWith() default "";
95 |
96 | String template();
97 | }
98 |
99 | interface MethodTransformation {
100 |
101 | String transform(String name);
102 |
103 | }
104 |
105 | /**
106 | * Naming rules to be used while the builder class is generated, the rules
107 | * are applied in order of declaration (array order) to each element.For
108 | * example, to add a prefix to a field while keeping the camelCase,
109 | * something like this can be used:
110 | * 1. Transform the first letter to upper case
111 | * and the second rule to append
112 | *
113 | */
114 | // BuilderNamingRules[]namingRules() default {};
115 |
116 | // TODO next version, no implementation yet
117 | // enum FieldNameConvention {
118 | // /**
119 | // * Fields prefixed with m for member (ex: mFoo, mBar, mFooBar)
120 | // */
121 | // ANDROID,
122 | //
123 | // /**
124 | // * Fields with no prefix (ex: foo, bar, fooBar)
125 | // */
126 | // SIMPLE
127 | // }
128 | //
129 | // /**
130 | // * Defines how field names are transformed when the builder is generated
131 | // */
132 | // FieldNameConvention convention() default FieldNameConvention.SIMPLE;
133 |
134 | /**
135 | * Defines how builder method name are sorted
136 | */
137 | enum Sort {
138 | CODE, INDEX, LEXICOGRAPHICAL, RANDOM
139 | }
140 |
141 | /**
142 | * Used in conjunction with {@link Sort} to specify order
143 | */
144 | enum Order {
145 | ASC, DSC
146 | }
147 |
148 | /**
149 | * Defines the order of the generated builder methods
150 | */
151 | Sort sort() default Sort.CODE;
152 |
153 | /**
154 | * The order of the {@link #sort()}, defaults to {@link Order#ASC}
155 | */
156 | Order order() default Order.ASC;
157 |
158 | Class extends ArgConcludingBuilder>concludingBuilder() default VoidBuilder.class;
159 |
160 | /**
161 | * Whether the class should be processed; useful for debugging
162 | */
163 | boolean enabled() default true;
164 |
165 | }
166 |
--------------------------------------------------------------------------------
/akatsuki-compiler/src/test/java/com/sora/util/akatsuki/BuilderIntegrationTestBase.java:
--------------------------------------------------------------------------------
1 | package com.sora.util.akatsuki;
2 |
3 | import java.lang.reflect.Array;
4 | import java.lang.reflect.Method;
5 | import java.util.Arrays;
6 | import java.util.HashSet;
7 | import java.util.function.Function;
8 | import java.util.stream.Collectors;
9 | import java.util.stream.Stream;
10 |
11 | import javax.lang.model.element.Modifier;
12 |
13 | import android.support.v4.app.Fragment;
14 |
15 | import com.google.common.collect.Sets;
16 | import com.sora.util.akatsuki.ArgConfig.BuilderType;
17 | import com.sora.util.akatsuki.BuilderTestEnvironment.SingleBuilderTester;
18 | import com.squareup.javapoet.AnnotationSpec;
19 |
20 | public abstract class BuilderIntegrationTestBase extends IntegrationTestBase {
21 |
22 | protected static final String TEST_PACKAGE_NAME = "testArg";
23 | public static final String TEST_FIELD_NAME = "a";
24 | public static final String[] TEST_PACKAGE_NAMES = new String[] { "a", "A", "b", "a.b", "a.b.c",
25 | "test.a", };
26 |
27 | public static final BuilderType[] CHECKED_TYPES = { BuilderType.CHECKED,
28 | BuilderType.CHAINED_CHECKED };
29 | public static final BuilderType[] UNCHECKED_TYPES = { BuilderType.UNCHECKED,
30 | BuilderType.CHAINED_UNCHECKED };
31 |
32 | static boolean isMethodPublic(Method method) {
33 | return java.lang.reflect.Modifier.isPublic(method.getModifiers());
34 | }
35 |
36 | static boolean methodParameterMatch(Method method, Class>... parameterClasses) {
37 | return Arrays.equals(method.getParameterTypes(), parameterClasses);
38 | }
39 |
40 | protected TestSource createTestSource(AnnotationSpec spec, String packageName,
41 | Class> parentClass, TestField... fields) {
42 | final HashSet