diff --git a/.gitignore b/.gitignore
old mode 100755
new mode 100644
index 0a885dc..621289a
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,9 @@ local.properties
.gradle
build
gradle.properties
+
+# IDEA
+./out
+.DS_Store
+.idea
+*.iml
diff --git a/LICENSE b/LICENSE
old mode 100755
new mode 100644
diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index 6c8afd3..9bf858d
--- a/README.md
+++ b/README.md
@@ -211,20 +211,20 @@ Note that the Active Annotations run at edit-time and simply generate the usual
Getting Started
===============
-Have a look at the [XtendApp skeleton app][xtendapp] to jump-start your project. It is pre-configured and works in Android Studio as well (although support for Xtend in Android Studio is still in development).
+Have a look at the [XtendApp skeleton app][xtendapp] to jump-start your project. It is a pre-configured skeleton Xtendroid app for Android Studio 2+. Simply clone it to begin your new project.
Method 1: Copy JAR file in
------------------------
- Download the latest release from https://github.com/tobykurien/Xtendroid/tree/master/Xtendroid/release
- Copy the JAR file into your Android project's `libs` folder
-- If your project isn't Xtend-enabled yet:
+- If your project isn't Xtend-enabled yet in Eclipse:
- Right-click on your project -> Properties -> Java Build Path
- Click Libraries -> Add library -> Xtend Library
- Now you can use it as documented [here][doc].
Method 2: Gradle build config
---------------------------
-- In your `build.gradle` file, add a compile dependency for ```com.github.tobykurien:xtendroid:0.12.0``` and also add the [Xtend compiler](https://github.com/oehme/xtend-gradle-plugin)
+- In your `build.gradle` file, add a compile dependency for ```com.github.tobykurien:xtendroid:0.13``` and also add the [Xtend compiler](http://xtext.github.io/xtext-gradle-plugin/xtend.html)
- A typical `build.gradle` file looks as follows:
```groovy
@@ -235,13 +235,13 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.2.3'
- classpath 'org.xtend:xtend-android-gradle-plugin:0.4.8'
+ classpath 'com.android.tools.build:gradle:2.1.0-alpha3'
+ classpath 'org.xtext:xtext-android-gradle-plugin:1.0.3'
}
}
apply plugin: 'android'
-apply plugin: 'org.xtend.xtend-android'
+apply plugin: 'org.xtext.android.xtend'
repositories {
mavenCentral()
@@ -249,9 +249,9 @@ repositories {
android {
dependencies {
- compile 'com.github.tobykurien:xtendroid:0.12.1'
+ compile 'com.github.tobykurien:xtendroid:0.13'
- compile 'org.eclipse.xtext:org.eclipse.xtext.xbase.lib:2.8.3'
+ compile 'org.eclipse.xtend:org.eclipse.xtend.lib:2.9.1'
// other dependencies here
}
@@ -263,24 +263,32 @@ android {
Xtend
=====
-The latest version of Xtendroid is built with Xtend v2.8.1. For more about the Xtend language, see [http://xtend-lang.org][xtend].
+The latest version of Xtendroid is built with Xtend v2.9.1. For more about the Xtend language, see [http://xtend-lang.org][xtend].
A port of Xtendroid to [Groovy][] is in the works, see [android-groovy-support][]
-Gotchas
-=======
+IDE Support
+===========
-Note that Xtend and Xtendroid are currently supported in Eclipse (Xtend is an Eclipse project), although projects using them can be compiled with Maven or Gradle. You can [use Xtendroid in Android Studio][android_studio], but the Android Studio Xtend editor is currently still under development. For Android Studio, an alternative is to use the [android-groovy-support] project.
+Xtend and Xtendroid are currently supported in Eclipse (Xtend is an Eclipse project) as well as Android Studio 2+ (or IntelliJ 15+). Here's how to [use Xtendroid in Android Studio][android_studio]. Also for Android Studio, check out the [android-groovy-support] project for a similar library for the Groovy language.
If you'd like to use Gradle for your build configuration, but still be able to develop in Eclipse, use the [Eclipse AAR plugin for Gradle][eclipse_aar_gradle]. This also allows you to use either Eclipse or Android Studio while maintaining a single build configuration.
+Gotchas
+=======
+
There are currently some bugs with the Eclipse Xtend editor that can lead to unexpected behaviour (e.g. compile errors).
Here are the current bugs you should know about:
- [Android: Editor not refreshing R class](https://bugs.eclipse.org/bugs/show_bug.cgi?id=433358)
- [Android: First-opened Xtend editor shows many errors and never clears those errors after build ](https://bugs.eclipse.org/bugs/show_bug.cgi?id=433589)
-If in doubt, clean the project, and re-open the editor.
+If in doubt, close and re-open the file, or worst-case, clean the project.
+
+Some Xtend Gradle plugin gotchas:
+
+- [First Gradle build fails, but works thereafter](https://github.com/xtext/xtend-gradle-plugin/issues/32)
+
[Xtend]: http://xtend-lang.org
[xtend-doc]: http://www.eclipse.org/xtend/documentation.html
@@ -291,7 +299,7 @@ If in doubt, clean the project, and re-open the editor.
[examples]: /examples
[Xtendroid Test app]: /XtendroidTest
[xtendapp]: https://github.com/tobykurien/XtendApp
-[android_studio]: https://github.com/tobykurien/Xtendroid/issues/62
+[android_studio]: https://github.com/tobykurien/Xtendroid/wiki/HowTo-setup-Android-Studio-%28aka-Intellij%29-support
[eclipse_aar_gradle]: https://github.com/ksoichiro/gradle-eclipse-aar-plugin
[Groovy]: http://groovy-lang.org
[android-groovy-support]: https://github.com/tobykurien/android-groovy-support
\ No newline at end of file
diff --git a/Xtendroid/.classpath b/Xtendroid/.classpath
old mode 100755
new mode 100644
index e36a6ec..54c971b
--- a/Xtendroid/.classpath
+++ b/Xtendroid/.classpath
@@ -2,9 +2,10 @@
-
-
-
-
+
+
+
+
+
diff --git a/Xtendroid/.gitignore b/Xtendroid/.gitignore
old mode 100755
new mode 100644
diff --git a/Xtendroid/.project b/Xtendroid/.project
old mode 100755
new mode 100644
index 8515f8e..9a24b09
--- a/Xtendroid/.project
+++ b/Xtendroid/.project
@@ -6,12 +6,12 @@
- com.android.ide.eclipse.adt.ResourceManagerBuilder
+ org.eclipse.andmore.ResourceManagerBuilder
- com.android.ide.eclipse.adt.PreCompilerBuilder
+ org.eclipse.andmore.PreCompilerBuilder
@@ -26,13 +26,13 @@
- com.android.ide.eclipse.adt.ApkBuilder
+ org.eclipse.andmore.ApkBuilder
- com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.andmore.AndroidNatureorg.eclipse.jdt.core.javanatureorg.eclipse.xtext.ui.shared.xtextNature
diff --git a/Xtendroid/.settings/org.eclipse.core.resources.prefs b/Xtendroid/.settings/org.eclipse.core.resources.prefs
old mode 100755
new mode 100644
diff --git a/Xtendroid/.settings/org.eclipse.jdt.core.prefs b/Xtendroid/.settings/org.eclipse.jdt.core.prefs
old mode 100755
new mode 100644
index ef8a789..3bec23d
--- a/Xtendroid/.settings/org.eclipse.jdt.core.prefs
+++ b/Xtendroid/.settings/org.eclipse.jdt.core.prefs
@@ -1,4 +1,13 @@
eclipse.preferences.version=1
+org.eclipse.jdt.core.builder.cleanOutputFolder=clean
+org.eclipse.jdt.core.builder.duplicateResourceTask=warning
+org.eclipse.jdt.core.builder.invalidClasspath=abort
+org.eclipse.jdt.core.builder.recreateModifiedClassFileInOutputFolder=enabled
+org.eclipse.jdt.core.builder.resourceCopyExclusionFilter=*._trace,*.xtend,*.xtendbin
+org.eclipse.jdt.core.circularClasspath=error
+org.eclipse.jdt.core.classpath.exclusionPatterns=enabled
+org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled
+org.eclipse.jdt.core.classpath.outputOverlappingAnotherSource=error
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
@@ -7,6 +16,9 @@ org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.maxProblemPerUnit=100
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.6
+org.eclipse.jdt.core.incompatibleJDKLevel=ignore
+org.eclipse.jdt.core.incompleteClasspath=error
diff --git a/Xtendroid/.settings/org.eclipse.xtend.core.Xtend.prefs b/Xtendroid/.settings/org.eclipse.xtend.core.Xtend.prefs
old mode 100755
new mode 100644
diff --git a/Xtendroid/AndroidManifest.xml b/Xtendroid/AndroidManifest.xml
old mode 100755
new mode 100644
index 11e2386..bb07e03
--- a/Xtendroid/AndroidManifest.xml
+++ b/Xtendroid/AndroidManifest.xml
@@ -5,7 +5,7 @@
+ android:targetSdkVersion="23" />
diff --git a/Xtendroid/build.gradle b/Xtendroid/build.gradle
old mode 100755
new mode 100644
index 8b995b1..c23b074
--- a/Xtendroid/build.gradle
+++ b/Xtendroid/build.gradle
@@ -1,38 +1,64 @@
-apply plugin: 'android-library'
-apply plugin: "org.xtend.xtend-android"
+buildscript {scriptHandler->
+ apply from: '../repositories.gradle', to: scriptHandler
+ apply from: '../dependencies.gradle'
+ dependencies {
+ classpath(project.ext.build.android_gradle)
+ classpath(project.ext.build.xtend_android_gradle)
+ }
+}
+
+repositories {
+ jcenter()
+}
+
+apply plugin: 'com.android.library'
+apply plugin: "org.xtext.android.xtend"
+apply from: '../dependencies.gradle'
+apply from: './maven-push.gradle'
dependencies {
- compile 'com.android.support:support-v4:20.0.0'
- compile 'org.eclipse.xtend:org.eclipse.xtend.lib:2.7.3'
- xtendCompileOnly 'com.github.oehme.xtend:xtend-contrib:0.4.+'
+ compile 'com.google.code.gson:gson:2.3.1' // required by @AndroidJsonizer
+ compile(project.ext.lib.android.support_v4)
+ compile(project.ext.lib.android.appcompat_v7)
+ compile(project.ext.lib.xtend)
}
android {
- compileSdkVersion 19
- buildToolsVersion "21.1.1"
-
- sourceSets {
- main {
- manifest {
- srcFile 'AndroidManifest.xml'
- }
- java {
- srcDir 'src'
- }
- res {
- srcDir 'res'
- }
- assets {
- srcDir 'assets'
- }
- resources {
- srcDir 'src'
- }
- aidl {
- srcDir 'src'
- }
+ compileSdkVersion(project.ext.compileSdkVersion)
+ buildToolsVersion(project.ext.buildToolsVersion)
+
+ sourceSets {
+ main {
+ manifest{ srcFile 'AndroidManifest.xml' }
+ java.srcDirs += ['src']
+ res.srcDirs += ['res']
+ assets.srcDirs += ['assets']
+ resources.srcDirs += ['src']
+ aidl.srcDirs += ['src']
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
- }
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ packagingOptions {
+ // from xtendlib >=2.9.2
+ exclude 'META-INF/ECLIPSE_.RSA'
+ exclude 'META-INF/ECLIPSE_.SF'
+ }
}
-apply from: 'maven-push.gradle'
+
diff --git a/Xtendroid/docs/coming_soon.md b/Xtendroid/docs/coming_soon.md
old mode 100755
new mode 100644
diff --git a/Xtendroid/docs/index.md b/Xtendroid/docs/index.md
old mode 100755
new mode 100644
index e945afb..6dcb762
--- a/Xtendroid/docs/index.md
+++ b/Xtendroid/docs/index.md
@@ -4,6 +4,7 @@ Documentation
**Contents**
- [Activities and Fragments](#activities-and-fragments)
+- [Dialogs](#dialogs)
- Background Tasks
- [AsyncBuilder](#background-tasks-using-asynctask)
- Data storage
@@ -52,6 +53,9 @@ You can do something similar in a fragment using the ```@AndroidFragment``` anno
```
+Dialogs
+-------
+
Dialogs in Android have become quite painful, because you can either use ```AlertDialog.Builder```, or implement a ```DialogFragment``` with a custom view, where you have to provide the title and buttons, and theme it yourself (which is difficult, since AppCompat doesn't help you with dialogs). To make this simpler, you can use the ```@AndroidDialogFragment``` annotation to implement an (for example) ```AlertDialog.Builder``` based dialog with custom content, and allows you to code the view widgets the same way you would in a regular ```DialogFragment```:
```xtend
@@ -61,7 +65,7 @@ Dialogs in Android have become quite painful, because you can either use ```Aler
// We can optionally implement our own dialog quite simply as follows:
override onCreateDialog(Bundle instance) {
- // Instead of AlertDialog.Buider, we for example could use
+ // Instead of AlertDialog.Builder, we for example could use
// MaterialDialog.Builder from https://github.com/afollestad/material-dialogs
new AlertDialog.Builder(activity)
.setTitle("My dialog")
@@ -101,7 +105,7 @@ class MyActivity extends Activity {
Background tasks using AsyncTask
--------------------------------
-A class called ```AsyncBuilder``` is provided, that extends the standard ```AsyncTask``` and works in much the same way, but provides lambda parameters for the background task and the UI task, thus reducing boilerplate. It also takes care of cancelling UI callbacks if the AsyncTask is cancelled, and also handles any errors occurring in the background or UI threads by passing the error to the ```onError``` lambda.
+A class called ```AsyncBuilder``` is provided, that extends the standard ```AsyncTask``` and works in much the same way, but provides lambda parameters for the background task and the UI task, thus reducing boilerplate. It also takes care of cancelling UI callbacks if the AsyncTask is cancelled, thread pooling, managing of progress bars/dialogs, handling of any errors occurring in the background or UI threads, and ensuring that the code executes in the correct thread (either background or UI thread). It also returns the ```AsyncTask``` reference, which you can use to cancel it or await completion, etc.
A simple example of usage:
@@ -151,13 +155,14 @@ async [
).start()
```
-Here is a complete example of using the ```AsyncBuilder```:
+Here is a more complete example of using the ```AsyncBuilder```:
```xtend
-// Use AsynBuilder to run a background task
+// Make a "loading" progress dialog
val pd = new ProgressDialog()
+pd.message = "Loading..."
-async(pd) [task, params|
+val task = async(pd) [task, params|
// Do some work in the background thread
for (i : 1..50) {
Thread.sleep(100)
@@ -185,7 +190,10 @@ async(pd) [task, params|
].onError [Exception error|
// this runs if an error occurred anywhere else
mainHello.text = '''Error! «error.class.name» «error.message»'''
-].start("Param1") // don't forget to call start to kick-off the task
+].start("Param1") // don't forget to call start, passing any needed params
+
+// in onPause() you could:
+if (task?.status == AsyncTask.Status.RUNNING) task.cancel(true)
```
>Note: Since Honeycomb, Android has defaulted to using a single thread for all AsyncTasks, because too many developers were writing non-thread-safe code. When using AsyncBuilder's ```start()``` method instead of the ```execute()``` method, it will run multiple AsyncTasks simultaneously using the THREAD_POOL_EXECUTOR, so be careful to write thread-safe code.
@@ -312,6 +320,8 @@ method to inflate and manage your recycled view.
}
```
+> Note: You can use the ```@AndroidViewHolder``` annotation in ```Activity``` and ```Fragment``` classes too, for example to load a header layout into a ```ListView``` header. You can even reuse the view holder across multiple classes that use the same layout!
+
Database
--------
@@ -464,18 +474,30 @@ In addition to using it at the class-level, you can also use it at the field-lev
>Note: The ```@JsonProperty``` annotation has been deprecated in favour of ```@AndroidJson```.
+The new ```@AndroidJsonized``` annotation can make this even easier! Simply paste in a sample JSON response, and it will automagically create the bean for you, inferring the member types e.g.:
+
+```xtend
+@AndroidJsonized('''{
+ "firstName": "John",
+ "lastName": "Smith",
+ "age": 32
+}''')
+class User {}
+
+var john = new User(new JSONObject(serverResponse))
+toast(john.firstName + " " + john.lastName + ", age " john.age)
+```
+
Intents and Bundles
-------------------
-The member-level `@BundleProperty` annotation, will create a convenience methods for extracting a value from a Bundle or Intent.
-
-If a `@BundleProperty` is applied to a member of an Activity then the generated convenience method will be applied to the intent of the Activity. If applied to a Fragment, then it will be applied to the Bundle (through the `getArguments()` method). If applied to a bean or a Service or any other type other than an Activity or Fragment, then the annotation will attempt to find an Intent among the members and do the same.
+The member-level `@BundleProperty` annotation will create convenience methods for extracting a value from a Bundle or Intent. If a `@BundleProperty` is applied to a member of an Activity then the generated convenience method will be applied to the intent of the Activity. If applied to a Fragment, then it will be applied to the Bundle (through the `getArguments()` method). If applied to a bean or a Service or any other type other than an Activity or Fragment, then the annotation will attempt to find an Intent among the members and do the same.
Suppose an Activity is expecting a "country" bundle extra, but will default to "South Africa" if not found:
```xtend
class MyActivity extends Activity {
- @BundleProperty String country = "South Africa"
+ @BundleProperty String country = "South Africa"
@BundleProperty String category
override onStart() {
@@ -497,23 +519,27 @@ startActivity(intent)
The above also works for using an arguments Bundle for Fragments too.
```xtend
-var bundle = new Bundle
-MyActivity.putCountry(bundle, "Finland")
-MyActivity.putCategory(bundle, "Sports")
+@AndroidFragment(R.layout.my_fragment) class MyFragment {
+ @BundleProperty String country
+ @BundleProperty String category
+}
-// set bundle as arguments, etc.
+var frag = new MyFragment
+frag.putCountry("Finland")
+frag.putCategory("Sports")
+
+// display the fragment using fragmentManager...
```
+You can also attach the ```@BundleProperty``` annotation to any ```Parcelable``` member as well (see below).
+
Parcelables
-----------
-Currently, the `@AndroidParcelable` is a type-level (read: class) annotation that ensures that all the member fields will be serialized using the android Parcel way of serializing stuff.
-
-All you need to do is just slap the annotation on top of the bean, and pass the Parcelable through an Intent. Here's an example:
+Currently, the `@AndroidParcelable` is a type-level (read: class) annotation that ensures that all the member fields will be serialized using the android ```Parcel``` way of serializing stuff. All you need to do is just slap the annotation on top of the bean. Here's an example:
```xtend
-@AndroidParcelable
-class ParcelableData {
+@AndroidParcelable class ParcelableData {
public int age
public long createdAt
public float likeAButterfly
@@ -528,7 +554,7 @@ class ParcelableData {
Now this Parcelable can be added to an Intent:
```xtend
-var p = new ParcelableData
+var p = new ParcelableData()
p.age = 1
p.createdAt = new Date().time
p.likeAButterfly = 0.1234f
@@ -539,7 +565,7 @@ intent2.putExtra("parcel", p)
startActivity(intent2)
```
-The receiving Fragment or Activity can retrieve the data using `Intent.getParcelableExtra("parcel")`.
+The receiving Fragment or Activity can retrieve the data using ```Intent.getParcelableExtra("parcel")```. A cleaner method is to use ```@BundleProperty``` to declare the arguments (see above).
Utilities
---------
@@ -552,11 +578,11 @@ toast("Upload started!")
toastLong("No internet connection")
confirm("Are you sure you want to exit?") [
- finish
+ finish()
]
```
-ViewUtils make getting widgets from views/activities/fragments/dialogs easier by eliminating the type-casting
+ViewUtils make getting widgets from views/activities/fragments/dialogs easier by eliminating the type-casting (see ```@AndroidActivity, @AndroidFragment, @AndroidDialogFragment, @AndroidViewHolder``` for a better method)
```xtend
import static extension org.xtendroid.utils.ViewUtils.*
diff --git a/Xtendroid/libs-compile/gson-2.6.2.jar b/Xtendroid/libs-compile/gson-2.6.2.jar
new file mode 100755
index 0000000..9d78626
Binary files /dev/null and b/Xtendroid/libs-compile/gson-2.6.2.jar differ
diff --git a/Xtendroid/proguard-project.txt b/Xtendroid/proguard-project.txt
old mode 100755
new mode 100644
diff --git a/Xtendroid/project.properties b/Xtendroid/project.properties
old mode 100755
new mode 100644
index 362a0a3..b2ef7dc
--- a/Xtendroid/project.properties
+++ b/Xtendroid/project.properties
@@ -11,5 +11,5 @@
#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
# Project target.
-target=android-22
+target=android-23
android.library=true
diff --git a/Xtendroid/release/xtendroid_v0.12.1.jar b/Xtendroid/release/xtendroid_v0.12.1.jar
deleted file mode 100755
index 35865c0..0000000
Binary files a/Xtendroid/release/xtendroid_v0.12.1.jar and /dev/null differ
diff --git a/Xtendroid/release/xtendroid_v0.13.jar b/Xtendroid/release/xtendroid_v0.13.jar
new file mode 100755
index 0000000..2db6e0d
Binary files /dev/null and b/Xtendroid/release/xtendroid_v0.13.jar differ
diff --git a/Xtendroid/res/values/strings.xml b/Xtendroid/res/values/strings.xml
old mode 100755
new mode 100644
diff --git a/Xtendroid/settings.gradle b/Xtendroid/settings.gradle
deleted file mode 100644
index 69217e8..0000000
--- a/Xtendroid/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':Xtendroid', ':examples:HelloWorld'
diff --git a/Xtendroid/src/asia/sonix/android/orm/AbatisService.java b/Xtendroid/src/asia/sonix/android/orm/AbatisService.java
old mode 100755
new mode 100644
diff --git a/Xtendroid/src/org/xtendroid/adapter/AndroidAdapter.xtend b/Xtendroid/src/org/xtendroid/adapter/AndroidAdapter.xtend
old mode 100755
new mode 100644
index 18aea9d..0fcd445
--- a/Xtendroid/src/org/xtendroid/adapter/AndroidAdapter.xtend
+++ b/Xtendroid/src/org/xtendroid/adapter/AndroidAdapter.xtend
@@ -181,6 +181,7 @@ class AdapterizeProcessor extends AbstractClassProcessor {
''']
returnType = int.newTypeReference
visibility = Visibility.PUBLIC
+ primarySourceElement = dataContainerField.primarySourceElement
]
clazz.addMethod("getItem") [
diff --git a/Xtendroid/src/org/xtendroid/adapter/AndroidViewHolder.xtend b/Xtendroid/src/org/xtendroid/adapter/AndroidViewHolder.xtend
old mode 100755
new mode 100644
diff --git a/Xtendroid/src/org/xtendroid/adapter/BeanAdapter.xtend b/Xtendroid/src/org/xtendroid/adapter/BeanAdapter.xtend
old mode 100755
new mode 100644
diff --git a/Xtendroid/src/org/xtendroid/annotations/AndroidDialogFragment.xtend b/Xtendroid/src/org/xtendroid/annotations/AndroidDialogFragment.xtend
old mode 100755
new mode 100644
index 5b780fb..8e564b5
--- a/Xtendroid/src/org/xtendroid/annotations/AndroidDialogFragment.xtend
+++ b/Xtendroid/src/org/xtendroid/annotations/AndroidDialogFragment.xtend
@@ -36,6 +36,7 @@ class DialogFragmentProcessor extends AbstractClassProcessor {
body = [
'''
// empty ctor prevents crashes
+ setArguments(new Bundle());
''']
]
}
diff --git a/Xtendroid/src/org/xtendroid/annotations/AndroidFragment.xtend b/Xtendroid/src/org/xtendroid/annotations/AndroidFragment.xtend
old mode 100755
new mode 100644
index e4cfd02..05cc34a
--- a/Xtendroid/src/org/xtendroid/annotations/AndroidFragment.xtend
+++ b/Xtendroid/src/org/xtendroid/annotations/AndroidFragment.xtend
@@ -46,6 +46,7 @@ class FragmentProcessor extends AbstractClassProcessor {
body = [
'''
// empty ctor prevents crashes
+ setArguments(new Bundle());
''']
]
}
diff --git a/Xtendroid/src/org/xtendroid/annotations/AndroidLoader.xtend b/Xtendroid/src/org/xtendroid/annotations/AndroidLoader.xtend
index aa2c2de..7a17d96 100644
--- a/Xtendroid/src/org/xtendroid/annotations/AndroidLoader.xtend
+++ b/Xtendroid/src/org/xtendroid/annotations/AndroidLoader.xtend
@@ -16,7 +16,6 @@ import org.eclipse.xtend.lib.macro.declaration.MutableClassDeclaration
import org.eclipse.xtend.lib.macro.declaration.Visibility
import static extension org.xtendroid.utils.NamingUtils.*
-import org.xtendroid.app.OnCreate
/**
*
@@ -50,10 +49,12 @@ class AndroidLoaderProcessor extends AbstractClassProcessor {
// we need at least one loader in the field
val mandatoryLoaderTypes = #['android.content.Loader', 'android.support.v4.content.Loader']
val loaderFields = clazz.declaredFields.filter[f|
- !f.type.inferred && f.initializer != null && (
+ !f.type.inferred /*&& f.initializer != null*/ && (
android.content.Loader.newTypeReference.isAssignableFrom(f.type) ||
Loader.newTypeReference.isAssignableFrom(f.type)
)]
+ // we also accept fields that are uninitialized, because this will result
+ // in broken first compilation that require a second build to complete
if (loaderFields.size == 0) {
clazz.declaredFields.filter[f|f.type.inferred].forEach[f|
@@ -84,8 +85,8 @@ class AndroidLoaderProcessor extends AbstractClassProcessor {
// generate ID tags with random numbers for each Loader
val className = clazz.simpleName // this was added to decrease the chance of collisions (but there are no guarantees)
- val randomInitialInt = loaderFields.map[f|className + f.simpleName].join().bytes.fold(0 as int,
- [_1, _2|_1 as int + _2 as int])
+ val randomInitialInt = loaderFields.map[f|className + f.simpleName].join().bytes.fold(0,
+ [_1, _2|_1 + _2])
for (var i = 0; i < loaderFields.length; i++) {
val int integer = i + randomInitialInt * (i + 1)
@@ -210,7 +211,7 @@ class AndroidLoaderProcessor extends AbstractClassProcessor {
]
val onCreateLoaderMethodBody = loaderFieldNames.map[n|
- String.format("if (%s == LOADER_ID) return get%sLoader();", n.loaderIdFromName,
+ String.format("if (%s == LOADER_ID) return get%s();", n.loaderIdFromName,
n.toJavaIdentifier.toFirstUpper)].join("\n")
// if multiple Loaders then no generic param
@@ -258,18 +259,19 @@ class AndroidLoaderProcessor extends AbstractClassProcessor {
// add getters for Loaders (NOTE: workaround/hack, because I don't know how to evaluate initializer exprs)
loaderFields.forEach [ f |
- clazz.addMethod("get" + f.simpleName.toJavaIdentifier.toFirstUpper + "Loader") [
+ clazz.addMethod("get" + f.simpleName.toJavaIdentifier.toFirstUpper) [
visibility = Visibility.PUBLIC
- body = f.initializer
+ if (f.initializer != null) {
+ body = f.initializer
+ }else
+ {
+ body = ['''
+ return «f.simpleName»;
+ ''']
+ }
returnType = f.type
+ primarySourceElement = f.primarySourceElement
]
]
-
- // remove useless fields
- clazz.declaredFields.filter[f|
- !f.type.inferred && f.initializer == null && (
- android.content.Loader.newTypeReference.isAssignableFrom(f.type) ||
- Loader.newTypeReference.isAssignableFrom(f.type)
- )].forEach[remove]
}
}
diff --git a/Xtendroid/src/org/xtendroid/annotations/AndroidPreference.xtend b/Xtendroid/src/org/xtendroid/annotations/AndroidPreference.xtend
old mode 100755
new mode 100644
index 0b5af75..46c5593
--- a/Xtendroid/src/org/xtendroid/annotations/AndroidPreference.xtend
+++ b/Xtendroid/src/org/xtendroid/annotations/AndroidPreference.xtend
@@ -91,6 +91,7 @@ class AndroidPreferenceProcessor implements TransformationParticipantIt uses java's @link{java.text.MessageFormat} in the values and the accessor methods will sport typed parameters according to the placeholders.
@@ -28,11 +38,21 @@ import static extension org.xtendroid.utils.XmlUtils.*
*
*/
@Active(AndroidResourcesProcessor)
-annotation AndroidResources {}
+@Target(value=#[ElementType.FIELD, ElementType.TYPE])
+annotation AndroidResources {
+ Class> type = Object // e.g. R.string, R.integer, R.boolean
+ String path = "build/intermediates/res/merged/debug/values/values.xml" // where everything is merged together
+}
+
+class AndroidResourcesProcessor implements TransformationParticipant, RegisterGlobalsParticipant {
-class AndroidResourcesProcessor extends AbstractClassProcessor {
- override doTransform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {
+ /**
+ *
+ * Transform a pojo class to support getters for resources
+ *
+ */
+ def dispatch void transform(MutableClassDeclaration annotatedClass, extension TransformationContext context) {
val resourcesType = Resources.newTypeReference
if (annotatedClass.findDeclaredMethod("getResources", resourcesType) == null) {
annotatedClass.addMethod('getResources') [
@@ -43,6 +63,13 @@ class AndroidResourcesProcessor extends AbstractClassProcessor {
annotatedClass.abstract = true
}
+ // Does this work with new style android gradle default file structure?
+ // This should be parameterizable, to support something like:
+ // ./XtendroidTest/build/intermediates/res/merged/debug/values/values.xml,
+ // because everything is merged here, and there is no guarantee that strings are stored in strings.xml
+ // these could be placed into e.g. strings1.xml, strings2.xml, moar-strings.xml, tanga.xml
+ // also there are no guarantees that these will be put into res/values either e.g.
+ // res/values--- are also valid locations
val stringsPath = annotatedClass.compilationUnit.filePath.projectFolder.append("res/values/strings.xml")
if (stringsPath.exists) {
stringsPath.contentsAsStream.document.traverseAllNodes [
@@ -89,4 +116,203 @@ class AndroidResourcesProcessor extends AbstractClassProcessor {
}
+ val mapResourceTypeToGetMethod = #{
+ 'R.string' -> 'getString'
+ , 'R.color' -> 'getColor'
+ , 'R.dimen' -> 'getDimension'
+ , 'R.bool' -> 'getBoolean' // TODO see also getBooleanArray... or something
+ , 'R.integer' -> 'getInteger'
+ //, 'R.fraction' -> 'getFraction' // tricky bugger
+ }
+
+ val mapResourceTypeToReturnType = #{
+ 'R.string' -> String
+ , 'R.color' -> Integer
+ , 'R.dimen' -> Float
+ , 'R.bool' -> Boolean // TODO see also getBooleanArray... or something
+ , 'R.integer' -> Integer
+ //, 'R.fraction' -> '?' // tricky bugger
+ }
+
+ val mapAggregateResourceTypeReturnType = #{
+ 'integer-array' -> typeof(int) //.newTypeReference.newArrayTypeReference
+ , 'string-array' -> typeof(String) //.newTypeReference.newArrayTypeReference
+ }
+
+ val mapAggregateResourceTypeToGetMethod = #{
+ 'integer-array' -> 'getIntArray'
+ , 'string-array' -> 'getStringArray'
+ }
+
+ def parseXmlAndGenerateGetters (MutableClassDeclaration annotatedClass, String xmlPath, String resourceTypeName, extension TransformationContext context)
+ {
+ val xmlSource = annotatedClass.compilationUnit.filePath.projectFolder.append(xmlPath)
+
+ if (!xmlSource.exists) {
+ annotatedClass.addError(String.format("The xml %s path does not exist", xmlPath)) // TODO throw exception, catch it in the calling method?
+ return // get out
+ }
+
+ if (!resourceTypeName.startsWith('R.')) {
+ annotatedClass.addError('The resource type must start with R. (e.g. R.string, R.integer etc.)')
+ }
+ val resourceNodeName = resourceTypeName.replaceFirst("R.", '')
+
+ // import package.R
+ val resourceType = resourceTypeName.findTypeGlobally.newTypeReference
+
+ // actually only and are supported
+ val aggregateResourceNodeName = resourceNodeName + '-array'
+
+ xmlSource.contentsAsStream.document.traverseAllNodes [
+ if (nodeName == resourceNodeName) {
+ val name = getAttribute('name')
+ annotatedClass.addMethod("get" + NamingUtils.toJavaIdentifier(name).toFirstUpper) [
+ returnType = mapResourceTypeToReturnType.get(resourceTypeName).newTypeReference
+ body = [
+ '''
+ if (mResources == null)
+ {
+ mResources = mContext.getResources();
+ }
+ return mResources.«mapResourceTypeToGetMethod.get(resourceTypeName)»(«toJavaCode(resourceType)».«name»);
+ '''
+ ]
+ ]
+ }
+
+ // actually only and are supported
+ if (nodeName == aggregateResourceNodeName) {
+ val name = getAttribute('name')
+ annotatedClass.addMethod("get" + NamingUtils.toJavaIdentifier(name).toFirstUpper) [
+ returnType = mapAggregateResourceTypeReturnType.get(aggregateResourceNodeName).newTypeReference.newArrayTypeReference
+ body = [
+ '''
+ if (mResources == null)
+ {
+ mResources = mContext.getResources();
+ }
+ return mResources.«mapAggregateResourceTypeToGetMethod.get(aggregateResourceNodeName)»(R.array.«name»);
+ '''
+ ]
+ ]
+ }
+ ]
+ }
+
+ /*
+
+ Usage:
+
+ 1. Apply to any type (Activity, Fragment, View, Poxo) type pray you don't have a name collision
+ 2. Apply to a member variable
+
+ */
+
+ val androidResourcesString = "AndroidResources"
+ def dispatch void transform(MutableFieldDeclaration field, extension TransformationContext context) {
+
+ val annotation = field.annotations.findFirst[ androidResourcesString.equals(annotationTypeDeclaration.simpleName) ]
+
+ // determine the resource type
+ val resourceTypeName = annotation.getExpression("type").toString
+
+ // field name == resource class name
+ val resourceHelperClassName = field.packageNameFromField + field.simpleName.toFirstUpper
+ val resourceHelperClass = context.findClass(resourceHelperClassName)
+
+ // DIY if you want android.R (this is merged eventually), choose your xml path wisely
+ if (resourceTypeName.contains('drawable'))
+ {
+ // TODO file based, not xml, als Resource#getDrawable/1 is deprecated from api level 22 onwards
+ }else
+ {
+ parseXmlAndGenerateGetters(resourceHelperClass, annotation.getStringValue('path'), resourceTypeName, context)
+ }
+
+ // determine that host class is an Activity, Fragment, Pojc
+ // then adapt the Context#getResources injection method
+ // where to get the inflater
+ resourceHelperClass.addField("mContext") [
+ visibility = Visibility.PRIVATE
+ type = Context.newTypeReference
+ final = true
+ ]
+
+ resourceHelperClass.addField("mResources") [
+ visibility = Visibility.PRIVATE
+ type = Resources.newTypeReference
+ ]
+
+ resourceHelperClass.addConstructor [
+ visibility = Visibility::PUBLIC
+ body = [
+ '''
+ this.mContext = context;
+ ''']
+ addParameter("context", Context.newTypeReference)
+ ]
+
+ /*
+ if (field...extendedClass.equals(Context.newTypeReference) {
+ field.initializer = '''new «field.simpleName.toFirstUpper»(this)''' // TODO when instantiating in an Activity or Service
+ }else if (field...extendedClass.equals(android.app.Fragment.newTypeReference)) { // api level >=11, TODO also add supportlib support
+ {
+ field.initializer = '''new «field.simpleName.toFirstUpper»(getActivity())''' // TODO when instantiating in a fragment
+ }else if (field...extendedClass.equals( android.view.View.newTypeReference)) {
+ {
+ field.initializer = '''new «field.simpleName.toFirstUpper»(mContext)''' // TODO when instantiating in a custom view
+ }else
+ {
+ clazz.addWarning("Currently the use-case beyond Activity/Service/View is out-of-scope.")
+ return // get out, you're on your own
+ }
+ */
+
+ // instantiate resource helper object
+ field.initializer = '''new «field.simpleName.toFirstUpper»(this)''' // TODO see the code block above, right now we only support Activity
+ field.type = resourceHelperClass.newTypeReference
+ field.final = true
+ }
+
+ override doTransform(List extends MutableMemberDeclaration> list, TransformationContext context) {
+ list.forEach[ transform(context) ]
+ }
+
+ /**
+ *
+ * Create a new type based on the R.
+ *
+ * This must hold: assertTrue(field.simpleName.equals(field.declaringType.simpleName))
+ *
+ */
+ override doRegisterGlobals(List list, RegisterGlobalsContext context) {
+
+ for (m : list)
+ {
+ try {
+ val field = m as FieldDeclaration
+
+ // assertTrue(field.simpleName.equals(field.declaringType.simpleName))
+ // because field doesn't have a type yet during this pass
+ val fullClassName = field.packageNameFromField + field.simpleName.toFirstUpper
+ if (context.findSourceClass(fullClassName) == null)
+ {
+ // register only once, this assumption holds unless you sneakily change e.g. values.xml
+ // mid-transpilation
+ context.registerClass(fullClassName)
+ }
+ } catch (ClassCastException ex) { /* continue */ }
+ }
+ }
+
+ // TODO remove duplicate in EnumProperty
+ def dispatch getPackageNameFromField(FieldDeclaration field) {
+ val fieldTypeSimpleName = field.declaringType.simpleName
+ val fieldTypeName = field.declaringType.qualifiedName
+ val package = fieldTypeName.replace(fieldTypeSimpleName, '')
+ package
+ }
+
+
}
diff --git a/Xtendroid/src/org/xtendroid/db/AndroidDatabase.xtend b/Xtendroid/src/org/xtendroid/db/AndroidDatabase.xtend
old mode 100755
new mode 100644
diff --git a/Xtendroid/src/org/xtendroid/db/BaseDbService.xtend b/Xtendroid/src/org/xtendroid/db/BaseDbService.xtend
old mode 100755
new mode 100644
index 23f36dc..2387cd1
--- a/Xtendroid/src/org/xtendroid/db/BaseDbService.xtend
+++ b/Xtendroid/src/org/xtendroid/db/BaseDbService.xtend
@@ -140,7 +140,7 @@ class BaseDbService extends AbatisService {
for (String key : values.keySet) {
var value = values.get(key)
if (value instanceof Date) {
- vals.put(key, (value as Date).time)
+ vals.put(key, value.time)
} else {
vals.put(key, String.valueOf(value))
}
diff --git a/Xtendroid/src/org/xtendroid/db/LazyList.xtend b/Xtendroid/src/org/xtendroid/db/LazyList.xtend
old mode 100755
new mode 100644
diff --git a/Xtendroid/src/org/xtendroid/json/AndroidJson.xtend b/Xtendroid/src/org/xtendroid/json/AndroidJson.xtend
old mode 100755
new mode 100644
index 27bd0f2..f0603b3
--- a/Xtendroid/src/org/xtendroid/json/AndroidJson.xtend
+++ b/Xtendroid/src/org/xtendroid/json/AndroidJson.xtend
@@ -131,6 +131,7 @@ class AndroidJsonProcessor implements TransformationParticipant '' }
+
+ private def void registerClassNamesRecursively(Iterable json, RegisterGlobalsContext context) {
+ for (jsonEntry : json) {
+ if (jsonEntry.isJsonObject) {
+ try {
+ context.registerClass(jsonEntry.className)
+ registerClassNamesRecursively(jsonEntry.childEntries, context)
+ }catch (java.lang.IllegalArgumentException e)
+ {
+ delayedErrorMessages.put(jsonEntry.className, String.format("There was a collision between %s and another registered type.\n%s", jsonEntry.key, e.stackTrace))
+ }
+ }
+ }
+ }
+
+ /**
+ * Called secondly. Modify the types.
+ *
+ */
+ override doTransform(MutableClassDeclaration clazz, extension TransformationContext context) {
+ clazz.addWarning(delayedErrorMessages.get(clazz.simpleName))
+ enhanceClassesRecursively(clazz, clazz.jsonEntries, context)
+ clazz.removeAnnotation(clazz.annotations.findFirst[annotationTypeDeclaration == AndroidJsonized.newTypeReference.type])
+ }
+
+ def void enhanceClassesRecursively(MutableClassDeclaration clazz, Iterable extends JsonObjectEntry> entries, extension TransformationContext context) {
+ clazz.addConstructor [
+ addParameter('jsonObject', JSONObject.newTypeReference)
+ body = '''
+ mJsonObject = jsonObject;
+ '''
+ ]
+
+ clazz.addField("mDirty") [
+ type = typeof(boolean).newTypeReference
+ visibility = Visibility.PRIVATE
+ ]
+
+ // do not add fields, directly modify the json object
+ clazz.addField("mJsonObject") [
+ type = JSONObject.newTypeReference
+ visibility = Visibility.PRIVATE
+ ]
+
+ clazz.addMethod("toJSONObject") [
+ returnType = JSONObject.newTypeReference
+ body = '''
+ return mJsonObject;
+ '''
+ ]
+
+ clazz.addMethod("isDirty") [
+ returnType = typeof(boolean).newTypeReference
+ body = '''
+ return mDirty;
+ '''
+ ]
+
+ // add accessors for the entries
+ for (entry : entries) {
+ val basicType = entry.getComponentType(context)
+ val realType = if(entry.isArray) getList(basicType) else basicType
+ val memberName = entry.key.replaceAll("[^\\x00-\\x7F]", "").replaceAll("[^A-Za-z0-9]", "").replaceAll("\\s+","")
+
+ // add JSONObject container for lazy-getting
+ if (entry.isJsonObject || entry.isArray)
+ {
+ clazz.addField('_' + memberName) [
+ type = realType
+ visibility = Visibility.PROTECTED
+ ]
+ }
+
+ // Hopefully sets have logarithmic costs, not linear (although the cost in our case is constant)
+ clazz.addMethod("get" + memberName.toFirstUpper + if (reservedKeywords.contains(memberName)) '_' else '') [
+ returnType = realType
+ exceptions = JSONException.newTypeReference
+ if (entry.isArray)
+ {
+ // populate List
+ body = ['''
+ if (_«memberName» == null) {
+ _«memberName» = new «toJavaCode(ArrayList.newTypeReference)»<«basicType.simpleName.toFirstUpper»>();
+ for (int i=0; i<_«memberName».size(); i++) {
+ _«memberName».add((«basicType.simpleName.toFirstUpper») mJsonObject.getJSONArray("«entry.key»").get(i));
+ }
+ }
+ return _«memberName»;
+ ''']
+ }else if (entry.isJsonObject)
+ {
+ body = ['''
+ if (_«memberName» == null) {
+ _«memberName» = new «basicType.simpleName»(mJsonObject.getJSONObject("«entry.key»"));
+ }
+ return _«memberName»;
+ ''']
+ }else { // is primitive (e.g. String, Number, Boolean)
+ body = ['''
+ return mJsonObject.get«basicType.simpleName.toFirstUpper»("«entry.key»");
+ ''']
+ }
+ ]
+
+ // chainable
+ clazz.addMethod("set" + memberName.toFirstUpper + if (reservedKeywords.contains(memberName)) '_' else '') [
+ addParameter('_' + memberName, realType)
+ returnType = clazz.newTypeReference
+ exceptions = JSONException.newTypeReference
+ if (entry.isArray)
+ {
+ // ArrayList === Collection
+ body = ['''
+ mDirty = true;
+ mJsonObject.put("«entry.key»", new «toJavaCode(JSONArray.newTypeReference)»(_«memberName»));
+ return this;
+ ''']
+ }else if (entry.isJsonObject)
+ {
+ body = ['''
+ mDirty = true;
+ mJsonObject.put("«entry.key»", _«memberName».toJSONObject());
+ return this;
+ ''']
+ }else {
+ body = ['''
+ mDirty = true;
+ mJsonObject.put("«entry.key»", _«memberName»);
+ return this;
+ ''']
+ }
+ ]
+
+ if (entry.isJsonObject)
+ enhanceClassesRecursively(findClass(entry.className), entry.childEntries, context)
+ }
+ }
+}
diff --git a/Xtendroid/src/org/xtendroid/json/JsonObjectEntry.xtend b/Xtendroid/src/org/xtendroid/json/JsonObjectEntry.xtend
new file mode 100644
index 0000000..40a6504
--- /dev/null
+++ b/Xtendroid/src/org/xtendroid/json/JsonObjectEntry.xtend
@@ -0,0 +1,178 @@
+package org.xtendroid.json
+//package de.itemis.jsonized // for some reason
+// the annotation refuses to import from a different package
+// NOTE: the IDE plugin gives a false impression.
+
+import com.google.gson.JsonArray
+import com.google.gson.JsonElement
+import com.google.gson.JsonObject
+import com.google.gson.JsonParser
+import com.google.gson.JsonPrimitive
+import java.io.InputStreamReader
+import java.net.URL
+import java.util.Map
+import org.eclipse.xtend.lib.macro.TransformationContext
+import org.eclipse.xtend.lib.macro.declaration.ClassDeclaration
+import org.eclipse.xtend.lib.macro.declaration.CompilationUnit
+import org.eclipse.xtend.lib.macro.declaration.TypeReference
+import org.eclipse.xtend.lib.annotations.Data
+
+/**
+ The IDE is probably going to show false positives, like java.lang.NoClassDefFoundError
+ but when running the tests, just run ./gradlew :XtendroidTest:cAT
+ */
+@Data class JsonObjectEntry {
+
+ /**
+ * Parses the value of the first annotation as JSON and turns it into an iterable of JsonObjectEntries.
+ */
+ def static Iterable getJsonEntries(ClassDeclaration clazz) throws NoClassDefFoundError {
+ val string = clazz.annotations.head.getValue('value').toString
+
+ // open a url instead
+ if (!string.trim.startsWith("{")) {
+ val in = new URL(string).openStream;
+ try {
+ val jsonElement = new JsonParser().parse(new InputStreamReader(in))
+ val jsonObject = if(jsonElement.jsonArray) {
+ jsonElement.asJsonArray.get(0) as JsonObject
+ } else {
+ jsonElement.asJsonObject
+ }
+ return jsonObject.getEntries(clazz.compilationUnit)
+ }catch (NoClassDefFoundError e) {
+ throw e // pass on to addError
+ } finally {
+ in.close
+ }
+ }
+
+ // json
+ return (new JsonParser().parse(string) as JsonObject).getEntries(clazz.compilationUnit)
+ }
+
+ /**
+ * @return an iterable of JsonObjectEntries
+ */
+ private static def Iterable getEntries(JsonElement e, CompilationUnit unit) {
+ switch e {
+ JsonObject : {
+ e.entrySet.map[new JsonObjectEntry(unit, it)]
+ }
+ default : #[]
+ }
+ }
+
+ CompilationUnit unit
+ Map.Entry entry
+
+ /**
+ * @return the entry key, i.e. the Json name
+ */
+ def String getKey() {
+ return entry.key
+ }
+
+ /**
+ * @return the value of this entry
+ */
+ def JsonElement getValue() {
+ return entry.value
+ }
+
+ /**
+ * @return whether this entry contains an array
+ */
+ def boolean isArray() {
+ entry.value instanceof JsonArray
+ }
+
+ /**
+ * @return whether this entry contains a nested JsonObject (directly or indirectly through a JsonArray)
+ */
+ def boolean isJsonObject() {
+ return getJsonObject != null
+ }
+
+ private def getJsonObject() {
+ var value = entry.value
+ if (isArray)
+ value = (value as JsonArray).head
+ if (value instanceof JsonObject) {
+ return value
+ }
+ return null
+ }
+
+ /**
+ * @return the property name. It's the JSON entry key turned into a Java identifer.
+ */
+ def getPropertyName() {
+ val result = entry.key.replace(' ', '_').toFirstLower
+ if (isArray)
+ return if (result.endsWith('s')) result else result + 's' // TODO WTF plural is with an 's'?
+ return if (result=='class') {
+ 'clazz'
+ } else {
+ result
+ }
+ }
+
+ /**
+ * @return the fully qualified class name to use if this is entry contains a JsonObject
+ */
+ def getClassName() {
+ if (isJsonObject) {
+ val simpleName = entry.key.replace(' ', '_').toFirstUpper
+ return if (unit.packageName != null)
+ unit.packageName + "." + simpleName
+ else
+ simpleName
+ }
+ return null
+ }
+
+ /**
+ * @return the component type, i.e. the type of the value or the type of the first entry if value is a JsonArray
+ */
+ def TypeReference getComponentType(extension TransformationContext ctx) {
+ val v = if (entry.value instanceof JsonArray) {
+ (entry.value as JsonArray).head
+ } else {
+ entry.value
+ }
+ switch v {
+ JsonPrimitive: {
+ if (v.isBoolean)
+ typeof(boolean).newTypeReference
+ else if (v.isNumber) {
+ if (v.asString.contains('.')) {
+ typeof(double).newTypeReference
+ }else {
+ typeof(long).newTypeReference
+ }
+ }else if (v.isString) {
+ String.newTypeReference
+ }else if (v.isJsonNull)
+ {
+ // TODO addWarning('We cannot determine the type, defaulting to a String type.\nConsider changing the type in the annotation.')
+ String.newTypeReference
+ }
+ }
+ JsonObject: {
+ findClass(className).newTypeReference
+ }
+ }
+ }
+
+ /**
+ * @return the JsonObjectEntrys or null if the value is not a JsonObject
+ */
+ def Iterable getChildEntries() {
+ if (isJsonObject) {
+ return getEntries(getJsonObject, unit)
+ }
+ return #[]
+ }
+
+}
\ No newline at end of file
diff --git a/Xtendroid/src/org/xtendroid/json/JsonProperty.xtend b/Xtendroid/src/org/xtendroid/json/JsonProperty.xtend
old mode 100755
new mode 100644
diff --git a/Xtendroid/src/org/xtendroid/parcel/ParcelableProperty.xtend b/Xtendroid/src/org/xtendroid/parcel/ParcelableProperty.xtend
old mode 100755
new mode 100644
index f504e49..50dd06f
--- a/Xtendroid/src/org/xtendroid/parcel/ParcelableProperty.xtend
+++ b/Xtendroid/src/org/xtendroid/parcel/ParcelableProperty.xtend
@@ -21,7 +21,6 @@ import org.json.JSONException
import org.json.JSONObject
import org.xtendroid.json.AndroidJsonProcessor
import org.xtendroid.json.AndroidJson
-import org.xtendroid.json.JsonProperty
@Active(ParcelableProcessor)
@Target(ElementType.TYPE)
@@ -197,7 +196,7 @@ class ParcelableProcessor extends AbstractClassProcessor
this.«f.simpleName» = in.createTypedArrayList(«f.type.actualTypeArguments.head.name».CREATOR);
«ENDIF»
«ELSE»
- this.«f.simpleName» = («f.type.name») «f.type.name».CREATOR.createFromParcel(in);
+ this.«f.simpleName» = («f.type.name») CREATOR.createFromParcel(in);
«ENDIF»
'''
@@ -278,14 +277,21 @@ class ParcelableProcessor extends AbstractClassProcessor
return new «clazz.simpleName»[size];
}
}''']
- ]
-
- clazz.addConstructor[
- body = ['''
- // empty ctor
- ''']
]
+// clazz.declaredConstructors.forEach[ /*body === null &&*/ clazz.addWarning(String.format('%s: %b', simpleName, parameters.empty)) ] // debug
+ val isEmptyCtorProvidedByUser = clazz.declaredConstructors.exists[ /*body === null &&*/ parameters.empty ]
+ if (!isEmptyCtorProvidedByUser)
+ {
+ clazz.addWarning('The user did not add an empty ctor. One will be generated.')
+ clazz.addConstructor[
+ visibility = Visibility::PUBLIC
+ body = ['''
+ // empty ctor
+ ''']
+ ]
+ }
+
val exceptionsTypeRef = if (fields.exists[type.name.startsWith("org.json.JSON")]) #[ JSONException.newTypeReference() ] else #[]
clazz.addConstructor[
addParameter('in', Parcel.newTypeReference)
diff --git a/Xtendroid/src/org/xtendroid/utils/AlertUtils.xtend b/Xtendroid/src/org/xtendroid/utils/AlertUtils.xtend
old mode 100755
new mode 100644
index ef79846..2f3be4d
--- a/Xtendroid/src/org/xtendroid/utils/AlertUtils.xtend
+++ b/Xtendroid/src/org/xtendroid/utils/AlertUtils.xtend
@@ -26,8 +26,8 @@ class AlertUtils {
/**
* Allow runOnUiThread from any Fragment
*/
- def public static runOnUiThread(Fragment frag, ()=>void uiCode) {
- val handler = new Handler(frag.activity.mainLooper)
+ def public static runOnUiThread(Fragment fragment, ()=>void uiCode) {
+ val handler = new Handler(fragment.getActivity.getMainLooper)
handler.post(uiCode)
}
diff --git a/Xtendroid/src/org/xtendroid/utils/AsyncBuilder.xtend b/Xtendroid/src/org/xtendroid/utils/AsyncBuilder.xtend
old mode 100755
new mode 100644
index f8f21fe..cebb8cf
--- a/Xtendroid/src/org/xtendroid/utils/AsyncBuilder.xtend
+++ b/Xtendroid/src/org/xtendroid/utils/AsyncBuilder.xtend
@@ -6,14 +6,14 @@ import android.os.Build
import org.eclipse.xtext.xbase.lib.Functions.Function2
class AsyncBuilder extends AsyncTask