featureClass,
112 | @NotNull String featureName) {
113 | //noinspection unchecked
114 | return (F) mFeatures.get(featureName);
115 | }
116 |
117 | protected void dispatch(Event event) {
118 | assertMainThread();
119 |
120 | if (mDispatchingEvent == null) {
121 | mDispatchingEvent = event;
122 |
123 | // dispatch event right away
124 | Event e = mDispatchingEvent;
125 | while (e != null) {
126 |
127 | // dispatch to all features first
128 | for (Feature feature : mFeatures.values()) {
129 | //noinspection unchecked
130 | e.dispatch(feature);
131 | }
132 |
133 | // dispatch event completion now
134 | if (e.mOnDispatchCompleted != null) {
135 | e.mOnDispatchCompleted.onDispatchCompleted();
136 | }
137 |
138 | // pick up next event
139 | e = e.mNextEvent;
140 | }
141 |
142 | mDispatchingEvent = null;
143 | return;
144 | }
145 |
146 | // already dispatching, put event to the end of chain
147 | Event e = mDispatchingEvent;
148 | while (e.mNextEvent != null) {
149 | e = e.mNextEvent;
150 | }
151 | e.mNextEvent = event;
152 | }
153 |
154 | private void assertMainThread() {
155 | if (Thread.currentThread() != Looper.getMainLooper().getThread()) {
156 | throw new IllegalStateException(
157 | "Dispatch must be called in MainThread. Current thread: "
158 | + Thread.currentThread());
159 | }
160 | }
161 |
162 | }
163 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/featured/src/main/java/de/halfbit/featured/Feature.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.halfbit.featured;
17 |
18 | import org.jetbrains.annotations.NotNull;
19 |
20 | /**
21 | * Feature defines a self-contained piece of application logic decoupled from the rest of
22 | * the application through asynchronous callbacks. Feature callbacks are events a feature
23 | * can receive from or send to other features. Features do only communicate to each other
24 | * through these asynchronous callbacks and never directly.
25 | *
26 | * Typically you will write multiple features implementing different aspects of your app.
27 | * There is no restriction on what feature implementation can do. There can be loading data
28 | * features, showing data features, logging features, UI features or even features adding
29 | * new UI elements and handling their behavior.
30 | *
31 | * First thing to do before implementing features, is to define events the features will
32 | * receive. For that you create a new class by extending this abstract {@code Feature} class
33 | * and defining feature callbacks.
34 | *
35 | *
36 | * public class LifecycleFeature extends Feature {
37 | * @FeatureEvent protected void onCreate(Bundle savedInstanceState) { }
38 | * @FeatureEvent protected void onStart() { }
39 | * @FeatureEvent protected void onStop() { }
40 | * @FeatureEvent protected void onDestroy() { }
41 | * }
42 | *
43 | * As you can see we created a feature type capable to handle standard Activity's or
44 | * Fragment's lifecycle events. Now we are ready to implement features by extending this
45 | * base class. Every feature can now override one or many of these callbacks to implement
46 | * its logic. A simple logger feature could look like this:
47 | *
48 | * public class LoggerFeature extends LifecycleFeature {
49 | * @Override protected void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate"); }
50 | * @Override protected void onStart() { Log.d(TAG, "onStart"); }
51 | * @Override protected void onStop() { Log.d(TAG, "onStop"); }
52 | * @Override protected void onDestroy() { Log.d(TAG, "onDestroy"); }
53 | * }
54 | *
55 | * Features live inside a shared container which is called {@link FeatureHost}. It is
56 | * responsible for hosting features and dispatching events between them. Once we have
57 | * feature implementations we can now register them in a feature host.
58 | *
59 | * LifecycleFeatureHost host = new LifecycleFeatureHost(this)
60 | * .with(new LoggerFeature())
61 | * .with(new ToolbarFeature())
62 | * .with(new LoadItemFeature())
63 | * .with(...);
64 | *
65 | * You might notice, there is no such class as {@code LifecycleFeatureHost} in the library,
66 | * but we reference it already twice. You are absolutely right. This is where the magic
67 | * begins. After we annotated callbacks methods of {@code LifecycleFeature} with
68 | * {@link FeatureEvent}, the library has created a feature host implementation for us,
69 | * which capable to dispatch those events between our features.
70 | *
71 | * Now we come to the last step. We need to dispatch activity events to the features. That
72 | * is really easy. We override activity's lifecycle callbacks as following:
73 | *
74 | * @Override public void onCreate(Bundle savedInstanceState) {
75 | * mFeatureHost = new LifecycleFeatureHost(this).with(new LoggerFeature());
76 | * mFeatureHost.dispatchOnCreate(savedInstanceState);
77 | * }
78 | *
79 | * @Override public void onStart() { mFeatureHost.dispatchOnStart(); }
80 | *
81 | * @Override public void onStop() { mFeatureHost.dispatchOnStop(); }
82 | *
83 | * @Override public void onDestroy() { mFeatureHost.dispatchOnDestroy(); }
84 | *
85 | * Dispatch methods are also generated by the library and they reflect same events you
86 | * defined when you created you base feature. Features can use {@link #getFeatureHost()}
87 | * method to access the feature host and call dispatch methods by themselves in order
88 | * to dispatch their events further.
89 | *
90 | * @param the feature host class the library generates for your feature class
91 | * @author sergej shafarenka
92 | * @see FeatureHost
93 | */
94 | public abstract class Feature {
95 |
96 | private FH mFeatureHost;
97 |
98 | @NotNull protected C getContext() {
99 | assertFeatureHostAttached();
100 | //noinspection unchecked
101 | return (C) mFeatureHost.getContext();
102 | }
103 |
104 | void attachFeatureHost(FH featureHost) {
105 | mFeatureHost = featureHost;
106 | }
107 |
108 | @NotNull protected FH getFeatureHost() {
109 | assertFeatureHostAttached();
110 | return mFeatureHost;
111 | }
112 |
113 | private void assertFeatureHostAttached() {
114 | if (mFeatureHost == null) {
115 | throw new IllegalStateException("FeatureHost is not attached");
116 | }
117 | }
118 |
119 | }
120 |
--------------------------------------------------------------------------------
/featured-compiler/src/main/java/de/halfbit/featured/compiler/FeatureModelValidator.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.halfbit.featured.compiler;
17 |
18 | import java.util.List;
19 | import java.util.Set;
20 |
21 | import javax.annotation.processing.Messager;
22 | import javax.lang.model.element.Element;
23 | import javax.lang.model.element.ExecutableElement;
24 | import javax.lang.model.element.Modifier;
25 | import javax.lang.model.element.TypeElement;
26 | import javax.lang.model.type.DeclaredType;
27 | import javax.lang.model.type.TypeKind;
28 | import javax.lang.model.type.TypeMirror;
29 | import javax.tools.Diagnostic;
30 |
31 | import de.halfbit.featured.compiler.model.FeatureNode;
32 | import de.halfbit.featured.compiler.model.MethodNode;
33 | import de.halfbit.featured.compiler.model.ModelNodeVisitor;
34 | import de.halfbit.featured.compiler.model.ParameterNode;
35 |
36 | public class FeatureModelValidator implements ModelNodeVisitor {
37 |
38 | private final Messager mMessager;
39 | private final Names mNames;
40 |
41 | public FeatureModelValidator(Messager messager, Names names) {
42 | mMessager = messager;
43 | mNames = names;
44 | }
45 |
46 | @Override public boolean onFeatureEnter(FeatureNode featureNode) {
47 | featureNode.setValid(true);
48 |
49 | // verify extends Feature,?> class
50 | TypeElement element = featureNode.getElement();
51 | String superType = mNames.getFeatureClassName().toString() + ",?>";
52 | if (!isSubtypeOfType(element.asType(), superType)) {
53 | error(featureNode, element,
54 | "%s must inherit from %s.", element.getQualifiedName(),
55 | mNames.getSuggestedSuperFeatureTypeName(featureNode).toString());
56 | }
57 |
58 | return true;
59 | }
60 |
61 | @Override public void onMethodEnter(MethodNode methodElement) {
62 |
63 | ExecutableElement element = methodElement.getElement();
64 |
65 | // verify return void
66 | TypeMirror returnType = element.getReturnType();
67 | if (returnType.getKind() != TypeKind.VOID) {
68 | error(methodElement.getParent(), element,
69 | "Feature event method %s() must return void.",
70 | element.getSimpleName());
71 | }
72 |
73 | Set modifiers = element.getModifiers();
74 |
75 | // verify not private
76 | if (modifiers.contains(Modifier.PRIVATE)) {
77 | error(methodElement.getParent(), element,
78 | "@%s void %s() must not be private.",
79 | mNames.getFeatureEventClassName(), element.getSimpleName());
80 | }
81 |
82 | // verify not static
83 | if (modifiers.contains(Modifier.STATIC)) {
84 | error(methodElement.getParent(), element,
85 | "@%s void %s() must not be static.",
86 | mNames.getFeatureEventClassName(), element.getSimpleName());
87 | }
88 | }
89 |
90 | @Override public void onParameter(ParameterNode parameter) {
91 | // nop
92 | }
93 |
94 | @Override public void onMethodExit(MethodNode methodElement) {
95 | // nop
96 | }
97 |
98 | @Override public void onFeatureExit(FeatureNode featureNode) {
99 | // nop
100 | }
101 |
102 | // Copyright 2013 Jake Wharton, http://jakewharton.github.com/butterknife/
103 | private boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) {
104 | if (otherType.equals(typeMirror.toString())) {
105 | return true;
106 | }
107 | if (typeMirror.getKind() != TypeKind.DECLARED) {
108 | return false;
109 | }
110 | DeclaredType declaredType = (DeclaredType) typeMirror;
111 | List extends TypeMirror> typeArguments = declaredType.getTypeArguments();
112 | if (typeArguments.size() > 0) {
113 | StringBuilder typeString = new StringBuilder(declaredType.asElement().toString());
114 | typeString.append('<');
115 | for (int i = 0; i < typeArguments.size(); i++) {
116 | if (i > 0) {
117 | typeString.append(',');
118 | }
119 | typeString.append('?');
120 | }
121 | typeString.append('>');
122 | if (typeString.toString().equals(otherType)) {
123 | return true;
124 | }
125 | }
126 | Element element = declaredType.asElement();
127 | if (!(element instanceof TypeElement)) {
128 | return false;
129 | }
130 | TypeElement typeElement = (TypeElement) element;
131 | TypeMirror superType = typeElement.getSuperclass();
132 | if (isSubtypeOfType(superType, otherType)) {
133 | return true;
134 | }
135 | for (TypeMirror interfaceType : typeElement.getInterfaces()) {
136 | if (isSubtypeOfType(interfaceType, otherType)) {
137 | return true;
138 | }
139 | }
140 | return false;
141 | }
142 |
143 | private void error(FeatureNode featureNode, Element element, String msg, Object... args) {
144 | featureNode.setValid(false);
145 | mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), element);
146 | }
147 |
148 | public void error(Element element, String msg, Object... args) {
149 | mMessager.printMessage(Diagnostic.Kind.ERROR, String.format(msg, args), element);
150 | }
151 |
152 | }
153 |
--------------------------------------------------------------------------------
/featured-compiler/src/main/java/de/halfbit/featured/compiler/FeatureProcessor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.halfbit.featured.compiler;
17 |
18 | import com.google.auto.common.SuperficialValidation;
19 | import com.google.auto.service.AutoService;
20 |
21 | import java.io.IOException;
22 | import java.util.Collection;
23 | import java.util.LinkedHashSet;
24 | import java.util.List;
25 | import java.util.Set;
26 |
27 | import javax.annotation.processing.AbstractProcessor;
28 | import javax.annotation.processing.Filer;
29 | import javax.annotation.processing.ProcessingEnvironment;
30 | import javax.annotation.processing.Processor;
31 | import javax.annotation.processing.RoundEnvironment;
32 | import javax.lang.model.SourceVersion;
33 | import javax.lang.model.element.Element;
34 | import javax.lang.model.element.ExecutableElement;
35 | import javax.lang.model.element.Name;
36 | import javax.lang.model.element.TypeElement;
37 | import javax.lang.model.element.VariableElement;
38 |
39 | import de.halfbit.featured.FeatureEvent;
40 | import de.halfbit.featured.compiler.model.FeatureNode;
41 | import de.halfbit.featured.compiler.model.MethodNode;
42 | import de.halfbit.featured.compiler.model.ModelNode;
43 | import de.halfbit.featured.compiler.model.ParameterNode;
44 |
45 | @AutoService(Processor.class)
46 | public final class FeatureProcessor extends AbstractProcessor {
47 |
48 | private Filer mFiler;
49 |
50 | private FeatureModelValidator mFeatureValidator;
51 | private Names mNames;
52 |
53 | @Override public synchronized void init(ProcessingEnvironment env) {
54 | super.init(env);
55 | mFiler = processingEnv.getFiler();
56 | mNames = new Names(processingEnv.getElementUtils(), processingEnv.getTypeUtils());
57 | mFeatureValidator = new FeatureModelValidator(processingEnv.getMessager(), mNames);
58 | }
59 |
60 | @Override public Set getSupportedAnnotationTypes() {
61 | Set types = new LinkedHashSet<>();
62 | types.add(FeatureEvent.class.getCanonicalName());
63 | return types;
64 | }
65 |
66 | @Override public SourceVersion getSupportedSourceVersion() {
67 | return SourceVersion.latestSupported();
68 | }
69 |
70 | @Override public boolean process(Set extends TypeElement> anns, RoundEnvironment env) {
71 |
72 | ModelNode model = new ModelNode();
73 |
74 | // process each @FeatureEvent element
75 | for (Element element : env.getElementsAnnotatedWith(FeatureEvent.class)) {
76 | if (!SuperficialValidation.validateElement(element)) continue;
77 |
78 | try {
79 |
80 | TypeElement parentElement = (TypeElement) element.getEnclosingElement();
81 | String featureName = parentElement.getQualifiedName().toString();
82 |
83 | FeatureNode featureNode = model.getFeatureNode(featureName);
84 | if (featureNode == null) {
85 | featureNode = new FeatureNode(featureName, parentElement);
86 | model.putFeatureNode(featureNode);
87 | }
88 |
89 | ExecutableElement executableElement = (ExecutableElement) element;
90 | Name methodName = executableElement.getSimpleName();
91 |
92 | if (!featureNode.hasMethod(methodName)) {
93 | MethodNode methodNode = new MethodNode(executableElement, featureNode);
94 | featureNode.addMethod(methodName, methodNode);
95 |
96 | List extends VariableElement> params = executableElement.getParameters();
97 | for (int i = 0, size = params.size(); i < size; i++) {
98 | VariableElement param = params.get(i);
99 | methodNode.addParameter(
100 | new ParameterNode(methodNode)
101 | .setName(param.getSimpleName().toString())
102 | .setType(mNames.getTypeNameByKind(param))
103 | .setDispatchCompleted(false));
104 | }
105 |
106 | if (methodNode.hasDispatchCompletedParameter()) {
107 | methodNode.addParameter(
108 | new ParameterNode(methodNode)
109 | .setName("onDispatchCompleted")
110 | .setType(mNames.getDispatchCompletedClassName())
111 | .setDispatchCompleted(true));
112 | }
113 | }
114 |
115 | } catch (Exception e) {
116 | mFeatureValidator.error(element, e.getMessage());
117 | }
118 | }
119 |
120 | // Add feature nodes coming from library. They are not annotated but must be
121 | // present in the model for proper generation of feature hosts.
122 | model.detectLibraryFeatures(processingEnv, mNames);
123 |
124 | // enhance model
125 | model.detectInheritance(processingEnv);
126 |
127 | // validate model nodes
128 | model.accept(mFeatureValidator);
129 |
130 | // generate source code
131 | FeatureCodeBrewer featureBrewer = new FeatureCodeBrewer(mNames);
132 | Collection featureNodes = model.getFeatureNodes();
133 | for (FeatureNode featureNode : featureNodes) {
134 | if (featureNode.isValid() && !featureNode.isLibraryNode()) {
135 | featureNode.accept(featureBrewer);
136 | try {
137 | featureBrewer.brewTo(mFiler);
138 | } catch (IOException e) {
139 | TypeElement element = featureNode.getElement();
140 | mFeatureValidator.error(element,
141 | "Unable to generate code for type %s: %s", element, e.getMessage());
142 | }
143 | }
144 | }
145 |
146 | return true;
147 | }
148 |
149 | }
150 |
--------------------------------------------------------------------------------
/gradle/gradle-mvn-push.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2013 Chris Banes
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | apply plugin: 'maven'
18 | apply plugin: 'signing'
19 |
20 | version = VERSION_NAME
21 | group = GROUP
22 |
23 | def isReleaseBuild() {
24 | return VERSION_NAME.contains("SNAPSHOT") == false
25 | }
26 |
27 | def getReleaseRepositoryUrl() {
28 | return hasProperty('RELEASE_REPOSITORY_URL') ? RELEASE_REPOSITORY_URL
29 | : "https://oss.sonatype.org/service/local/staging/deploy/maven2/"
30 | }
31 |
32 | def getSnapshotRepositoryUrl() {
33 | return hasProperty('SNAPSHOT_REPOSITORY_URL') ? SNAPSHOT_REPOSITORY_URL
34 | : "https://oss.sonatype.org/content/repositories/snapshots/"
35 | }
36 |
37 | def getRepositoryUsername() {
38 | return hasProperty('SONATYPE_NEXUS_USERNAME') ? SONATYPE_NEXUS_USERNAME : ""
39 | }
40 |
41 | def getRepositoryPassword() {
42 | return hasProperty('SONATYPE_NEXUS_PASSWORD') ? SONATYPE_NEXUS_PASSWORD : ""
43 | }
44 |
45 | afterEvaluate { project ->
46 | uploadArchives {
47 | repositories {
48 | mavenDeployer {
49 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
50 |
51 | pom.groupId = GROUP
52 | pom.artifactId = POM_ARTIFACT_ID
53 | pom.version = VERSION_NAME
54 |
55 | repository(url: getReleaseRepositoryUrl()) {
56 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
57 | }
58 | snapshotRepository(url: getSnapshotRepositoryUrl()) {
59 | authentication(userName: getRepositoryUsername(), password: getRepositoryPassword())
60 | }
61 |
62 | pom.project {
63 | name POM_NAME
64 | packaging POM_PACKAGING
65 | description POM_DESCRIPTION
66 | url POM_URL
67 |
68 | scm {
69 | url POM_SCM_URL
70 | connection POM_SCM_CONNECTION
71 | developerConnection POM_SCM_DEV_CONNECTION
72 | }
73 |
74 | licenses {
75 | license {
76 | name POM_LICENCE_NAME
77 | url POM_LICENCE_URL
78 | distribution POM_LICENCE_DIST
79 | }
80 | }
81 |
82 | developers {
83 | developer {
84 | id POM_DEVELOPER_ID
85 | name POM_DEVELOPER_NAME
86 | }
87 | }
88 | }
89 | }
90 | }
91 | }
92 |
93 | signing {
94 | required { isReleaseBuild() && gradle.taskGraph.hasTask("uploadArchives") }
95 | sign configurations.archives
96 | }
97 |
98 | if (project.getPlugins().hasPlugin('com.android.application') ||
99 | project.getPlugins().hasPlugin('com.android.library')) {
100 | task install(type: Upload, dependsOn: assemble) {
101 | repositories.mavenInstaller {
102 | configuration = configurations.archives
103 |
104 | pom.groupId = GROUP
105 | pom.artifactId = POM_ARTIFACT_ID
106 | pom.version = VERSION_NAME
107 |
108 | pom.project {
109 | name POM_NAME
110 | packaging POM_PACKAGING
111 | description POM_DESCRIPTION
112 | url POM_URL
113 |
114 | scm {
115 | url POM_SCM_URL
116 | connection POM_SCM_CONNECTION
117 | developerConnection POM_SCM_DEV_CONNECTION
118 | }
119 |
120 | licenses {
121 | license {
122 | name POM_LICENCE_NAME
123 | url POM_LICENCE_URL
124 | distribution POM_LICENCE_DIST
125 | }
126 | }
127 |
128 | developers {
129 | developer {
130 | id POM_DEVELOPER_ID
131 | name POM_DEVELOPER_NAME
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 | /*
139 | task androidJavadocs(type: Javadoc) {
140 | source = android.sourceSets.main.java.source
141 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
142 | }
143 | */
144 |
145 | /*
146 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
147 | classifier = 'javadoc'
148 | from androidJavadocs.destinationDir
149 | }
150 | */
151 |
152 | android.libraryVariants.all { variant ->
153 | if (variant.name == 'release') {
154 | task androidJavadocs(type: Javadoc) {
155 | source = variant.javaCompiler.source
156 | classpath = files(((Object) android.bootClasspath.join(File.pathSeparator)))
157 | classpath += variant.javaCompiler.classpath
158 | }
159 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
160 | classifier = 'javadoc'
161 | from androidJavadocs.destinationDir
162 | }
163 | }
164 | }
165 |
166 | task androidSourcesJar(type: Jar) {
167 | classifier = 'sources'
168 | from android.sourceSets.main.java.source
169 | }
170 | } else {
171 | install {
172 | repositories.mavenInstaller {
173 | pom.groupId = GROUP
174 | pom.artifactId = POM_ARTIFACT_ID
175 | pom.version = VERSION_NAME
176 |
177 | pom.project {
178 | name POM_NAME
179 | packaging POM_PACKAGING
180 | description POM_DESCRIPTION
181 | url POM_URL
182 |
183 | scm {
184 | url POM_SCM_URL
185 | connection POM_SCM_CONNECTION
186 | developerConnection POM_SCM_DEV_CONNECTION
187 | }
188 |
189 | licenses {
190 | license {
191 | name POM_LICENCE_NAME
192 | url POM_LICENCE_URL
193 | distribution POM_LICENCE_DIST
194 | }
195 | }
196 |
197 | developers {
198 | developer {
199 | id POM_DEVELOPER_ID
200 | name POM_DEVELOPER_NAME
201 | }
202 | }
203 | }
204 | }
205 | }
206 |
207 | task sourcesJar(type: Jar, dependsOn:classes) {
208 | classifier = 'sources'
209 | from sourceSets.main.allSource
210 | }
211 |
212 | task javadocJar(type: Jar, dependsOn:javadoc) {
213 | classifier = 'javadoc'
214 | from javadoc.destinationDir
215 | }
216 | }
217 |
218 | if (JavaVersion.current().isJava8Compatible()) {
219 | allprojects {
220 | tasks.withType(Javadoc) {
221 | options.addStringOption('Xdoclint:none', '-quiet')
222 | }
223 | }
224 | }
225 |
226 | artifacts {
227 | if (project.getPlugins().hasPlugin('com.android.application') ||
228 | project.getPlugins().hasPlugin('com.android.library')) {
229 | archives androidSourcesJar
230 | archives androidJavadocsJar
231 | } else {
232 | archives sourcesJar
233 | archives javadocJar
234 | }
235 | }
236 | }
237 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "{}"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright {yyyy} {name of copyright owner}
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/featured-compiler/src/main/java/de/halfbit/featured/compiler/FeatureCodeBrewer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.halfbit.featured.compiler;
17 |
18 | import com.squareup.javapoet.ClassName;
19 | import com.squareup.javapoet.CodeBlock;
20 | import com.squareup.javapoet.JavaFile;
21 | import com.squareup.javapoet.MethodSpec;
22 | import com.squareup.javapoet.ParameterSpec;
23 | import com.squareup.javapoet.TypeName;
24 | import com.squareup.javapoet.TypeSpec;
25 | import com.squareup.javapoet.TypeVariableName;
26 |
27 | import java.io.IOException;
28 |
29 | import javax.annotation.processing.Filer;
30 | import javax.lang.model.element.Modifier;
31 |
32 | import de.halfbit.featured.compiler.model.FeatureNode;
33 | import de.halfbit.featured.compiler.model.MethodNode;
34 | import de.halfbit.featured.compiler.model.ModelNodeVisitor;
35 | import de.halfbit.featured.compiler.model.ParameterNode;
36 |
37 | import static de.halfbit.featured.compiler.Assertions.assertNotNull;
38 |
39 | class FeatureCodeBrewer implements ModelNodeVisitor {
40 |
41 | private final Names mNames;
42 |
43 | // feature host
44 | private ClassName mFeatureClassName;
45 | private ClassName mFeatureHostClassName;
46 | private TypeSpec.Builder mFeatureHostTypeBuilder;
47 |
48 | // event
49 | private ClassName mEventClassName;
50 | private TypeSpec.Builder mEventTypeBuilder;
51 | private MethodSpec.Builder mEventConstructorBuilder;
52 | private MethodSpec.Builder mEventDispatchMethodBuilder;
53 |
54 | // common
55 | private StringBuilder mListedFields;
56 | private StringBuilder mListedParams;
57 |
58 | FeatureCodeBrewer(Names names) {
59 | mNames = names;
60 | }
61 |
62 | @Override
63 | public boolean onFeatureEnter(FeatureNode featureNode) {
64 | if (featureNode.isLibraryNode()) {
65 | return false;
66 | }
67 |
68 | mFeatureClassName = mNames.getFeatureClassName(featureNode);
69 | mFeatureHostClassName = mNames.getFeatureHostClassName(featureNode);
70 |
71 | brewClassFeatureHost(featureNode);
72 | brewMethodWithFeature(featureNode);
73 | brewMethodWithFeatureName(featureNode);
74 |
75 | return true;
76 | }
77 |
78 | @Override
79 | public void onMethodEnter(MethodNode methodElement) {
80 | mEventClassName = mNames.getEventClassName(methodElement);
81 |
82 | mEventTypeBuilder = TypeSpec.classBuilder(mEventClassName)
83 | .addModifiers(Modifier.STATIC, Modifier.FINAL)
84 | .superclass(mNames.getEventSuperClassName());
85 |
86 | if (methodElement.hasParameters()) {
87 | mEventConstructorBuilder = MethodSpec.constructorBuilder();
88 | mListedFields = prepareStringBuilder(mListedFields);
89 | mListedParams = prepareStringBuilder(mListedParams);
90 | }
91 |
92 | mEventDispatchMethodBuilder = MethodSpec
93 | .methodBuilder(mNames.getDispatchMethodName(methodElement))
94 | .addModifiers(Modifier.PUBLIC);
95 | }
96 |
97 | @Override
98 | public void onParameter(ParameterNode param) {
99 |
100 | String fieldName = "m" + Names.capitalize(param.getName());
101 | if (!param.isDispatchCompleted()) {
102 | mEventTypeBuilder.addField(
103 | param.getType(), fieldName, Modifier.PRIVATE, Modifier.FINAL);
104 | mListedFields.append(fieldName).append(", ");
105 | }
106 | mListedParams.append(param.getName()).append(", ");
107 |
108 | mEventConstructorBuilder
109 | .addParameter(param.getType(), param.getName())
110 | .addStatement("$L = $L", fieldName, param.getName());
111 |
112 | mEventDispatchMethodBuilder
113 | .addParameter(param.getType(), param.getName());
114 | }
115 |
116 | @Override
117 | public void onMethodExit(MethodNode methodElement) {
118 |
119 | String fieldNames = "";
120 | String paramNames = "";
121 | if (methodElement.hasParameters()) {
122 | mEventTypeBuilder.addMethod(mEventConstructorBuilder.build());
123 | fieldNames = removeLastComma(mListedFields).toString();
124 | paramNames = removeLastComma(mListedParams).toString();
125 | }
126 |
127 | mFeatureHostTypeBuilder
128 |
129 | // event class
130 | .addType(mEventTypeBuilder
131 | .addMethod(MethodSpec.methodBuilder("dispatch")
132 | .addModifiers(Modifier.PROTECTED)
133 | .addAnnotation(mNames.getOverrideClassName())
134 | .addParameter(ParameterSpec
135 | .builder(mNames.getFeatureClassName(), "feature")
136 | .addAnnotation(mNames.getNonNullClassName())
137 | .build())
138 | .addCode(CodeBlock.builder()
139 | .beginControlFlow("if (feature instanceof $T)",
140 | mFeatureClassName)
141 | .addStatement("(($T) feature).$L($L)", mFeatureClassName,
142 | mNames.getFeatureMethodName(methodElement),
143 | fieldNames)
144 | .endControlFlow()
145 | .build())
146 | .build())
147 | .build())
148 |
149 | // dispatch method
150 | .addMethod(mEventDispatchMethodBuilder
151 | .addStatement("dispatch(new $T($L))", mEventClassName, paramNames)
152 | .build());
153 | }
154 |
155 | @Override
156 | public void onFeatureExit(FeatureNode featureNode) {
157 | // nop
158 | }
159 |
160 | private void brewClassFeatureHost(FeatureNode featureNode) {
161 | TypeName superFeatureHostType = mNames.getFeatureHostSuperTypeName(featureNode);
162 | ClassName featureContextSuperClassName =
163 | mNames.getFeatureContextSuperClassName(featureNode);
164 |
165 | if (featureNode.hasInheritingFeatureNodes()) {
166 | // public abstract class FeatureAHost
167 | // extends FeatureHost {
168 |
169 | TypeVariableName contextTypeVariable =
170 | mNames.getFeatureContextTypeVariableName(featureNode);
171 | TypeVariableName featureHostType = mNames.getFeatureHostTypeVariableName(featureNode);
172 |
173 | mFeatureHostTypeBuilder = TypeSpec
174 | .classBuilder(mFeatureHostClassName.simpleName())
175 | .addTypeVariable(assertNotNull(featureHostType, featureNode))
176 | .addTypeVariable(assertNotNull(contextTypeVariable, featureNode))
177 | .superclass(superFeatureHostType)
178 | .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
179 | .addMethod(MethodSpec.constructorBuilder()
180 | .addModifiers(Modifier.PUBLIC)
181 | .addParameter(ParameterSpec
182 | .builder(contextTypeVariable, "context")
183 | .addAnnotation(mNames.getNonNullClassName())
184 | .build())
185 | .addStatement("super(context)")
186 | .build());
187 |
188 | } else {
189 | // public class FeatureBHost extends FeatureAHost {
190 |
191 | mFeatureHostTypeBuilder = TypeSpec
192 | .classBuilder(mFeatureHostClassName.simpleName())
193 | .superclass(superFeatureHostType)
194 | .addModifiers(Modifier.PUBLIC)
195 | .addMethod(MethodSpec.constructorBuilder()
196 | .addModifiers(Modifier.PUBLIC)
197 | .addParameter(ParameterSpec
198 | .builder(featureContextSuperClassName, "context")
199 | .addAnnotation(mNames.getNonNullClassName())
200 | .build())
201 | .addStatement("super(context)")
202 | .build());
203 | }
204 | }
205 |
206 | private void brewMethodWithFeature(FeatureNode featureNode) {
207 | MethodSpec.Builder withMethod = MethodSpec.methodBuilder("with")
208 | .addModifiers(Modifier.PUBLIC)
209 | .addAnnotation(mNames.getNonNullClassName())
210 | .addParameter(ParameterSpec.builder(mFeatureClassName, "feature")
211 | .addAnnotation(mNames.getNonNullClassName())
212 | .build());
213 |
214 | TypeName featureHostType = mNames.getFeatureHostParameterTypeName(featureNode);
215 |
216 | if (featureHostType == null) {
217 | withMethod.addCode(CodeBlock.builder()
218 | .addStatement("addFeature(feature, feature.getClass().toString())")
219 | .addStatement("return this")
220 | .build())
221 | .returns(mFeatureHostClassName);
222 |
223 | } else {
224 | withMethod.addCode(CodeBlock.builder()
225 | .addStatement("addFeature(feature, feature.getClass().toString())")
226 | .addStatement("return ($T) this", featureHostType)
227 | .build())
228 | .returns(featureHostType);
229 | }
230 |
231 | mFeatureHostTypeBuilder
232 | .addMethod(withMethod.build());
233 | }
234 |
235 | private void brewMethodWithFeatureName(FeatureNode featureNode) {
236 | MethodSpec.Builder withMethod = MethodSpec.methodBuilder("with")
237 | .addModifiers(Modifier.PUBLIC)
238 | .addAnnotation(mNames.getNonNullClassName())
239 | .addParameter(ParameterSpec.builder(mFeatureClassName, "feature")
240 | .addAnnotation(mNames.getNonNullClassName())
241 | .build())
242 | .addParameter(ParameterSpec.builder(mNames.getStringClassName(), "featureName")
243 | .addAnnotation(mNames.getNonNullClassName())
244 | .build());
245 |
246 | TypeName featureHostType = mNames.getFeatureHostParameterTypeName(featureNode);
247 |
248 | if (featureHostType == null) {
249 | withMethod.addCode(CodeBlock.builder()
250 | .addStatement("addFeature(feature, featureName)")
251 | .addStatement("return this")
252 | .build())
253 | .returns(mFeatureHostClassName);
254 |
255 | } else {
256 | withMethod.addCode(CodeBlock.builder()
257 | .addStatement("addFeature(feature, featureName)")
258 | .addStatement("return ($T) this", featureHostType)
259 | .build())
260 | .returns(featureHostType);
261 | }
262 |
263 | mFeatureHostTypeBuilder
264 | .addMethod(withMethod.build());
265 | }
266 |
267 | private static StringBuilder prepareStringBuilder(StringBuilder builder) {
268 | if (builder == null) {
269 | return new StringBuilder(40);
270 | }
271 | builder.setLength(0);
272 | return builder;
273 | }
274 |
275 | private static StringBuilder removeLastComma(StringBuilder builder) {
276 | if (builder.length() > 1) {
277 | builder.setLength(builder.length() - 2);
278 | }
279 | return builder;
280 | }
281 |
282 | void brewTo(Filer filer) throws IOException {
283 | JavaFile javaFile = JavaFile
284 | .builder(mFeatureHostClassName.packageName(), mFeatureHostTypeBuilder.build())
285 | .addFileComment("Featured code. Do not modify!")
286 | .skipJavaLangImports(true)
287 | .indent(" ")
288 | .build();
289 | javaFile.writeTo(filer);
290 | }
291 |
292 | }
293 |
--------------------------------------------------------------------------------
/featured-compiler/src/main/java/de/halfbit/featured/compiler/Names.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.halfbit.featured.compiler;
17 |
18 | import com.squareup.javapoet.AnnotationSpec;
19 | import com.squareup.javapoet.ArrayTypeName;
20 | import com.squareup.javapoet.ClassName;
21 | import com.squareup.javapoet.ParameterizedTypeName;
22 | import com.squareup.javapoet.TypeName;
23 | import com.squareup.javapoet.TypeVariableName;
24 |
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | import javax.lang.model.element.AnnotationMirror;
29 | import javax.lang.model.element.Element;
30 | import javax.lang.model.element.ElementKind;
31 | import javax.lang.model.element.PackageElement;
32 | import javax.lang.model.element.TypeElement;
33 | import javax.lang.model.element.VariableElement;
34 | import javax.lang.model.type.ArrayType;
35 | import javax.lang.model.type.DeclaredType;
36 | import javax.lang.model.type.TypeKind;
37 | import javax.lang.model.type.TypeMirror;
38 | import javax.lang.model.type.TypeVariable;
39 | import javax.lang.model.util.Elements;
40 | import javax.lang.model.util.Types;
41 |
42 | import de.halfbit.featured.compiler.model.FeatureNode;
43 | import de.halfbit.featured.compiler.model.MethodNode;
44 |
45 | public class Names {
46 |
47 | public static final String PACKAGE_NAME = "de.halfbit.featured";
48 |
49 | private static final ClassName FEATURE =
50 | ClassName.get(PACKAGE_NAME, "Feature");
51 | private static final ClassName FEATURE_EVENT =
52 | ClassName.get(PACKAGE_NAME, "FeatureEvent");
53 | private static final ClassName FEATURE_HOST =
54 | ClassName.get(PACKAGE_NAME, "FeatureHost");
55 | private static final ClassName FEATURE_HOST_EVENT =
56 | ClassName.get(PACKAGE_NAME, "FeatureHost", "Event");
57 | private static final ClassName FEATURE_HOST_DISPATCH_COMPLETED =
58 | ClassName.get(PACKAGE_NAME, "FeatureHost", "OnDispatchCompleted");
59 | private static final ClassName CONTEXT =
60 | ClassName.get("android.content", "Context");
61 | private static final ClassName STRING =
62 | ClassName.get("java.lang", "String");
63 | private static final ClassName NOT_NULL =
64 | ClassName.get("org.jetbrains.annotations", "NotNull");
65 | private static final ClassName OVERRIDE =
66 | ClassName.get("java.lang", "Override");
67 |
68 | private static final int HOST_PARAMETER_INDEX = 0;
69 | private static final int CONTEXT_PARAMETER_INDEX = 1;
70 |
71 | private final Elements mElementUtils;
72 | private final Types mTypeUtils;
73 |
74 | public Names(Elements elementUtils, Types typeUtils) {
75 | mElementUtils = elementUtils;
76 | mTypeUtils = typeUtils;
77 | }
78 |
79 | private String getPackageName(TypeElement type) {
80 | return mElementUtils.getPackageOf(type).getQualifiedName().toString();
81 | }
82 |
83 | public ClassName getFeatureClassName(FeatureNode featureNode) {
84 | TypeElement element = featureNode.getElement();
85 | return ClassName.get(getPackageName(element), element.getSimpleName().toString());
86 | }
87 |
88 | public ClassName getFeatureHostClassName(FeatureNode featureNode) {
89 |
90 | // we read host name from the feature class parameters
91 |
92 | TypeMirror superType = featureNode.getElement().getSuperclass();
93 | if (superType.getKind() != TypeKind.DECLARED) {
94 | throw new IllegalArgumentException();
95 | }
96 |
97 | TypeName featureHostName = getFeatureParameterTypeVariableName(
98 | (DeclaredType) superType, HOST_PARAMETER_INDEX);
99 | ClassName featureHostClassName = null;
100 |
101 | if (featureHostName instanceof ClassName) {
102 | // FeatureA extends Feature
103 | featureHostClassName = (ClassName) featureHostName;
104 | }
105 |
106 | if (featureHostName instanceof TypeVariableName) {
107 | // FeatureA extends Feature
108 | TypeVariableName featureHostVariableTypeName = (TypeVariableName) featureHostName;
109 | if (featureHostVariableTypeName.bounds.size() <= HOST_PARAMETER_INDEX) {
110 | throw new IllegalArgumentException("Missing feature host parameter. \n"
111 | + featureNode + "\n" + featureHostVariableTypeName);
112 | }
113 | TypeName featureHostTypeName = featureHostVariableTypeName.bounds
114 | .get(HOST_PARAMETER_INDEX);
115 | if (featureHostTypeName instanceof ClassName) {
116 | featureHostClassName = (ClassName) featureHostTypeName;
117 | }
118 | }
119 |
120 | if (featureHostClassName == null) {
121 | throw new IllegalArgumentException("Unsupported feature host name declaration. \n"
122 | + featureNode + "\n" + featureHostName);
123 | }
124 |
125 | PackageElement packageElement = mElementUtils.getPackageOf(featureNode.getElement());
126 | return ClassName.get(packageElement.toString(), featureHostClassName.simpleName());
127 | }
128 |
129 | public ClassName getFeatureContextSuperClassName(FeatureNode featureNode) {
130 | return getFeatureParameterClass(featureNode, CONTEXT_PARAMETER_INDEX);
131 | }
132 |
133 | private ClassName getFeatureParameterClass(FeatureNode featureNode, int parameterIndex) {
134 | TypeMirror superClass = featureNode.getElement().getSuperclass();
135 | if (superClass.getKind() != TypeKind.DECLARED) {
136 | throw new IllegalArgumentException(
137 | "Check model validator. It must check feature super class.");
138 | }
139 |
140 | Element argElem = getFeatureParameterElement((DeclaredType) superClass, parameterIndex);
141 | if (argElem == null || argElem.getKind() == ElementKind.TYPE_PARAMETER) {
142 | return null;
143 | }
144 | return ClassName.get((TypeElement) argElem);
145 | }
146 |
147 | public Element getFeatureParameterElement(DeclaredType clazz, int parameterIndex) {
148 | List extends TypeMirror> args = clazz.getTypeArguments();
149 | if (parameterIndex >= args.size()) {
150 | return null;
151 | }
152 | return mTypeUtils.asElement(args.get(parameterIndex));
153 | }
154 |
155 | public TypeVariableName getFeatureContextTypeVariableName(FeatureNode featureNode) {
156 | return getParameterTypeVariableName(featureNode, CONTEXT_PARAMETER_INDEX);
157 | }
158 |
159 | public TypeVariableName getFeatureHostTypeVariableName(FeatureNode featureNode) {
160 | return getParameterTypeVariableName(featureNode, HOST_PARAMETER_INDEX);
161 | }
162 |
163 | private TypeVariableName getParameterTypeVariableName(FeatureNode featureNode,
164 | int parameterIndex) {
165 | TypeMirror type = featureNode.getElement().asType();
166 | if (type.getKind() != TypeKind.DECLARED) {
167 | throw new IllegalArgumentException("FeatureNode type is not supported: " + featureNode);
168 | }
169 | Element paramElem = getFeatureParameterElement((DeclaredType) type, parameterIndex);
170 | if (paramElem.getKind() == ElementKind.TYPE_PARAMETER) {
171 | return TypeVariableName.get((TypeVariable) paramElem.asType());
172 | }
173 | throw new IllegalArgumentException(
174 | "Expecting paramElem of kind TYPE_PARAMETER, while received" + paramElem);
175 | }
176 |
177 | public ClassName getNonNullClassName() {
178 | return NOT_NULL;
179 | }
180 |
181 | public ClassName getStringClassName() {
182 | return STRING;
183 | }
184 |
185 | public ClassName getOverrideClassName() {
186 | return OVERRIDE;
187 | }
188 |
189 | public ClassName getFeatureClassName() {
190 | return FEATURE;
191 | }
192 |
193 | public ClassName getFeatureEventClassName() {
194 | return FEATURE_EVENT;
195 | }
196 |
197 | public String getDispatchMethodName(MethodNode methodElement) {
198 | String methodName = methodElement.getElement().getSimpleName().toString();
199 | return "dispatch" + capitalize(methodName);
200 | }
201 |
202 | public static String capitalize(String text) {
203 | return text.substring(0, 1).toUpperCase() + text.substring(1, text.length());
204 | }
205 |
206 | public ClassName getEventClassName(MethodNode methodElement) {
207 | ClassName parentName = getFeatureHostClassName(methodElement.getParent());
208 | String eventName = getEventName(methodElement);
209 | return ClassName.get(parentName.packageName(), parentName.simpleName(), eventName);
210 | }
211 |
212 | private String getEventName(MethodNode methodElement) {
213 | return capitalize(getFeatureMethodName(methodElement)) + "Event";
214 | }
215 |
216 | public TypeName getEventSuperClassName() {
217 | return FEATURE_HOST_EVENT;
218 | }
219 |
220 | public String getFeatureMethodName(MethodNode methodElement) {
221 | return methodElement.getElement().getSimpleName().toString();
222 | }
223 |
224 | public TypeName getSuggestedSuperFeatureTypeName(FeatureNode featureNode) {
225 | String featureHostPackage = getPackageName(featureNode.getElement());
226 | String featureHostName = featureNode.getName() + "Host";
227 | ClassName suggestedFeatureHostClassName = ClassName
228 | .get(featureHostPackage, featureHostName);
229 | return ParameterizedTypeName.get(FEATURE, suggestedFeatureHostClassName, CONTEXT);
230 | }
231 |
232 | public TypeName getFeatureHostSuperTypeName(FeatureNode featureNode) {
233 |
234 | // public class FeatureA extends Feature {
235 | // public class FeatureA extends Feature {
236 | // public class FeatureB extends FeatureA {
237 |
238 | TypeMirror superType = featureNode.getElement().getSuperclass();
239 | if (superType.getKind() != TypeKind.DECLARED) {
240 | throw new IllegalArgumentException();
241 | }
242 |
243 | TypeName hostTypeName = getFeatureParameterTypeVariableName(
244 | (DeclaredType) superType, HOST_PARAMETER_INDEX);
245 |
246 | TypeName contextTypeName = getFeatureParameterTypeVariableName(
247 | (DeclaredType) superType, CONTEXT_PARAMETER_INDEX);
248 |
249 | FeatureNode superFeatureNode = featureNode.getSuperFeatureNode();
250 | if (superFeatureNode == null) {
251 | DeclaredType declaredType = (DeclaredType) superType;
252 | TypeName featureClassName = ClassName.get(declaredType.asElement().asType());
253 | if (featureClassName instanceof ParameterizedTypeName) {
254 | ParameterizedTypeName ptn = (ParameterizedTypeName) featureClassName;
255 | ClassName featureHostClass = ClassName.get(
256 | ptn.rawType.packageName(), ptn.rawType.simpleName() + "Host");
257 | return ParameterizedTypeName.get(featureHostClass, hostTypeName, contextTypeName);
258 | }
259 | }
260 |
261 | ClassName featureHostClass = superFeatureNode == null
262 | ? FEATURE_HOST : getFeatureHostClassName(superFeatureNode);
263 |
264 | return ParameterizedTypeName.get(featureHostClass, hostTypeName, contextTypeName);
265 | }
266 |
267 | public TypeName getFeatureHostParameterTypeName(FeatureNode featureNode) {
268 | TypeMirror type = featureNode.getElement().asType();
269 | if (type.getKind() != TypeKind.DECLARED) {
270 | throw new IllegalArgumentException();
271 | }
272 | return getFeatureParameterTypeVariableName((DeclaredType) type, HOST_PARAMETER_INDEX);
273 | }
274 |
275 | private TypeName getFeatureParameterTypeVariableName(DeclaredType featureType,
276 | int featureParameterIndex) {
277 | Element paramElem = getFeatureParameterElement(featureType, featureParameterIndex);
278 | if (paramElem == null) {
279 | return null;
280 | }
281 |
282 | if (paramElem.getKind() == ElementKind.TYPE_PARAMETER) {
283 | return TypeVariableName.get((TypeVariable) paramElem.asType());
284 |
285 | } else if (paramElem.getKind() == ElementKind.CLASS) {
286 | return TypeName.get(paramElem.asType());
287 | }
288 |
289 | return null;
290 | }
291 |
292 | public TypeName getTypeNameByKind(VariableElement param) {
293 | switch (param.asType().getKind()) {
294 |
295 | case BOOLEAN:
296 | return TypeName.BOOLEAN;
297 | case BYTE:
298 | return TypeName.BYTE;
299 | case CHAR:
300 | return TypeName.CHAR;
301 | case DOUBLE:
302 | return TypeName.DOUBLE;
303 | case FLOAT:
304 | return TypeName.FLOAT;
305 | case INT:
306 | return TypeName.INT;
307 | case LONG:
308 | return TypeName.LONG;
309 | case SHORT:
310 | return TypeName.SHORT;
311 |
312 | case DECLARED:
313 | TypeMirror type = param.asType();
314 | TypeName typeName = ClassName.get(type);
315 | typeName = applyAnnotations(typeName, param);
316 | return typeName;
317 |
318 | case ARRAY:
319 | ArrayType arrayType = (ArrayType) param.asType();
320 | TypeName arrayTypeName = ArrayTypeName.get(arrayType);
321 | arrayTypeName = applyAnnotations(arrayTypeName, param);
322 | return arrayTypeName;
323 |
324 | default:
325 | throw new IllegalStateException("unsupported kind: " + param.asType().getKind());
326 | }
327 | }
328 |
329 | private static TypeName applyAnnotations(TypeName typeName, VariableElement param) {
330 | List extends AnnotationMirror> annotationMirrors = param.getAnnotationMirrors();
331 | if (annotationMirrors.size() > 0) {
332 | List annotationSpecs =
333 | new ArrayList<>(annotationMirrors.size());
334 | for (AnnotationMirror annotationMirror : annotationMirrors) {
335 | annotationSpecs.add(AnnotationSpec.get(annotationMirror));
336 | }
337 | typeName = typeName.annotated(annotationSpecs);
338 | }
339 | return typeName;
340 | }
341 |
342 | public ClassName getDispatchCompletedClassName() {
343 | return FEATURE_HOST_DISPATCH_COMPLETED;
344 | }
345 |
346 | }
347 |
--------------------------------------------------------------------------------
/featured-compiler/src/test/java/de/halfbit/featured/compiler/FeatureProcessorTests.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2016 Sergej Shafarenka, www.halfbit.de
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package de.halfbit.featured.compiler;
17 |
18 | import com.google.testing.compile.JavaFileObjects;
19 |
20 | import org.junit.Test;
21 | import org.junit.runner.RunWith;
22 | import org.junit.runners.JUnit4;
23 |
24 | import javax.tools.JavaFileObject;
25 |
26 | import static com.google.common.truth.Truth.assertAbout;
27 | import static com.google.testing.compile.JavaSourceSubjectFactory.javaSource;
28 |
29 | @RunWith(JUnit4.class)
30 | public class FeatureProcessorTests {
31 |
32 | @Test
33 | public void checkOnEvent() throws Exception {
34 |
35 | JavaFileObject source = JavaFileObjects
36 | .forSourceLines("de.halfbit.featured.test.TestFeature",
37 | "",
38 | "package de.halfbit.featured.test;",
39 | "import android.app.Application;",
40 | "import de.halfbit.featured.FeatureEvent;",
41 | "import de.halfbit.featured.Feature;",
42 | "public class TestFeature extends Feature {",
43 | " @FeatureEvent protected void onStart() { }",
44 | "}"
45 | );
46 |
47 | JavaFileObject expectedSource = JavaFileObjects
48 | .forSourceLines("de.halfbit.featured.test.TestFeatureHost",
49 | "",
50 | "package de.halfbit.featured.test;",
51 | "import android.app.Application;",
52 | "import de.halfbit.featured.Feature;",
53 | "import de.halfbit.featured.FeatureHost;",
54 | "import org.jetbrains.annotations.NotNull;",
55 | "",
56 | "public class TestFeatureHost extends FeatureHost {",
57 | " public TestFeatureHost(@NotNull Application context) {",
58 | " super(context);",
59 | " }",
60 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature) {",
61 | " addFeature(feature, feature.getClass().toString());",
62 | " return this;",
63 | " }",
64 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
65 | " addFeature(feature, featureName);",
66 | " return this;",
67 | " }",
68 | " public void dispatchOnStart() {",
69 | " dispatch(new OnStartEvent());",
70 | " }",
71 | " static final class OnStartEvent extends FeatureHost.Event {",
72 | " @Override protected void dispatch(@NotNull Feature feature) {",
73 | " if (feature instanceof TestFeature) {",
74 | " ((TestFeature) feature).onStart();",
75 | " }",
76 | " }",
77 | "}"
78 | );
79 |
80 | assertAbout(javaSource()).that(source)
81 | .processedWith(new FeatureProcessor())
82 | .compilesWithoutError()
83 | .and()
84 | .generatesSources(expectedSource);
85 | }
86 |
87 | @Test
88 | public void checkCustomFeatureHostName() throws Exception {
89 |
90 | JavaFileObject source = JavaFileObjects
91 | .forSourceLines("de.halfbit.featured.test.TestFeature",
92 | "",
93 | "package de.halfbit.featured.test;",
94 | "import android.app.Application;",
95 | "import de.halfbit.featured.FeatureEvent;",
96 | "import de.halfbit.featured.Feature;",
97 | "public class TestFeature extends Feature {",
98 | " @FeatureEvent protected void onStart() { }",
99 | "}"
100 | );
101 |
102 | JavaFileObject expectedSource = JavaFileObjects
103 | .forSourceLines("de.halfbit.featured.test.CustomTestFeatureHost",
104 | "",
105 | "package de.halfbit.featured.test;",
106 | "import android.app.Application;",
107 | "import de.halfbit.featured.Feature;",
108 | "import de.halfbit.featured.FeatureHost;",
109 | "import org.jetbrains.annotations.NotNull;",
110 | "",
111 | "public class CustomTestFeatureHost extends FeatureHost {",
112 | " public CustomTestFeatureHost(@NotNull Application context) {",
113 | " super(context);",
114 | " }",
115 | " @NotNull public CustomTestFeatureHost with(@NotNull TestFeature feature) {",
116 | " addFeature(feature, feature.getClass().toString());",
117 | " return this;",
118 | " }",
119 | " @NotNull public CustomTestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
120 | " addFeature(feature, featureName);",
121 | " return this;",
122 | " }",
123 | " public void dispatchOnStart() {",
124 | " dispatch(new OnStartEvent());",
125 | " }",
126 | " static final class OnStartEvent extends FeatureHost.Event {",
127 | " @Override protected void dispatch(@NotNull Feature feature) {",
128 | " if (feature instanceof TestFeature) {",
129 | " ((TestFeature) feature).onStart();",
130 | " }",
131 | " }",
132 | "}"
133 | );
134 |
135 | assertAbout(javaSource()).that(source)
136 | .processedWith(new FeatureProcessor())
137 | .compilesWithoutError()
138 | .and()
139 | .generatesSources(expectedSource);
140 | }
141 |
142 | @Test
143 | public void checkOnEventWithParameters() throws Exception {
144 |
145 | JavaFileObject source = JavaFileObjects
146 | .forSourceLines("de.halfbit.featured.test.TestFeature",
147 | "",
148 | "package de.halfbit.featured.test;",
149 | "",
150 | "import android.content.Context;",
151 | "import de.halfbit.featured.FeatureEvent;",
152 | "import de.halfbit.featured.Feature;",
153 | "",
154 | "public class TestFeature extends Feature {",
155 | " @FeatureEvent protected void onStart(long time, boolean valid, int count, Object state) { }",
156 | "}"
157 | );
158 |
159 | JavaFileObject expectedSource = JavaFileObjects
160 | .forSourceLines("de.halfbit.featured.test.TestFeatureHost",
161 | "",
162 | "package de.halfbit.featured.test;",
163 | "",
164 | "import android.content.Context;",
165 | "import de.halfbit.featured.Feature;",
166 | "import de.halfbit.featured.FeatureHost;",
167 | "import org.jetbrains.annotations.NotNull;",
168 | "",
169 | "public class TestFeatureHost extends FeatureHost {",
170 | " public TestFeatureHost(@NotNull Context context) {",
171 | " super(context);",
172 | " }",
173 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature) {",
174 | " addFeature(feature, feature.getClass().toString());",
175 | " return this;",
176 | " }",
177 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
178 | " addFeature(feature, featureName);",
179 | " return this;",
180 | " }",
181 | " public void dispatchOnStart(long time, boolean valid, int count, Object state) {",
182 | " dispatch(new OnStartEvent(time, valid, count, state));",
183 | " }",
184 | " static final class OnStartEvent extends FeatureHost.Event {",
185 | " private final long mTime;",
186 | " private final boolean mValid;",
187 | " private final int mCount;",
188 | " private final Object mState;",
189 | " OnStartEvent(long time, boolean valid, int count, Object state) {",
190 | " mTime = time;",
191 | " mValid = valid;",
192 | " mCount = count;",
193 | " mState = state;",
194 | " }",
195 | " @Override protected void dispatch(@NotNull Feature feature) {",
196 | " if (feature instanceof TestFeature) {",
197 | " ((TestFeature) feature).onStart(mTime, mValid, mCount, mState);",
198 | " }",
199 | " }",
200 | " }",
201 | "}"
202 | );
203 |
204 | assertAbout(javaSource()).that(source)
205 | .processedWith(new FeatureProcessor())
206 | .compilesWithoutError()
207 | .and()
208 | .generatesSources(expectedSource);
209 | }
210 |
211 | @Test
212 | public void checkOnEventWithParametersGenerics() throws Exception {
213 |
214 | JavaFileObject source = JavaFileObjects
215 | .forSourceLines("de.halfbit.featured.test.TestFeature",
216 | "",
217 | "package de.halfbit.featured.test;",
218 | "",
219 | "import android.content.Context;",
220 | "import de.halfbit.featured.FeatureEvent;",
221 | "import de.halfbit.featured.Feature;",
222 | "import java.util.List;",
223 | "",
224 | "public class TestFeature extends Feature {",
225 | " @FeatureEvent protected void onStart(List names) { }",
226 | "}"
227 | );
228 |
229 | JavaFileObject expectedSource = JavaFileObjects
230 | .forSourceLines("de.halfbit.featured.test.TestFeatureHost",
231 | "",
232 | "package de.halfbit.featured.test;",
233 | "",
234 | "import android.content.Context;",
235 | "import de.halfbit.featured.Feature;",
236 | "import de.halfbit.featured.FeatureHost;",
237 | "import java.util.List;",
238 | "import org.jetbrains.annotations.NotNull;",
239 | "",
240 | "public class TestFeatureHost extends FeatureHost {",
241 | " public TestFeatureHost(@NotNull Context context) {",
242 | " super(context);",
243 | " }",
244 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature) {",
245 | " addFeature(feature, feature.getClass().toString());",
246 | " return this;",
247 | " }",
248 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
249 | " addFeature(feature, featureName);",
250 | " return this;",
251 | " }",
252 | " public void dispatchOnStart(List names) {",
253 | " dispatch(new OnStartEvent(names));",
254 | " }",
255 | " static final class OnStartEvent extends FeatureHost.Event {",
256 | " private final List mNames;",
257 | " OnStartEvent(List names) {",
258 | " mNames = names;",
259 | " }",
260 | " @Override protected void dispatch(@NotNull Feature feature) {",
261 | " if (feature instanceof TestFeature) {",
262 | " ((TestFeature) feature).onStart(mNames);",
263 | " }",
264 | " }",
265 | " }",
266 | "}"
267 | );
268 |
269 | assertAbout(javaSource()).that(source)
270 | .processedWith(new FeatureProcessor())
271 | .compilesWithoutError()
272 | .and()
273 | .generatesSources(expectedSource);
274 | }
275 |
276 | @Test
277 | public void checkOnEventDispatchCompleted() throws Exception {
278 |
279 | JavaFileObject source = JavaFileObjects
280 | .forSourceLines("de.halfbit.featured.test.TestFeature",
281 | "",
282 | "package de.halfbit.featured.test;",
283 | "import android.content.Context;",
284 | "import de.halfbit.featured.FeatureEvent;",
285 | "import de.halfbit.featured.Feature;",
286 | "public class TestFeature extends Feature {",
287 | " @FeatureEvent(dispatchCompleted = true) protected void onStart() { }",
288 | "}"
289 | );
290 |
291 | JavaFileObject expectedSource = JavaFileObjects
292 | .forSourceLines("de.halfbit.featured.test.TestFeatureHost",
293 | "package de.halfbit.featured.test;",
294 | "",
295 | "import android.content.Context;",
296 | "import de.halfbit.featured.Feature;",
297 | "import de.halfbit.featured.FeatureHost;",
298 | "import org.jetbrains.annotations.NotNull;",
299 | "",
300 | "public class TestFeatureHost extends FeatureHost {",
301 | " public TestFeatureHost(@NotNull Context context) {",
302 | " super(context);",
303 | " }",
304 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature) {",
305 | " addFeature(feature, feature.getClass().toString());",
306 | " return this;",
307 | " }",
308 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
309 | " addFeature(feature, featureName);",
310 | " return this;",
311 | " }",
312 | " public void dispatchOnStart(FeatureHost.OnDispatchCompleted onDispatchCompleted) {",
313 | " dispatch(new OnStartEvent(onDispatchCompleted));",
314 | " }",
315 | " static final class OnStartEvent extends FeatureHost.Event {",
316 | " OnStartEvent(FeatureHost.OnDispatchCompleted onDispatchCompleted) {",
317 | " mOnDispatchCompleted = onDispatchCompleted;",
318 | " }",
319 | " @Override protected void dispatch(@NotNull Feature feature) {",
320 | " if (feature instanceof TestFeature) {",
321 | " ((TestFeature) feature).onStart();",
322 | " }",
323 | " }",
324 | " }",
325 | "}"
326 | );
327 |
328 | assertAbout(javaSource()).that(source)
329 | .processedWith(new FeatureProcessor())
330 | .compilesWithoutError()
331 | .and()
332 | .generatesSources(expectedSource);
333 | }
334 |
335 | @Test
336 | public void checkOnEventDispatchCompletedWithParameters() throws Exception {
337 |
338 | JavaFileObject source = JavaFileObjects
339 | .forSourceLines("de.halfbit.featured.test.TestFeature",
340 | "",
341 | "package de.halfbit.featured.test;",
342 | "import android.content.Context;",
343 | "import de.halfbit.featured.FeatureEvent;",
344 | "import de.halfbit.featured.Feature;",
345 | "",
346 | "public class TestFeature extends Feature {",
347 | " @FeatureEvent(dispatchCompleted = true) protected void onStart(int time) { }",
348 | "}"
349 | );
350 |
351 | JavaFileObject expectedSource = JavaFileObjects
352 | .forSourceLines("de.halfbit.featured.test.TestFeatureHost",
353 | "",
354 | "package de.halfbit.featured.test;",
355 | "",
356 | "import android.content.Context;",
357 | "import de.halfbit.featured.Feature;",
358 | "import de.halfbit.featured.FeatureHost;",
359 | "import org.jetbrains.annotations.NotNull;",
360 | "",
361 | "public class TestFeatureHost extends FeatureHost {",
362 | " public TestFeatureHost(@NotNull Context context) {",
363 | " super(context);",
364 | " }",
365 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature) {",
366 | " addFeature(feature, feature.getClass().toString());",
367 | " return this;",
368 | " }",
369 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
370 | " addFeature(feature, featureName);",
371 | " return this;",
372 | " }",
373 | " public void dispatchOnStart(int time, FeatureHost.OnDispatchCompleted onDispatchCompleted) {",
374 | " dispatch(new OnStartEvent(time, onDispatchCompleted));",
375 | " }",
376 | " static final class OnStartEvent extends FeatureHost.Event {",
377 | " private final int mTime;",
378 | " OnStartEvent(int time, FeatureHost.OnDispatchCompleted onDispatchCompleted) {",
379 | " mTime = time;",
380 | " mOnDispatchCompleted = onDispatchCompleted;",
381 | " }",
382 | " @Override protected void dispatch(@NotNull Feature feature) {",
383 | " if (feature instanceof TestFeature) {",
384 | " ((TestFeature) feature).onStart(mTime);",
385 | " }",
386 | " }",
387 | " }",
388 | "}"
389 | );
390 |
391 | assertAbout(javaSource()).that(source)
392 | .processedWith(new FeatureProcessor())
393 | .compilesWithoutError()
394 | .and()
395 | .generatesSources(expectedSource);
396 | }
397 |
398 | @Test
399 | public void checkOnEventWithAnnotatedParameters() throws Exception {
400 |
401 | JavaFileObject source = JavaFileObjects
402 | .forSourceLines("de.halfbit.featured.test.TestFeature",
403 | "",
404 | "package de.halfbit.featured.test;",
405 | "",
406 | "import android.content.Context;",
407 | "import de.halfbit.featured.FeatureEvent;",
408 | "import de.halfbit.featured.Feature;",
409 | "import org.jetbrains.annotations.NotNull;",
410 | "import org.jetbrains.annotations.Nullable;",
411 | "",
412 | "public class TestFeature extends Feature {",
413 | " @FeatureEvent protected void onStart(@NotNull String event, @Nullable Object data) { }",
414 | "}"
415 | );
416 |
417 | JavaFileObject expectedSource = JavaFileObjects
418 | .forSourceLines("de.halfbit.featured.test.TestFeatureHost",
419 | "",
420 | "package de.halfbit.featured.test;",
421 | "import android.content.Context;",
422 | "import de.halfbit.featured.Feature;",
423 | "import de.halfbit.featured.FeatureHost;",
424 | "import org.jetbrains.annotations.NotNull;",
425 | "import org.jetbrains.annotations.Nullable;",
426 | "public class TestFeatureHost extends FeatureHost {",
427 | " public TestFeatureHost(@NotNull Context context) {",
428 | " super(context);",
429 | " }",
430 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature) {",
431 | " addFeature(feature, feature.getClass().toString());",
432 | " return this;",
433 | " }",
434 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
435 | " addFeature(feature, featureName);",
436 | " return this;",
437 | " }",
438 | " public void dispatchOnStart(@NotNull String event, @Nullable Object data) {",
439 | " dispatch(new OnStartEvent(event, data));",
440 | " }",
441 | " static final class OnStartEvent extends FeatureHost.Event {",
442 | " private final @NotNull String mEvent;",
443 | " private final @Nullable Object mData;",
444 | " OnStartEvent(@NotNull String event, @Nullable Object data) {",
445 | " mEvent = event;",
446 | " mData = data;",
447 | " }",
448 | " @Override protected void dispatch(@NotNull Feature feature) {",
449 | " if (feature instanceof TestFeature) {",
450 | " ((TestFeature) feature).onStart(mEvent, mData);",
451 | " }",
452 | " }",
453 | " }",
454 | "}"
455 | );
456 |
457 | assertAbout(javaSource()).that(source)
458 | .processedWith(new FeatureProcessor())
459 | .compilesWithoutError()
460 | .and()
461 | .generatesSources(expectedSource);
462 | }
463 |
464 | @Test
465 | public void checkOnEventErrorInheritFeature() throws Exception {
466 |
467 | JavaFileObject source = JavaFileObjects
468 | .forSourceLines("de.halfbit.featured.test.TestFeature",
469 | "",
470 | "package de.halfbit.featured.test;",
471 | "import de.halfbit.featured.FeatureEvent;",
472 | "import de.halfbit.featured.Feature;",
473 | "public class TestFeature {",
474 | " @FeatureEvent protected void onStart() { }",
475 | "}"
476 | );
477 |
478 | assertAbout(javaSource()).that(source)
479 | .processedWith(new FeatureProcessor())
480 | .failsToCompile()
481 | .withErrorContaining("must inherit from");
482 | }
483 |
484 | @Test
485 | public void checkOnEventErrorExpectReturnVoid() throws Exception {
486 |
487 | JavaFileObject source = JavaFileObjects
488 | .forSourceLines("de.halfbit.featured.test.TestFeature",
489 | "",
490 | "package de.halfbit.featured.test;",
491 | "",
492 | "import android.content.Context;",
493 | "import de.halfbit.featured.FeatureEvent;",
494 | "import de.halfbit.featured.Feature;",
495 | "",
496 | "public class TestFeature extends Feature {",
497 | " @FeatureEvent protected boolean onStart() { return true; }",
498 | "}"
499 | );
500 |
501 | assertAbout(javaSource()).that(source)
502 | .processedWith(new FeatureProcessor())
503 | .failsToCompile()
504 | .withErrorContaining("must return void");
505 | }
506 |
507 | @Test
508 | public void checkOnEventErrorExpectNotPrivate() throws Exception {
509 |
510 | JavaFileObject source = JavaFileObjects
511 | .forSourceLines("de.halfbit.featured.test.TestFeature",
512 | "",
513 | "package de.halfbit.featured.test;",
514 | "",
515 | "import android.content.Context;",
516 | "import de.halfbit.featured.FeatureEvent;",
517 | "import de.halfbit.featured.Feature;",
518 | "",
519 | "public class TestFeature extends Feature {",
520 | " @FeatureEvent private void onStart() { }",
521 | "}"
522 | );
523 |
524 | assertAbout(javaSource()).that(source)
525 | .processedWith(new FeatureProcessor())
526 | .failsToCompile()
527 | .withErrorContaining("must not be private");
528 | }
529 |
530 | @Test
531 | public void checkOnEventErrorExpectNotStatic() throws Exception {
532 |
533 | JavaFileObject source = JavaFileObjects
534 | .forSourceLines("de.halfbit.featured.test.TestFeature",
535 | "",
536 | "package de.halfbit.featured.test;",
537 | "",
538 | "import android.content.Context;",
539 | "import de.halfbit.featured.FeatureEvent;",
540 | "import de.halfbit.featured.Feature;",
541 | "",
542 | "public class TestFeature extends Feature {",
543 | " @FeatureEvent static void onStart() { }",
544 | "}"
545 | );
546 |
547 | assertAbout(javaSource()).that(source)
548 | .processedWith(new FeatureProcessor())
549 | .failsToCompile()
550 | .withErrorContaining("must not be static");
551 | }
552 |
553 | @Test
554 | public void checkFeatureInheritance() throws Exception {
555 |
556 | JavaFileObject source = JavaFileObjects
557 | .forSourceLines("de.halfbit.featured.test.TestFeatures",
558 | "",
559 | "package de.halfbit.featured.test;",
560 | "",
561 | "import android.content.Context;",
562 | "import de.halfbit.featured.FeatureEvent;",
563 | "import de.halfbit.featured.Feature;",
564 | "",
565 | "class FeatureA extends Feature {",
566 | " @FeatureEvent protected void onMessageA() { }",
567 | "}",
568 | "",
569 | "class FeatureB extends FeatureA {",
570 | " @FeatureEvent protected void onMessageB() { }",
571 | "}"
572 | );
573 |
574 | JavaFileObject expectedFeatureHostA = JavaFileObjects
575 | .forSourceLines("de.halfbit.featured.test.FeatureAHost",
576 | "",
577 | "package de.halfbit.featured.test;",
578 | "",
579 | "import android.content.Context;",
580 | "import de.halfbit.featured.Feature;",
581 | "import de.halfbit.featured.FeatureHost;",
582 | "import org.jetbrains.annotations.NotNull;",
583 | "",
584 | "public abstract class FeatureAHost extends FeatureHost {",
585 | " public FeatureHostA(@NotNull C context) {",
586 | " super(context);",
587 | " }",
588 | " @NotNull public FH with(@NotNull FeatureA feature) {",
589 | " addFeature(feature, feature.getClass().toString());",
590 | " return (FH) this;",
591 | " }",
592 | " @NotNull public FH with(@NotNull FeatureA feature, @NotNull String featureName) {",
593 | " addFeature(feature, featureName);",
594 | " return (FH) this;",
595 | " }",
596 | " public void dispatchOnMessageA() {",
597 | " dispatch(new OnMessageAEvent());",
598 | " }",
599 | " static final class OnMessageAEvent extends FeatureHost.Event {",
600 | " @Override",
601 | " protected void dispatch(@NotNull Feature feature) {",
602 | " if (feature instanceof FeatureA) {",
603 | " ((FeatureA) feature).onMessageA();",
604 | " }",
605 | " }",
606 | " }",
607 | "}"
608 | );
609 |
610 | JavaFileObject expectedFeatureHostB = JavaFileObjects
611 | .forSourceLines("de.halfbit.featured.test.FeatureBHost",
612 | "",
613 | "package de.halfbit.featured.test;",
614 | "",
615 | "import android.content.Context;",
616 | "import de.halfbit.featured.Feature;",
617 | "import de.halfbit.featured.FeatureHost;",
618 | "import org.jetbrains.annotations.NotNull;",
619 | "",
620 | "public class FeatureBHost extends FeatureAHost {",
621 | " public FeatureBHost(@NotNull Context context) {",
622 | " super(context);",
623 | " }",
624 | " @NotNull public FeatureBHost with(@NotNull FeatureB feature) {",
625 | " addFeature(feature, feature.getClass().toString());",
626 | " return this;",
627 | " }",
628 | " @NotNull public FeatureBHost with(@NotNull FeatureB feature, @NotNull String featureName) {",
629 | " addFeature(feature, featureName);",
630 | " return this;",
631 | " }",
632 | " public void dispatchOnMessageB() {",
633 | " dispatch(new OnMessageBEvent());",
634 | " }",
635 | " static final class OnMessageBEvent extends FeatureHost.Event {",
636 | " @Override",
637 | " protected void dispatch(@NotNull Feature feature) {",
638 | " if (feature instanceof FeatureB) {",
639 | " ((FeatureB) feature).onMessageB();",
640 | " }",
641 | " }",
642 | " }",
643 | "}"
644 | );
645 |
646 | assertAbout(javaSource()).that(source)
647 | .processedWith(new FeatureProcessor())
648 | .compilesWithoutError()
649 | .and()
650 | .generatesSources(expectedFeatureHostA, expectedFeatureHostB);
651 |
652 | }
653 |
654 | @Test
655 | public void checkFeatureInheritanceFeatureWithGenerics() throws Exception {
656 |
657 | JavaFileObject source = JavaFileObjects
658 | .forSourceLines("de.halfbit.featured.test.TestFeatures",
659 | "",
660 | "package de.halfbit.featured.test;",
661 | "import android.app.Application;",
662 | "import de.halfbit.featured.FeatureEvent;",
663 | "import de.halfbit.featured.Feature;",
664 | "",
665 | "class FeatureA extends Feature {",
666 | " @FeatureEvent protected void onMessageA() { }",
667 | "}"
668 | );
669 |
670 | JavaFileObject expectedFeatureHostA = JavaFileObjects
671 | .forSourceLines("de.halfbit.featured.test.FeatureAHost",
672 | "",
673 | "package de.halfbit.featured.test;",
674 | "import android.app.Application;",
675 | "import de.halfbit.featured.Feature;",
676 | "import de.halfbit.featured.FeatureHost;",
677 | "import org.jetbrains.annotations.NotNull;",
678 | "",
679 | "public abstract class FeatureAHost extends FeatureHost {",
680 | " public FeatureHostA(@NotNull C context) {",
681 | " super(context);",
682 | " }",
683 | " @NotNull public FH with(@NotNull FeatureA feature) {",
684 | " addFeature(feature, feature.getClass().toString());",
685 | " return (FH) this;",
686 | " }",
687 | " @NotNull public FH with(@NotNull FeatureA feature, @NotNull String featureName) {",
688 | " addFeature(feature, featureName);",
689 | " return (FH) this;",
690 | " }",
691 | " public void dispatchOnMessageA() {",
692 | " dispatch(new OnMessageAEvent());",
693 | " }",
694 | " static final class OnMessageAEvent extends FeatureHost.Event {",
695 | " @Override protected void dispatch(@NotNull Feature feature) {",
696 | " if (feature instanceof FeatureA) {",
697 | " ((FeatureA) feature).onMessageA();",
698 | " }",
699 | " }",
700 | " }",
701 | "}"
702 | );
703 |
704 | assertAbout(javaSource()).that(source)
705 | .processedWith(new FeatureProcessor())
706 | .compilesWithoutError()
707 | .and()
708 | .generatesSources(expectedFeatureHostA);
709 |
710 | }
711 |
712 | @Test
713 | public void checkFeatureInheritanceFeatureInLibrary() throws Exception {
714 |
715 | // This is the case, when base feature has been defined in library
716 | // and used in the app. In this case processor won't be able analyse
717 | // base features' annotations.
718 |
719 | JavaFileObject source = JavaFileObjects
720 | .forSourceLines("de.halfbit.featured.test.TestFeatures",
721 | "",
722 | "package de.halfbit.featured.test;",
723 | "import android.content.Context;",
724 | "import de.halfbit.featured.FeatureEvent;",
725 | "import de.halfbit.featured.Feature;",
726 | "import de.halfbit.featured.FeatureHost;",
727 | "import org.jetbrains.annotations.NotNull;",
728 |
729 | // this feature and its host are given (part of a library)
730 |
731 | "class FeatureA extends Feature {",
732 | " protected void onMessageA() { }",
733 | "}",
734 | "",
735 | "class FeatureAHost extends FeatureHost {",
736 | " public FeatureAHost(@NotNull C context) {",
737 | " super(context);",
738 | " }",
739 | "}",
740 |
741 | // this feature needs to be processed and a host needs to be generated
742 |
743 | "class FeatureB extends FeatureA {",
744 | " @FeatureEvent protected void onMessageB() { }",
745 | "}"
746 | );
747 |
748 | JavaFileObject expectedFeatureHostA = JavaFileObjects
749 | .forSourceLines("de.halfbit.featured.test.FeatureBHost",
750 | "",
751 | "package de.halfbit.featured.test;",
752 | "import android.content.Context;",
753 | "import de.halfbit.featured.Feature;",
754 | "import de.halfbit.featured.FeatureHost;",
755 | "import org.jetbrains.annotations.NotNull;",
756 | "",
757 | "public class FeatureBHost extends FeatureAHost {",
758 | " public FeatureBHost(@NotNull Context context) {",
759 | " super(context);",
760 | " }",
761 | " @NotNull public FeatureBHost with(@NotNull FeatureB feature) {",
762 | " addFeature(feature, feature.getClass().toString());",
763 | " return this;",
764 | " }",
765 | " @NotNull public FeatureBHost with(@NotNull FeatureB feature, @NotNull String featureName) {",
766 | " addFeature(feature, featureName);",
767 | " return this;",
768 | " }",
769 | " public void dispatchOnMessageB() {",
770 | " dispatch(new OnMessageBEvent());",
771 | " }",
772 | " static final class OnMessageBEvent extends FeatureHost.Event {",
773 | " @Override protected void dispatch(@NotNull Feature feature) {",
774 | " if (feature instanceof FeatureB) {",
775 | " ((FeatureB) feature).onMessageB();",
776 | " }",
777 | " }",
778 | " }",
779 | "}"
780 | );
781 |
782 | assertAbout(javaSource()).that(source)
783 | .processedWith(new FeatureProcessor())
784 | .compilesWithoutError()
785 | .and()
786 | .generatesSources(expectedFeatureHostA);
787 |
788 | }
789 |
790 | @Test
791 | public void checkOnEventWithArrayParameters() throws Exception {
792 |
793 | JavaFileObject source = JavaFileObjects
794 | .forSourceLines("de.halfbit.featured.test.TestFeature",
795 | "",
796 | "package de.halfbit.featured.test;",
797 | "",
798 | "import android.content.Context;",
799 | "import de.halfbit.featured.FeatureEvent;",
800 | "import de.halfbit.featured.Feature;",
801 | "import org.jetbrains.annotations.NotNull;",
802 | "",
803 | "public class TestFeature extends Feature {",
804 | " @FeatureEvent protected void onCreate(@NotNull int[] value1, Object[] value2) { }",
805 | "}"
806 | );
807 |
808 | JavaFileObject expectedSource = JavaFileObjects
809 | .forSourceLines("de.halfbit.featured.test.TestFeatureHost",
810 | "",
811 | "package de.halfbit.featured.test;",
812 | "",
813 | "import android.content.Context;",
814 | "import de.halfbit.featured.Feature;",
815 | "import de.halfbit.featured.FeatureHost;",
816 | "import org.jetbrains.annotations.NotNull;",
817 | "",
818 | "public class TestFeatureHost extends FeatureHost {",
819 | " public TestFeatureHost(@NotNull Context context) {",
820 | " super(context);",
821 | " }",
822 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature) {",
823 | " addFeature(feature, feature.getClass().toString());",
824 | " return this;",
825 | " }",
826 | " @NotNull public TestFeatureHost with(@NotNull TestFeature feature, @NotNull String featureName) {",
827 | " addFeature(feature, featureName);",
828 | " return this;",
829 | " }",
830 | " public void dispatchOnCreate(@NotNull int[] value1, Object[] value2) {",
831 | " dispatch(new OnCreateEvent(value1, value2));",
832 | " }",
833 | " static final class OnCreateEvent extends FeatureHost.Event {",
834 | " private final @NotNull int[] mValue1;",
835 | " private final Object[] mValue2;",
836 | " OnCreateEvent(@NotNull int[] value1, Object[] value2) {",
837 | " mValue1 = value1;",
838 | " mValue2 = value2;",
839 | " }",
840 | " @Override protected void dispatch(@NotNull Feature feature) {",
841 | " if (feature instanceof TestFeature) {",
842 | " ((TestFeature) feature).onCreate(mValue1, mValue2);",
843 | " }",
844 | " }",
845 | " }",
846 | "}"
847 | );
848 |
849 | assertAbout(javaSource()).that(source)
850 | .processedWith(new FeatureProcessor())
851 | .compilesWithoutError()
852 | .and()
853 | .generatesSources(expectedSource);
854 |
855 | }
856 |
857 | }
858 |
--------------------------------------------------------------------------------