diff --git a/.github/workflows/functional-tests.yml b/.github/workflows/functional-tests.yml
index 18b0cdfd80..85a38471e8 100644
--- a/.github/workflows/functional-tests.yml
+++ b/.github/workflows/functional-tests.yml
@@ -188,7 +188,7 @@ jobs:
swift-mac:
name: Swift on MacOS
- runs-on: macos-11
+ runs-on: macos-12
steps:
- uses: actions/checkout@v2
@@ -221,6 +221,11 @@ jobs:
run: |
./scripts/build-swift-namerules --publish
working-directory: functional-tests
+ - name: Build examples
+ run: |
+ mkdir build_example_calculator
+ cmake -B build_example_calculator examples/calculator -GXcode -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS
+ cmake --build build_example_calculator
dart:
name: Dart
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 27248dfed7..c49c8d4104 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -7,4 +7,7 @@
+
+
+
\ No newline at end of file
diff --git a/docs/lime_idl.md b/docs/lime_idl.md
index 4ea1d3bbb6..92f9d4a110 100644
--- a/docs/lime_idl.md
+++ b/docs/lime_idl.md
@@ -25,54 +25,11 @@ LimeIDL syntax
### Example
-This example only shows how syntax looks like, it's not ready to use out of the box.
-For working examples check lime files in [functional tests](https://github.com/heremaps/gluecodium/tree/master/functional-tests/functional/input/lime).
-Also [cmake tests](https://github.com/heremaps/gluecodium/tree/master/cmake/tests/unit) demonstrate how
-to integrate Gluecodium to CMake-based project and how to use it for more complex scenarios when you need
-multiple modules which interact with each other.
+Start with [examples](../examples/README.md).
-```
-package com.example
-
-import com.example.utils.GenericResult
-
-class SomeImportantProcessor {
- constructor create(options: Options?) throws SomethingWrongException
-
- fun process(mode: Mode, input: String): GenericResult
-
- property processingTime: ProcessorHelperTypes.Timestamp { get }
-
- @Internal
- static property secretDelegate: ProcessorDelegate?
-
- enum Mode {
- SLOW,
- FAST,
- CHEAP
- }
-
- @Immutable
- struct Options {
- flagOption: Boolean
- uintOption: UShort
- additionalOptions: List = {}
- }
+For more advanced lime examples check [functional tests](https://github.com/heremaps/gluecodium/tree/master/functional-tests/functional/input/lime).
- exception SomethingWrongException(String)
-}
-
-interface ProcessorDelegate: com.example.utils.GenericDelegate {
- fun onProcessorEvent(message: String)
-}
-
-struct ProcessorHelperTypes {
- typealias Timestamp = Date
-
- const DefaultOptions: SomeImportantProcessor.Options = {flagOption = true, uintOption = 42, {}}
-}
-
-```
+Also [cmake tests](https://github.com/heremaps/gluecodium/tree/master/cmake/tests/unit) demonstrate how to use CMake scripts for more complex scenarios when you need multiple modules which interact with each other.
### General remarks on syntax
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 0000000000..78f45a806f
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,31 @@
+## Overview
+
+[README.md](../README.md) in the root provides basic information about Gluecodium.
+It's simplier to keep C++ code in shared library when binding is necessary for Java and for Swift.
+In examples simpliest configuration is used. For more advanced check `cmake/tests/unit` tests.
+
+## CMake Gluecodium wraper.
+
+This is a set of CMake functions which provide the following functionality:
+- Download Gluecodium from artifactory.
+- Add step to generate code.
+- Set options to configure generated code.
+- Help to configure a target to include generated C++/Swift/Java/Flutter sources, add include directories, etc.
+
+While the examples work with the shipped CMake Gluecodium wrapper for a real application it's handy to clone only the subtree `cmake/modules`.
+
+## Example `calculator`.
+
+What this example demonstrates:
+- How to configure the project to generate C++, Java and Swift source code.
+- How to use the generated code in Android and iOS applications.
+- How to describe basic primitives like `class`, `interface`, `struct` and how to interact with them.
+- How to make platform-only comments and links.
+
+What this example DOESN'T demonstrate:
+- How to make complex build setup with several modules.
+- How to describe custom types.
+- How to make a Flutter plugin.
+- How to use advanced Gluecodium features such as tags, advanced manipulations with comments or properties.
+
+For a detailed description check [calculator/README.md](calculator/README.md)
diff --git a/examples/calculator/CMakeLists.txt b/examples/calculator/CMakeLists.txt
new file mode 100644
index 0000000000..7f6ab6cefb
--- /dev/null
+++ b/examples/calculator/CMakeLists.txt
@@ -0,0 +1,77 @@
+# Copyright (C) 2016-2023 HERE Europe B.V.
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# License-Filename: LICENSE
+
+cmake_minimum_required(VERSION 3.12)
+
+project(gluecodium.calculator)
+
+option(ENABLE_APP "Enables iOS app which demostrates how to use the generated code" ON)
+
+set(CMAKE_CXX_STANDARD 17)
+
+if(CMAKE_GENERATOR STREQUAL "Xcode")
+ enable_language(Swift)
+endif()
+
+# Path to Gluecodium CMake wrapper
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../cmake/modules")
+
+include(gluecodium/Gluecodium)
+
+# Android requires C++ code to be built as shared library.
+# For iOS it's recommended (but not required) to do Framework because it's consistent
+# to Android setup and simplifies CMake scripts and it allows to use framework with
+# Xcode projects which are not originated from CMake (for example cocoapods or
+# generated by Xcode itself).
+add_library(mylibrary SHARED "${CMAKE_CURRENT_LIST_DIR}/cpp/CalculatorImpl.cpp")
+
+# C++ (cpp) generator usually always necessary, android and swift depend on current platform.
+set (_generators cpp)
+if (ANDROID)
+ list (APPEND _generators android)
+elseif (CMAKE_GENERATOR STREQUAL "Xcode")
+ list (APPEND _generators swift)
+endif()
+
+# Add step to generate sources. Lime files are specified below.
+gluecodium_generate(mylibrary GENERATORS ${_generators})
+
+# Gluecodium has plenty options which can be configured with target properties
+# prfixed with `GLUECODIUM_`. To learn all the properties configure the project
+# with `-DGLUECODIUM_PRINT_KNOWN_PROPERTIES=ON` parameter.
+set_target_properties(mylibrary PROPERTIES
+ # Not required, but highly desired Java annotations.
+ GLUECODIUM_JAVA_NONNULL_ANNOTATION "androidx.annotation.NonNull"
+ GLUECODIUM_JAVA_NULLABLE_ANNOTATION "androidx.annotation.Nullable"
+
+ # Set prefix for java package.
+ GLUECODIUM_JAVA_PACKAGE "com")
+
+# This is the most convenient way to add lime files from which Gluecodium generates code.
+set_property(TARGET mylibrary APPEND PROPERTY GLUECODIUM_LIME_SOURCES
+ "${CMAKE_CURRENT_LIST_DIR}/lime/Calculator.lime")
+
+if (CMAKE_GENERATOR STREQUAL "Xcode")
+ # Make CMake to build framework.
+ set_target_properties(mylibrary PROPERTIES
+ FRAMEWORK TRUE
+ XCODE_ATTRIBUTE_DEFINES_MODULE YES)
+
+ if(ENABLE_APP AND IOS)
+ add_subdirectory("ios")
+ endif()
+endif()
diff --git a/examples/calculator/README.md b/examples/calculator/README.md
new file mode 100644
index 0000000000..76ac578628
--- /dev/null
+++ b/examples/calculator/README.md
@@ -0,0 +1,150 @@
+# Calculator
+
+This simple application demonstrates how to use basic entities like `struct`, `class`, `interface`, `exception`, `lambda`, `optional values`, `documentation`, `platform-only documentation` and interact between Java, Swift and C++. Flutter is out of this example.
+
+# Requirements
+
+* For Apple: Xcode, CMake the newer the better.
+* For Android: CMake, Android SDK, optionally Android Studio
+
+# How to build
+
+## Android
+
+Option 1. Command line
+
+Build and install the app from command line:
+
+```
+cd android
+./gradlew installDebug
+```
+Then find "Gluecodium calculator" on our simulator or device and run.
+
+Option 2. IDE
+
+Run Android Studio not older than Giraffe, open directory `android`, press "Run".
+
+## Apple
+First of all Xcode solution must be configured with CMake
+
+Generated code and implementation for interfaces are built as framework. For iOS there is also test application which uses this framework.
+
+Create directory and `cd` there. There are many options to configure solution for Xcode:
+```
+# iOS, simulator, M1
+cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS
+
+# iOS, simulator, Intel
+cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS
+
+# iOS, simulator, both M1 and Intel (i.e. lipoed)
+cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_SYSTEM_NAME=iOS
+
+# iOS, device
+cmake path/to/example -GXcode -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_SYSTEM_NAME=iOS
+
+# MacOS. Only framework will be built, without test application.
+cmake path/to/example -GXcode
+```
+Optionally parameter `-DENABLE_APP=OFF` can be passed to skip building the test application. In this case only framework is built which can be used in another existing Xcode project.
+
+To build framework from console:
+```
+# Build debug
+cmake --build . --target mylibrary
+
+# Build release
+cmake --build . --target mylibrary --config Release
+```
+
+Test application is easier to build from Xcode. Open Xcode, opern generated project, click "Run" button.
+
+# Highlights
+
+## CMake
+
+File [CMakeLists.txt](CMakeLists.txt) describes library and adds compilation step to generate code with Gluecodium.
+
+Path to gluecodium cmake files must be specified before including gluecodium cmake files. In examples it's hardcoded, but can be specified outside:
+```
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../cmake/modules")
+```
+
+Gluecodium options can be configured with target properties preficed with `GLUECODIUM_`. To learn all the properties configure the project with `-DGLUECODIUM_PRINT_KNOWN_PROPERTIES=ON` parameter.
+
+The most convenient way to add lime files is to append them to `GLUECODIUM_LIME_SOURCES` property:
+```
+set_property(TARGET target APPEND PROPERTY GLUECODIUM_LIME_SOURCES
+ "path/to/file1.lime"
+ "path/to/file2.lime")
+```
+
+## Lime file
+
+File [lime/Calculator.lime](lime/Calculator.lime) describes entities to be generated by Gluecoium. Syntax should look like very simplified Kotlin or Swift. Text which begins with `#` is local comment and not processed by Gluecodium somehow special. Text which begins with `//` is official documentation and Gluecodium uses it to generate Javadoc/Jazzy/Doxygen compatible documentation for related generated code. Check the lime file for more detailed description for used entites.
+
+For simplification everything is described in single file, but it's possible to use as many lime files. In case when type from different package is referenced then either fully qualified name or `import` should be used.
+
+Only simple subset of features are used, for more please refer to Gluecodium documentation.
+
+## Android sources
+
+Major part of android sources are generated automatically by Android Studio when simple project for this example was created. The most significant changes are the following:
+
+Gradle build script [android/app/build.gradle](android/app/build.gradle). It specifies path to folder to generate code using CMake variable `GLUECODIUM_BASE_OUTPUT_DIR_DEFAULT`:
+```
+externalNativeBuild {
+ cmake {
+ // Put generated sources outside of default build directory because Android Gradle Plugin
+ // makes build directory for C++ using some unpredictable random-like path.
+ // Generated Java sources should be added to `sourceSet` below, so it's better to specify
+ // and know where code is generated.
+ arguments "-DGLUECODIUM_BASE_OUTPUT_DIR_DEFAULT=${project.buildDir}/generated/gluecodium"
+ }
+}
+```
+
+Below directories with generated sources are added to Java source set. Note that this is based on the directory which is specified with `GLUECODIUM_BASE_OUTPUT_DIR_DEFAULT` and C++ target name:
+```
+sourceSets {
+ main {
+ // Add generated Java code.
+ java.srcDirs += ["${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/common/android",
+ "${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/main/android"]
+ }
+}
+```
+
+Relative path to [CMakeLists.txt](CMakeLists.txt) is specified:
+```
+externalNativeBuild {
+ cmake {
+ // Path to root `CMakeLists.txt`
+ path file('../../CMakeLists.txt')
+ version '3.22.1+'
+ }
+}
+```
+
+And finally it's necessary to compile Java code only after C++ is compiled (and Java sources are generated):
+```
+tasks.configureEach { task ->
+ // Java compilation depends on .java created by Gluecodium,
+ // so depend Java compilation task on build C++ task.
+ if (task.name == 'javaPreCompileDebug') {
+ task.dependsOn 'externalNativeBuildDebug'
+ }
+ if (task.name == 'javaPreCompileRelease') {
+ task.dependsOn 'externalNativeBuildRelease'
+ }
+}
+```
+
+Usage of generated code is in [android/app/src/main/java/com/gluecodium/calculator/MainActivity.java](android/app/src/main/java/com/gluecodium/calculator/MainActivity.java)
+
+## iOS
+
+Optional CMake file [ios/CMakeLists.txt](ios/CMakeLists.txt) describes simple iOS application. It's optional because the framework which is built in the root file [CMakeLists.txt](CMakeLists.txt) can be used in Xcode project which is created by Xcode itself or by for example `cocoapods`. There is nothing specific for Gluecodium.
+
+Usage of generated code is in [ios/ContentView.swift](ios/ContentView.swift).
diff --git a/examples/calculator/android/.gitignore b/examples/calculator/android/.gitignore
new file mode 100644
index 0000000000..aa724b7707
--- /dev/null
+++ b/examples/calculator/android/.gitignore
@@ -0,0 +1,15 @@
+*.iml
+.gradle
+/local.properties
+/.idea/caches
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/.idea/navEditor.xml
+/.idea/assetWizardSettings.xml
+.DS_Store
+/build
+/captures
+.externalNativeBuild
+.cxx
+local.properties
diff --git a/examples/calculator/android/app/.gitignore b/examples/calculator/android/app/.gitignore
new file mode 100644
index 0000000000..42afabfd2a
--- /dev/null
+++ b/examples/calculator/android/app/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/examples/calculator/android/app/build.gradle b/examples/calculator/android/app/build.gradle
new file mode 100644
index 0000000000..4293df88ab
--- /dev/null
+++ b/examples/calculator/android/app/build.gradle
@@ -0,0 +1,77 @@
+plugins {
+ id 'com.android.application'
+}
+
+android {
+ namespace 'com.gluecodium.calculator'
+ compileSdk 33
+
+ defaultConfig {
+ applicationId "com.gluecodium.calculator"
+ minSdk 24
+ targetSdk 33
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ externalNativeBuild {
+ cmake {
+ // Put generated sources outside of default build directory because Android Gradle Plugin
+ // makes build directory for C++ using some unpredictable random-like path.
+ // Generated Java sources should be added to `sourceSet` below, so it's better to specify
+ // and know where code is generated.
+ arguments "-DGLUECODIUM_BASE_OUTPUT_DIR_DEFAULT=${project.buildDir}/generated/gluecodium"
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ sourceSets {
+ main {
+ // Add generated Java code.
+ java.srcDirs += ["${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/common/android",
+ "${project.buildDir}/generated/gluecodium/mylibrary/android-cpp/main/android"]
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ externalNativeBuild {
+ cmake {
+ // Path to root `CMakeLists.txt`
+ path file('../../CMakeLists.txt')
+ version '3.22.1+'
+ }
+ }
+ buildFeatures {
+ viewBinding true
+ }
+}
+
+tasks.configureEach { task ->
+ // Java compilation depends on .java created by Gluecodium,
+ // so depend Java compilation task on build C++ task.
+ if (task.name == 'javaPreCompileDebug') {
+ task.dependsOn 'externalNativeBuildDebug'
+ }
+ if (task.name == 'javaPreCompileRelease') {
+ task.dependsOn 'externalNativeBuildRelease'
+ }
+}
+
+dependencies {
+
+ implementation 'androidx.appcompat:appcompat:1.6.1'
+ implementation 'com.google.android.material:material:1.8.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ testImplementation 'junit:junit:4.13.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+}
\ No newline at end of file
diff --git a/examples/calculator/android/app/proguard-rules.pro b/examples/calculator/android/app/proguard-rules.pro
new file mode 100644
index 0000000000..481bb43481
--- /dev/null
+++ b/examples/calculator/android/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/androidTest/java/com/gluecodium/calculator/ExampleInstrumentedTest.java b/examples/calculator/android/app/src/androidTest/java/com/gluecodium/calculator/ExampleInstrumentedTest.java
new file mode 100644
index 0000000000..69e905f875
--- /dev/null
+++ b/examples/calculator/android/app/src/androidTest/java/com/gluecodium/calculator/ExampleInstrumentedTest.java
@@ -0,0 +1,40 @@
+// Copyright (C) 2016-2023 HERE Europe B.V.
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0
+// License-Filename: LICENSE
+
+package com.gluecodium.calculator;
+
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see Testing documentation
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ }
+}
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/AndroidManifest.xml b/examples/calculator/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000000..1b8af2b1b6
--- /dev/null
+++ b/examples/calculator/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/java/com/gluecodium/calculator/MainActivity.java b/examples/calculator/android/app/src/main/java/com/gluecodium/calculator/MainActivity.java
new file mode 100644
index 0000000000..1afb833789
--- /dev/null
+++ b/examples/calculator/android/app/src/main/java/com/gluecodium/calculator/MainActivity.java
@@ -0,0 +1,197 @@
+// Copyright (C) 2016-2023 HERE Europe B.V.
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0
+// License-Filename: LICENSE
+
+package com.gluecodium.calculator;
+
+import static android.widget.Toast.LENGTH_LONG;
+
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+
+import android.graphics.Color;
+import android.os.Bundle;
+import android.util.Pair;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.gluecodium.calculator.databinding.ActivityMainBinding;
+
+public class MainActivity extends AppCompatActivity {
+
+ // Used to load the 'libmylibrary.so' library on application startup.
+ static {
+ System.loadLibrary("mylibrary");
+ }
+
+ final private Calculator calculator = new Calculator();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ com.gluecodium.calculator.databinding.ActivityMainBinding binding = ActivityMainBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ findViewById(R.id.button_plus).setOnClickListener(view -> {
+ final Pair values = readValues();
+ if (values == null) {
+ return;
+ }
+ try {
+ final int result = calculator.summarize(values.first, values.second);
+ final String resultText = values.first + " + " + values.second + " = " + result;
+ showResult(resultText);
+ } catch (Calculator.CalculatorExceptionException e) {
+ showError("Failure: " + e.getMessage());
+ }
+ });
+
+ findViewById(R.id.button_minus).setOnClickListener(view -> {
+ final Pair values = readValues();
+ if (values == null) {
+ return;
+ }
+ calculator.subtract(values.first, values.second, (error, result) -> {
+ if (error != null) {
+ showError("Failure: " + error);
+ } else {
+ final String resultText = values.first + " - " + values.second + " = " + result;
+ showResult(resultText);
+ }
+ });
+ });
+
+ findViewById(R.id.button_multiply).setOnClickListener(view -> {
+ final Pair values = readValues();
+ if (values == null) {
+ return;
+ }
+
+ calculator.multiply(values.first, values.second, new Calculator.MultiplyCallback() {
+ @Override
+ public void onError(@NonNull Calculator.CalculatorError error) {
+ showError("Failure: " + error.toString());
+ }
+
+ @Override
+ public void onResult(int result) {
+ final String resultText = values.first + " x " + values.second + " = " + result;
+ showResult(resultText);
+ }
+ });
+ });
+
+ findViewById(R.id.button_divide).setOnClickListener(view -> {
+ final Pair values = readValues();
+ if (values == null) {
+ return;
+ }
+ final Calculator.DivideResult result = calculator.divide(
+ new Calculator.DivideArguments(values.first, values.second));
+ if (result.error != null) {
+ showError("Failure: " + result.error.toString());
+ } else {
+ final String resultText = values.first + " / " + values.second + " = " + result.result;
+ showResult(resultText);
+ }
+ });
+
+ findViewById(R.id.button_min).setOnClickListener(view -> {
+ final Pair values = readValues();
+ if (values == null) {
+ return;
+ }
+ final Calculator.MinResultRetriever resultRetriever = calculator.min(values.first, values.second);
+ final int result = resultRetriever.getResult();
+ final String resultText = "min(" + values.first + ", " + values.second + ") = " + result;
+ showResult(resultText);
+ });
+
+ findViewById(R.id.button_max).setOnClickListener(view -> {
+ Integer number1 = null;
+ Integer number2 = null;
+
+ final String inputText1 = ((EditText)(findViewById(R.id.edit_number_1))).getText().toString();
+ if (!inputText1.isEmpty()) {
+ number1 = Integer.parseInt(inputText1);
+ }
+
+ final String inputText2 = ((EditText)(findViewById(R.id.edit_number_2))).getText().toString();
+ if (!inputText2.isEmpty()) {
+ number2 = Integer.parseInt(inputText2);
+ }
+
+ final Integer result = calculator.max(number1, number2);
+
+ if (result != null) {
+ String resultText = "max(";
+ if (number1 != null && number2 != null) {
+ resultText += number1 + ", " + number2;
+ } else if (number1 != null) {
+ resultText += number1;
+ } else if (number2 != null) {
+ resultText += number2;
+ }
+ resultText += ") = " + result;
+ showResult(resultText);
+ } else {
+ showResult("null");
+ }
+ });
+ }
+
+ private Pair readValues() {
+ try {
+ final int value1 = readValue1();
+ final int value2 = readValue2();
+ return new Pair<>(value1, value2);
+ } catch (Exception e) {
+ Toast.makeText(getApplicationContext(), e.getMessage(), LENGTH_LONG).show();
+ return null;
+ }
+ }
+
+ private void showResult(@NonNull String resultText) {
+ TextView textView = findViewById(R.id.text_result);
+ textView.setTextColor(Color.BLACK);
+ textView.setText(resultText);
+ }
+
+ private void showError(@NonNull String resultText) {
+ TextView textView = findViewById(R.id.text_result);
+ textView.setTextColor(Color.RED);
+ textView.setText(resultText);
+ }
+
+ private int readValue1() {
+ return readValueById(R.id.edit_number_1);
+ }
+
+ private int readValue2() {
+ return readValueById(R.id.edit_number_2);
+ }
+
+ private int readValueById(int id) {
+ EditText editText = findViewById(id);
+ final String inputText = editText.getText().toString();
+ if (inputText.isEmpty()) {
+ throw new RuntimeException("Input value is empty");
+ }
+ return Integer.parseInt(inputText);
+ }
+}
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/drawable/ic_launcher_background.xml b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..07d5da9cbf
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/calculator/android/app/src/main/res/drawable/ic_launcher_foreground.xml b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..2b068d1146
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/layout/activity_main.xml b/examples/calculator/android/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000000..f1c2976c1e
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000000..6f3b755bf5
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000000..6f3b755bf5
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000000..c209e78ecd
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..b2dfe3d1ba
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000000..4f0f1d64e5
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..62b611da08
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..948a3070fe
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..1b9a6956b3
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..28d4b77f9f
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9287f50836
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..aa7d6427e6
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ
diff --git a/examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9126ae37cb
Binary files /dev/null and b/examples/calculator/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ
diff --git a/examples/calculator/android/app/src/main/res/values-night/themes.xml b/examples/calculator/android/app/src/main/res/values-night/themes.xml
new file mode 100644
index 0000000000..1ae2207c2a
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/values/colors.xml b/examples/calculator/android/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000000..f8c6127d32
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/values/colors.xml
@@ -0,0 +1,10 @@
+
+
+ #FFBB86FC
+ #FF6200EE
+ #FF3700B3
+ #FF03DAC5
+ #FF018786
+ #FF000000
+ #FFFFFFFF
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/values/strings.xml b/examples/calculator/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000000..1981563eb2
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ Gluecodium calculator
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/values/themes.xml b/examples/calculator/android/app/src/main/res/values/themes.xml
new file mode 100644
index 0000000000..912e3b3124
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/values/themes.xml
@@ -0,0 +1,16 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/xml/backup_rules.xml b/examples/calculator/android/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 0000000000..fa0f996d2c
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/main/res/xml/data_extraction_rules.xml b/examples/calculator/android/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000000..9ee9997b0b
--- /dev/null
+++ b/examples/calculator/android/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/calculator/android/app/src/test/java/com/gluecodium/calculator/ExampleUnitTest.java b/examples/calculator/android/app/src/test/java/com/gluecodium/calculator/ExampleUnitTest.java
new file mode 100644
index 0000000000..bc6201f2f0
--- /dev/null
+++ b/examples/calculator/android/app/src/test/java/com/gluecodium/calculator/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.gluecodium.calculator;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/examples/calculator/android/build.gradle b/examples/calculator/android/build.gradle
new file mode 100644
index 0000000000..16f2923590
--- /dev/null
+++ b/examples/calculator/android/build.gradle
@@ -0,0 +1,4 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+plugins {
+id 'com.android.application' version '8.1.2' apply false
+}
\ No newline at end of file
diff --git a/examples/calculator/android/gradle.properties b/examples/calculator/android/gradle.properties
new file mode 100644
index 0000000000..3e927b11ef
--- /dev/null
+++ b/examples/calculator/android/gradle.properties
@@ -0,0 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
+android.useAndroidX=true
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
\ No newline at end of file
diff --git a/examples/calculator/android/gradle/wrapper/gradle-wrapper.properties b/examples/calculator/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..83eeb5c2b8
--- /dev/null
+++ b/examples/calculator/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Oct 28 20:30:20 CEST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/examples/calculator/android/gradlew b/examples/calculator/android/gradlew
new file mode 100755
index 0000000000..4f906e0c81
--- /dev/null
+++ b/examples/calculator/android/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# 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
+#
+# https://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.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# 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
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# 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
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+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" -a "$nonstop" = "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 or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/examples/calculator/android/gradlew.bat b/examples/calculator/android/gradlew.bat
new file mode 100644
index 0000000000..ac1b06f938
--- /dev/null
+++ b/examples/calculator/android/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@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
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@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="-Xmx64m" "-Xms64m"
+
+@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 execute
+
+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 execute
+
+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
+
+: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 %*
+
+: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/examples/calculator/android/settings.gradle b/examples/calculator/android/settings.gradle
new file mode 100644
index 0000000000..e58b4920fc
--- /dev/null
+++ b/examples/calculator/android/settings.gradle
@@ -0,0 +1,17 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+dependencyResolutionManagement {
+ repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "Gluecodium calculator"
+include ':app'
diff --git a/examples/calculator/cpp/CalculatorImpl.cpp b/examples/calculator/cpp/CalculatorImpl.cpp
new file mode 100644
index 0000000000..de6eafbef2
--- /dev/null
+++ b/examples/calculator/cpp/CalculatorImpl.cpp
@@ -0,0 +1,108 @@
+// Copyright (C) 2016-2023 HERE Europe B.V.
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0
+// License-Filename: LICENSE
+
+#include "gluecodium/calculator/Calculator.h"
+
+#include
+
+namespace {
+
+bool is_out_of_int32_bounds(int64_t value64) {
+ return value64 < static_cast(std::numeric_limits::min()) ||
+ value64 > static_cast(std::numeric_limits::max());
+}
+
+class CalculatorImpl : public gluecodium::calculator::Calculator {
+ // If method throws an exception in Java/Swift it should return error in C++.
+ // Note: Gluecodiom doesn't catch any C++ exceptions.
+ ::gluecodium::Return< int32_t, ::std::error_code >
+ summarize( const int32_t first, const int32_t second ) override {
+ const int64_t result64 = static_cast(first) + static_cast(second);
+ if (is_out_of_int32_bounds(result64)) {
+ return {CalculatorError::RESULT_OUT_OF_BOUNDS};
+ }
+ return {static_cast(result64)};
+ }
+
+ void
+ subtract( const int32_t minuend, const int32_t subtrahend,
+ const ::gluecodium::calculator::Calculator::SubtructCallback& callback ) override {
+ const auto result = summarize(minuend, -subtrahend);
+ if (result.has_value()) {
+ callback(std::nullopt, result.unsafe_value());
+ } else {
+ callback(static_cast(result.error().value()), std::nullopt);
+ }
+ }
+
+ void
+ multiply( const int32_t first, const int32_t second,
+ const ::std::shared_ptr< ::gluecodium::calculator::Calculator::MultiplyCallback >& multiply_callback ) override {
+ const int64_t result64 = static_cast(first) * static_cast(second);
+ if (is_out_of_int32_bounds(result64)) {
+ multiply_callback->on_error(CalculatorError::RESULT_OUT_OF_BOUNDS);
+ } else {
+ multiply_callback->on_result(static_cast(result64));
+ }
+ }
+
+ ::gluecodium::calculator::Calculator::DivideResult
+ divide( const Calculator::DivideArguments& divideArguments) override {
+ ::gluecodium::calculator::Calculator::DivideResult result;
+ if (divideArguments.divider == 0) {
+ result.error = CalculatorError::DIVIDE_BY_ZERO;
+ } else {
+ result.result = static_cast(divideArguments.dividend) / static_cast(divideArguments.divider);
+ }
+ return result;
+ }
+
+ class MinResultRetrieverImpl final: public MinResultRetriever {
+ public:
+ explicit MinResultRetrieverImpl(int32_t result): m_result(result) {
+
+ }
+ int32_t get_result( ) override {
+ return m_result;
+ }
+ private:
+ const int32_t m_result;
+ };
+
+ std::shared_ptr
+ min( const int32_t first, const int32_t second ) override {
+ return std::make_shared(std::min(first, second));
+ }
+
+ std::optional< int32_t >
+ max( const std::optional< int32_t >& first, const std::optional< int32_t >& second ) override {
+ if (first && second) {
+ return std::max(first.value(), second.value());
+ }
+ return first ? first : second;
+ }
+};
+}
+
+namespace gluecodium::calculator {
+// This is factory method which is called from generated Java/Swift constructor.
+::std::shared_ptr< ::gluecodium::calculator::Calculator >
+Calculator::make()
+{
+ return std::make_shared();
+}
+}
diff --git a/examples/calculator/ios/CMakeLists.txt b/examples/calculator/ios/CMakeLists.txt
new file mode 100644
index 0000000000..1a78290bcb
--- /dev/null
+++ b/examples/calculator/ios/CMakeLists.txt
@@ -0,0 +1,46 @@
+# Copyright (C) 2016-2023 HERE Europe B.V.
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# License-Filename: LICENSE
+
+cmake_minimum_required(VERSION 3.12)
+
+project(gluecodium.calculator.app VERSION "1.0.0" LANGUAGES Swift)
+
+set (APP_BUNDLE_IDENTIFIER "com.gluecodium.calculator.app")
+
+set (PRODUCT_NAME "Gluecodium test app")
+set (MACOSX_BUNDLE_EXECUTABLE_NAME gluecodium-calculator-app)
+set (MACOSX_BUNDLE_INFO_STRING ${APP_BUNDLE_IDENTIFIER})
+set (MACOSX_BUNDLE_GUI_IDENTIFIER ${APP_BUNDLE_IDENTIFIER})
+set (MACOSX_BUNDLE_BUNDLE_NAME ${APP_BUNDLE_IDENTIFIER})
+set (MACOSX_BUNDLE_ICON_FILE icon.icns)
+set (MACOSX_BUNDLE_LONG_VERSION_STRING ${PROJECT_VERSION})
+set (MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION})
+set (MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION})
+
+add_executable (gluecodium-calculator-app MACOSX_BUNDLE
+ "ContentView.swift"
+ "gluecodium_calculator_appApp.swift")
+
+set_target_properties (
+ gluecodium-calculator-app
+ PROPERTIES XCODE_ATTRIBUTE_FRAMEWORK_SEARCH_PATHS "${CMAKE_BINARY_DIR}/Debug-iphonesimulator"
+ XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "/usr/lib/swift @executable_path/Frameworks"
+ XCODE_GENERATE_SCHEME YES
+ XCODE_ATTRIBUTE_SWIFT_OPTIMIZATION_LEVEL "$,-Onone,-O>"
+ XCODE_EMBED_FRAMEWORKS_CODE_SIGN_ON_COPY TRUE)
+
+add_dependencies (gluecodium-calculator-app mylibrary)
diff --git a/examples/calculator/ios/ContentView.swift b/examples/calculator/ios/ContentView.swift
new file mode 100644
index 0000000000..8d62ae90a5
--- /dev/null
+++ b/examples/calculator/ios/ContentView.swift
@@ -0,0 +1,186 @@
+// Copyright (C) 2016-2023 HERE Europe B.V.
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0
+// License-Filename: LICENSE
+
+import SwiftUI
+import mylibrary
+
+struct ContentView: View {
+
+ @State private var number1: Int32?
+ @State private var number2: Int32?
+
+ @State private var resultText: String = "Here result is printed"
+ @State private var resultColor: Color = Color.black
+
+ private let calculator = Calculator()
+
+ var body: some View {
+ VStack {
+ TextField("Number 1", value: $number1, format: .number)
+ .textFieldStyle(.roundedBorder)
+ TextField("Number 2", value: $number2, format: .number)
+ .textFieldStyle(.roundedBorder)
+ Text(resultText).foregroundColor(resultColor)
+ Grid {
+ GridRow {
+ Button(action: summarize) {
+ Text("+").frame(maxWidth: .infinity)
+ }.buttonStyle(.bordered).controlSize(.large)
+ Button(action: subtract) {
+ Text("-").frame(maxWidth: .infinity)
+ }.buttonStyle(.bordered).controlSize(.large)
+ }
+ Divider()
+ GridRow {
+ Button(action: multiply) {
+ Text("x").frame(maxWidth: .infinity)
+ }.buttonStyle(.bordered).controlSize(.large)
+ Button(action: divide) {
+ Text("/").frame(maxWidth: .infinity)
+ }.buttonStyle(.bordered).controlSize(.large)
+ }
+ Divider()
+ GridRow {
+ Button(action: min) {
+ Text("min").frame(maxWidth: .infinity)
+ }.buttonStyle(.bordered).controlSize(.large)
+ Button(action: max) {
+ Text("max").frame(maxWidth: .infinity)
+ }.buttonStyle(.bordered).controlSize(.large)
+ }
+ }
+ }
+ .padding()
+ }
+
+ private func summarize() {
+ guard let number1 = number1, let number2 = number2 else {
+ showError(text: "Input is empty")
+ return
+ }
+
+ do {
+ let result = try calculator.summarize(first: number1, second: number2)
+ showResult(text: "\(number1) + \(number2) = \(result)")
+ } catch let error as Calculator.CalculatorExceptionError {
+ showError(text: "\(error)")
+ } catch {
+ showError(text: "\(error)")
+ }
+ }
+
+ private func subtract() {
+ guard let number1 = number1, let number2 = number2 else {
+ showError(text: "Input is empty")
+ return
+ }
+
+ calculator.subtract(minuend: number1, subtrahend: number2) { (error, result) in
+ if let error = error {
+ showError(text: "\(error)")
+ } else if let result = result {
+ showResult(text: "\(number1) - \(number2) = \(result)")
+ }
+ }
+ }
+
+ private func multiply() {
+ guard let number1 = number1, let number2 = number2 else {
+ showError(text: "Input is empty")
+ return
+ }
+
+ class MultiplyCallbackImpl: MultiplyCallback {
+ var showError: (String) -> ()
+ var showResult: (Int32) -> ()
+
+ init(showError: @escaping (String) -> Void, showResult: @escaping (Int32) -> Void) {
+ self.showError = showError
+ self.showResult = showResult
+ }
+
+ func onError(error: Calculator.CalculatorError) {
+ showError("\(error)")
+ }
+
+ func onResult(result: Int32) {
+ showResult(result)
+ }
+ }
+
+ calculator.multiply(first: number1, second: number2,
+ multiplyCallback: MultiplyCallbackImpl(
+ showError: {error in self.showError(text: error)},
+ showResult: {result in self.showResult(text: "\(number1) x \(number2) = \(result)")}))
+ }
+
+ private func divide() {
+ guard let number1 = number1, let number2 = number2 else {
+ showError(text: "Input is empty")
+ return
+ }
+
+ let result = calculator.divide(divideArguments: Calculator.DivideArguments(dividend: number1,
+ divider: number2))
+
+ if let error = result.error {
+ showError(text: "\(error)")
+ } else if let result = result.result {
+ showResult(text: "\(number1) / \(number2) = \(result)")
+ }
+ }
+
+ private func min() {
+ guard let number1 = number1, let number2 = number2 else {
+ showError(text: "Input is empty")
+ return
+ }
+
+ let resultRetriever = calculator.min(first: number1, second: number2)
+ let result = resultRetriever.getResult()
+ showResult(text: "min(\(number1), \(number2)) = \(result)")
+ }
+
+ private func max() {
+ if let result = calculator.max(first: number1, second: number2) {
+ // let number1: Int32 = number1 ?
+ if let number1 = number1, let number2 = number2 {
+ showResult(text: "max(\(number1), \(number2)) = \(result)")
+ } else if let number = number1 ?? number2 {
+ showResult(text: "max(\(number)) = \(result)")
+ }
+ } else {
+ showResult(text: "nil")
+ }
+ }
+
+ private func showError(text: String) {
+ resultText = text
+ resultColor = Color.red
+ }
+
+ private func showResult(text: String) {
+ resultText = text
+ resultColor = Color.black
+ }
+}
+
+struct ContentView_Previews: PreviewProvider {
+ static var previews: some View {
+ ContentView()
+ }
+}
diff --git a/examples/calculator/ios/gluecodium_calculator_appApp.swift b/examples/calculator/ios/gluecodium_calculator_appApp.swift
new file mode 100644
index 0000000000..633c3620bd
--- /dev/null
+++ b/examples/calculator/ios/gluecodium_calculator_appApp.swift
@@ -0,0 +1,27 @@
+// Copyright (C) 2016-2023 HERE Europe B.V.
+//
+// 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.
+//
+// SPDX-License-Identifier: Apache-2.0
+// License-Filename: LICENSE
+
+import SwiftUI
+
+@main
+struct gluecodium_calculator_appApp: App {
+ var body: some Scene {
+ WindowGroup {
+ ContentView()
+ }
+ }
+}
diff --git a/examples/calculator/lime/Calculator.lime b/examples/calculator/lime/Calculator.lime
new file mode 100644
index 0000000000..aae2209475
--- /dev/null
+++ b/examples/calculator/lime/Calculator.lime
@@ -0,0 +1,201 @@
+# Copyright (C) 2016-2023 HERE Europe B.V.
+#
+# 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.
+#
+# SPDX-License-Identifier: Apache-2.0
+# License-Filename: LICENSE
+
+package gluecodium.calculator
+
+# This is just local comment which is not taken into account by Gluecodium. It starts with #
+# Following class has several methods which represent
+
+// This is comment which will be generated as part of code. It can be then processed by
+// Javadoc for Java, Jazzy for Swift or Doxygen for C++.
+class Calculator {
+
+ ###################################################
+ # Constructors like functions might have #
+ # parameters. While in Java/Swift and Flutter #
+ # normal constructor is generated in C++ it's #
+ # static function which needs to be implemented. #
+ ###################################################
+
+ // Construct a new instance.
+ constructor make()
+
+ ###################################################
+ # 1. Function with exception. #
+ # #
+ # Function `summorize` demonstrates how to #
+ # declare a function which throws an exception. #
+ # Also link is demonstrated, it's between [ and ] #
+ ###################################################
+
+ // Possible errors during calculation.
+ enum CalculatorError {
+ // Result is out of int32 bounds.
+ RESULT_OUT_OF_BOUNDS,
+ // Divider is zero.
+ DIVIDE_BY_ZERO
+ }
+
+ # Note: exception can also be based on String or any other type
+
+ // Exception to be thrown on calculation error.
+ exception CalculatorException(CalculatorError)
+
+ // Calculates sum of the given values.
+ fun summarize(
+ // The first value.
+ first: Int,
+ // The second value.
+ second: Int
+ ): Int throws
+ // [CalculatorError.RESULT_OUT_OF_BOUNDS] is thrown if result if out of int32 bounds.
+ // For example 2000000000 + 2000000000
+ CalculatorException
+
+ ###################################################
+ # 2. Lambda function as callback. #
+ # #
+ # Function `subtract` accepts lambda function as #
+ # parameter and it's invoked in C++. #
+ ###################################################
+
+ // Lambda which receives `error` or `result`.
+ // @param[p0] The error.
+ // @param[p1] The result.
+ lambda SubtructCallback = (@Java("error") CalculatorError?, @Java("result") Int?) -> Void
+
+ // Calculates difference of the given values. Implemented using [Calculator.summarize].
+ fun subtract(
+ // The minuend.
+ minuend: Int,
+ // The subtrahend.
+ subtrahend: Int,
+ // The callback which retrieves the result or [CalculatorError.RESULT_OUT_OF_BOUNDS] error.
+ callback: SubtructCallback
+ )
+
+ ###################################################
+ # 3. Interface implemented in Java/Swift #
+ # #
+ # Function `multiply` accepts interface which is #
+ # implemented in Java/Swift as parameter and its #
+ # methods are invoked in C++. #
+ ###################################################
+
+ // Interface with two callbacks to retrieve result or error.
+ interface MultiplyCallback {
+ // The callback to be called on error.
+ fun onError(
+ // The error.
+ error: CalculatorError)
+ // The callback to be called on result.
+ fun onResult(
+ // The result.
+ result: Int)
+ }
+
+ // Calculates multiplication of the given values.
+ fun multiply(
+ // The first value.
+ first: Int,
+ // The second value.
+ second: Int,
+ // The interface which implements result and error callbacks.
+ multiplyCallback: MultiplyCallback
+ )
+
+ ###################################################
+ # 4. Struct as parameter and return type. #
+ # #
+ # Function `divide` accepts struct as parameter #
+ # and returns another struct. #
+ ###################################################
+
+ // Arguments for division.
+ // @constructor Creates a new instance.
+ struct DivideArguments {
+ // Divident
+ dividend: Int
+ // Divider
+ divider: Int
+ }
+
+ // Result of division.
+ // @constructor Creates a new instance.
+ struct DivideResult {
+ // The error.
+ error: CalculatorError?
+ // The result.
+ result: Double?
+ }
+
+ // Calculates division of the given values.
+ fun divide(
+ // The arguments for division
+ divideArguments: DivideArguments
+ ):
+ // The result of division.
+ DivideResult
+
+ ###################################################
+ # 5. Interface implemented in C++. #
+ # #
+ # Function `min` returns interface which is #
+ # implemented in C++ and its methods are called #
+ # in Java/Swift. #
+ ###################################################
+
+ // Result retriever for function.
+ interface MinResultRetriever {
+ // Returns minimum of the given values.
+ fun get_result():
+ // The minimum.
+ Int
+ }
+
+ // Finds minimum of the given values.
+ fun min(
+ // The first value
+ first: Int,
+ // The seond value
+ second: Int
+ ):
+ // The result retriever
+ MinResultRetriever
+
+ ###################################################
+ # 6. Optional parameters and return type. #
+ # #
+ # Function `max` has optional parameters and #
+ # return type is also optional. #
+ # text in {@Lang ...} where Lang is Java, Swift, #
+ # Cpp or Dart is generated only on mentioned #
+ # platform. #
+ ###################################################
+
+ // Finds maximum of the given values. If any parameter is
+ // {@Java `null`}{@Swift `nil`}{@Cpp `std::nullopt`} then
+ // another parameter is returned.
+ fun max(
+ // The optional first value.
+ first: Int?,
+ // The optional second value.
+ second: Int?
+ ):
+ // The maximum of the given values or {@Java `null`}{@Swift `nil`}{@Cpp `std::nullopt`}.
+ Int?
+}