diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6627b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,52 @@ +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea + +# Keystore files +*.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..9e30b4f --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,187 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae935d5 --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +Alfred – Android `ViewModel` instantiation made effortless +--- + +[![JitPack][1]][2] + +![Alfred, your trusty View Model butler.](http://i.imgur.com/EDNJaWB.jpg) + + +### Why should I use it? + +``` + // It's so you can just do this... + + DopeViewModel dopeViewModel = DopeViewModelProvider.get(this, "...", 0L); +``` + +``` + // Instead this... + + LameViewModel lameViewModel = ViewModelProviders.of(this).get(LameViewModel.class); + lameViewModel.setMessage("..."); + lameViewModel.setFucksGiven(1L); +``` + +``` + // Or, God forbid, this... + + EvenLamerViewModel evenLamerViewModel = ViewModelProviders.of(this, new CustomViewModelFactory("...", 1000L)).get(EvenLamerViewModel.class); + + ... + + public final class CustomViewModelFactory implements ViewModelProvider.Factory { + + // About 28 lines of code here (given your ViewModel takes 2 arguments like above). + } +``` + + +### Including `Alfred` to your project + +Include `Alfred` to your Gradle project by adding it as a dependency in your `build.gradle`: + +``` + apply plugin: 'com.neenbedankt.android-apt' + + repositories { + maven { url "https://jitpack.io" } + } + + dependencies { + compile 'com.hadisatrio.Alfred:annotations:v1.0.0-RC.1' + apt 'com.hadisatrio.Alfred:compiler:v1.0.0-RC.1' + } +``` + +You'll also need `android-apt` in your project. See how to do it [here.](https://bitbucket.org/hvisser/android-apt) + + +### Contributions + +Any kind of contribution will be appreciated. PR away! + + +### License + +`Alfred` is published under the [Apache License 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). + +--- + +_**p.s.**, Please let me know if you're using `Alfred` in your projects. Drop an email at +hi[you-know-what-to-put-here]hadisatrio.com._ ;) + +[1]: https://jitpack.io/v/MrHadiSatrio/Alfred.svg?style=flat-square +[2]: https://jitpack.io/#MrHadiSatrio/Alfred diff --git a/annotations/.gitignore b/annotations/.gitignore new file mode 100644 index 0000000..9ff7f0e --- /dev/null +++ b/annotations/.gitignore @@ -0,0 +1,17 @@ +# Built files +build/ + +# Gradle files +.gradle/ + +# IntelliJ files +.idea + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/annotations/build.gradle b/annotations/build.gradle new file mode 100644 index 0000000..17a6f3f --- /dev/null +++ b/annotations/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'java-library' +apply plugin: 'maven' + +group = "com.github.MrHadiSatrio" +version = rootProject.alfredAnnotationsVersion + +sourceCompatibility = rootProject.sourceCompatibilityVersion +targetCompatibility = rootProject.targetCompatibilityVersion + +repositories { + mavenCentral() +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} diff --git a/annotations/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/GeneratedProvider.java b/annotations/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/GeneratedProvider.java new file mode 100644 index 0000000..dfad005 --- /dev/null +++ b/annotations/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/GeneratedProvider.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.libs.android.viewmodelprovider; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.CLASS) +public @interface GeneratedProvider { +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..59a92c9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +buildscript { + + apply from: rootProject.file("gradle/versions.gradle") + apply from: rootProject.file("gradle/dependencies.gradle") + + repositories { + google() + jcenter() + } + + dependencies { + classpath androidToolsPlugin + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + allprojects.each { project -> + delete project.buildDir + } +} + +task zip(type: Zip) { + from project.projectDir + exclude '.gradle' + exclude '.idea' + exclude '.okbuck' + exclude 'zips' + exclude 'build' + exclude '**/build' + exclude 'local.properties' + exclude '*.iml' + archiveName project.name + " (" + new Date().format("yyyyMMddHHmmss") + ").zip" + destinationDir file(project.projectDir.path + "/zips") +} diff --git a/compiler/.gitignore b/compiler/.gitignore new file mode 100644 index 0000000..9ff7f0e --- /dev/null +++ b/compiler/.gitignore @@ -0,0 +1,17 @@ +# Built files +build/ + +# Gradle files +.gradle/ + +# IntelliJ files +.idea + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/compiler/build.gradle b/compiler/build.gradle new file mode 100644 index 0000000..67db2c0 --- /dev/null +++ b/compiler/build.gradle @@ -0,0 +1,38 @@ +apply plugin: 'java-library' +apply plugin: 'maven' + +group = "com.github.MrHadiSatrio" +version = rootProject.alfredCompilerVersion + +sourceCompatibility = rootProject.sourceCompatibilityVersion +targetCompatibility = rootProject.targetCompatibilityVersion + +repositories { + mavenCentral() +} + +dependencies { + + implementation alfredAnnotations + + implementation autoservice + implementation javapoet + + api rootProject.supportv4 + api rootProject.archComponentsExtensions +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives sourcesJar + archives javadocJar +} diff --git a/compiler/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/GeneratedProviderProcessor.java b/compiler/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/GeneratedProviderProcessor.java new file mode 100644 index 0000000..ebc6459 --- /dev/null +++ b/compiler/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/GeneratedProviderProcessor.java @@ -0,0 +1,348 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.libs.android.viewmodelprovider; + +import com.google.auto.service.AutoService; +import com.squareup.javapoet.ClassName; +import com.squareup.javapoet.FieldSpec; +import com.squareup.javapoet.JavaFile; +import com.squareup.javapoet.MethodSpec; +import com.squareup.javapoet.ParameterizedTypeName; +import com.squareup.javapoet.TypeName; +import com.squareup.javapoet.TypeSpec; +import com.squareup.javapoet.TypeVariableName; + +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; +import javax.tools.Diagnostic; + +@AutoService(Processor.class) +public final class GeneratedProviderProcessor extends AbstractProcessor { + + private static final String VIEW_MODEL_CLASS_NAME = "android.arch.lifecycle.ViewModel"; + private static final String VIEW_MODEL_FACTORY_CLASS_NAME = "android.arch.lifecycle.ViewModelProvider.Factory"; + private static final String VIEW_MODEL_PROVIDERS_CLASS_NAME = "android.arch.lifecycle.ViewModelProviders"; + private static final String FRAGMENT_ACTIVITY_CLASS_NAME = "android.support.v4.app.FragmentActivity"; + private static final String FRAGMENT_CLASS_NAME = "android.support.v4.app.Fragment"; + + private static final String PROVIDER_CLASS_SUFFIX = "Provider"; + private static final String FACTORY_CLASS_SUFFIX = "Factory"; + private static final String PARAMS_PREFIX = "p"; + private static final String VARIABLE_PREFIX = "var"; + + private Elements elementUtils; + private Types typeUtils; + private Filer filer; + private Messager messager; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + elementUtils = processingEnv.getElementUtils(); + typeUtils = processingEnv.getTypeUtils(); + filer = processingEnv.getFiler(); + messager = processingEnv.getMessager(); + } + + @Override + public Set getSupportedAnnotationTypes() { + final Set annotations = new LinkedHashSet<>(); + annotations.add(GeneratedProvider.class.getCanonicalName()); + return annotations; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + + for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GeneratedProvider.class)) { + if (annotatedElement.getKind() != ElementKind.CLASS) { + error( + annotatedElement, + "Only classes can be annotated with @%s.", + GeneratedProvider.class.getSimpleName() + ); + return true; // Error message printed, exit processing. + } + + // We can cast it, because we know that it of ElementKind.CLASS. + final TypeElement typeElement = (TypeElement) annotatedElement; + + // Check whether or not this class meets our pre-requisites. + try { + if (!isValidClass(typeElement)) { + return true; // Error message printed, exit processing. + } + } catch (ClassNotFoundException e) { + error( + "Couldn't find the required classes. " + + "Have you declare Android Architecture Components libraries as " + + "your project's dependency?" + ); + return true; // Error message printed, exit processing. + } + + try { + generateFactory(typeElement); + } catch (IOException | ClassNotFoundException | NoPackageNameException e) { + error("Error while generating factory for class %s. Cause: %s.", typeElement, e); + return true; // Error message printed, exit processing. + } + + try { + generateProvider(typeElement); + } catch (IOException | ClassNotFoundException | NoPackageNameException e) { + error("Error while generating provider for class %s. Cause: %s.", typeElement, e); + return true; // Error message printed, exit processing. + } + } + + // We have finished processing. + return true; + } + + private void error(Element e, String message, Object... args) { + messager.printMessage( + Diagnostic.Kind.ERROR, + String.format(message, args), + e + ); + } + + private void error(String message, Object... args) { + messager.printMessage( + Diagnostic.Kind.ERROR, + String.format(message, args) + ); + } + + private boolean isValidClass(TypeElement classElement) throws ClassNotFoundException { + + /* Req #1: Annotated class has to be public. */ + if (!classElement.getModifiers().contains(Modifier.PUBLIC)) { + error(classElement, "The class %s is not public.", + classElement.getQualifiedName().toString()); + return false; + } + + /* Req #2: Annotated class has to be concrete (not abstract). */ + if (classElement.getModifiers().contains(Modifier.ABSTRACT)) { + error(classElement, "The class %s is abstract. You can't annotate abstract classes with @%.", + classElement.getQualifiedName().toString(), GeneratedProvider.class.getSimpleName()); + return false; + } + + /* Req #3: Annotated class has to be a subclass of ViewModel. */ + TypeElement currentClass = classElement; + while (true) { + final TypeMirror superClassType = currentClass.getSuperclass(); + + if (superClassType.getKind() == TypeKind.NONE) { + // Basis class (java.lang.Object) reached, so exit. + error(classElement, "The class %s annotated with @%s must inherit from %s.", + classElement.getQualifiedName().toString(), GeneratedProvider.class.getSimpleName(), + Class.forName(VIEW_MODEL_CLASS_NAME)); + return false; + } + + if (superClassType.toString().equals(VIEW_MODEL_CLASS_NAME)) { + // Required super class found. Break the loop so we can return. + break; + } + + // Moving up in inheritance tree. + currentClass = (TypeElement) typeUtils.asElement(superClassType); + } + + return true; + } + + private void generateFactory(TypeElement typeElement) + throws IOException, ClassNotFoundException, NoPackageNameException { + + final String packageName = getPackageName(typeElement); + final String genClassName = typeElement.getSimpleName() + FACTORY_CLASS_SUFFIX; + final List ctorParams = getConstructorParamTypes(typeElement); + + // Define the fields based on previously queried constructor params. + final List fieldSpecs = new ArrayList<>(); + final StringBuilder fieldTypesCsv = new StringBuilder(); + final StringBuilder fieldNamesCsv = new StringBuilder(); + for (int i = 0; i < ctorParams.size(); i++) { + fieldSpecs.add( + FieldSpec.builder( + TypeName.get(ctorParams.get(i)), + VARIABLE_PREFIX + i, + Modifier.PRIVATE, + Modifier.FINAL + ).build() + ); + + if (fieldTypesCsv.length() > 0) fieldTypesCsv.append(','); + fieldTypesCsv.append(TypeName.get(ctorParams.get(i))).append(".class"); + + if (fieldNamesCsv.length() > 0) fieldNamesCsv.append(','); + fieldNamesCsv.append(VARIABLE_PREFIX).append(i); + } + + // Define the constructor of the generated class. + final MethodSpec.Builder ctorSpecBuilder = MethodSpec.constructorBuilder(); + for (int i = 0; i < ctorParams.size(); i++) { + ctorSpecBuilder.addParameter( + TypeName.get(ctorParams.get(i)), + PARAMS_PREFIX + i + ); + + // Create statement to assign parameter value to its appropriate field. + ctorSpecBuilder.addStatement( + "this.$L = $L", + VARIABLE_PREFIX + i, + PARAMS_PREFIX + i + ); + } + final MethodSpec ctorSpec = ctorSpecBuilder.build(); + + // Define the `create()` method that will be called by ViewModelProviders + // to actually instantiate the ViewModel. + final TypeVariableName typeVariableName = TypeVariableName.get("T", Class.forName(VIEW_MODEL_CLASS_NAME)); + final MethodSpec createSpec = MethodSpec.methodBuilder("create") + .addAnnotation(Override.class) + .addModifiers(Modifier.PUBLIC) + .addTypeVariable(typeVariableName) + .returns(typeVariableName) + .addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), typeVariableName), "modelClass") + .beginControlFlow("if ($T.class.isAssignableFrom($L))", typeElement, "modelClass") + .beginControlFlow("try") + .addStatement("return $L.getConstructor($L).newInstance($L)", "modelClass", fieldTypesCsv, fieldNamesCsv) + .nextControlFlow("catch ($T | $T | $T | $T e)", NoSuchMethodException.class, IllegalAccessException.class, InstantiationException.class, InvocationTargetException.class) + .addStatement("throw new $T(\"Couldn't create an instance of $T\", e)", RuntimeException.class, typeElement) + .endControlFlow() + .nextControlFlow("else") + .addStatement("throw new $T(\"Couldn't create an instance of $T\")", RuntimeException.class, typeElement) + .endControlFlow() + .build(); + + // Define the class using the previously defined specs. + final TypeSpec factory = TypeSpec.classBuilder(genClassName) + .addSuperinterface(ClassName.bestGuess(VIEW_MODEL_FACTORY_CLASS_NAME)) + .addModifiers(Modifier.FINAL) + .addFields(fieldSpecs) + .addMethod(ctorSpec) + .addMethod(createSpec) + .build(); + + // Write the class. + JavaFile.builder(packageName, factory) + .build().writeTo(filer); + } + + private String getPackageName(TypeElement typeElement) throws NoPackageNameException { + final PackageElement pkg = elementUtils.getPackageOf(typeElement); + if (pkg.isUnnamed()) { + throw new NoPackageNameException(typeElement); + } + return pkg.getQualifiedName().toString(); + } + + private List getConstructorParamTypes(TypeElement typeElement) { + final List subjectCtorParams = new ArrayList<>(); + for (Element element : typeElement.getEnclosedElements()) { + if (element.getKind() == ElementKind.CONSTRUCTOR) { + final ExecutableElement ctor = ((ExecutableElement) element); + for (VariableElement ctorParameter : ctor.getParameters()) { + subjectCtorParams.add(ctorParameter.asType()); + } + } + } + return subjectCtorParams; + } + + private void generateProvider(TypeElement typeElement) + throws IOException, ClassNotFoundException, NoPackageNameException { + + final String packageName = getPackageName(typeElement); + final String genClassName = typeElement.getSimpleName() + PROVIDER_CLASS_SUFFIX; + final List subjectCtorParams = getConstructorParamTypes(typeElement); + final Class viewModelProviderClass = Class.forName(VIEW_MODEL_PROVIDERS_CLASS_NAME); + + final StringBuilder ctorParamNamesCsv = new StringBuilder(); + for (int i = 0; i < subjectCtorParams.size(); i++) { + if (ctorParamNamesCsv.length() > 0) ctorParamNamesCsv.append(','); + ctorParamNamesCsv.append(PARAMS_PREFIX).append(i + 1); + } + + // Generate `get()` method to be called from activities. + final MethodSpec.Builder activityGetBuilder = MethodSpec.methodBuilder("get") + .returns(TypeName.get(typeElement.asType())) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(ClassName.bestGuess(FRAGMENT_ACTIVITY_CLASS_NAME), "activity"); + for (int i = 0; i < subjectCtorParams.size(); i++) { + activityGetBuilder.addParameter(TypeName.get(subjectCtorParams.get(i)), (PARAMS_PREFIX + (i + 1))); + } + activityGetBuilder.addStatement("return $T.of(activity, new $TFactory($L)).get($T.class)", viewModelProviderClass, typeElement, ctorParamNamesCsv, typeElement); + final MethodSpec activityGet = activityGetBuilder.build(); + + // Generate `get()` method to be called from fragments. + final MethodSpec.Builder fragmentGetBuilder = MethodSpec.methodBuilder("get") + .returns(TypeName.get(typeElement.asType())) + .addModifiers(Modifier.PUBLIC, Modifier.STATIC) + .addParameter(ClassName.bestGuess(FRAGMENT_CLASS_NAME), "fragment"); + for (int i = 0; i < subjectCtorParams.size(); i++) { + fragmentGetBuilder.addParameter(TypeName.get(subjectCtorParams.get(i)), (PARAMS_PREFIX + (i + 1))); + } + fragmentGetBuilder.addStatement("return $T.of(fragment, new $TFactory($L)).get($T.class)", viewModelProviderClass, typeElement, ctorParamNamesCsv, typeElement); + final MethodSpec fragmentGet = fragmentGetBuilder.build(); + + // Define the class using the previously defined specs. + final TypeSpec provider = TypeSpec.classBuilder(genClassName) + .addModifiers(Modifier.PUBLIC, Modifier.FINAL) + .addMethod(activityGet) + .addMethod(fragmentGet) + .build(); + + // Write the class. + JavaFile.builder(packageName, provider) + .build().writeTo(filer); + } +} diff --git a/compiler/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/NoPackageNameException.java b/compiler/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/NoPackageNameException.java new file mode 100644 index 0000000..3de61c5 --- /dev/null +++ b/compiler/src/main/java/com/hadisatrio/libs/android/viewmodelprovider/NoPackageNameException.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.libs.android.viewmodelprovider; + +import javax.lang.model.element.TypeElement; + +class NoPackageNameException extends Exception { + + NoPackageNameException(TypeElement typeElement) { + super("The package of " + typeElement.getSimpleName() + " has no name."); + } +} diff --git a/demo/.gitignore b/demo/.gitignore new file mode 100644 index 0000000..d6627b6 --- /dev/null +++ b/demo/.gitignore @@ -0,0 +1,52 @@ +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# Intellij +*.iml +.idea + +# Keystore files +*.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# OS-specific files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/demo/build.gradle b/demo/build.gradle new file mode 100644 index 0000000..f7b43dd --- /dev/null +++ b/demo/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'com.android.application' + +android { + + compileSdkVersion rootProject.compileSdkVersion + buildToolsVersion rootProject.buildToolsVersion + + defaultConfig { + + applicationId "com.hadisatrio.apps.android.alfreddemo" + + minSdkVersion rootProject.minSdkVersion + targetSdkVersion targetSdkVersion + versionCode rootProject.demoVersionCode + versionName rootProject.demoVersionName + + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + + buildTypes { + debug { + minifyEnabled false + } + release { + minifyEnabled false + } + } +} + +dependencies { + + implementation rootProject.supportv4 + implementation rootProject.appcompatv7 + implementation rootProject.constraintLayout + + implementation rootProject.archComponents + implementation rootProject.archComponentsExtensions + annotationProcessor rootProject.archComponentsCompiler + + implementation rootProject.alfredAnnotations + annotationProcessor rootProject.alfredCompiler + + testImplementation rootProject.junit + androidTestImplementation rootProject.runner + androidTestImplementation rootProject.espressoCore +} diff --git a/demo/src/main/AndroidManifest.xml b/demo/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ef44333 --- /dev/null +++ b/demo/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/CustomViewModelFactory.java b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/CustomViewModelFactory.java new file mode 100644 index 0000000..c8b54cd --- /dev/null +++ b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/CustomViewModelFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.apps.android.alfreddemo; + +import android.arch.lifecycle.ViewModel; +import android.arch.lifecycle.ViewModelProvider; +import android.content.Context; + +import java.lang.reflect.InvocationTargetException; + +public final class CustomViewModelFactory implements ViewModelProvider.Factory { + + private final Context context; + private final Long fucksGiven; + + CustomViewModelFactory(Context context, Long fucksGiven) { + this.context = context; + this.fucksGiven = fucksGiven; + } + + @Override + public T create(Class modelClass) { + if (DopeViewModel.class.isAssignableFrom(modelClass)) { + try { + return modelClass.getConstructor(android.content.Context.class, java.lang.Long.class) + .newInstance(context, fucksGiven); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Cannot create an instance of " + modelClass, e); + } catch (IllegalAccessException e) { + throw new RuntimeException("Cannot create an instance of " + modelClass, e); + } catch (InstantiationException e) { + throw new RuntimeException("Cannot create an instance of " + modelClass, e); + } catch (InvocationTargetException e) { + throw new RuntimeException("Cannot create an instance of " + modelClass, e); + } + } else { + throw new RuntimeException("Couldn't create an instance of " + modelClass); + } + } +} diff --git a/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/DopeViewModel.java b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/DopeViewModel.java new file mode 100644 index 0000000..46cb1a1 --- /dev/null +++ b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/DopeViewModel.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.apps.android.alfreddemo; + +import android.arch.lifecycle.ViewModel; +import android.content.Context; + +import com.hadisatrio.libs.android.viewmodelprovider.GeneratedProvider; + +@GeneratedProvider +public final class DopeViewModel extends ViewModel { + + private final Context context; + private final Long fucksGiven; + + public DopeViewModel(Context context, Long fucksGiven) { + this.context = context; + this.fucksGiven = fucksGiven; + } +} diff --git a/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/EvenLamerViewModel.java b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/EvenLamerViewModel.java new file mode 100644 index 0000000..e19e032 --- /dev/null +++ b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/EvenLamerViewModel.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.apps.android.alfreddemo; + +import android.arch.lifecycle.ViewModel; +import android.content.Context; + +public final class EvenLamerViewModel extends ViewModel { + + private Context context; + private Long fucksGiven; + + public EvenLamerViewModel() { + } + + public void setContext(Context context) { + this.context = context; + } + + public void setFucksGiven(Long fucksGiven) { + this.fucksGiven = fucksGiven; + } +} diff --git a/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/LameViewModel.java b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/LameViewModel.java new file mode 100644 index 0000000..3cefc2d --- /dev/null +++ b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/LameViewModel.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.apps.android.alfreddemo; + +import android.arch.lifecycle.ViewModel; +import android.content.Context; + +public final class LameViewModel extends ViewModel { + + private final Context context; + private final Long fucksGiven; + + public LameViewModel(Context context, Long fucksGiven) { + this.context = context; + this.fucksGiven = fucksGiven; + } +} diff --git a/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/SeeAlfredInAction.java b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/SeeAlfredInAction.java new file mode 100644 index 0000000..58c51db --- /dev/null +++ b/demo/src/main/java/com/hadisatrio/apps/android/alfreddemo/SeeAlfredInAction.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017 Hadi Satrio + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.hadisatrio.apps.android.alfreddemo; + +import android.arch.lifecycle.ViewModelProviders; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +public final class SeeAlfredInAction extends AppCompatActivity { + + private DopeViewModel dopeViewModel; + private LameViewModel lameViewModel; + private EvenLamerViewModel evenLamerViewModel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_see_alfred_in_action); + + // This is dope... + dopeViewModel = DopeViewModelProvider.get(this, this, 0L); + + // ...this is lame.. + lameViewModel = ViewModelProviders.of(this, new CustomViewModelFactory(this, 0L)).get(LameViewModel.class); + + // ..and don't even get me started with this. + evenLamerViewModel = ViewModelProviders.of(this).get(EvenLamerViewModel.class); + evenLamerViewModel.setContext(this); + evenLamerViewModel.setFucksGiven(1000L); + } +} diff --git a/demo/src/main/res/drawable/ic_launcher_background.xml b/demo/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..de7cd4f --- /dev/null +++ b/demo/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demo/src/main/res/layout/activity_see_alfred_in_action.xml b/demo/src/main/res/layout/activity_see_alfred_in_action.xml new file mode 100644 index 0000000..ac5b263 --- /dev/null +++ b/demo/src/main/res/layout/activity_see_alfred_in_action.xml @@ -0,0 +1,32 @@ + + + + + + diff --git a/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..10a73ce --- /dev/null +++ b/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..10a73ce --- /dev/null +++ b/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/demo/src/main/res/mipmap-hdpi/ic_launcher.png b/demo/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..5507303 Binary files /dev/null and b/demo/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demo/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/demo/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..4e526c9 Binary files /dev/null and b/demo/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png b/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..8fab6a3 Binary files /dev/null and b/demo/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/demo/src/main/res/mipmap-mdpi/ic_launcher.png b/demo/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..6bc7fcd Binary files /dev/null and b/demo/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demo/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/demo/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..2c38c71 Binary files /dev/null and b/demo/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png b/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..1eecc0e Binary files /dev/null and b/demo/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/demo/src/main/res/mipmap-xhdpi/ic_launcher.png b/demo/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..ec87dce Binary files /dev/null and b/demo/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demo/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/demo/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..072467e Binary files /dev/null and b/demo/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..05ca079 Binary files /dev/null and b/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..6f67f21 Binary files /dev/null and b/demo/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demo/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/demo/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..78a6b7a Binary files /dev/null and b/demo/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8bac0f2 Binary files /dev/null and b/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..0327e13 Binary files /dev/null and b/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..68ebe33 Binary files /dev/null and b/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..bacd3e7 Binary files /dev/null and b/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/demo/src/main/res/values/colors.xml b/demo/src/main/res/values/colors.xml new file mode 100644 index 0000000..0c25182 --- /dev/null +++ b/demo/src/main/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/demo/src/main/res/values/strings.xml b/demo/src/main/res/values/strings.xml new file mode 100644 index 0000000..602d77b --- /dev/null +++ b/demo/src/main/res/values/strings.xml @@ -0,0 +1,19 @@ + + + + Alfred + diff --git a/demo/src/main/res/values/styles.xml b/demo/src/main/res/values/styles.xml new file mode 100644 index 0000000..a210f89 --- /dev/null +++ b/demo/src/main/res/values/styles.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..6ed0f8f --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +org.gradle.jvmargs=-Xmx1536m diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle new file mode 100644 index 0000000..5d2e88e --- /dev/null +++ b/gradle/dependencies.gradle @@ -0,0 +1,25 @@ +ext { + + // Alfred + alfredAnnotations = project(":annotations") + alfredCompiler = project(":compiler") + + // Gradle Plugins + androidToolsPlugin = "com.android.tools.build:gradle:$androidGradleVersion" + + // 1st Party Android Libraries + supportv4 = "com.android.support:support-v4:$supportLibraryVersion" + appcompatv7 = "com.android.support:appcompat-v7:$supportLibraryVersion" + constraintLayout = "com.android.support.constraint:constraint-layout:1.0.2" + archComponents = "android.arch.lifecycle:runtime:$archComponentsVersion" + archComponentsExtensions = "android.arch.lifecycle:extensions:$archComponentsVersion" + archComponentsCompiler = "android.arch.lifecycle:compiler:$archComponentsVersion" + testingSupportLib = "com.android.support.test:testing-support-lib:0.1" + runner = "com.android.support.test:runner:1.0.0" + + // 3rd Party Libraries + autoservice = "com.google.auto.service:auto-service:$autoserviceVersion" + javapoet = "com.squareup:javapoet:$javapoetVersion" + espressoCore = "com.android.support.test.espresso:espresso-core:$espressoVersion" + junit = "junit:junit:4.12" +} diff --git a/gradle/versions.gradle b/gradle/versions.gradle new file mode 100644 index 0000000..a16f722 --- /dev/null +++ b/gradle/versions.gradle @@ -0,0 +1,35 @@ +ext { + + // Alfred Versions + alfredAnnotationsVersion = "1.0.0-RC.1" + alfredCompilerVersion = "1.0.0-RC.1" + + // Demo App-Related Versions + demoVersionCode = 1 + demoVersionName = "1.0.0-RC.1" + minSdkVersion = 14 + targetSdkVersion = 26 + compileSdkVersion = 26 + + // Build-Related Versions + buildToolsVersion = "26.0.1" + sourceCompatibilityVersion = "1.7" // JavaVersion.VERSION_1_7 + targetCompatibilityVersion = "1.7" // JavaVersion.VERSION_1_7 + + // Plugins / Build-Level Versions + androidGradleVersion = "3.0.0-beta2" + + // 1st Party Android Library Versions + supportLibraryVersion = "26.0.1" + constraintLayoutVersion = "1.0.2" + archComponentsVersion = "1.0.0-alpha9" + + // 3rd Party Library Versions + autoserviceVersion = "1.0-rc3" + javapoetVersion = "1.9.0" + + // Test-Related Library Versions + junitVersion = "4.12" + mockitoVersion = "2.8.47" + espressoVersion = "3.0.0" +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..13372ae Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..957b5db --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,20 @@ +# +# Copyright (C) 2017 Hadi Satrio +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-rc-1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..9d82f78 --- /dev/null +++ b/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8a0b282 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..4754320 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':demo', ':annotations', ':compiler'