summaryrefslogtreecommitdiff
path: root/generator
diff options
context:
space:
mode:
authorKostiantyn Sologubov <ksologubov@luxoft.com>2020-04-08 18:28:28 +0300
committerKostiantyn Sologubov <ksologubov@luxoft.com>2020-04-08 19:54:25 +0300
commite3c4b4f98135760ebe934b2e92af9a6a656fb83b (patch)
tree005ccbcefc88d050dd9e7dba90ba99e4b471ea26 /generator
parent18b4c49d14cf6086e258327d845cc4db0ec67832 (diff)
downloadsdl_android-e3c4b4f98135760ebe934b2e92af9a6a656fb83b.tar.gz
moving generators to repository root
Diffstat (limited to 'generator')
-rw-r--r--generator/README.md1075
-rw-r--r--generator/__init__.py0
-rwxr-xr-xgenerator/generator.py442
-rw-r--r--generator/paths.ini8
-rw-r--r--generator/requirements.txt5
m---------generator/rpc_spec0
-rw-r--r--generator/templates/base_template.java87
-rw-r--r--generator/templates/enum_template.java88
-rw-r--r--generator/templates/function_template.java70
-rw-r--r--generator/templates/javadoc_return.java13
-rw-r--r--generator/templates/javadoc_template.java13
-rw-r--r--generator/templates/struct_function_template.java49
-rw-r--r--generator/templates/struct_template.java63
-rw-r--r--generator/test/__init__.py0
-rwxr-xr-xgenerator/test/runner.py40
-rw-r--r--generator/test/test_code_format_and_quality.py34
-rw-r--r--generator/test/test_enums.py282
-rw-r--r--generator/test/test_functions.py234
-rw-r--r--generator/test/test_structs.py160
-rw-r--r--generator/transformers/__init__.py0
-rw-r--r--generator/transformers/common_producer.py120
-rw-r--r--generator/transformers/enums_producer.py87
-rw-r--r--generator/transformers/functions_producer.py149
-rw-r--r--generator/transformers/generate_error.py12
-rw-r--r--generator/transformers/structs_producer.py129
25 files changed, 3160 insertions, 0 deletions
diff --git a/generator/README.md b/generator/README.md
new file mode 100644
index 000000000..3c81e64eb
--- /dev/null
+++ b/generator/README.md
@@ -0,0 +1,1075 @@
+# Proxy Library RPC Generator
+
+## Overview
+
+This script provides the possibility to auto-generate Java code based on a given SDL MOBILE_API XML specification.
+
+## Requirements
+
+The script requires **Python 3** pre-installed on the host system. The minimal supported Python 3 version is **3.7.6**. It may work on versions back to 3.5 (the minimal version that has not yet reached [the end-of-life](https://devguide.python.org/devcycle/#end-of-life-branches)), but this is not supported and may break in the future.
+
+Required libraries are described in `requirements.txt` and should be pre-installed by the command:
+```shell script
+python3 -m pip install -r requirements.txt
+```
+Please also make sure before usage the `utils/generator/rpc_spec` Git submodule is successfully initialized, because the script uses the XML parser provided there.
+```shell script
+git submodule update --init
+```
+
+## Usage
+```shell script
+usage: generator.py [-h] [-v] [-xml SOURCE_XML] [-xsd SOURCE_XSD]
+ [-d OUTPUT_DIRECTORY] [-t [TEMPLATES_DIRECTORY]]
+ [-r REGEX_PATTERN] [--verbose] [-e] [-s] [-m] [-y] [-n]
+
+Proxy Library RPC Generator
+
+optional arguments:
+ -h, --help show this help message and exit
+ -v, --version print the version and exit
+ -xml SOURCE_XML, --source-xml SOURCE_XML, --input-file SOURCE_XML
+ should point to MOBILE_API.xml
+ -xsd SOURCE_XSD, --source-xsd SOURCE_XSD
+ -d OUTPUT_DIRECTORY, --output-directory OUTPUT_DIRECTORY
+ define the place where the generated output should be
+ placed
+ -t [TEMPLATES_DIRECTORY], --templates-directory [TEMPLATES_DIRECTORY]
+ path to directory with templates
+ -r REGEX_PATTERN, --regex-pattern REGEX_PATTERN
+ only elements matched with defined regex pattern will
+ be parsed and generated
+ --verbose display additional details like logs etc
+ -e, --enums only specified elements will be generated, if present
+ -s, --structs only specified elements will be generated, if present
+ -m, -f, --functions only specified elements will be generated, if present
+ -y, --overwrite force overwriting of existing files in output
+ directory, ignore confirmation message
+ -n, --skip skip overwriting of existing files in output
+ directory, ignore confirmation message
+```
+
+# Java Transformation rules
+
+## Overview
+These are the general transformation rules for RPC classes of SDL Java Suite Library. The description of base classes, already included in the library, is not provided here, for details please view the source code.
+
+The JavaDoc is used for inline documentation of generated code. All non-XML values should follow Contributing to SDL Projects [CONTRIBUTING.md](https://github.com/smartdevicelink/sdl_android/blob/master/.github/CONTRIBUTING.md)
+
+## Output Directory Structure and Package definitions
+
+The generator script creates corresponding RPC classes for `<enum>`, `<struct>` and `<function>` elements of `MOBILE_API.xml`.
+According to existing structure of Java Suite the output directory will contain following folders and files:
+
+* com
+ * proxy
+ * rpc
+ * enums
+ * `[- all <enum> classes except FunctionID -]`
+ * `[- all <struct> classes -]`
+ * `[- all <function> classes -]`
+
+Each Enum class should be stored as a single file in the folder named `com/smartdevicelink/rpc/enums` and the name of the file should be equal to the value from the `"name"` attribute of `<enum>` followed by the extension `.java`. FunctionID enum generation is skipped due to the high complexity of structure.
+
+Example:
+```shell script
+# <enum name="ImageType" />
+com/smartdevicelink/proxy/rpc/enums/ImageType.java
+```
+
+Each Enum class should include the package definition:
+```java
+package com.smartdevicelink.proxy.rpc.enums;
+```
+
+Each Struct or Function class should be stored as a single file in the folder named `com/smartdevicelink/proxy/rpc` and the name of the file should be equal to the value from the `"name"` attribute of `<struct>` or `<function>` (followed by additional suffix `Response` if the `"name"` doesn't end with it and the `"messagetype"` attribute is set to `response`) followed by the extension `.java`.
+
+Example:
+```shell script
+# <struct name="VehicleDataResult" />
+com/smartdevicelink/proxy/rpc/VehicleDataResult.java
+
+# <function name="AddCommand" messagetype="request" />
+com/smartdevicelink/proxy/rpc/AddCommand.java
+# <function name="AddCommand" messagetype="response" />
+com/smartdevicelink/proxy/rpc/AddCommandResponse.java
+# <function name="OnLanguageChange" messagetype="notification" />
+com/smartdevicelink/proxy/rpc/OnLanguageChange.java
+```
+
+The package definition for Struct or Function classes is:
+```java
+package com.smartdevicelink.proxy.rpc;
+```
+
+## The License Header
+
+All files should start from the comment with the license information which includes dynamic `[year]` field in the copyright line with the current year.
+
+## `<enum>`
+
+The name of the class is the value from the `"name"` attribute of `<enum>`.
+
+The class should have the next JavaDoc comment:
+```java
+/**
+ * [description]
+ *
+ * @deprecated
+ * @since SmartDeviceLink [since_version]
+ * @see [see_reference]
+ */
+```
+Where:
+* `[description]` is `<description>` of the current `<enum>`, if exists.
+* `@deprecated` indicates the deprecation state if the `"deprecated"` attribute exists and is "true".
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `@see` shows the custom reference in `[see_reference]`.
+
+The class should have the `@Deprecated` decorator if the `"deprecated"` attribute of the `<enum>` exists and is "true".
+
+The set of `<element>` should be mapped to the set of Enum constants. Based on the `<element>` attributes, constants could be with or without fields.
+
+The following list are general rules for constant names and its fields:
+1. The `"name"` attribute of `<element>` is the default name of the constant.
+ * if the name starts from digit, the leading `_` (underscore) symbol should be added.
+ * if the name contains a `-` (dash) then it should be replaced with `_` (underscore)
+1. Uses of the "sync" prefix shall be replaced with "sdl" (where it would not break functionality). E.g. `SyncMsgVersion -> SdlMsgVersion`. This applies to member variables and their accessors. The key used when creating the RPC message JSON should match that of the RPC Spec.
+
+The constant definition could have the next JavaDoc comment:
+```java
+/**
+ * [description]
+ *
+ * @since SmartDeviceLink [since_version]
+ * @see [see_reference]
+ */
+```
+Where:
+* `[description]` is `<description>` of the current `<element>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `@see` shows the custom reference in `[see_reference]`.
+
+The constant definition should have the `@Deprecated` decorator if the `"deprecated"` attribute exists and is "true".
+
+### Constants without field:
+
+This type of enums doesn't require constructor and requires additional method `valueForString` to be defined. It should return the Enum constant based on its string name, or `null` if the constant is not found.
+```java
+ /**
+ * Convert String to [enum_name]
+ *
+ * @param value String
+ * @return [enum_name]
+ */
+ public static [enum_name] valueForString(String value) {
+ try {
+ return valueOf(value);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+```
+Where `[enum_name]` is the `"name"` attribute of `<enum>`
+
+Example:
+
+XML:
+```xml
+ <enum name="TemperatureUnit" since="4.5">
+ <element name="FAHRENHEIT"/>
+ <element name="CELSIUS"/>
+ </enum>
+```
+
+Output (javadoc comments skipped):
+```java
+package com.smartdevicelink.proxy.rpc.enums;
+
+public enum TemperatureUnit {
+ FAHRENHEIT,
+ CELSIUS;
+
+ public static TemperatureUnit valueForString(String value) {
+ try {
+ return valueOf(value);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
+```
+
+### Constants with field:
+
+In case of at least one name of constant was modified according the rules described below (by adding `_` before digit, replacing `-` with `_` and replacing `sync`) then original name is passed as parameter to constant.
+
+Constant definition:
+```java
+ [name]("[original_name]")
+```
+Where `[name]` is the `"name"` attribute of `<element>`, `[original_name]` is the original name of attribute before modification.
+
+Private field:
+```java
+ private final String VALUE;
+```
+
+The private constructor should be defined to accept the value from the constant and and set the private field.
+```java
+ private [enum_name](String value) {
+ this.VALUE = value;
+ }
+```
+Where `[enum_name]` is the `"name"` attribute of `<enum>`.
+
+The `toString` method should be overridden to return the private field instead of the constant name.
+```java
+ @Override
+ public String toString() {
+ return VALUE;
+ }
+```
+
+The additional `valueForString` should be defined. It should return the Enum constant based on the private field above, or `null` if the constant is not found.
+```java
+ /**
+ * Convert String to [enum_name]
+ *
+ * @param value String
+ * @return [enum_name]
+ */
+ public static [enum_name] valueForString(String value) {
+ if (value == null) {
+ return null;
+ }
+
+ for ([enum_name] anEnum : EnumSet.allOf([enum_name].class)) {
+ if (anEnum.toString().equals(value)) {
+ return anEnum;
+ }
+ }
+ return null;
+ }
+```
+Where `[enum_name]` is the `"name"` attribute of `<enum>`.
+
+The `valueForString` method requires the import of `EnumSet` collection:
+```java
+import java.util.EnumSet;
+```
+
+Example:
+
+XML:
+```xml
+ <enum name="Dimension" since="2.0">
+ <description>The supported dimensions of the GPS</description>
+ <element name="NO_FIX" internal_name="Dimension_NO_FIX">
+ <description>No GPS at all</description>
+ </element>
+ <element name="2D" internal_name="Dimension_2D">
+ <description>Longitude and latitude</description>
+ </element>
+ <element name="3D" internal_name="Dimension_3D">
+ <description>Longitude and latitude and altitude</description>
+ </element>
+ </enum>
+```
+
+Output (javadoc comments skipped):
+```java
+package com.smartdevicelink.proxy.rpc.enums;
+
+import java.util.EnumSet;
+
+public enum Dimension {
+ NO_FIX("NO_FIX"),
+ _2D("2D"),
+ _3D("3D");
+
+ private final String VALUE;
+
+ private Dimension(String value) {
+ this.VALUE = value;
+ }
+
+ public static Dimension valueForString(String value) {
+ if (value == null) {
+ return null;
+ }
+
+ for (Dimension anEnum : EnumSet.allOf(Dimension.class)) {
+ if (anEnum.toString().equals(value)) {
+ return anEnum;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return VALUE;
+ }
+}
+```
+
+## `<struct>`
+
+The Struct class should extend the base `RPCStruct` class:
+```java
+import com.smartdevicelink.proxy.RPCStruct;
+```
+
+The name of the class is the value from the `"name"` attribute of `<struct>`.
+
+The class should have the next JavaDoc comment:
+```java
+/**
+ * [description]
+ *
+ * <p><b>Parameter List</b></p>
+ *
+ * <table border="1" rules="all">
+ * <tr>
+ * <th>Param Name</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * <th>Required</th>
+ * <th>Version Available</th>
+ * </tr>
+ * <tr>
+ * <td>[param_name]</td>
+ * <td>[param_type|List<[param_type]>]</td>
+ * <td>[param_description]</td>
+ * <td>[Y|N]</td>
+ * <td>SmartDeviceLink [param_since]</td>
+ * </tr>
+ * </table>
+ *
+ * @deprecated
+ * @since SmartDeviceLink [since_version]
+ * @see [see_reference]
+ */
+```
+Where:
+* `[description]` is `<description>` of the current `<struct>`, if exists.
+* `@deprecated` indicates the deprecation state if the `"deprecated"` attribute exists and is "true".
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `@see` shows the custom reference in `[see_reference]`.
+* The `Parameter List` table should include all set of `<param>`.
+* `[param_name]` is `"name"` attribute of the `<param>`.
+* `[param_type]` is `"type"` attribute of the `<param>`, `[List<[param_type]>]` applied if `"array"` attribute of `<param>` is "true".
+* `[param_description]` is `<description>` of the `<param>`, could be empty if not exists.
+* `[Y|N]` means exactly `Y` character, if `"mandatory"` attribute of the `<param>` exists and is "true", `N` character otherwise.
+* `[param_since]` should be present, if the `"since"` attribute of the `<param>` exists, and `[since]` is the `Major.Minor.Patch` formatted value of this attribute.
+
+There are all Enum classes that are used in the represented structure should be additionally imported.
+
+The class should have the `@Deprecated` decorator if the `"deprecated"` attribute of the `<enum>` exists and is "true".
+
+The set of `<param>` should be mapped to the `public static final String` fields of the new class by following rules:
+
+1. The name of the fields is the `SCREAMING_SNAKE_CASE` formatted value of the `"name"` attribute of `<param>` with the `KEY_` prefix.
+1. The value of the fields is the value of the `"name"` attribute of `<param>`
+1. Uses of the "sync" prefix shall be replaced with "sdl" (where it would not break functionality). E.g. `KEY_SYNC_MSG_VERSION -> KEY_SDL_MSG_VERSION`. This applies to member variables and their accessors. The key used when creating the RPC message JSON should match that of the RPC Spec.
+
+Field definition template:
+```java
+/**
+ * @deprecated
+ * @see [see_reference]
+ */
+@Deprecated
+public static final String [normalized_name] = "[name]";
+```
+Where:
+* `[normalized_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`.
+* `[name]` is the `"name"` attribute of `<param>`.
+* `@deprecated` indicates the deprecation state if the `"deprecated"` attribute exists and is "true".
+* `@see` shows the custom reference in `[see_reference]`.
+
+The field definition should have the `@Deprecated` decorator if the `"deprecated"` attribute of the `<param>` exists and is "true".
+
+The Struct class contains 3 different constructors:
+* without parameters.
+* with `Hashtable` parameter.
+* with all required parameters, based on `"mandatory"` attribute of the `<param>`
+
+### Constructor without parameters
+
+Template:
+```java
+ /**
+ * Constructs a new [name] object
+ */
+ public [name]() { }
+```
+Where `[name]` is the value from the `"name"` attribute of `<struct>`.
+
+### Constructor with `Hashtable` parameter
+
+This constructor requires the import of `Hashtable` class
+```java
+import java.util.Hashtable;
+```
+
+Template:
+```java
+ /**
+ * Constructs a new [name] object indicated by the Hashtable parameter
+ *
+ * @param hash The Hashtable to use
+ */
+ public [name](Hashtable<String, Object> hash) {
+ super(hash);
+ }
+```
+Where `[name]` is the value from the `"name"` attribute of `<struct>`.
+
+### Constructor with all required parameters, based on `"mandatory"` attribute of the `<param>`
+This constructor requires the import of `NonNull` annotation
+```java
+import android.support.annotation.NonNull;
+```
+
+The constructor should include all set of `<param>` with the `"mandatory"` attribute is "true". JavaDoc should include all constructor parameters and the constructor should call all corresponding setters inside itself.
+
+Template:
+```java
+ /**
+ * Constructs a new [name] object
+ *
+ * @param [param_name] [description]
+ * [description]
+ */
+ public [name](@NonNull [param_type|List<[param_type]>] [param_name]) {
+ this();
+ [setter_name]([param_name]);
+ }
+```
+Where:
+* `[name]` is the value from the `"name"` attribute of `<struct>`.
+* `[param_name]` is `"name"` attribute of the `<param>`.
+* `[param_type]` is `"type"` attribute of the `<param>`, `[List<[param_type]>]` applied if `"array"` attribute of `<param>` is "true".
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `[setter_name]` is the name of the corresponding setter method
+
+For each `<param>` the getter and setter methods should be defined in the class:
+
+1. The name of the setter/getter is the `PascalCase` formatted value of the `"name"` attribute with the `get` prefix for the getter, for the setter the prefix should be `set`.
+1. Uses of the "sync" prefix shall be replaced with "sdl" (where it would not break functionality). E.g. `SyncMsgVersion -> SdlMsgVersion`. This applies to member variables and their accessors. The key used when creating the RPC message JSON should match that of the RPC Spec.
+1. The setter method:
+ * Accepts the single parameter with the type defined in the `"type"` attribute and the name defined in the `"name"` attribute of the `<param>`;
+ * The parameter should be decorated by `@NonNull` annotation if the `"mandatory"` attribute of the `<param>` is "true";
+ * Should call the `setValue` method, where the first parameter is the value of the corresponding static field described above, the second is the value passed into setter;
+1. The getter method:
+ * If `"type"` attribute of the `<param>` has the one of `Boolean`, `Integer` or `String`
+ * the getter should call and return the corresponding `getBoolean`, `getInteger` or `getString` method, the parameter of that method is the value of the corresponding static field described above;
+ * If `"type"` attribute of the `<param>` is `Float`:
+ * the getter should call the `getValue` method, the parameter of that method is the value of the corresponding static field described above;
+ * the getter should return `SdlDataTypeConverter.objectToFloat(object)` where the `object` is the value previously received from `getValue`;
+ * If the `<param>` has the `"type"` attribute value as the one of `<enum>` or `<struct>` name:
+ * The getter should call and return the result of the `getObject` method, where the first parameter is the corresponding Struct or Enum class, the second is the value of the corresponding static field described above;
+
+Setter template:
+```java
+ /**
+ * Sets the [name].
+ *
+ * @param [name] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ public void [setter_name]([type|List<[type]>] [name]) {
+ setValue([field_name], [name]);
+ }
+```
+Where:
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[type]` is `"type"` attribute of the `<param>`, `[List<[type]>]` applied if `"array"` attribute is "true".
+* `[setter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `set` prefix.
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+
+
+`Boolean`, `Integer` or `String` type getter template:
+```java
+ /**
+ * Gets the [name].
+ *
+ * @return [type|List<[type]>] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ @SuppressWarnings("unchecked")
+ public [type|List<[type]>] [getter_name]() {
+ return get[type]([field_name]);
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[getter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `get` prefix.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+* `[type]` is `"type"` attribute of the `<param>`, `[List<[type]>]` applied if `"array"` attribute is "true".
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `@SuppressWarnings("unchecked")` applied if `"array"` attribute is "true".
+
+
+`Float` type getter template:
+```java
+ /**
+ * Gets the [name].
+ *
+ * @return [Float|List<Float>] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ @SuppressWarnings("unchecked")
+ public Float|List<Float> [getter_name]() {
+ Object object = getValue([field_name]);
+ return SdlDataTypeConverter.objectToFloat(object);
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[getter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `get` prefix.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+* `[List<Float>]` applied if `"array"` attribute is "true".
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `@SuppressWarnings("unchecked")` applied if `"array"` attribute is "true".
+
+`<enum>` or `<struct>` type getter template:
+```java
+ /**
+ * Gets the [name].
+ *
+ * @return [type|List<[type]>] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ @SuppressWarnings("unchecked")
+ public [type|List<[type]>] [getter_name]() {
+ return ([type|List<[type]>]) getObject([type].class, [field_name]);
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[type]` is `"type"` attribute of the `<param>`, `[List<[type]>]` applied if `"array"` attribute is "true".
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `@SuppressWarnings("unchecked")` applied if `"array"` attribute is "true".
+* `[getter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `get` prefix.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+
+Take a note if some parameters have `"array"` attribute is true, the class requires the `List` collection to be imported:
+```java
+import java.util.List;
+```
+
+Example:
+
+XML:
+```xml
+ <struct name="Temperature" since="4.5">
+ <param name="unit" type="TemperatureUnit" mandatory="true">
+ <description>Temperature Unit</description>
+ </param>
+ <param name="value" type="Float" mandatory="true">
+ <description>Temperature Value in TemperatureUnit specified unit. Range depends on OEM and is not checked by SDL.</description>
+ </param>
+ </struct>
+```
+
+Output (javadoc comments skipped):
+```java
+package com.smartdevicelink.proxy.rpc;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.proxy.RPCStruct;
+import com.smartdevicelink.proxy.rpc.enums.TemperatureUnit;
+import com.smartdevicelink.util.SdlDataTypeConverter;
+
+import java.util.Hashtable;
+
+public class Temperature extends RPCStruct {
+ public static final String KEY_UNIT = "unit";
+ public static final String KEY_VALUE = "value";
+
+ public Temperature() { }
+
+ public Temperature(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+
+ public Temperature(@NonNull TemperatureUnit unit, @NonNull Float value) {
+ this();
+ setUnit(unit);
+ setValue(value);
+ }
+
+ public void setUnit(@NonNull TemperatureUnit unit) {
+ setValue(KEY_UNIT, unit);
+ }
+
+ public TemperatureUnit getUnit() {
+ return (TemperatureUnit) getObject(TemperatureUnit.class, KEY_UNIT);
+ }
+
+ public void setValue(@NonNull Float value) {
+ setValue(KEY_VALUE, value);
+ }
+
+ public Float getValue() {
+ Object object = getValue(KEY_VALUE);
+ return SdlDataTypeConverter.objectToFloat(object);
+ }
+}
+
+```
+
+## `<function>`
+
+Based on the value of the `"messagetype"` attribute of `<function>`the Function class should extend the class `RPCRequest`, `RPCResponse` or `RPCNotification`:
+```java
+import com.smartdevicelink.proxy.RPCRequest;
+// or
+import com.smartdevicelink.proxy.RPCResponse;
+// or
+import com.smartdevicelink.proxy.RPCNotification;
+```
+
+The name of the class is the value from the `"name"` attribute of `<function>` (followed by additional suffix `Response` if the `"name"` doesn't end with it and `"messagetype"` attribute is set to `response`.
+
+The script should import `FunctionID` Enum class to get the `functionID` hex value of the current RPC function. The key of the required `<element>` of `FunctionID` enum is the value of the `"functionID"` attribute of `<function>`.
+```java
+import com.smartdevicelink.protocol.enums.FunctionID;
+```
+
+The class should have the next JavaDoc comment:
+```java
+/**
+ * [description]
+ *
+ * <p><b>Parameter List</b></p>
+ *
+ * <table border="1" rules="all">
+ * <tr>
+ * <th>Param Name</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * <th>Required</th>
+ * <th>Version Available</th>
+ * </tr>
+ * <tr>
+ * <td>[param_name]</td>
+ * <td>[param_type|List<[param_type]>]</td>
+ * <td>[param_description]</td>
+ * <td>[Y|N]</td>
+ * <td>SmartDeviceLink [param_since]</td>
+ * </tr>
+ * </table>
+ *
+ * @deprecated
+ * @since SmartDeviceLink [since_version]
+ * @see [see_reference]
+ */
+```
+Where:
+* `[description]` is `<description>` of the current `<struct>`, if exists.
+* `@deprecated` indicates the deprecation state if the `"deprecated"` attribute exists and is "true".
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `@see` shows the custom reference in `[see_reference]`.
+* The `Parameter List` table should include all set of `<param>`.
+* `[param_name]` is `"name"` attribute of the `<param>`.
+* `[param_type]` is `"type"` attribute of the `<param>`, `[List<[param_type]>]` applied if `"array"` attribute of `<param>` is "true".
+* `[param_description]` is `<description>` of the `<param>`, could be empty if not exists.
+* `[Y|N]` means exactly `Y` character, if `"mandatory"` attribute of the `<param>` exists and is "true", `N` character otherwise.
+* `[param_since]` should be present, if the `"since"` attribute of the `<param>` exists, and `[since]` is the `Major.Minor.Patch` formatted value of this attribute.
+
+There are all Enum classes that are used in the represented structure should be additionally imported.
+
+The class should have the `@Deprecated` decorator if the `"deprecated"` attribute of the `<enum>` exists and is "true".
+
+The set of `<param>` should be mapped to the `public static final String` fields of the new class by following rules:
+
+1. The name of the fields is the `SCREAMING_SNAKE_CASE` formatted value of the `"name"` attribute of `<param>` with the `KEY_` prefix.
+1. The value of the fields is the value of the `"name"` attribute of `<param>`
+1. Uses of the "sync" prefix shall be replaced with "sdl" (where it would not break functionality). E.g. `KEY_SYNC_MSG_VERSION -> KEY_SDL_MSG_VERSION`. This applies to member variables and their accessors. The key used when creating the RPC message JSON should match that of the RPC Spec.
+1. The exclusion are `<param>` with name `success`, `resultCode` and `info` of `<function>` with the attribute `messagetype="response"`, in this case they should be omitted because they are already predefined in the parent class.
+
+Field definition template:
+```java
+/**
+ * @deprecated
+ * @see [see_reference]
+ */
+@Deprecated
+public static final String [normalized_name] = "[name]";
+```
+Where:
+* `[normalized_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`.
+* `[name]` is the `"name"` attribute of `<param>`.
+* `@deprecated` indicates the deprecation state if the `"deprecated"` attribute exists and is "true".
+* `@see` shows the custom reference in `[see_reference]`.
+
+The field definition should have the `@Deprecated` decorator if the `"deprecated"` attribute of the `<param>` exists and is "true".
+
+The Function class contains 3 different constructors:
+* without parameters.
+* with `Hashtable` parameter.
+* with all required parameters, based on `"mandatory"` attribute of the `<param>`
+
+The `response` Function class has additional constructor with `success` and `resultCode` mandatory parameters. Order of this parameters is dependent of order in XML.
+
+### Constructor without parameters
+
+This constructor should pass the corresponding stringified constant value of the `FunctionID` Enum class into parent class.
+
+Template:
+```java
+ /**
+ * Constructs a new [name] object
+ */
+ public [name]() {
+ super(FunctionID.[normalized_name].toString());
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<function>`.
+* `[normalized_name]` is the `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of the `<function>`.
+
+### Constructor with `Hashtable` parameter
+
+This constructor requires the import of `Hashtable` class
+```java
+import java.util.Hashtable;
+```
+
+Template:
+```java
+ /**
+ * Constructs a new [name] object indicated by the Hashtable parameter
+ *
+ * @param hash The Hashtable to use
+ */
+ public [name](Hashtable<String, Object> hash) {
+ super(hash);
+ }
+```
+Where `[name]` is the value from the `"name"` attribute of `<function>`.
+
+### Constructor with all required parameters, based on `"mandatory"` attribute of the `<param>`
+This constructor requires the import of `NonNull` annotation
+```java
+import android.support.annotation.NonNull;
+```
+
+The constructor should include all set of `<param>` with the `"mandatory"` attribute is "true". JavaDoc should include all constructor parameters and the constructor should call all corresponding setters inside itself.
+
+Template:
+```java
+ /**
+ * Constructs a new [name] object
+ *
+ * @param [param_name] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ public [name](@NonNull [param_type|List<[param_type]>] [param_name]) {
+ this();
+ [setter_name]([param_name]);
+ }
+```
+Where:
+* `[name]` is the value from the `"name"` attribute of `<function>`.
+* `[param_name]` is `"name"` attribute of the `<param>`.
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `[param_type]` is `"type"` attribute of the `<param>`, `[List<[param_type]>]` applied if `"array"` attribute of `<param>` is "true".
+* `[setter_name]` is the name of the corresponding setter method
+
+For each `<param>` the getter and setter methods should be defined in the class:
+
+1. The name of the setter/getter is the `PascalCase` formatted value of the `"name"` attribute with the `get` prefix for the getter, for the setter the prefix should be `set`.
+1. Uses of the "sync" prefix shall be replaced with "sdl" (where it would not break functionality). E.g. `SyncMsgVersion -> SdlMsgVersion`. This applies to member variables and their accessors. The key used when creating the RPC message JSON should match that of the RPC Spec.
+1. The setter method:
+ * Accepts the single parameter with the type defined in the `"type"` attribute and the name defined in the `"name"` attribute of the `<param>`;
+ * The parameter should be decorated by `@NonNull` annotation if the `"mandatory"` attribute of the `<param>` is "true";
+ * Should call the `setParameters` method, where the first parameter is the value of the corresponding static field described above, the second is the value passed into setter;
+1. The getter method:
+ * If `"type"` attribute of the `<param>` has the one of `Boolean`, `Integer` or `String`
+ * the getter should call and return the corresponding `getBoolean`, `getInteger` or `getString` method, the parameter of that method is the value of the corresponding static field described above;
+ * If `"type"` attribute of the `<param>` is `Float`:
+ * the getter should call the `getValue` method, the parameter of that method is the value of the corresponding static field described above;
+ * the getter should return `SdlDataTypeConverter.objectToFloat(object)` where the `object` is the value previously received from `getValue`;
+ * If the `<param>` has the `"type"` attribute value as the one of `<enum>` or `<struct>` name:
+ * The getter should call and return the result of the `getObject` method, where the first parameter is the corresponding Struct or Enum class, the second is the value of the corresponding static field described above;
+1. The exclusion are `<param>` with name `success`, `resultCode` and `info` of `<function>` with the attribute `messagetype="response"`, in this case they should be omitted because they are already predefined in the parent class.
+
+Setter template:
+```java
+ /**
+ * Sets the [name].
+ *
+ * @param [name] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ public void [setter_name]([type|List<[type]>] [name]) {
+ setParameters([field_name], [name]);
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `[type]` is `"type"` attribute of the `<param>`, `[List<[type]>]` applied if `"array"` attribute is "true".
+* `[setter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `set` prefix.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+
+
+`Boolean`, `Integer` or `String` type getter template:
+```java
+ /**
+ * Gets the [name].
+ *
+ * @return [type|List<[type]>] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ @SuppressWarnings("unchecked")
+ public [type|List<[type]>] [getter_name]() {
+ return get[type]([field_name]);
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `[getter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `get` prefix.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+* `[type]` is `"type"` attribute of the `<param>`, `[List<[type]>]` applied if `"array"` attribute is "true".
+* `@SuppressWarnings("unchecked")` applied if `"array"` attribute is "true".
+
+
+`Float` type getter template:
+```java
+ /**
+ * Gets the [name].
+ *
+ * @return [Float|List<Float>] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ @SuppressWarnings("unchecked")
+ public Float|List<Float> [getter_name]() {
+ Object object = getValue([field_name]);
+ return SdlDataTypeConverter.objectToFloat(object);
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `[getter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `get` prefix.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+* `[List<Float>]` applied if `"array"` attribute is "true".
+* `@SuppressWarnings("unchecked")` applied if `"array"` attribute is "true".
+
+`<enum>` or `<struct>` type getter template:
+```java
+ /**
+ * Gets the [name].
+ *
+ * @return [type|List<[type]>] [description]
+ * [description]
+ * @since SmartDeviceLink [since_version]
+ */
+ @SuppressWarnings("unchecked")
+ public [type|List<[type]>] [getter_name]() {
+ return ([type|List<[type]>]) getObject([type].class, [field_name]);
+ }
+```
+Where:
+* `[name]` is `"name"` attribute of the `<param>`.
+* `[description]` is `<description>` of the `<param>`, if exists.
+* `@since` should be present, if the `"since"` attribute exists, and `[since_version]` is the `Major.Minor.Patch` formatted value of this attribute.
+* `[type]` is `"type"` attribute of the `<param>`, `[List<[type]>]` applied if `"array"` attribute is "true".
+* `@SuppressWarnings("unchecked")` applied if `"array"` attribute is "true".
+* `[getter_name]` is the `PascalCase` formatted `"name"` attribute of the `<param>` with the `get` prefix.
+* `[field_name]` is the normalized and `SCREAMING_SNAKE_CASE` formatted `"name"` attribute of `<param>`, `[name]` is the `"name"` attribute.
+
+Take a note if some parameters have `"array"` attribute is true, the class requires the `List` collection to be imported:
+```java
+import java.util.List;
+```
+
+Example Request:
+
+XML:
+```xml
+ <function name="UpdateTurnList" functionID="UpdateTurnListID" messagetype="request" since="2.0">
+ <param name="turnList" type="Turn" minsize="1" maxsize="100" array="true" mandatory="false">
+ </param>
+ <param name="softButtons" type="SoftButton" minsize="0" maxsize="1" array="true" mandatory="false">
+ <description>If omitted on supported displays, app-defined SoftButton will be left blank.</description>
+ </param>
+ </function>
+```
+
+Output (javadoc comments skipped):
+```java
+package com.smartdevicelink.proxy.rpc;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCRequest;
+
+import java.util.Hashtable;
+import java.util.List;
+
+public class UpdateTurnList extends RPCRequest {
+ public static final String KEY_TURN_LIST = "turnList";
+ public static final String KEY_SOFT_BUTTONS = "softButtons";
+
+ public UpdateTurnList() {
+ super(FunctionID.UPDATE_TURN_LIST.toString());
+ }
+
+ public UpdateTurnList(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+
+ public void setTurnList(List<Turn> turnList) {
+ setParameters(KEY_TURN_LIST, turnList);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<Turn> getTurnList() {
+ return (List<Turn>) getObject(Turn.class, KEY_TURN_LIST);
+ }
+
+ public void setSoftButtons(List<SoftButton> softButtons) {
+ setParameters(KEY_SOFT_BUTTONS, softButtons);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<SoftButton> getSoftButtons() {
+ return (List<SoftButton>) getObject(SoftButton.class, KEY_SOFT_BUTTONS);
+ }
+}
+```
+
+Example Response:
+
+XML:
+```xml
+ <function name="UpdateTurnList" functionID="UpdateTurnListID" messagetype="response" since="2.0">
+ <param name="success" type="Boolean" platform="documentation" mandatory="true">
+ <description> true, if successful; false, if failed </description>
+ </param>
+ <param name="resultCode" type="Result" platform="documentation" mandatory="true">
+ <description>See Result</description>
+ <element name="SUCCESS"/>
+ <element name="INVALID_DATA"/>
+ <element name="OUT_OF_MEMORY"/>
+ <element name="TOO_MANY_PENDING_REQUESTS"/>
+ <element name="APPLICATION_NOT_REGISTERED"/>
+ <element name="GENERIC_ERROR"/>
+ <element name="REJECTED"/>
+ <element name="DISALLOWED"/>
+ <element name="UNSUPPORTED_REQUEST"/>
+ <element name="UNSUPPORTED_RESOURCE"/>
+ </param>
+ <param name="info" type="String" maxlength="1000" mandatory="false" platform="documentation">
+ <description>Provides additional human readable info regarding the result.</description>
+ </param>
+ </function>
+```
+
+Output (javadoc comments skipped):
+```java
+package com.smartdevicelink.proxy.rpc;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.rpc.enums.Result;
+
+import java.util.Hashtable;
+
+public class UpdateTurnListResponse extends RPCResponse {
+
+ public UpdateTurnListResponse() {
+ super(FunctionID.UPDATE_TURN_LIST.toString());
+ }
+
+ public UpdateTurnListResponse(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+
+ public UpdateTurnListResponse(@NonNull Boolean success, @NonNull Result resultCode) {
+ this();
+ setSuccess(success);
+ setResultCode(resultCode);
+ }
+}
+```
+
+Example Notification:
+
+XML:
+```xml
+ <function name="OnWayPointChange" functionID="OnWayPointChangeID" messagetype="notification" since="4.1">
+ <description>Notification which provides the entire LocationDetails when there is a change to any waypoints or destination.</description>
+ <param name="wayPoints" type="LocationDetails" mandatory="true" array="true" minsize="1" maxsize="10">
+ <description>See LocationDetails</description>
+ </param>
+ </function>
+```
+
+Output (javadoc comments skipped):
+```java
+package com.smartdevicelink.proxy.rpc;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCNotification;
+
+import java.util.Hashtable;
+import java.util.List;
+
+public class OnWayPointChange extends RPCNotification {
+ public static final String KEY_WAY_POINTS = "wayPoints";
+
+ public OnWayPointChange() {
+ super(FunctionID.ON_WAY_POINT_CHANGE.toString());
+ }
+
+ public OnWayPointChange(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+
+ public OnWayPointChange(@NonNull List<LocationDetails> wayPoints) {
+ this();
+ setWayPoints(wayPoints);
+ }
+
+ public void setWayPoints(@NonNull List<LocationDetails> wayPoints) {
+ setParameters(KEY_WAY_POINTS, wayPoints);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List<LocationDetails> getWayPoints() {
+ return (List<LocationDetails>) getObject(LocationDetails.class, KEY_WAY_POINTS);
+ }
+}
+``` \ No newline at end of file
diff --git a/generator/__init__.py b/generator/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/generator/__init__.py
diff --git a/generator/generator.py b/generator/generator.py
new file mode 100755
index 000000000..a60d7eed5
--- /dev/null
+++ b/generator/generator.py
@@ -0,0 +1,442 @@
+#!/usr/bin/env python3
+"""This is main runner of generator
+
+"""
+import datetime
+import logging
+import re
+import sys
+from argparse import ArgumentParser
+from collections import namedtuple, OrderedDict
+from inspect import getfile
+from os.path import basename
+from pprint import pformat
+from time import sleep
+from xml.etree.ElementTree import ParseError as XMLSchemaError
+
+from jinja2 import Environment, FileSystemLoader, TemplateNotFound, UndefinedError
+from pathlib2 import Path
+from xmlschema import XMLSchema
+
+ROOT = Path(__file__).absolute().parents[0]
+
+sys.path.append(ROOT.joinpath('rpc_spec/InterfaceParser').as_posix())
+
+try:
+ from parsers.sdl_rpc_v2 import Parser
+ from parsers.parse_error import ParseError as InterfaceError
+ from model.interface import Interface
+ from transformers.generate_error import GenerateError
+ from transformers.common_producer import InterfaceProducerCommon
+ from transformers.enums_producer import EnumsProducer
+ from transformers.functions_producer import FunctionsProducer
+ from transformers.structs_producer import StructsProducer
+except ImportError as message:
+ print('{}. probably you did not initialize submodule'.format(message))
+ sys.exit(1)
+
+
+class Generator:
+ """
+ This class contains only technical features, as follow:
+ - parsing command-line arguments, or evaluating required Paths interactively;
+ - calling parsers to get Model from xml;
+ - calling producers to transform initial Model to dict used in jinja2 templates
+ Not required to be covered by unit tests cause contains only technical features.
+ """
+
+ def __init__(self):
+ self.logger = logging.getLogger(self.__class__.__name__)
+ self._env = None
+
+ @property
+ def env(self):
+ """
+ :return: jinja2 Environment
+ """
+ return self._env
+
+ @env.setter
+ def env(self, value):
+ """
+ :param value: path with directory with templates
+ :return: jinja2 Environment
+ """
+ if not Path(value).exists():
+ self.logger.critical('Directory with templates not found %s', value)
+ sys.exit(1)
+ else:
+ self._env = Environment(loader=FileSystemLoader(value))
+
+ @property
+ def get_version(self):
+ """
+ :return: current version of Generator
+ """
+ return InterfaceProducerCommon.version
+
+ def config_logging(self, verbose):
+ """
+ Configure logging
+ :param verbose: boolean
+ """
+ handler = logging.StreamHandler()
+ handler.setFormatter(logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
+ datefmt='%m-%d %H:%M'))
+ if verbose:
+ handler.setLevel(logging.DEBUG)
+ self.logger.setLevel(logging.DEBUG)
+ else:
+ handler.setLevel(logging.ERROR)
+ self.logger.setLevel(logging.ERROR)
+ logging.getLogger().handlers.clear()
+ root_logger = logging.getLogger()
+ root_logger.addHandler(handler)
+
+ def evaluate_source_xml_xsd(self, xml, xsd):
+ """
+ :param xml: path to MOBILE_API.xml file
+ :param xsd: path to .xsd file (optional)
+ :return: validated path to .xsd file
+ """
+ if not Path(xml).exists():
+ self.logger.critical('File not found: %s', xml)
+ sys.exit(1)
+
+ if xsd and Path(xsd).exists():
+ return xsd
+
+ replace = xml.replace('.xml', '.xsd')
+ if xsd and not Path(xsd).exists():
+ self.logger.critical('File not found: %s', xsd)
+ sys.exit(1)
+ elif not xsd and not Path(replace).exists():
+ self.logger.critical('File not found: %s', replace)
+ sys.exit(1)
+ else:
+ return replace
+
+ def evaluate_output_directory(self, output_directory):
+ """
+ :param output_directory: path to output_directory
+ :return: validated path to output_directory
+ """
+ if output_directory.startswith('/'):
+ path = Path(output_directory).absolute().resolve()
+ else:
+ path = ROOT.joinpath(output_directory).resolve()
+ if not path.exists():
+ self.logger.warning('Directory not found: %s, trying to create it', path)
+ try:
+ path.mkdir(parents=True, exist_ok=True)
+ except OSError as message1:
+ self.logger.critical('Failed to create directory %s, %s', path.as_posix(), message1)
+ sys.exit(1)
+ return path
+
+ def get_parser(self):
+ """
+ Parsing command-line arguments, or evaluating required Paths interactively.
+ :return: an instance of argparse.ArgumentParser
+ """
+
+ if len(sys.argv) == 2 and sys.argv[1] in ('-v', '--version'):
+ print(self.get_version)
+ sys.exit(0)
+
+ Paths = namedtuple('Paths', 'name path')
+ xml = Paths('source_xml', ROOT.joinpath('rpc_spec/MOBILE_API.xml'))
+ required_source = not xml.path.exists()
+
+ out = Paths('output_directory', ROOT.parents[1].joinpath('base/src/main/java/'))
+ output_required = not out.path.exists()
+
+ parser = ArgumentParser(description='Proxy Library RPC Generator')
+ parser.add_argument('-v', '--version', action='store_true', help='print the version and exit')
+ parser.add_argument('-xml', '--source-xml', '--input-file', required=required_source,
+ help='should point to MOBILE_API.xml')
+ parser.add_argument('-xsd', '--source-xsd', required=False)
+ parser.add_argument('-d', '--output-directory', required=output_required,
+ help='define the place where the generated output should be placed')
+ parser.add_argument('-t', '--templates-directory', nargs='?', default=ROOT.joinpath('templates').as_posix(),
+ help='path to directory with templates')
+ parser.add_argument('-r', '--regex-pattern', required=False,
+ help='only elements matched with defined regex pattern will be parsed and generated')
+ parser.add_argument('--verbose', action='store_true', help='display additional details like logs etc')
+ parser.add_argument('-e', '--enums', required=False, action='store_true',
+ help='only specified elements will be generated, if present')
+ parser.add_argument('-s', '--structs', required=False, action='store_true',
+ help='only specified elements will be generated, if present')
+ parser.add_argument('-m', '-f', '--functions', required=False, action='store_true',
+ help='only specified elements will be generated, if present')
+ parser.add_argument('-y', '--overwrite', action='store_true',
+ help='force overwriting of existing files in output directory, ignore confirmation message')
+ parser.add_argument('-n', '--skip', action='store_true',
+ help='skip overwriting of existing files in output directory, ignore confirmation message')
+
+ args, unknown = parser.parse_known_args()
+
+ if unknown:
+ self.logger.critical('found unknown arguments: %s', ' '.join(unknown))
+ parser.print_help(sys.stderr)
+ sys.exit(1)
+
+ if args.skip and args.overwrite:
+ self.logger.critical('please select only one option skip or overwrite')
+ sys.exit(1)
+
+ if not args.enums and not args.structs and not args.functions:
+ args.enums = args.structs = args.functions = True
+
+ for intermediate in (xml, out):
+ if not getattr(args, intermediate.name) and intermediate.path.exists():
+ while True:
+ try:
+ confirm = input('Confirm default path {} for {} Y/Enter = yes, N = no'
+ .format(intermediate.path, intermediate.name))
+ if confirm.lower() == 'y' or not confirm:
+ self.logger.warning('%s set to %s', intermediate.name, intermediate.path)
+ setattr(args, intermediate.name, intermediate.path.as_posix())
+ sleep(0.05)
+ break
+ if confirm.lower() == 'n':
+ self.logger.warning('provide argument %s', intermediate.name)
+ sys.exit(1)
+ except KeyboardInterrupt:
+ print('\nThe user interrupted the execution of the program')
+ sys.exit(1)
+
+ self.config_logging(args.verbose)
+
+ args.source_xsd = self.evaluate_source_xml_xsd(args.source_xml, args.source_xsd)
+
+ args.output_directory = self.evaluate_output_directory(args.output_directory)
+
+ self.env = args.templates_directory
+
+ self.logger.info('parsed arguments:\n%s', pformat((vars(args))))
+ return args
+
+ def versions_compatibility_validating(self):
+ """version of generator script requires the same or lesser version of parser script.
+ if the parser script needs to fix a bug (and becomes, e.g. 1.0.1) and the generator script stays at 1.0.0.
+ As long as the generator script is the same or greater major version, it should be parsable.
+ This requires some level of backward compatibility. E.g. they have to be the same major version.
+
+ """
+
+ regex = r'(\d+\.\d+).(\d)'
+
+ parser_origin = Parser().get_version
+ parser_split = re.findall(regex, parser_origin).pop()
+ generator_split = re.findall(regex, self.get_version).pop()
+
+ parser_major = float(parser_split[0])
+ generator_major = float(generator_split[0])
+
+ if parser_major > generator_major:
+ self.logger.critical('Generator (%s) requires the same or lesser version of Parser (%s)',
+ self.get_version, parser_origin)
+ sys.exit(1)
+
+ self.logger.info('Parser type: %s, version %s,\tGenerator version %s',
+ basename(getfile(Parser().__class__)), parser_origin, self.get_version)
+
+ def get_file_content(self, file_name: Path) -> list:
+ """
+
+ :param file_name:
+ :return:
+ """
+ try:
+ with file_name.open('r') as file:
+ content = file.readlines()
+ return content
+ except FileNotFoundError as message1:
+ self.logger.error(message1)
+ return []
+
+ def get_key_words(self, file_name=ROOT.joinpath('rpc_spec/RpcParser/RESERVED_KEYWORDS')):
+ """
+ :param file_name:
+ :return:
+ """
+ content = self.get_file_content(file_name)
+ content = tuple(map(lambda e: re.sub(r'\n', r'', e).strip().casefold(), content))
+ try:
+ content = tuple(filter(lambda e: not re.search(r'^#+\s+.+|^$', e), content))
+ self.logger.debug('key_words: %s', ', '.join(content))
+ return content
+ except (IndexError, ValueError, StopIteration) as error1:
+ self.logger.error('Error while getting key_words, %s %s', type(error1).__name__, error1)
+ return []
+
+ def get_paths(self, file_name=ROOT.joinpath('paths.ini')):
+ """
+ :param file_name: path to file with Paths
+ :return: namedtuple with Paths to key elements
+ """
+ fields = ('struct_class', 'request_class', 'response_class',
+ 'notification_class', 'enums_package', 'structs_package', 'functions_package')
+ data = OrderedDict()
+ content = self.get_file_content(file_name)
+
+ for line in content:
+ if line.startswith('#'):
+ self.logger.warning('commented property %s, which will be skipped', line.strip())
+ continue
+ if re.match(r'^(\w+)\s?=\s?(.+)', line):
+ if len(line.split('=')) > 2:
+ self.logger.critical('can not evaluate value, too many separators %s', str(line))
+ sys.exit(1)
+ name, var = line.partition('=')[::2]
+ if name.strip() in data:
+ self.logger.critical('duplicate key %s', name)
+ sys.exit(1)
+ data[name.strip().lower()] = var.strip()
+
+ for line in fields:
+ if line not in data:
+ self.logger.critical('in %s missed fields: %s ', content, str(line))
+ sys.exit(1)
+
+ Paths = namedtuple('Paths', ' '.join(fields))
+ return Paths(**data)
+
+ def write_file(self, file_name, template, data):
+ """
+ Calling producer/transformer instance to transform initial Model to dict used in jinja2 templates.
+ Applying transformed dict to jinja2 templates and writing to appropriate file
+ :param file_name: output java file
+ :param template: name of template
+ :param data: transformed model ready for apply to Jinja2 template
+ """
+ file_name.parents[0].mkdir(parents=True, exist_ok=True)
+ try:
+ render = self.env.get_template(template).render(data)
+ with file_name.open('w', encoding='utf-8') as file:
+ file.write(render)
+ except (TemplateNotFound, UndefinedError) as message1:
+ self.logger.error('skipping %s, template not found %s', file_name.as_posix(), message1)
+
+ def process(self, directory, skip, overwrite, items, transformer):
+ """
+ Process each item from initial Model. According to provided arguments skipping, overriding or asking what to to.
+ :param directory: output directory for writing output files
+ :param skip: if file exist skip it
+ :param overwrite: if file exist overwrite it
+ :param items: elements initial Model
+ :param transformer: producer/transformer instance
+ """
+
+ directory.mkdir(parents=True, exist_ok=True)
+ template = type(items[0]).__name__.lower() + '_template.java'
+ year = datetime.datetime.utcnow().year
+ for item in items:
+ if item.name == 'FunctionID':
+ self.logger.warning('%s will be skipped', item.name)
+ continue # Skip FunctionID generation
+ data = transformer.transform(item)
+ data['year'] = year
+ file = data['class_name'] + '.java'
+ file = directory.joinpath(data['package_name'].replace('.', '/')).joinpath(file)
+ if file.is_file():
+ if skip:
+ self.logger.info('Skipping %s', file)
+ continue
+ if overwrite:
+ self.logger.info('Overriding %s', file)
+ file.unlink()
+ self.write_file(file, template, data)
+ else:
+ while True:
+ try:
+ confirm = input('File already exists {}. Overwrite? Y/Enter = yes, N = no\n'.format(file))
+ if confirm.lower() == 'y' or not confirm:
+ self.logger.info('Overriding %s', file)
+ file.unlink()
+ self.write_file(file, template, data)
+ break
+ if confirm.lower() == 'n':
+ self.logger.info('Skipping %s', file)
+ break
+ except KeyboardInterrupt:
+ print('\nThe user interrupted the execution of the program')
+ sys.exit(1)
+ else:
+ self.logger.info('Writing new %s', file)
+ self.write_file(file, template, data)
+
+ def parser(self, xml, xsd, pattern=None):
+ """
+ Validate xml to match with xsd. Calling parsers to get Model from xml. If provided pattern, filtering Model.
+ :param xml: path to MOBILE_API.xml
+ :param xsd: path to MOBILE_API.xsd
+ :param pattern: regex-pattern from command-line arguments to filter element from initial Model
+ :return: initial Model
+ """
+ self.logger.info('''Validating XML and generating model with following parameters:
+ Source xml : %s
+ Source xsd : %s''', xml, xsd)
+
+ try:
+ schema = XMLSchema(xsd)
+ if not schema.is_valid(xml):
+ raise GenerateError(schema.validate(xml))
+ interface = Parser().parse(xml)
+ except (InterfaceError, XMLSchemaError, GenerateError) as message1:
+ self.logger.critical('Invalid XML file content: %s, %s', xml, message1)
+ sys.exit(1)
+
+ enum_names = tuple(interface.enums.keys())
+ struct_names = tuple(interface.structs.keys())
+
+ if pattern:
+ intermediate = OrderedDict()
+ intermediate.update({'params': interface.params})
+ for kind, content in vars(interface).items():
+ if kind == 'params':
+ continue
+ for name, item in content.items():
+ if re.match(pattern, item.name):
+ self.logger.info('%s/%s match with %s', kind, item.name, pattern)
+ if kind in intermediate:
+ intermediate[kind].update({name: item})
+ else:
+ intermediate.update({kind: {name: item}})
+ interface = Interface(**intermediate)
+
+ self.logger.debug({'enums': tuple(interface.enums.keys()),
+ 'structs': tuple(interface.structs.keys()),
+ 'functions': tuple(map(lambda i: i.function_id.name, interface.functions.values())),
+ 'params': interface.params})
+ return enum_names, struct_names, interface
+
+ def main(self):
+ """
+ Entry point for parser and generator
+ :return: None
+ """
+ args = self.get_parser()
+
+ self.versions_compatibility_validating()
+
+ enum_names, struct_names, interface = self.parser(xml=args.source_xml, xsd=args.source_xsd,
+ pattern=args.regex_pattern)
+
+ paths = self.get_paths()
+ key_words = self.get_key_words()
+
+ if args.enums and interface.enums:
+ self.process(args.output_directory, args.skip, args.overwrite, tuple(interface.enums.values()),
+ EnumsProducer(paths, key_words))
+ if args.structs and interface.structs:
+ self.process(args.output_directory, args.skip, args.overwrite, tuple(interface.structs.values()),
+ StructsProducer(paths, enum_names, struct_names, key_words))
+ if args.functions and interface.functions:
+ self.process(args.output_directory, args.skip, args.overwrite, tuple(interface.functions.values()),
+ FunctionsProducer(paths, enum_names, struct_names, key_words))
+
+
+if __name__ == '__main__':
+ Generator().main()
diff --git a/generator/paths.ini b/generator/paths.ini
new file mode 100644
index 000000000..a329ec34a
--- /dev/null
+++ b/generator/paths.ini
@@ -0,0 +1,8 @@
+ENUMS_PACKAGE = com.smartdevicelink.proxy.rpc.enums
+STRUCTS_PACKAGE = com.smartdevicelink.proxy.rpc
+FUNCTIONS_PACKAGE = com.smartdevicelink.proxy.rpc
+
+STRUCT_CLASS = com.smartdevicelink.proxy.RPCStruct
+REQUEST_CLASS = com.smartdevicelink.proxy.RPCRequest
+RESPONSE_CLASS = com.smartdevicelink.proxy.RPCResponse
+NOTIFICATION_CLASS = com.smartdevicelink.proxy.RPCNotification
diff --git a/generator/requirements.txt b/generator/requirements.txt
new file mode 100644
index 000000000..c59028039
--- /dev/null
+++ b/generator/requirements.txt
@@ -0,0 +1,5 @@
+xmlschema
+Jinja2
+coverage
+pathlib2
+flake8 \ No newline at end of file
diff --git a/generator/rpc_spec b/generator/rpc_spec
new file mode 160000
+Subproject c5132e0016cf34b30fec2a2687c59cbfd44c543
diff --git a/generator/templates/base_template.java b/generator/templates/base_template.java
new file mode 100644
index 000000000..0f1d58c95
--- /dev/null
+++ b/generator/templates/base_template.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2017 - {{year}}, SmartDeviceLink Consortium, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the SmartDeviceLink Consortium Inc. nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package {{package_name}};
+{% for i in imports %}
+{%- if i != '' %}
+import {{i}};{{ '\n' if loop.last }}
+{%- else %}
+{{''}}
+{%- endif %}
+{%- endfor %}
+{%- if description is defined or since is defined or see is defined or deprecated is defined %}
+/**
+ {%- if description is defined %}
+ {%- for d in description %}
+ * {{d}}
+ {%- endfor %}{%- endif %}
+ {%- if params is defined and ((kind is defined and kind not in ["response", "simple", "custom"]) or kind is not defined) %}
+ *
+ * <p><b>Parameter List</b></p>
+ *
+ * <table border="1" rules="all">
+ * <tr>
+ * <th>Param Name</th>
+ * <th>Type</th>
+ * <th>Description</th>
+ * <th>Required</th>
+ * <th>Version Available</th>
+ * </tr>
+ {%- for param in params %}
+ * <tr>
+ * <td>{{param.origin}}</td>
+ * <td>{{param.return_type}}</td>
+ * <td>{%- for d in param.description %}{{d}}{%- endfor %}</td>
+ * <td>{%- if param.mandatory is eq true %}Y{%- else %}N{%- endif %}</td>
+ * <td>{%- if param.since is defined %}SmartDeviceLink {{param.since}}{%- endif %}</td>
+ * </tr>
+ {%- endfor %}
+ * </table>
+ {%- endif %}
+ {%- if description is defined and (see is defined or since is defined) %}
+ *
+ {%- endif %}
+ {%- if deprecated is not none %}
+ * @deprecated
+ {%- endif %}
+ {%- if see is defined %}
+ * @see {{see}}
+ {%- endif %}
+ {%- if since is defined %}
+ * @since SmartDeviceLink {{since}}
+ {%- endif %}
+ */
+{%- endif %}
+{%- if deprecated is not none %}
+@Deprecated
+{%- endif %}
+{%- block body %}
+{% endblock -%}
diff --git a/generator/templates/enum_template.java b/generator/templates/enum_template.java
new file mode 100644
index 000000000..0d3f1fa26
--- /dev/null
+++ b/generator/templates/enum_template.java
@@ -0,0 +1,88 @@
+{% extends "base_template.java" %}
+{% block body %}
+public enum {{class_name}} {
+ {%- for param in params %}
+ {%- if param.description is defined or param.since is defined %}
+ /**
+ {%- if param.description is defined %}
+ {%- for d in param.description %}
+ * {{d}}
+ {%- endfor %}{% endif -%}
+ {%- if param.description is defined and (param.since is defined or param.see is defined) %}
+ *
+ {%- endif %}
+ {%- if param.since is defined %}
+ * @since SmartDeviceLink {{param.since}}
+ {%- endif %}
+ {%- if param.see is defined %}
+ * @see {{param.see}}
+ {%- endif %}
+ */
+ {%- endif %}
+ {%- if param.deprecated is defined %}
+ @Deprecated
+ {%- endif %}
+ {%- if kind == "simple" %}
+ {{param.name}}{{ "," if not loop.last }}
+ {%- elif kind == "custom" %}
+ {{param.name}}({{param.internal}}){{ "," if not loop.last }}
+ {%- endif %}
+ {%- endfor %};
+
+ {%- if kind == "simple" %}
+
+ /**
+ * Convert String to {{class_name}}
+ *
+ * @param value String
+ * @return {{class_name}}
+ */
+ public static {{class_name}} valueForString(String value) {
+ try {
+ return valueOf(value);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+ {%- elif kind == "custom" %}
+
+ private final String VALUE;
+
+ /**
+ * Private constructor
+ */
+ private {{class_name}}(String value) {
+ this.VALUE = value;
+ }
+
+ /**
+ * Convert String to {{class_name}}
+ *
+ * @param value String
+ * @return {{class_name}}
+ */
+ public static {{class_name}} valueForString(String value) {
+ if (value == null) {
+ return null;
+ }
+
+ for ({{class_name}} anEnum : EnumSet.allOf({{class_name}}.class)) {
+ if (anEnum.toString().equals(value)) {
+ return anEnum;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return String value of element
+ *
+ * @return String
+ */
+ @Override
+ public String toString() {
+ return VALUE;
+ }
+ {%- endif %}
+}
+{% endblock -%} \ No newline at end of file
diff --git a/generator/templates/function_template.java b/generator/templates/function_template.java
new file mode 100644
index 000000000..f587a636d
--- /dev/null
+++ b/generator/templates/function_template.java
@@ -0,0 +1,70 @@
+{% extends "struct_function_template.java" %}
+
+ {%- block params %}
+ {%- if params is defined %}
+ {%- for p in params %}
+ {%- if p.origin not in ('success', 'resultCode', 'info') or kind != "response" %}
+ {%- if p.see is defined or p.deprecated is not none %}
+ /**
+ {%- if p.deprecated is not none %}
+ * @deprecated
+ {%- endif %}
+ {%- if p.see is defined %}
+ * @see {{p.see}}
+ {%- endif %}
+ */
+ {%- endif %}
+ {%- if p.deprecated is not none %}
+ @Deprecated
+ {%- endif %}
+ public static final String {{p.key}} = "{{p.origin}}";
+ {%- endif %}
+ {%- endfor %}
+ {%- endif %}
+ {%- endblock %}
+
+ {%- block constructor_simple %}
+ public {{class_name}}() {
+ super(FunctionID.{{function_id}}.toString());
+ }{% endblock -%}
+
+ {%- block setter %}
+ {%- for p in params|rejectattr('name') %}
+ {%- if p.origin not in ('success', 'resultCode', 'info') or kind != "response" %}
+
+ /**
+ * Sets the {{p.origin}}.
+ *
+ {%- include "javadoc_template.java" %}
+ */
+ public void set{{p.title}}({% if p.mandatory %}@NonNull {% endif %}{{p.return_type}} {{p.last}}) {
+ setParameters({{p.key}}, {{p.last}});
+ }
+
+ /**
+ * Gets the {{p.origin}}.
+ *
+ {%- include "javadoc_return.java" %}
+ */
+ {%- if p.SuppressWarnings is defined %}
+ @SuppressWarnings("{{p.SuppressWarnings}}")
+ {%- endif %}
+ public {{p.return_type}} get{{p.title}}() {
+ {%- if p.return_type in ['String', 'Boolean', 'Integer'] %}
+ return get{{p.return_type}}({{p.key}});
+ {%- elif p.return_type in ['Float'] %}
+ Object object = getParameters({{p.key}});
+ return SdlDataTypeConverter.objectToFloat(object);
+ {%- elif p.return_type in ['Double'] %}
+ Object object = getParameters({{p.key}});
+ return SdlDataTypeConverter.objectToDouble(object);
+ {%- else %}
+ {%- set clazz = p.return_type %}
+ {%- if p.return_type.startswith('List')%}{%set clazz = p.return_type[5:-1]%}{% endif %}
+ return ({{p.return_type}}) getObject({{clazz}}.class, {{p.key}});
+ {%- endif %}
+ }
+
+ {%- endif %}
+ {%- endfor %}
+ {%- endblock %} \ No newline at end of file
diff --git a/generator/templates/javadoc_return.java b/generator/templates/javadoc_return.java
new file mode 100644
index 000000000..d2f4a3c54
--- /dev/null
+++ b/generator/templates/javadoc_return.java
@@ -0,0 +1,13 @@
+ {%- if p.description is defined %}
+ {%- for d in p.description %}
+ {%- if loop.index == 1 %}
+ * @return {{p.return_type}} {{d}}
+ {%- else %}
+ * {{d}}
+ {%- endif %}{%- endfor %}
+ {%- else %}
+ * @return {{p.return_type}}
+ {%- endif %}
+ {%- if p.since is defined %}
+ * @since SmartDeviceLink {{p.since}}
+ {%- endif %} \ No newline at end of file
diff --git a/generator/templates/javadoc_template.java b/generator/templates/javadoc_template.java
new file mode 100644
index 000000000..5b5d3bc0c
--- /dev/null
+++ b/generator/templates/javadoc_template.java
@@ -0,0 +1,13 @@
+ {%- if p.description is defined %}
+ {%- for d in p.description %}
+ {%- if loop.index == 1 %}
+ * @param {{p.last}} {{d}}
+ {%- else %}
+ * {{d}}
+ {%- endif %}{%- endfor %}
+ {%- else %}
+ * @param {{p.last}}
+ {%- endif %}
+ {%- if p.since is defined %}
+ * @since SmartDeviceLink {{p.since}}
+ {%- endif %} \ No newline at end of file
diff --git a/generator/templates/struct_function_template.java b/generator/templates/struct_function_template.java
new file mode 100644
index 000000000..a0ab096f9
--- /dev/null
+++ b/generator/templates/struct_function_template.java
@@ -0,0 +1,49 @@
+{% extends "base_template.java" %}
+
+{% block body %}
+public class {{class_name}} extends {{extends_class}} {
+ {%- block params %}
+ {%- endblock %}
+
+ /**
+ * Constructs a new {{class_name}} object
+ */
+ {%- block constructor_simple %}
+ {% endblock %}
+
+ /**
+ * Constructs a new {{class_name}} object indicated by the Hashtable parameter
+ *
+ * @param hash The Hashtable to use
+ */
+ public {{class_name}}(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+ {%- if params is defined %}
+ {%- set constructor = [] %}
+ {%- for p in params|selectattr('mandatory') %}{{ constructor.append('@NonNull ' + p.return_type + ' ' + p.last) or '' }}
+ {%- endfor %}
+ {%- if constructor|length > 0 %}
+
+ /**
+ * Constructs a new {{class_name}} object
+ *
+ {%- for p in params|selectattr('mandatory') %}
+ {%- include "javadoc_template.java" %}
+ {%- endfor %}
+ */
+ public {{class_name}}({{ constructor|join(', ') }}) {
+ this();
+ {%- for p in params|selectattr('mandatory') %}
+ set{{p.title}}({{p.last}});
+ {%- endfor %}
+ }
+ {%- endif %}
+ {%- endif %}
+
+ {%- if params is defined %}
+ {%- block setter %}
+ {%- endblock%}
+ {%- endif %}
+}
+{% endblock -%}
diff --git a/generator/templates/struct_template.java b/generator/templates/struct_template.java
new file mode 100644
index 000000000..6307509f8
--- /dev/null
+++ b/generator/templates/struct_template.java
@@ -0,0 +1,63 @@
+{% extends "struct_function_template.java" %}
+
+ {%- block params %}
+ {%- if params is defined %}
+ {%- for p in params %}
+ {%- if p.see is defined or p.deprecated is not none %}
+ /**
+ {%- if p.deprecated is not none %}
+ * @deprecated
+ {%- endif %}
+ {%- if p.see is defined %}
+ * @see {{p.see}}
+ {%- endif %}
+ */
+ {%- endif %}
+ {%- if p.deprecated is not none %}
+ @Deprecated
+ {%- endif %}
+ public static final String {{p.key}} = "{{p.origin}}";
+ {%- endfor %}
+ {%- endif %}
+ {%- endblock %}
+
+ {%- block constructor_simple %}
+ public {{class_name}}() { }
+ {%- endblock -%}
+
+ {%- block setter %}
+ {%- for p in params|rejectattr('name') %}
+
+ /**
+ * Sets the {{p.origin}}.
+ *
+ {%- include "javadoc_template.java" %}
+ */
+ public void set{{p.title}}({% if p.mandatory %}@NonNull {% endif %}{{p.return_type}} {{p.last}}) {
+ setValue({{p.key}}, {{p.last}});
+ }
+
+ /**
+ * Gets the {{p.origin}}.
+ *
+ {%- include "javadoc_return.java" %}
+ */
+ {%- if p.SuppressWarnings is defined %}
+ @SuppressWarnings("{{p.SuppressWarnings}}")
+ {%- endif %}
+ public {{p.return_type}} get{{p.title}}() {
+ {%- if p.return_type in ['String', 'Boolean', 'Integer'] %}
+ return get{{p.return_type}}({{p.key}});
+ {%- elif p.return_type in ['Float'] %}
+ Object object = getValue({{p.key}});
+ return SdlDataTypeConverter.objectToFloat(object);
+ {%- else %}
+ {%- set clazz = p.return_type %}
+ {%- if p.return_type.startswith('List')%}{%set clazz = p.return_type[5:-1]%}{% endif %}
+ return ({{p.return_type}}) getObject({{clazz}}.class, {{p.key}});
+ {%- endif %}
+ }
+
+ {%- endfor %}
+ {%- endblock %}
+
diff --git a/generator/test/__init__.py b/generator/test/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/generator/test/__init__.py
diff --git a/generator/test/runner.py b/generator/test/runner.py
new file mode 100755
index 000000000..a6754d57e
--- /dev/null
+++ b/generator/test/runner.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python3
+"""
+Main entry point to run all tests
+"""
+import sys
+from pathlib import Path
+from unittest import TestLoader, TestSuite, TextTestRunner
+
+PATH = Path(__file__).absolute()
+
+sys.path.append(PATH.parents[1].joinpath('rpc_spec/InterfaceParser').as_posix())
+sys.path.append(PATH.parents[1].as_posix())
+
+try:
+ from test_enums import TestEnumsProducer
+ from test_functions import TestFunctionsProducer
+ from test_structs import TestStructsProducer
+ from test_code_format_and_quality import CodeFormatAndQuality
+except ImportError as message:
+ print('{}. probably you did not initialize submodule'.format(message))
+ sys.exit(1)
+
+
+def main():
+ """
+ Main entry point to run all tests
+ """
+
+ suite = TestSuite()
+ suite.addTests(TestLoader().loadTestsFromTestCase(TestFunctionsProducer))
+ suite.addTests(TestLoader().loadTestsFromTestCase(TestEnumsProducer))
+ suite.addTests(TestLoader().loadTestsFromTestCase(TestStructsProducer))
+ suite.addTests(TestLoader().loadTestsFromTestCase(CodeFormatAndQuality))
+
+ runner = TextTestRunner(verbosity=2)
+ runner.run(suite)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/generator/test/test_code_format_and_quality.py b/generator/test/test_code_format_and_quality.py
new file mode 100644
index 000000000..43eab71ec
--- /dev/null
+++ b/generator/test/test_code_format_and_quality.py
@@ -0,0 +1,34 @@
+"""Interface model unit test
+
+"""
+
+import unittest
+from os import walk
+from os.path import join
+from pathlib import Path
+
+from flake8.api import legacy as flake8
+
+
+class CodeFormatAndQuality(unittest.TestCase):
+
+ def setUp(self):
+ """Searching for all python files to be checked
+
+ """
+ self.list_of_files = []
+ for (directory, _, filenames) in walk(Path(__file__).absolute().parents[1].as_posix()):
+ self.list_of_files += [join(directory, file) for file in filenames
+ if file.endswith('.py') and 'rpc_spec' not in directory]
+
+ def test_check(self):
+ """Performing checks by flake8
+
+ """
+ style_guide = flake8.get_style_guide(max_line_length=120, ignore=['W504', 'N802'])
+ report = style_guide.check_files(self.list_of_files)
+ self.assertEqual(report.total_errors, 0)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/generator/test/test_enums.py b/generator/test/test_enums.py
new file mode 100644
index 000000000..c3b3c5208
--- /dev/null
+++ b/generator/test/test_enums.py
@@ -0,0 +1,282 @@
+import unittest
+from collections import namedtuple, OrderedDict
+
+from model.enum import Enum
+from model.enum_element import EnumElement
+from transformers.enums_producer import EnumsProducer
+
+
+class TestEnumsProducer(unittest.TestCase):
+ def setUp(self):
+ self.maxDiff = None
+ Paths = namedtuple('Prop', 'enums_package')
+ paths = Paths(enums_package='com.smartdevicelink.proxy.rpc.enums')
+ self.producer = EnumsProducer(paths)
+
+ def comparison(self, expected, actual):
+ actual = OrderedDict(sorted(actual.items()))
+ actual['params'] = tuple(sorted(actual['params'], key=lambda x: x.name))
+ expected = OrderedDict(sorted(expected.items()))
+ actual_params = dict(zip(map(lambda k: k.name, actual['params']), actual['params']))
+ expected_params = dict(zip(map(lambda k: k.name, expected['params']), expected['params']))
+ for key, param in actual_params.copy().items():
+ self.assertTrue(key in expected_params)
+ for key, param in expected_params.copy().items():
+ self.assertTrue(key in actual_params)
+ keys = actual_params[key]._asdict()
+ Params = namedtuple('Params', sorted(keys))
+ p = {name: getattr(param, name) for name in keys.keys()}
+ expected_params[key] = Params(**p)
+ expected['params'] = tuple(sorted(expected_params.values(), key=lambda x: x.name))
+
+ self.assertDictEqual(expected, actual)
+
+ def test_deprecated(self):
+ item = Enum(name='TestDeprecated', deprecated=True, elements={
+ 'PRIMARY_WIDGET': EnumElement(name='PRIMARY_WIDGET', internal_name='PRIMARY_WIDGET', value=1,
+ deprecated=True)
+ })
+ expected = {
+ 'kind': 'custom',
+ 'return_type': 'int',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'TestDeprecated',
+ 'imports': {'java.util.EnumSet'},
+ 'params': (
+ self.producer.params(name='PRIMARY_WIDGET', origin='PRIMARY_WIDGET', deprecated=True,
+ internal=1, description=None, since=None, value=None),),
+ 'since': None,
+ 'deprecated': True
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_Language(self):
+ item = Enum(name='Language', elements={
+ 'EN-US': EnumElement(name='EN-US', internal_name='EN-US')
+ })
+ expected = {
+ 'kind': 'custom',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'Language',
+ 'params': (
+ self.producer.params(name='EN_US', origin='EN-US', internal='"EN-US"', description=None, since=None,
+ value=None, deprecated=None),),
+ 'since': None,
+ 'deprecated': None,
+ 'imports': {'java.util.EnumSet'}
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_PredefinedWindows(self):
+ elements = OrderedDict()
+ elements['DEFAULT_WINDOW'] = EnumElement(name='DEFAULT_WINDOW', value=0)
+ elements['PRIMARY_WIDGET'] = EnumElement(name='PRIMARY_WIDGET', value=1)
+ item = Enum(name='PredefinedWindows', elements=elements)
+ expected = {
+ 'kind': 'custom',
+ 'return_type': 'int',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'PredefinedWindows',
+ 'imports': {'java.util.EnumSet'},
+ 'params': (self.producer.params(name='DEFAULT_WINDOW', origin='DEFAULT_WINDOW',
+ internal=0, description=None, since=None, value=None,
+ deprecated=None),
+ self.producer.params(name='PRIMARY_WIDGET', origin='PRIMARY_WIDGET',
+ internal=1, description=None, since=None, value=None,
+ deprecated=None)),
+ 'since': None,
+ 'deprecated': None
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_SamplingRate(self):
+ item = Enum(name='SamplingRate', elements={
+ '8KHZ': EnumElement(name='8KHZ', internal_name='SamplingRate_8KHZ')
+ })
+ expected = {
+ 'kind': 'custom',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'SamplingRate',
+ 'imports': {'java.util.EnumSet'},
+ 'params': (
+ self.producer.params(name='_8KHZ', origin='8KHZ', internal='"8KHZ"', description=None, since=None,
+ value=None,
+ deprecated=None),),
+ 'since': None,
+ 'deprecated': None
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_Result(self):
+ elements = OrderedDict()
+ elements['SUCCESS'] = EnumElement(name='SUCCESS', description=['The request succeeded'])
+ elements['VEHICLE_DATA_NOT_AVAILABLE'] = EnumElement(name='VEHICLE_DATA_NOT_AVAILABLE', since='2.0.0')
+ item = Enum(name='Result', elements=elements)
+ expected = {
+ 'kind': 'simple',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'Result',
+ 'params': (
+ self.producer.params(name='SUCCESS', origin='SUCCESS', internal=None,
+ description=['The request succeeded'],
+ since=None, value=None, deprecated=None),
+ self.producer.params(name='VEHICLE_DATA_NOT_AVAILABLE', origin='VEHICLE_DATA_NOT_AVAILABLE',
+ internal=None, description=None, since='2.0.0', value=None, deprecated=None)),
+ 'since': None,
+ 'deprecated': None
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_DisplayType(self):
+ item = Enum(name='DisplayType', deprecated=True, since='5.0.0', elements={
+ 'CID': EnumElement(name='CID', since='3.0.0')
+ })
+ expected = {
+ 'kind': 'simple',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'DisplayType',
+ 'params': (
+ self.producer.params(name='CID', origin='CID', internal=None, description=None,
+ since='3.0.0', value=None, deprecated=None),),
+ 'since': '5.0.0',
+ 'deprecated': True
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_SpeechCapabilities(self):
+ item = Enum(name='SpeechCapabilities', since='1.0.0', elements={
+ 'TEXT': EnumElement(name='TEXT', internal_name='SC_TEXT')
+ })
+ expected = {
+ 'kind': 'simple',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'SpeechCapabilities',
+ 'params': (
+ self.producer.params(name='TEXT', origin='TEXT', description=None,
+ since=None, value=None, deprecated=None, internal=None),),
+ 'since': '1.0.0',
+ 'deprecated': None
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_VrCapabilities1(self):
+ item = Enum(name='VrCapabilities', elements={
+ 'TEXT': EnumElement(name='TEXT', internal_name='VR_TEXT')
+ })
+ expected = {
+ 'kind': 'simple',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'VrCapabilities',
+ 'params': (
+ self.producer.params(name='TEXT', origin='TEXT', description=None,
+ since=None, value=None, deprecated=None, internal=None),),
+ 'since': None,
+ 'deprecated': None
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_ButtonName(self):
+ item = Enum(name='ButtonName', elements={
+ 'OK': EnumElement(name='OK')
+ })
+ expected = {
+ 'kind': 'simple',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'ButtonName',
+ 'params': (
+ self.producer.params(deprecated=None, value=None, description=None,
+ name='OK', origin='OK', since=None, internal=None),),
+ 'since': None,
+ 'deprecated': None
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_Dimension(self):
+ item = Enum(name='Dimension', elements={
+ 'NO_FIX': EnumElement(name='NO_FIX', internal_name='Dimension_NO_FIX'),
+ '2D': EnumElement(name='2D', internal_name='Dimension_2D')
+ })
+ expected = {
+ 'kind': 'custom',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'Dimension',
+ 'params': (
+ self.producer.params(deprecated=None, value=None, description=None,
+ name='NO_FIX', origin='NO_FIX', since=None, internal='"NO_FIX"'),
+ self.producer.params(deprecated=None, value=None, description=None,
+ name='_2D', origin='2D', since=None, internal='"2D"'),),
+ 'since': None,
+ 'deprecated': None,
+ 'imports': {'java.util.EnumSet'}
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_VehicleDataEventStatus(self):
+ item = Enum(name='VehicleDataEventStatus', elements={
+ 'NO_EVENT': EnumElement(name='NO_EVENT', internal_name='VDES_NO_EVENT')
+ })
+ expected = {
+ 'kind': 'simple',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'VehicleDataEventStatus',
+ 'params': (
+ self.producer.params(deprecated=None, value=None, description=None,
+ name='NO_EVENT', origin='NO_EVENT', since=None, internal=None),),
+ 'since': None,
+ 'deprecated': None,
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_SystemCapabilityType(self):
+ elements = OrderedDict()
+ elements['DISPLAYS'] = EnumElement(name='DISPLAYS')
+ elements['NAVIGATION'] = EnumElement(name='NAVIGATION')
+
+ item = Enum(name='SystemCapabilityType', elements=elements)
+ expected = {
+ 'kind': 'simple',
+ 'return_type': 'String',
+ 'package_name': 'com.smartdevicelink.proxy.rpc.enums',
+ 'class_name': 'SystemCapabilityType',
+ 'params': (
+ self.producer.params(deprecated=None, value=None, description=None,
+ name='DISPLAYS', origin='DISPLAYS', since=None, internal=None),
+ self.producer.params(deprecated=None, value=None, description=None,
+ name='NAVIGATION', origin='NAVIGATION', since=None, internal=None)),
+ 'since': None,
+ 'deprecated': None,
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
diff --git a/generator/test/test_functions.py b/generator/test/test_functions.py
new file mode 100644
index 000000000..d6a932141
--- /dev/null
+++ b/generator/test/test_functions.py
@@ -0,0 +1,234 @@
+import re
+import unittest
+from collections import namedtuple, OrderedDict
+
+from model.array import Array
+from model.boolean import Boolean
+from model.enum import Enum
+from model.enum_element import EnumElement
+from model.float import Float
+from model.function import Function
+from model.integer import Integer
+from model.param import Param
+from model.string import String
+from model.struct import Struct
+from transformers.functions_producer import FunctionsProducer
+
+
+class TestFunctionsProducer(unittest.TestCase):
+ def setUp(self):
+ self.maxDiff = None
+ Paths = namedtuple('Prop',
+ 'enums_package structs_package functions_package request_class response_class '
+ 'notification_class')
+ paths = Paths(enums_package='com.smartdevicelink.proxy.rpc.enums',
+ structs_package='com.smartdevicelink.proxy.rpc',
+ functions_package='com.smartdevicelink.proxy.rpc',
+ request_class='com.smartdevicelink.proxy.RPCRequest',
+ response_class='com.smartdevicelink.proxy.RPCResponse',
+ notification_class='com.smartdevicelink.proxy.RPCNotification')
+ self.expected_template = OrderedDict()
+ self.expected_template['package_name'] = 'com.smartdevicelink.proxy.rpc'
+ self.expected_template['since'] = None
+ self.expected_template['deprecated'] = None
+ enum_names = ('FileType', 'Language')
+ struct_names = ('SdlMsgVersion', 'TemplateColorScheme', 'TTSChunk', 'Choice')
+ self.producer = FunctionsProducer(paths, enum_names, struct_names)
+
+ def comparison(self, expected, actual):
+ actual_params = dict(zip(map(lambda k: k.title, actual['params']), actual['params']))
+ for param in expected['params']:
+ for field in self.producer.params._fields:
+ self.assertEqual(getattr(param, field), getattr(actual_params[param.title], field, None))
+ expected_filtered = dict(filter(lambda e: e[0] != 'params', expected.items()))
+ actual_filtered = dict(filter(lambda e: e[0] != 'params', actual.items()))
+
+ self.assertDictEqual(expected_filtered, actual_filtered)
+
+ def test_Version(self):
+ version = self.producer.get_version
+ self.assertIsNotNone(version)
+ self.assertTrue(re.match(r'^\d*\.\d*\.\d*$', version))
+
+ def test_GetVehicleDataResponse(self):
+ params = OrderedDict()
+ params['speed'] = Param(name='speed', param_type=Float(max_value=700.0, min_value=0.0))
+ item = Function(name='GetVehicleData', function_id=None,
+ message_type=EnumElement(name='response'), params=params)
+ expected = self.expected_template.copy()
+ expected['kind'] = 'response'
+ expected['function_id'] = 'GET_VEHICLE_DATA'
+ expected['class_name'] = 'GetVehicleDataResponse'
+ expected['extends_class'] = 'RPCResponse'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID',
+ 'com.smartdevicelink.proxy.RPCResponse',
+ 'com.smartdevicelink.proxy.rpc.enums.Result',
+ 'com.smartdevicelink.util.SdlDataTypeConverter', '',
+ 'java.util.Hashtable']
+ expected['params'] = (self.producer.params(deprecated=None, key='KEY_SPEED', description=None,
+ last='speed', mandatory=True, SuppressWarnings=None,
+ origin='speed', return_type='Float',
+ since=None, title='Speed', param_doc=None, name=None),)
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_RegisterAppInterfaceResponse(self):
+ params = OrderedDict()
+ params['language'] = Param(name='language', param_type=Enum(name='Language'))
+ params['success'] = Param(name='success', param_type=Boolean())
+ item = Function(name='RegisterAppInterface', function_id=None,
+ message_type=EnumElement(name='response'), params=params)
+ expected = self.expected_template.copy()
+ expected['kind'] = 'response'
+ expected['function_id'] = 'REGISTER_APP_INTERFACE'
+ expected['class_name'] = 'RegisterAppInterfaceResponse'
+ expected['extends_class'] = 'RPCResponse'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID', 'com.smartdevicelink.proxy.RPCResponse',
+ 'com.smartdevicelink.proxy.rpc.enums.Language',
+ 'com.smartdevicelink.proxy.rpc.enums.Result', '', 'java.util.Hashtable']
+ expected['params'] = (
+ self.producer.params(deprecated=None, key='KEY_LANGUAGE', last='language', mandatory=True,
+ origin='language', return_type='Language', since=None, title='Language',
+ description=None, SuppressWarnings=None, param_doc=None, name=None),)
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_RegisterAppInterface(self):
+ params = OrderedDict()
+ params['syncMsgVersion'] = Param(name='syncMsgVersion', param_type=Struct(name='SyncMsgVersion'))
+ params['ttsName'] = Param(name='ttsName', param_type=Array(element_type=Struct(name='TTSChunk')))
+ item = Function(name='RegisterAppInterface', function_id=None,
+ message_type=EnumElement(name='request'), params=params)
+ expected = self.expected_template.copy()
+ expected['kind'] = 'request'
+ expected['function_id'] = 'REGISTER_APP_INTERFACE'
+ expected['class_name'] = 'RegisterAppInterface'
+ expected['extends_class'] = 'RPCRequest'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID',
+ 'com.smartdevicelink.proxy.RPCRequest',
+ '', 'java.util.Hashtable', 'java.util.List']
+ expected['params'] = (
+ self.producer.params(deprecated=None, key='KEY_SDL_MSG_VERSION',
+ last='sdlMsgVersion', mandatory=True, SuppressWarnings=None,
+ origin='syncMsgVersion', return_type='SdlMsgVersion',
+ since=None, title='SdlMsgVersion', description=None, param_doc=None, name=None),
+ self.producer.params(SuppressWarnings='unchecked', deprecated=None,
+ key='KEY_TTS_NAME', last='ttsName',
+ mandatory=True, origin='ttsName', description=None,
+ return_type='List<TTSChunk>', since=None, title='TtsName', param_doc=None, name=None))
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_PutFileRequest(self):
+ params = OrderedDict()
+ params['fileType'] = Param(name='fileType',
+ param_type=Enum(name='FileType', description=['Enumeration listing'], elements={
+ 'AUDIO_MP3': EnumElement(name='AUDIO_MP3')
+ }), description=['Selected file type.'])
+ params['syncFileName'] = Param(name='syncFileName', param_type=String(max_length=255, min_length=1))
+
+ item = Function(name='PutFile', function_id=None, description=['\n Used to'],
+ message_type=EnumElement(name='request'), params=params)
+ expected = self.expected_template.copy()
+ expected['kind'] = 'request'
+ expected['function_id'] = 'PUT_FILE'
+ expected['class_name'] = 'PutFile'
+ expected['extends_class'] = 'RPCRequest'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID', 'com.smartdevicelink.proxy.RPCRequest',
+ 'com.smartdevicelink.proxy.rpc.enums.FileType', '', 'java.util.Hashtable']
+ expected['description'] = ['Used to']
+ expected['params'] = (
+ self.producer.params(deprecated=None, description=['Selected file type.'], key='KEY_FILE_TYPE',
+ last='fileType', mandatory=True, origin='fileType', SuppressWarnings=None,
+ return_type='FileType', since=None, title='FileType', param_doc=None, name=None),
+ self.producer.params(deprecated=None, key='KEY_SDL_FILE_NAME', last='sdlFileName', mandatory=True,
+ origin='syncFileName', return_type='String', description=None,
+ since=None, title='SdlFileName', SuppressWarnings=None, param_doc=None, name=None))
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_OnEncodedSyncPDataNotification(self):
+ item = Function(name='OnEncodedSyncPData', function_id=None, description=['\n Callback including \n'],
+ message_type=EnumElement(name='notification'), params={
+ 'URL': Param(name='URL', param_type=String(), description=['\n If '])
+ })
+ expected = self.expected_template.copy()
+ expected['kind'] = 'notification'
+ expected['function_id'] = 'ON_ENCODED_SYNC_PDATA'
+ expected['class_name'] = 'OnEncodedSyncPData'
+ expected['extends_class'] = 'RPCNotification'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID',
+ 'com.smartdevicelink.proxy.RPCNotification', '', 'java.util.Hashtable']
+ expected['description'] = ['Callback including']
+ expected['params'] = (
+ self.producer.params(deprecated=None, description=['If'], key='KEY_URL', last='URL', title='URL',
+ SuppressWarnings=None, mandatory=True, origin='URL', return_type='String',
+ since=None, param_doc=None, name=None),)
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_DeleteCommand(self):
+ item = Function(name='DeleteCommand', function_id=None,
+ message_type=EnumElement(name='request'), params={
+ 'cmdID': Param(name='cmdID', param_type=Integer(max_value=2000000000, min_value=0))
+ })
+ expected = self.expected_template.copy()
+ expected['kind'] = 'request'
+ expected['function_id'] = 'DELETE_COMMAND'
+ expected['class_name'] = 'DeleteCommand'
+ expected['extends_class'] = 'RPCRequest'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID', 'com.smartdevicelink.proxy.RPCRequest',
+ '', 'java.util.Hashtable']
+ expected['params'] = (
+ self.producer.params(deprecated=None, key='KEY_CMD_ID', last='cmdID', mandatory=True, origin='cmdID',
+ return_type='Integer', since=None, title='CmdID', description=None,
+ SuppressWarnings=None, param_doc=None, name=None),)
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_Alert(self):
+ item = Function(name='Alert', function_id=None,
+ message_type=EnumElement(name='request'), params={
+ 'alertText2': Param(name='alertText2', param_type=String(max_length=500))
+ })
+ expected = self.expected_template.copy()
+ expected['kind'] = 'request'
+ expected['function_id'] = 'ALERT'
+ expected['class_name'] = 'Alert'
+ expected['extends_class'] = 'RPCRequest'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID',
+ 'com.smartdevicelink.proxy.RPCRequest', '', 'java.util.Hashtable']
+ expected['params'] = (
+ self.producer.params(deprecated=None, key='KEY_ALERT_TEXT_2', last='alertText2',
+ mandatory=True, origin='alertText2', param_doc=None,
+ return_type='String', since=None, title='AlertText2', description=None,
+ SuppressWarnings=None, name=None),)
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_ReleaseInteriorVehicleDataModule(self):
+ item = Function(name='ReleaseInteriorVehicleDataModule', function_id=None,
+ message_type=EnumElement(name='request'), params={
+ 'moduleType': Param(name='moduleType', param_type=Enum(name='ModuleType'))
+ })
+ expected = self.expected_template.copy()
+ expected['kind'] = 'request'
+ expected['function_id'] = 'RELEASE_INTERIOR_VEHICLE_DATA_MODULE'
+ expected['class_name'] = 'ReleaseInteriorVehicleDataModule'
+ expected['extends_class'] = 'RPCRequest'
+ expected['imports'] = ['android.support.annotation.NonNull', '',
+ 'com.smartdevicelink.protocol.enums.FunctionID',
+ 'com.smartdevicelink.proxy.RPCRequest', '', 'java.util.Hashtable']
+ expected['params'] = (
+ self.producer.params(deprecated=None, key='KEY_MODULE_TYPE', last='moduleType', mandatory=True,
+ origin='moduleType', return_type='ModuleType', since=None, title='ModuleType',
+ description=None, SuppressWarnings=None, param_doc=None, name=None),)
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
diff --git a/generator/test/test_structs.py b/generator/test/test_structs.py
new file mode 100644
index 000000000..b8985cba8
--- /dev/null
+++ b/generator/test/test_structs.py
@@ -0,0 +1,160 @@
+import unittest
+from collections import namedtuple, OrderedDict
+
+from model.array import Array
+from model.enum import Enum
+from model.float import Float
+from model.param import Param
+from model.string import String
+from model.struct import Struct
+from transformers.structs_producer import StructsProducer
+
+
+class TestStructsProducer(unittest.TestCase):
+ def setUp(self):
+ self.maxDiff = None
+ Paths = namedtuple('Prop', 'enums_package structs_package struct_class')
+ paths = Paths(enums_package='com.smartdevicelink.proxy.rpc.enums',
+ structs_package='com.smartdevicelink.proxy.rpc',
+ struct_class='com.smartdevicelink.proxy.RPCStruct')
+ self.producer = StructsProducer(paths, ['SamplingRate'], ('Image',))
+
+ def comparison(self, expected, actual):
+ actual_params = dict(zip(map(lambda k: k.origin, actual['params']), actual['params']))
+ for param in expected['params']:
+ for field in self.producer.params._fields:
+ self.assertEqual(getattr(param, field), getattr(actual_params[param.origin], field, None))
+ expected_filtered = dict(filter(lambda e: e[0] != 'params', expected.items()))
+ actual_filtered = dict(filter(lambda e: e[0] != 'params', actual.items()))
+
+ self.assertDictEqual(expected_filtered, actual_filtered)
+
+ def test_AudioPassThruCapabilities(self):
+ members = OrderedDict()
+ members['range'] = Param(name='range', param_type=Float(max_value=10000.0, min_value=0.0))
+ members['samplingRate'] = Param(name='samplingRate', param_type=Enum(name='SamplingRate'))
+ item = Struct(name='AudioPassThruCapabilities', members=members)
+ expected = {
+ 'class_name': 'AudioPassThruCapabilities',
+ 'extends_class': 'RPCStruct',
+ 'package_name': 'com.smartdevicelink.proxy.rpc',
+ 'imports': ['android.support.annotation.NonNull', '', 'com.smartdevicelink.proxy.RPCStruct',
+ 'com.smartdevicelink.proxy.rpc.enums.SamplingRate',
+ 'com.smartdevicelink.util.SdlDataTypeConverter', '', 'java.util.Hashtable'],
+ 'deprecated': None,
+ 'since': None,
+ 'params': (self.producer.params(deprecated=None, key='KEY_RANGE',
+ last='range', mandatory=True,
+ origin='range', return_type='Float',
+ since=None, title='Range', description=None, param_doc=None, name=None),
+ self.producer.params(deprecated=None, key='KEY_SAMPLING_RATE',
+ last='samplingRate', mandatory=True,
+ origin='samplingRate', return_type='SamplingRate', name=None,
+ since=None, title='SamplingRate', description=None, param_doc=None)),
+ }
+ actual = self.producer.transform(item)
+
+ self.comparison(expected, actual)
+
+ def test_CloudAppProperties(self):
+ item = Struct(name='CloudAppProperties', members={
+ 'nicknames': Param(name='nicknames',
+ param_type=Array(element_type=String(max_length=100, min_length=0), max_size=100,
+ min_size=0)),
+ })
+ expected = {
+ 'class_name': 'CloudAppProperties',
+ 'extends_class': 'RPCStruct',
+ 'package_name': 'com.smartdevicelink.proxy.rpc',
+ 'imports': ['android.support.annotation.NonNull', '', 'com.smartdevicelink.proxy.RPCStruct', '',
+ 'java.util.Hashtable', 'java.util.List'],
+ 'deprecated': None,
+ 'since': None,
+ 'params': (self.producer.params(deprecated=None, key='KEY_NICKNAMES',
+ last='nicknames', mandatory=True,
+ origin='nicknames', return_type='List<String>', name=None,
+ since=None, title='Nicknames', description=None, param_doc=None),),
+ }
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_SoftButton(self):
+ item = Struct(name='SoftButton', members={
+ 'image': Param(name='image', param_type=Struct(name='Image'), description=['Optional image']),
+ }, description=['\n Describes different audio type configurations for PerformAudioPassThru.\n '])
+ expected = {
+ 'package_name': 'com.smartdevicelink.proxy.rpc',
+ 'imports': ['android.support.annotation.NonNull', '', 'com.smartdevicelink.proxy.RPCStruct', '',
+ 'java.util.Hashtable'],
+ 'class_name': 'SoftButton',
+ 'extends_class': 'RPCStruct',
+ 'since': None,
+ 'deprecated': None,
+ 'description': ['Describes different audio type configurations for '
+ 'PerformAudioPassThru.'],
+ 'params': (self.producer.params(deprecated=None, description=['Optional image'], key='KEY_IMAGE',
+ last='image', mandatory=True, origin='image', return_type='Image',
+ since=None, title='Image', param_doc=None, name=None),)
+ }
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_OASISAddress(self):
+ item = Struct(name='OASISAddress', members={
+ 'countryName': Param(name='countryName', param_type=String(max_length=200))
+ })
+ expected = {
+ 'package_name': 'com.smartdevicelink.proxy.rpc',
+ 'imports': ['android.support.annotation.NonNull', '', 'com.smartdevicelink.proxy.RPCStruct', '',
+ 'java.util.Hashtable'],
+ 'class_name': 'OASISAddress',
+ 'extends_class': 'RPCStruct',
+ 'since': None,
+ 'deprecated': None,
+ 'params': (
+ self.producer.params(deprecated=None, key='KEY_COUNTRY_NAME', last='countryName', mandatory=True,
+ origin='countryName', return_type='String', since=None, title='CountryName',
+ description=None, param_doc=None, name=None),)
+ }
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_LocationDetails(self):
+ item = Struct(name='LocationDetails', members={
+ 'searchAddress': Param(name='searchAddress', param_type=Struct(name='OASISAddress'))
+ })
+ expected = {
+ 'package_name': 'com.smartdevicelink.proxy.rpc',
+ 'imports': ['android.support.annotation.NonNull', '', 'com.smartdevicelink.proxy.RPCStruct', '',
+ 'java.util.Hashtable'],
+ 'class_name': 'LocationDetails',
+ 'extends_class': 'RPCStruct',
+ 'since': None,
+ 'deprecated': None,
+ 'params': (
+ self.producer.params(deprecated=None, key='KEY_SEARCH_ADDRESS', last='searchAddress', mandatory=True,
+ origin='searchAddress', return_type='OASISAddress', since=None,
+ title='SearchAddress', description=None, param_doc=None, name=None),)
+ }
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
+
+ def test_SingleTireStatus(self):
+ item = Struct(name='SingleTireStatus', members={
+ 'tpms': Param(name='tpms', param_type=Enum(name='TPMS'))
+ })
+ expected = {
+ 'package_name': 'com.smartdevicelink.proxy.rpc',
+ 'imports': ['android.support.annotation.NonNull', '', 'com.smartdevicelink.proxy.RPCStruct', '',
+ 'java.util.Hashtable'],
+ 'class_name': 'SingleTireStatus',
+ 'extends_class': 'RPCStruct',
+ 'since': None,
+ 'deprecated': None,
+ 'params': (
+ self.producer.params(deprecated=None, key='KEY_TPMS', last='tpms', mandatory=True, origin='tpms',
+ return_type='TPMS', since=None, title='Tpms', description=None, param_doc=None,
+ name=None),)
+ }
+ actual = self.producer.transform(item)
+ self.comparison(expected, actual)
diff --git a/generator/transformers/__init__.py b/generator/transformers/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/generator/transformers/__init__.py
diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py
new file mode 100644
index 000000000..eaac41a6b
--- /dev/null
+++ b/generator/transformers/common_producer.py
@@ -0,0 +1,120 @@
+"""
+Common transformation
+"""
+
+import logging
+import re
+from abc import ABC
+from collections import namedtuple
+
+from model.array import Array
+from model.enum import Enum
+from model.struct import Struct
+
+
+class InterfaceProducerCommon(ABC):
+ """
+ Common transformation
+ """
+
+ version = '1.0.0'
+
+ def __init__(self, container_name, enums_package, structs_package, package_name,
+ enum_names=(), struct_names=(), key_words=()):
+ self.logger = logging.getLogger('Generator.InterfaceProducerCommon')
+ self.container_name = container_name
+ self.enum_names = enum_names
+ self.struct_names = struct_names
+ self.key_words = key_words
+ self.enums_package = enums_package
+ self.structs_package = structs_package
+ self.package_name = package_name
+ self._params = namedtuple('params', 'deprecated description key last mandatory origin return_type since title '
+ 'param_doc name')
+
+ @property
+ def get_version(self):
+ return self.version
+
+ @property
+ def params(self):
+ """
+ :return: namedtuple params(name='', origin='')
+ """
+ return self._params
+
+ @staticmethod
+ def key(param: str):
+ """
+ Convert param string to uppercase and inserting underscores
+ :param param: camel case string
+ :return: string in uppercase with underscores
+ """
+ if re.match(r'^[A-Z_\d]+$', param):
+ return param
+ else:
+ return re.sub(r'([a-z]|[A-Z]{2,})([A-Z]|\d$)', r'\1_\2', param).upper()
+
+ @staticmethod
+ def ending_cutter(n: str):
+ """
+ If string not contains only uppercase letters and end with 'ID' deleting 'ID' from end of string
+ :param n: string to evaluate and deleting 'ID' from end of string
+ :return: if match cut string else original string
+ """
+ if re.match(r'^\w+[a-z]+([A-Z]{2,})?ID$', n):
+ return n[:-2]
+ else:
+ return n
+
+ @staticmethod
+ def extract_description(d):
+ """
+ Evaluate, align and delete @TODO
+ :param d: list with description
+ :return: evaluated string
+ """
+ return re.sub(r'(\s{2,}|\n|\[@TODO.+)', ' ', ''.join(d)).strip() if d else ''
+
+ @staticmethod
+ def replace_sync(name):
+ """
+ :param name: string with item name
+ :return: string with replaced 'sync' to 'Sdl'
+ """
+ if name:
+ return re.sub(r'^([sS])ync(.+)$', r'\1dl\2', name)
+ return name
+
+ def replace_keywords(self, name: str = '') -> str:
+ """
+ if :param name in self.key_words, :return: name += 'Param'
+ :param name: string with item name
+ """
+ if any(map(lambda k: re.search(r'^(get|set|key_)?{}$'.format(name.casefold()), k), self.key_words)):
+ origin = name
+ if name.isupper():
+ name += '_PARAM'
+ else:
+ name += 'Param'
+ self.logger.debug('Replacing %s with %s', origin, name)
+ return self.replace_sync(name)
+
+ def extract_type(self, param):
+ """
+ Evaluate and extract type
+ :param param: sub-element Param of element from initial Model
+ :return: string with sub-element type
+ """
+
+ def evaluate(t1):
+ if isinstance(t1, Struct) or isinstance(t1, Enum):
+ name = t1.name
+ return name
+ else:
+ return type(t1).__name__
+
+ if isinstance(param.param_type, Array):
+ return 'List<{}>'.format(evaluate(param.param_type.element_type))
+ else:
+ return evaluate(param.param_type)
diff --git a/generator/transformers/enums_producer.py b/generator/transformers/enums_producer.py
new file mode 100644
index 000000000..a04046441
--- /dev/null
+++ b/generator/transformers/enums_producer.py
@@ -0,0 +1,87 @@
+"""
+Enums transformation
+"""
+
+import logging
+import textwrap
+from collections import namedtuple, OrderedDict
+
+from model.enum import Enum
+from model.enum_element import EnumElement
+from transformers.common_producer import InterfaceProducerCommon
+
+
+class EnumsProducer(InterfaceProducerCommon):
+ """
+ Enums transformation
+ """
+
+ def __init__(self, paths, key_words):
+ super(EnumsProducer, self).__init__(
+ container_name='elements',
+ enums_package=None,
+ structs_package=None,
+ package_name=paths.enums_package,
+ key_words=key_words)
+ self.logger = logging.getLogger('EnumsProducer')
+ self._params = namedtuple('params', 'origin name internal description since value deprecated')
+
+ @staticmethod
+ def converted(name):
+ if name[0].isdigit():
+ name = '_' + name
+ if '-' in name:
+ name = name.replace('-', '_')
+ return name
+
+ def transform(self, item: Enum) -> dict:
+ """
+ Override
+ :param item: particular element from initial Model
+ :return: dictionary to be applied to jinja2 template
+ """
+ imports = set()
+ params = OrderedDict()
+ if any(map(lambda l: l.name != self.converted(l.name), getattr(item, self.container_name).values())):
+ kind = 'custom'
+ imports.add('java.util.EnumSet')
+ else:
+ kind = 'simple'
+
+ for param in getattr(item, self.container_name).values():
+ p = self.extract_param(param, kind)
+ params[p.name] = p
+
+ render = OrderedDict()
+ render['kind'] = kind
+ render['package_name'] = self.package_name
+ render['class_name'] = item.name[:1].upper() + item.name[1:]
+ render['params'] = params
+ render['since'] = item.since
+ render['deprecated'] = item.deprecated
+
+ description = self.extract_description(item.description)
+ if description:
+ render['description'] = description
+ if imports:
+ render['imports'] = imports
+
+ render['params'] = tuple(render['params'].values())
+ if 'description' in render and isinstance(render['description'], str):
+ render['description'] = textwrap.wrap(render['description'], 90)
+
+ return render
+
+ def extract_param(self, param: EnumElement, kind):
+ d = {'origin': param.name, 'name': self.key(self.converted(param.name))}
+ if kind == 'custom':
+ d['internal'] = '"{}"'.format(param.name)
+
+ if getattr(param, 'since', None):
+ d['since'] = param.since
+ if getattr(param, 'deprecated', None):
+ d['deprecated'] = param.deprecated
+ if getattr(param, 'description', None):
+ d['description'] = textwrap.wrap(self.extract_description(param.description), 90)
+ Params = namedtuple('Params', sorted(d))
+ return Params(**d)
diff --git a/generator/transformers/functions_producer.py b/generator/transformers/functions_producer.py
new file mode 100644
index 000000000..5f692a4d0
--- /dev/null
+++ b/generator/transformers/functions_producer.py
@@ -0,0 +1,149 @@
+"""
+Functions transformation
+"""
+
+import logging
+import textwrap
+from collections import namedtuple, OrderedDict
+
+from model.function import Function
+from model.param import Param
+from transformers.common_producer import InterfaceProducerCommon
+
+
+class FunctionsProducer(InterfaceProducerCommon):
+ """
+ Functions transformation
+ """
+
+ def __init__(self, paths, enum_names, struct_names, key_words):
+ super(FunctionsProducer, self).__init__(
+ container_name='params',
+ enums_package=paths.enums_package,
+ structs_package=paths.structs_package,
+ enum_names=enum_names,
+ struct_names=struct_names,
+ package_name=paths.functions_package,
+ key_words=key_words)
+ self.logger = logging.getLogger('FunctionsProducer')
+ self.request_class = paths.request_class
+ self.response_class = paths.response_class
+ self.notification_class = paths.notification_class
+ self._params = namedtuple('Params', self._params._fields + ('SuppressWarnings',))
+
+ def transform(self, item: Function) -> dict:
+ """
+ Override
+ :param item: particular element from initial Model
+ :return: dictionary to be applied to jinja2 template
+ """
+ class_name = self.replace_keywords(item.name[:1].upper() + item.name[1:])
+
+ imports = {'java.util.Hashtable', 'com.smartdevicelink.protocol.enums.FunctionID'}
+ extends_class = None
+ if item.message_type.name == 'response':
+ extends_class = self.response_class
+ if not class_name.endswith("Response"):
+ class_name += 'Response'
+ imports.add('com.smartdevicelink.proxy.rpc.enums.Result')
+ imports.add('android.support.annotation.NonNull')
+ elif item.message_type.name == 'request':
+ extends_class = self.request_class
+ elif item.message_type.name == 'notification':
+ extends_class = self.notification_class
+ if extends_class:
+ imports.add(extends_class)
+ extends_class = extends_class.rpartition('.')[-1]
+
+ params = OrderedDict()
+
+ for param in getattr(item, self.container_name).values():
+ param.origin = param.name
+ param.name = self.replace_keywords(param.name)
+ i, p = self.extract_param(param)
+ imports.update(i)
+ params.update({param.name: p})
+
+ render = OrderedDict()
+ render['kind'] = item.message_type.name
+ render['package_name'] = self.package_name
+ render['imports'] = self.sort_imports(imports)
+ render['function_id'] = self.key(self.replace_keywords(item.name))
+ render['class_name'] = class_name
+ render['extends_class'] = extends_class
+ render['since'] = item.since
+ render['deprecated'] = item.deprecated
+
+ description = self.extract_description(item.description)
+ if description:
+ render['description'] = description
+
+ if params:
+ render['params'] = tuple(params.values())
+ if 'description' in render and isinstance(render['description'], str):
+ render['description'] = textwrap.wrap(render['description'], 90)
+
+ return render
+
+ def sort_imports(self, imports: set):
+ sorted_imports = []
+ if 'android.support.annotation.NonNull' in imports:
+ sorted_imports.append('android.support.annotation.NonNull')
+ imports.remove('android.support.annotation.NonNull')
+ sorted_imports.append('')
+ sorted_imports.append('com.smartdevicelink.protocol.enums.FunctionID')
+ imports.remove('com.smartdevicelink.protocol.enums.FunctionID')
+ tmp = []
+ for i in imports:
+ if i.rpartition('.')[0] != self.package_name and not i.startswith('java'):
+ tmp.append(i)
+ if tmp:
+ tmp.sort()
+ sorted_imports.extend(tmp)
+ if 'java.util.Hashtable' in imports or 'java.util.List' in imports:
+ sorted_imports.append('')
+ for i in ('java.util.Hashtable', 'java.util.List', 'java.util.ArrayList', 'java.util.Collections',
+ 'java.util.zip.CRC32'):
+ if i in imports:
+ sorted_imports.append(i)
+ return sorted_imports
+
+ def extract_param(self, param: Param):
+ imports = set()
+ p = OrderedDict()
+ p['title'] = param.name[:1].upper() + param.name[1:]
+ p['key'] = 'KEY_' + self.key(param.name)
+ p['mandatory'] = param.is_mandatory
+ p['last'] = param.name
+ if param.since:
+ p['since'] = param.since
+ p['deprecated'] = param.deprecated
+ p['origin'] = param.origin
+ d = self.extract_description(param.description)
+ if param.name == 'success':
+ d = 'whether the request is successfully processed'
+ if param.name == 'resultCode':
+ d = 'additional information about a response returning a failed outcome'
+ if d:
+ p['description'] = textwrap.wrap(d, 90)
+ t = self.extract_type(param)
+ if t == 'EnumSubset' and param.name == 'resultCode':
+ t = 'Result'
+ tr = t
+ if t.startswith('List'):
+ imports.add('java.util.List')
+ p['SuppressWarnings'] = 'unchecked'
+ tr = t.replace('List<', '').rstrip('>')
+ if t.startswith('Float'):
+ imports.add('com.smartdevicelink.util.SdlDataTypeConverter')
+ p['return_type'] = self.replace_keywords(t)
+
+ if tr in self.enum_names:
+ imports.add('{}.{}'.format(self.enums_package, tr))
+ if tr in self.struct_names:
+ imports.add('{}.{}'.format(self.structs_package, tr))
+ if param.is_mandatory:
+ imports.add('android.support.annotation.NonNull')
+
+ Params = namedtuple('Params', sorted(p))
+ return imports, Params(**p)
diff --git a/generator/transformers/generate_error.py b/generator/transformers/generate_error.py
new file mode 100644
index 000000000..3fe1a75ed
--- /dev/null
+++ b/generator/transformers/generate_error.py
@@ -0,0 +1,12 @@
+"""
+Generate error.
+"""
+
+
+class GenerateError(Exception):
+ """Generate error.
+
+ This exception is raised when generator is unable to create
+ output from given model.
+
+ """
diff --git a/generator/transformers/structs_producer.py b/generator/transformers/structs_producer.py
new file mode 100644
index 000000000..ea9b5d5f5
--- /dev/null
+++ b/generator/transformers/structs_producer.py
@@ -0,0 +1,129 @@
+"""
+Structs transformation
+"""
+
+import logging
+import textwrap
+from collections import namedtuple, OrderedDict
+
+from model.param import Param
+from model.struct import Struct
+from transformers.common_producer import InterfaceProducerCommon
+
+
+class StructsProducer(InterfaceProducerCommon):
+ """
+ Structs transformation
+ """
+
+ def __init__(self, paths, enum_names, struct_names, key_words):
+ super(StructsProducer, self).__init__(
+ container_name='members',
+ enums_package=paths.enums_package,
+ structs_package=paths.structs_package,
+ enum_names=enum_names,
+ struct_names=struct_names,
+ package_name=paths.structs_package,
+ key_words=key_words)
+ self.logger = logging.getLogger('StructsProducer')
+ self.struct_class = paths.struct_class
+
+ def transform(self, item: Struct) -> dict:
+ """
+ Override
+ :param item: particular element from initial Model
+ :return: dictionary to be applied to jinja2 template
+ """
+ class_name = self.replace_keywords(item.name[:1].upper() + item.name[1:])
+
+ imports = {'java.util.Hashtable'}
+ extends_class = self.struct_class
+
+ imports.add(extends_class)
+ extends_class = extends_class.rpartition('.')[-1]
+
+ params = OrderedDict()
+
+ for param in getattr(item, self.container_name).values():
+ param.name = self.replace_keywords(param.name)
+ i, p = self.extract_param(param)
+ imports.update(i)
+ params[param.name] = p
+
+ render = OrderedDict()
+ render['class_name'] = class_name
+ render['extends_class'] = extends_class
+ render['package_name'] = self.package_name
+ render['imports'] = imports
+ render['deprecated'] = item.deprecated
+ render['since'] = item.since
+
+ description = self.extract_description(item.description)
+ if description:
+ render['description'] = description
+ if params:
+ render['params'] = params
+
+ if 'imports' in render:
+ render['imports'] = self.sort_imports(render['imports'])
+ if params:
+ render['params'] = tuple(render['params'].values())
+ if 'description' in render and isinstance(render['description'], str):
+ render['description'] = textwrap.wrap(render['description'], 90)
+
+ return render
+
+ def sort_imports(self, imports: set):
+ sorted_imports = []
+ if 'android.support.annotation.NonNull' in imports:
+ sorted_imports.append('android.support.annotation.NonNull')
+ imports.remove('android.support.annotation.NonNull')
+ sorted_imports.append('')
+ tmp = []
+ for i in imports:
+ if i.rpartition('.')[0] != self.package_name and not i.startswith('java'):
+ tmp.append(i)
+ if tmp:
+ tmp.sort()
+ sorted_imports.extend(tmp)
+ if 'java.util.Hashtable' in imports or 'java.util.List' in imports:
+ sorted_imports.append('')
+ for i in ('java.util.Hashtable', 'java.util.List', 'java.util.ArrayList', 'java.util.Collections'):
+ if i in imports:
+ sorted_imports.append(i)
+ return sorted_imports
+
+ def extract_param(self, param: Param):
+ imports = set()
+ p = OrderedDict()
+ p['title'] = param.name[:1].upper() + param.name[1:]
+ p['key'] = 'KEY_' + self.key(param.name)
+ p['mandatory'] = param.is_mandatory
+ p['last'] = param.name
+ if param.since:
+ p['since'] = param.since
+ p['deprecated'] = param.deprecated
+ p['origin'] = param.name
+
+ d = self.extract_description(param.description)
+ if d:
+ p['description'] = textwrap.wrap(d, 90)
+ t = self.extract_type(param)
+ tr = t
+ if t.startswith('List'):
+ imports.add('java.util.List')
+ p['SuppressWarnings'] = 'unchecked'
+ tr = t.replace('List<', '').rstrip('>')
+ if t.startswith('Float'):
+ imports.add('com.smartdevicelink.util.SdlDataTypeConverter')
+ p['return_type'] = self.replace_sync(t)
+
+ if tr in self.enum_names:
+ imports.add('{}.{}'.format(self.enums_package, tr))
+ if tr in self.struct_names:
+ imports.add('{}.{}'.format(self.structs_package, tr))
+ if param.is_mandatory:
+ imports.add('android.support.annotation.NonNull')
+
+ Params = namedtuple('Params', sorted(p))
+ return imports, Params(**p)