diff --git a/code-gen-projects/schema/sequence.isl b/code-gen-projects/schema/sequence.isl new file mode 100644 index 0000000..8331aff --- /dev/null +++ b/code-gen-projects/schema/sequence.isl @@ -0,0 +1,5 @@ +type::{ + name: sequence, + type: list, + element: string +} diff --git a/src/bin/ion/commands/generate/generator.rs b/src/bin/ion/commands/generate/generator.rs index ad14a97..54574d8 100644 --- a/src/bin/ion/commands/generate/generator.rs +++ b/src/bin/ion/commands/generate/generator.rs @@ -1,7 +1,7 @@ -use crate::commands::generate::context::CodeGenContext; +use crate::commands::generate::context::{CodeGenContext, SequenceType}; use crate::commands::generate::model::{ AbstractDataType, DataModelNode, FieldPresence, FieldReference, FullyQualifiedTypeReference, - ScalarBuilder, StructureBuilder, WrappedScalarBuilder, + ScalarBuilder, SequenceBuilder, StructureBuilder, WrappedScalarBuilder, WrappedSequenceBuilder, }; use crate::commands::generate::result::{ invalid_abstract_data_type_error, invalid_abstract_data_type_raw_error, CodeGenResult, @@ -298,7 +298,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { // pop out the nested type name from the fully qualified namespace as it has been already added to the type store and to nested types self.current_type_fully_qualified_name.pop(); data_model_node - .fully_qualified_type_ref() + .fully_qualified_type_ref::() .ok_or(invalid_abstract_data_type_raw_error( "Can not determine fully qualified name for the data model", )) @@ -345,12 +345,36 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { let constraints = isl_type.constraints(); - // Initialize `AbstractDataType` according to the first constraint in the list of constraints + // Initialize `AbstractDataType` according to the list of constraints + // Below are some checks to verify which AbstractDatatype variant should be constructed based on given ISL constraints: + // * If given list of constraints has any `fields` constraint then `AbstractDataType::Structure` needs to be constructed. + // * Since currently, code generation doesn't support open ended types having `type: struct` alone is not enough for constructing + // `AbstractDataType::Structure`. + // * If given list of constraints has any `element` constraint then `AbstractDataType::Sequence` needs to be constructed. + // * Since currently, code generation doesn't support open ended types having `type: list` or `type: sexp` alone is not enough for constructing + // `AbstractDataType::Sequence`. + // * The sequence type for `Sequence` will be stored based on `type` constraint with either `list` or `sexp`. + // * If given list of constraints has any `type` constraint except `type: list`, `type: struct` and `type: sexp`, then `AbstractDataType::Scalar` needs to be constructed. + // * The `base_type` for `Scalar` will be stored based on `type` constraint. + // * All the other constraints except the above ones are not yet supported by code generator. let abstract_data_type = if constraints .iter() .any(|it| matches!(it.constraint(), IslConstraintValue::Fields(_, _))) { self.build_structure_from_constraints(constraints, code_gen_context, isl_type)? + } else if constraints + .iter() + .any(|it| matches!(it.constraint(), IslConstraintValue::Element(_, _))) + { + if is_nested_type { + self.build_sequence_from_constraints(constraints, code_gen_context, isl_type)? + } else { + self.build_wrapped_sequence_from_constraints( + constraints, + code_gen_context, + isl_type, + )? + } } else if Self::contains_scalar_constraints(constraints) { if is_nested_type { self.build_scalar_from_constraints(constraints, code_gen_context, isl_type)? @@ -358,7 +382,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { self.build_wrapped_scalar_from_constraints(constraints, code_gen_context, isl_type)? } } else { - todo!("Support for sequences, maps, scalars, and tuples not implemented yet.") + todo!("Support for maps and tuples not implemented yet.") }; let data_model_node = DataModelNode { @@ -370,7 +394,7 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { // TODO: verify the `occurs` value within a field, by default the fields are optional. // add current data model node into the data model store self.data_model_store.insert( - abstract_data_type.fully_qualified_type_ref().ok_or( + abstract_data_type.fully_qualified_type_ref::().ok_or( invalid_abstract_data_type_raw_error( "Can not determine fully qualified name for the data model", ), @@ -463,6 +487,27 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { name } + /// Returns error if duplicate constraints are present based `found_constraint` flag + fn handle_duplicate_constraint( + &mut self, + found_constraint: bool, + constraint_name: &str, + isl_type: &IslTypeRef, + code_gen_context: &mut CodeGenContext, + ) -> CodeGenResult { + if found_constraint { + return invalid_abstract_data_type_error(format!( + "Multiple `{}` constraints in the type definitions are not supported in code generation as it can lead to conflicting types.", constraint_name + )); + } + + self.fully_qualified_type_ref_name(isl_type, code_gen_context)? + .ok_or(invalid_abstract_data_type_raw_error(format!( + "Could not determine `FullQualifiedTypeReference` for type {:?}", + isl_type + ))) + } + /// Builds `AbstractDataType::Structure` from the given constraints. /// e.g. for a given type definition as below: /// ``` @@ -577,20 +622,19 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { for constraint in constraints { match constraint.constraint() { IslConstraintValue::Type(isl_type) => { - if found_base_type { - return invalid_abstract_data_type_error("Multiple `type` constraints in the type definitions are not supported in code generation as it can lead to conflicting types."); - } - let type_name = self - .fully_qualified_type_ref_name(isl_type, code_gen_context)? - .ok_or(invalid_abstract_data_type_raw_error(format!( - "Could not determine `FullQualifiedTypeReference` for type {:?}", - isl_type - )))?; - - // by default fields aren't closed + let type_name = self.handle_duplicate_constraint( + found_base_type, + "type", + isl_type, + code_gen_context, + )?; wrapped_scalar_builder.base_type(type_name); found_base_type = true; } + IslConstraintValue::ContainerLength(_) => { + // TODO: add support for container length + // this is currently not supported and is a no-op + } _ => { return invalid_abstract_data_type_error( "Could not determine the abstract data type due to conflicting constraints", @@ -634,15 +678,12 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { for constraint in constraints { match constraint.constraint() { IslConstraintValue::Type(isl_type) => { - if found_base_type { - return invalid_abstract_data_type_error("Multiple `type` constraints in the type definitions are not supported in code generation as it can lead to conflicting types."); - } - let type_name = self - .fully_qualified_type_ref_name(isl_type, code_gen_context)? - .ok_or(invalid_abstract_data_type_raw_error( - "Could not determine `FullQualifiedTypeReference` for `struct`, `list` or `sexp` as open ended container types aren't supported." - ))?; - + let type_name = self.handle_duplicate_constraint( + found_base_type, + "type", + isl_type, + code_gen_context, + )?; scalar_builder.base_type(type_name); found_base_type = true; } @@ -656,6 +697,139 @@ impl<'a, L: Language + 'static> CodeGenerator<'a, L> { Ok(AbstractDataType::Scalar(scalar_builder.build()?)) } + + /// Builds `AbstractDataType::WrappedSequence` from the given constraints. + /// ``` + /// type::{ + /// name: foo, + /// type: list, + /// element: string, + /// } + /// ``` + /// This method builds `AbstractDataType`as following: + /// ``` + /// AbstractDataType::WrappedSequence( + /// WrappedSequence { + /// name: vec!["org", "example", "Foo"] // assuming the namespace here is `org.example` + /// element_type: FullyQualifiedTypeReference { type_name: vec!["String"], parameters: vec![] } // Represents the element type for the list + /// sequence_type: SequenceType::List, // Represents list type for the given sequence + /// doc_comment: None // There is no doc comment defined in above ISL type def + /// source: IslType { .. } // Represents the `IslType` that is getting converted to `AbstractDataType` + /// } + /// ) + /// ``` + fn build_wrapped_sequence_from_constraints( + &mut self, + constraints: &[IslConstraint], + code_gen_context: &mut CodeGenContext, + parent_isl_type: &IslType, + ) -> CodeGenResult { + let mut wrapped_sequence_builder = WrappedSequenceBuilder::default(); + wrapped_sequence_builder + .name(self.current_type_fully_qualified_name.to_owned()) + .source(parent_isl_type.to_owned()); + let mut found_base_type = false; + let mut found_element_constraint = false; + for constraint in constraints { + match constraint.constraint() { + IslConstraintValue::Element(isl_type_ref, _) => { + let type_name = self.handle_duplicate_constraint( + found_element_constraint, + "type", + isl_type_ref, + code_gen_context, + )?; + + wrapped_sequence_builder.element_type(type_name); + found_element_constraint = true; + } + IslConstraintValue::Type(isl_type_ref) => { + if found_base_type { + return invalid_abstract_data_type_error( + "Multiple `type` constraints in the type definitions are not supported in code generation as it can lead to conflicting types." + ); + } + if isl_type_ref.name() == "sexp" { + wrapped_sequence_builder.sequence_type(SequenceType::SExp); + } else if isl_type_ref.name() == "list" { + wrapped_sequence_builder.sequence_type(SequenceType::List); + } + found_base_type = true; + } + IslConstraintValue::ContainerLength(_) => { + // TODO: add support for container length + // this is currently not supported and is a no-op + } + _ => { + return invalid_abstract_data_type_error( + "Could not determine the abstract data type due to conflicting constraints", + ); + } + } + } + Ok(AbstractDataType::WrappedSequence( + wrapped_sequence_builder.build()?, + )) + } + + /// Builds `AbstractDataType::Sequence` from the given constraints. + /// ``` + /// { + /// type: list, + /// element: string, + /// } + /// ``` + /// This method builds `AbstractDataType`as following: + /// ``` + /// AbstractDataType::Sequence( + /// Sequence { + /// element_type: FullyQualifiedTypeReference { type_name: vec!["String"], parameters: vec![] } // Represents the element type for the list + /// sequence_type: SequenceType::List, // Represents list type for the given sequence + /// doc_comment: None // There is no doc comment defined in above ISL type def + /// source: IslType { .. } // Represents the `IslType` that is getting converted to `AbstractDataType` + /// } + /// ) + /// ``` + fn build_sequence_from_constraints( + &mut self, + constraints: &[IslConstraint], + code_gen_context: &mut CodeGenContext, + parent_isl_type: &IslType, + ) -> CodeGenResult { + let mut sequence_builder = SequenceBuilder::default(); + sequence_builder.source(parent_isl_type.to_owned()); + for constraint in constraints { + match constraint.constraint() { + IslConstraintValue::Element(isl_type_ref, _) => { + let type_name = self + .fully_qualified_type_ref_name(isl_type_ref, code_gen_context)? + .ok_or(invalid_abstract_data_type_raw_error(format!( + "Could not determine `FullQualifiedTypeReference` for type {:?}", + isl_type_ref + )))?; + + sequence_builder.element_type(type_name); + } + IslConstraintValue::Type(isl_type_ref) => { + if isl_type_ref.name() == "sexp" { + sequence_builder.sequence_type(SequenceType::SExp); + } else if isl_type_ref.name() == "list" { + sequence_builder.sequence_type(SequenceType::List); + } + } + IslConstraintValue::ContainerLength(_) => { + // TODO: add support for container length + // this is currently not supported and is a no-op + } + _ => { + return invalid_abstract_data_type_error( + "Could not determine the abstract data type due to conflicting constraints", + ); + } + } + } + Ok(AbstractDataType::Sequence(sequence_builder.build()?)) + } } #[cfg(test)] @@ -694,7 +868,9 @@ mod isl_to_model_tests { )?; let abstract_data_type = data_model_node.code_gen_type.unwrap(); assert_eq!( - abstract_data_type.fully_qualified_type_ref().unwrap(), + abstract_data_type + .fully_qualified_type_ref::() + .unwrap(), FullyQualifiedTypeReference { type_name: vec![ "org".to_string(), @@ -780,7 +956,9 @@ mod isl_to_model_tests { )?; let abstract_data_type = data_model_node.code_gen_type.unwrap(); assert_eq!( - abstract_data_type.fully_qualified_type_ref().unwrap(), + abstract_data_type + .fully_qualified_type_ref::() + .unwrap(), FullyQualifiedTypeReference { type_name: vec![ "org".to_string(), @@ -838,7 +1016,7 @@ mod isl_to_model_tests { .code_gen_type .as_ref() .unwrap() - .fully_qualified_type_ref(), + .fully_qualified_type_ref::(), Some(FullyQualifiedTypeReference { type_name: vec![ "org".to_string(), diff --git a/src/bin/ion/commands/generate/model.rs b/src/bin/ion/commands/generate/model.rs index e001ccc..7eedcc2 100644 --- a/src/bin/ion/commands/generate/model.rs +++ b/src/bin/ion/commands/generate/model.rs @@ -10,6 +10,7 @@ use std::fmt::{Display, Formatter}; // _Note: This model will eventually use a map (FullQualifiedTypeReference, DataModel) to resolve some the references in container types(sequence or structure)._ // TODO: This is not yet used in the implementation, modify current implementation to use this data model. use crate::commands::generate::context::SequenceType; +use crate::commands::generate::utils::Language; use serde::Serialize; use serde_json::Value; @@ -70,10 +71,10 @@ impl DataModelNode { false } - pub fn fully_qualified_type_ref(&mut self) -> Option { + pub fn fully_qualified_type_ref(&mut self) -> Option { self.code_gen_type .as_ref() - .and_then(|t| t.fully_qualified_type_ref()) + .and_then(|t| t.fully_qualified_type_ref::()) } } @@ -170,14 +171,13 @@ impl FullyQualifiedTypeReference { #[derive(Debug, Clone, PartialEq, Serialize)] pub enum AbstractDataType { // Represents a scalar type which also has a name attached to it and is nominally distinct from its base type. - #[allow(dead_code)] WrappedScalar(WrappedScalar), // Represents a scalar value (e.g. a string or integer or user defined type) - #[allow(dead_code)] Scalar(Scalar), // A series of zero or more values whose type is described by the nested `element_type` - #[allow(dead_code)] Sequence(Sequence), + // Represents a sequence type which also has name attached to it and is nominally distinct from its enclosed type. + WrappedSequence(WrappedSequence), // A collection of field name/value pairs (e.g. a map) Structure(Structure), } @@ -192,20 +192,30 @@ impl AbstractDataType { AbstractDataType::Scalar(Scalar { doc_comment, .. }) => { doc_comment.as_ref().map(|s| s.as_str()) } - AbstractDataType::Sequence(Sequence { doc_comment, .. }) => Some(doc_comment.as_str()), + AbstractDataType::Sequence(Sequence { doc_comment, .. }) => { + doc_comment.as_ref().map(|s| s.as_str()) + } + AbstractDataType::WrappedSequence(WrappedSequence { doc_comment, .. }) => { + doc_comment.as_ref().map(|s| s.as_str()) + } AbstractDataType::Structure(Structure { doc_comment, .. }) => { doc_comment.as_ref().map(|s| s.as_str()) } } } - pub fn fully_qualified_type_ref(&self) -> Option { + pub fn fully_qualified_type_ref(&self) -> Option { match self { AbstractDataType::WrappedScalar(w) => { Some(w.fully_qualified_type_name().to_owned().into()) } AbstractDataType::Scalar(s) => Some(s.base_type.to_owned()), - AbstractDataType::Sequence(seq) => Some(seq.element_type.to_owned()), + AbstractDataType::Sequence(seq) => { + Some(L::target_type_as_sequence(seq.element_type.to_owned())) + } + AbstractDataType::WrappedSequence(seq) => { + Some(L::target_type_as_sequence(seq.element_type.to_owned())) + } AbstractDataType::Structure(structure) => Some(structure.name.to_owned().into()), } } @@ -289,8 +299,6 @@ impl WrappedScalar { /// Represents series of zero or more values whose type is described by the nested `element_type` /// and sequence type is described by nested `sequence_type` (e.g. List or SExp). -/// If there is no `element` constraint present in schema type then `element_type` will be None. -/// If there is no `type` constraint present in schema type then `sequence_type` will be None. /// e.g. Given below ISL, /// ``` /// type::{ @@ -308,11 +316,12 @@ impl WrappedScalar { #[allow(dead_code)] #[derive(Debug, Clone, Builder, PartialEq, Serialize)] #[builder(setter(into))] -pub struct Sequence { +pub struct WrappedSequence { // Represents the fully qualified name for this data model name: FullyQualifiedTypeName, // Represents doc comment for the generated code - doc_comment: String, + #[builder(default)] + doc_comment: Option, // Represents the fully qualified name with namespace where each element of vector stores a module name or class/struct name. // _Note: that a hashmap with (FullQualifiedTypeReference, DataModel) pairs will be stored in code generator to get information on the element_type name used here._ element_type: FullyQualifiedTypeReference, @@ -326,6 +335,41 @@ pub struct Sequence { source: IslType, } +/// Represents series of zero or more values whose type is described by the nested `element_type` +/// and sequence type is described by nested `sequence_type` (e.g. List or SExp). +/// e.g. Given below ISL, +/// ``` +/// type::{ +/// name: sequence_type, +/// element: int, +/// type: list +/// } +/// ``` +/// Corresponding generated code in Rust would look like following: +/// ``` +/// struct SequenceType { +/// value: Vec +/// } +/// ``` +#[derive(Debug, Clone, Builder, PartialEq, Serialize)] +#[builder(setter(into))] +pub struct Sequence { + // Represents doc comment for the generated code + #[builder(default)] + pub(crate) doc_comment: Option, + // Represents the fully qualified name with namespace where each element of vector stores a module name or class/struct name. + // _Note: that a hashmap with (FullQualifiedTypeReference, DataModel) pairs will be stored in code generator to get information on the element_type name used here._ + pub(crate) element_type: FullyQualifiedTypeReference, + // Represents the type of the sequence which is either `sexp` or `list`. + pub(crate) sequence_type: SequenceType, + // Represents the source ISL type which can be used to get other constraints useful for this type. + // For example, getting the length of this sequence from `container_length` constraint or getting a `regex` value for string type. + // This will also be useful for `text` type to verify if this is a `string` or `symbol`. + // TODO: `IslType` does not implement `Serialize`, define a custom implementation or define methods on this field that returns values which could be serialized. + #[serde(skip_serializing)] + pub(crate) source: IslType, +} + /// Represents a collection of field name/value pairs (e.g. a map) /// e.g. Given below ISL, /// ``` @@ -448,8 +492,7 @@ mod model_tests { #[test] fn sequence_builder_test() { let expected_seq = Sequence { - name: vec![], - doc_comment: "This is sequence type of strings".to_string(), + doc_comment: Some("This is sequence type of strings".to_string()), element_type: FullyQualifiedTypeReference { type_name: vec!["String".to_string()], parameters: vec![], @@ -465,8 +508,7 @@ mod model_tests { // sets all the information about the sequence except the `element_type` seq_builder - .name(vec![]) - .doc_comment("This is sequence type of strings") + .doc_comment(Some("This is sequence type of strings".to_string())) .sequence_type(SequenceType::List) .source(anonymous_type(vec![ type_constraint(named_type_ref("list")), diff --git a/src/bin/ion/commands/generate/result.rs b/src/bin/ion/commands/generate/result.rs index d18dd19..8c6dcd6 100644 --- a/src/bin/ion/commands/generate/result.rs +++ b/src/bin/ion/commands/generate/result.rs @@ -1,5 +1,6 @@ use crate::commands::generate::model::{ ScalarBuilderError, SequenceBuilderError, StructureBuilderError, WrappedScalarBuilderError, + WrappedSequenceBuilderError, }; use ion_schema::result::IonSchemaError; use thiserror::Error; @@ -71,6 +72,14 @@ impl From for CodeGenError { } } +impl From for CodeGenError { + fn from(value: WrappedSequenceBuilderError) -> Self { + CodeGenError::DataModelBuilderError { + description: value.to_string(), + } + } +} + impl From for CodeGenError { fn from(value: StructureBuilderError) -> Self { CodeGenError::DataModelBuilderError { diff --git a/src/bin/ion/commands/generate/templates/java/class.templ b/src/bin/ion/commands/generate/templates/java/class.templ index ec7f8cb..085f2a2 100644 --- a/src/bin/ion/commands/generate/templates/java/class.templ +++ b/src/bin/ion/commands/generate/templates/java/class.templ @@ -119,13 +119,13 @@ import java.io.IOException; {% set field_value = field_val.0 | fully_qualified_type_name %} writer.setFieldName("{{ field_name }}"); {% if field_value | is_built_in_type == false %} - this.{{ field_name | camel }}.writeTo(writer); - {% else %} - {% if field_value is containing("ArrayList") %} + {% if field_value is containing("ArrayList") %} {{ util_macros::write_as_sequence(field_value=field_value,field_name=field_name,type_store=type_store) }} - {% else %} + {% else %} + this.{{ field_name | camel }}.writeTo(writer); + {% endif %} + {% else %} writer.write{{ field_value | replace(from="double", to="float") | replace(from="boolean", to="bool") | upper_camel }}(this.{{ field_name | camel }}); - {% endif %} {% endif %} {% endfor %} writer.stepOut(); diff --git a/src/bin/ion/commands/generate/templates/java/nested_type.templ b/src/bin/ion/commands/generate/templates/java/nested_type.templ index 299a608..d59c015 100644 --- a/src/bin/ion/commands/generate/templates/java/nested_type.templ +++ b/src/bin/ion/commands/generate/templates/java/nested_type.templ @@ -49,7 +49,7 @@ {{ sequence_info["element_type"] | fully_qualified_type_name }} value {% elif inline_type.code_gen_type is containing("Scalar") %} {% set scalar_info = model.code_gen_type["WrappedScalar"] %} - {% set base_type = scalar_info["name"]["parameters"][0] | fully_qualified_type_name %} + {% set base_type = scalar_info["base_type"] | fully_qualified_type_name %} {{ base_type }} value {% endif %} {% endmacro %} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/templates/java/sequence.templ b/src/bin/ion/commands/generate/templates/java/sequence.templ index 0f4c8a7..1537830 100644 --- a/src/bin/ion/commands/generate/templates/java/sequence.templ +++ b/src/bin/ion/commands/generate/templates/java/sequence.templ @@ -1,81 +1,83 @@ -package {{ namespace }}; -import java.util.ArrayList; +{% macro sequence(model) %} + +{% if is_nested == false %} +{% set full_namespace = namespace | join(sep=".") %} + +package {{ full_namespace }}; import com.amazon.ion.IonReader; import com.amazon.ion.IonException; import com.amazon.ion.IonWriter; import com.amazon.ion.IonType; import java.io.IOException; +{% endif %} + +{# Verify that the abstract data type is a sequence type and store information for this sequence value #} +{% set sequence_info = model.code_gen_type["WrappedSequence"] %} -public class {{ target_kind_name }} { - private {{ fields[0].value_type }} value; +class {{ model.name }} { + private java.util.ArrayList<{{ sequence_info["element_type"] | fully_qualified_type_name }}> value; - public {{ target_kind_name }}() {} + public {{ model.name }}() {} - public {{ fields[0].value_type }} getValue() { + public java.util.ArrayList<{{ sequence_info["element_type"] | fully_qualified_type_name }}> getValue() { return this.value; } - public void setValue({{ fields[0].value_type }} value) { + public void setValue(java.util.ArrayList<{{ sequence_info["element_type"] | fully_qualified_type_name }}> value) { this.value = value; return; } /** - * Reads a {{ target_kind_name }} from an {@link IonReader}. + * Reads a {{ model.name }} from an {@link IonReader}. * * This method does not advance the reader at the current level. * The caller is responsible for positioning the reader on the value to read. */ - public static {{ target_kind_name }} readFrom(IonReader reader) { + public static {{ model.name }} readFrom(IonReader reader) { {# Initializes all the fields of this class #} - {{ fields[0].value_type }} value = - {% if fields[0].value_type == "boolean" %} - false - {% elif fields[0].value_type == "int" or fields[0].value_type == "double" %} - 0 - {% else %} - null - {% endif %}; + java.util.ArrayList<{{ sequence_info["element_type"] | fully_qualified_type_name }}> value = new java.util.ArrayList<{{ sequence_info["element_type"] | fully_qualified_type_name }}>(); {# Reads `Sequence` class with a single field `value` that is an `ArrayList` #} - if(reader.getType() != IonType.{{ abstract_data_type["Sequence"].sequence_type | upper }}) { - throw new IonException("Expected {{ abstract_data_type["Sequence"].sequence_type }}, found " + reader.getType() + " while reading {{ fields[0].name | camel }}."); + if(reader.getType() != IonType.{{ sequence_info["sequence_type"] | upper }}) { + throw new IonException("Expected {{ sequence_info["sequence_type"] }}, found " + reader.getType() + " while reading value."); } reader.stepIn(); - value = new {{ fields[0].value_type }}(); - {# Iterate through the `ArrayList` and read each element in it based on the data type provided in `abstract_data_type[Sequence]` #} + {# Iterate through the `ArrayList` and read each element in it based on the data type provided in `sequence_info["sequence_type"]` #} while (reader.hasNext()) { reader.next(); - {% if abstract_data_type["Sequence"].element_type | is_built_in_type == false %} - value.add({{ abstract_data_type["Sequence"].element_type }}.readFrom(reader)); - {% elif abstract_data_type["Sequence"].element_type == "bytes[]" %} + {% if sequence_info["element_type"] |fully_qualified_type_name | is_built_in_type == false %} + value.add({{ sequence_info["element_type"] | fully_qualified_type_name }}.readFrom(reader)); + {% elif sequence_info["element_type"] | fully_qualified_type_name == "bytes[]" %} value.add(reader.newBytes()); {% else %} - value.add(reader.{{ abstract_data_type["Sequence"].element_type | camel }}Value()); + value.add(reader.{{ sequence_info["element_type"] | fully_qualified_type_name | camel }}Value()); {% endif %} } reader.stepOut(); - {{ target_kind_name }} {{ target_kind_name | camel }} = new {{ target_kind_name }}(); - {{ target_kind_name | camel }}.value = value; + {{ model.name }} {{ model.name | camel }} = new {{ model.name }}(); + {{ model.name | camel }}.value = value; - return {{ target_kind_name | camel }}; + return {{ model.name | camel }}; } /** - * Writes a {{ target_kind_name }} as Ion from an {@link IonWriter}. + * Writes a {{ model.name }} as Ion from an {@link IonWriter}. * * This method does not close the writer after writing is complete. * The caller is responsible for closing the stream associated with the writer. */ public void writeTo(IonWriter writer) throws IOException { {# Writes `Sequence` class with a single field `value` that is an `ArrayList` as an Ion sequence #} - writer.stepIn(IonType.{{ abstract_data_type["Sequence"].sequence_type | upper }}); - for ({{ abstract_data_type["Sequence"].element_type }} value: this.value) { - {% if abstract_data_type["Sequence"].element_type | is_built_in_type == false %} + writer.stepIn(IonType.{{ sequence_info["sequence_type"] | upper }}); + for ({{ sequence_info["element_type"] | fully_qualified_type_name }} value: this.value) { + {% if sequence_info["element_type"] | fully_qualified_type_name | is_built_in_type == false %} value.writeTo(writer); {% else %} - writer.write{{ abstract_data_type["Sequence"].element_type | upper_camel }}(value); + writer.write{{ sequence_info["element_type"] | fully_qualified_type_name | replace(from="double", to="float") | replace(from="boolean", to="bool") | upper_camel }}(value); {% endif %} } writer.stepOut(); } } +{% endmacro %} +{{ self::sequence(model=model) }} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/templates/java/util_macros.templ b/src/bin/ion/commands/generate/templates/java/util_macros.templ index c3c81d6..a03f092 100644 --- a/src/bin/ion/commands/generate/templates/java/util_macros.templ +++ b/src/bin/ion/commands/generate/templates/java/util_macros.templ @@ -1,34 +1,36 @@ {# following macro defines statements to read a class field as sequence #} -{% macro read_as_sequence(field) %} - new {{ field.value_type }}(); +{% macro read_as_sequence(field_name, field_value, type_store) %} + {% set field_value_model = type_store[field_value] %} + new {{ field_value }}(); {# Reads `Sequence` field that is an `ArrayList` #} - if(reader.getType() != IonType.{{ field.abstract_data_type["Sequence"].sequence_type | upper }}) { - throw new IonException("Expected {{ field.abstract_data_type["Sequence"].sequence_type }}, found " + reader.getType() + " while reading {{ field.name | camel }}."); + if(reader.getType() != IonType.{{ field_value_model.code_gen_type["Sequence"].sequence_type | upper }}) { + throw new IonException("Expected {{ field_value_model.code_gen_type["Sequence"].sequence_type }}, found " + reader.getType() + " while reading {{ field_name | camel }}."); } reader.stepIn(); {# Iterate through the `ArrayList` and read each element in it based on the data type provided in `field.abstract_data_type[Sequence]` #} while (reader.hasNext()) { reader.next(); - {% if field.abstract_data_type["Sequence"].element_type | is_built_in_type == false %} - {{ field.name | camel }}.add({{ field.abstract_data_type["Sequence"].element_type }}.readFrom(reader)); - {% elif field.abstract_data_type["Sequence"].element_type == "bytes[]" %} - {{ field.name | camel }}.add(reader.newBytes()); + {% if field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | is_built_in_type == false %} + {{ field_name | camel }}.add({{ field_value_model.code_gen_type["Sequence"].element_type }}.readFrom(reader)); + {% elif field_value_model.code_gen_type["Sequence"].element_type == "bytes[]" %} + {{ field_name | camel }}.add(reader.newBytes()); {% else %} - {{ field.name | camel }}.add(reader.{{ field.abstract_data_type["Sequence"].element_type | camel }}Value()); + {{ field_name | camel }}.add(reader.{{ field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | camel }}Value()); {% endif %} } reader.stepOut(); {% endmacro %} {# following macro defines statements to write a class field as sequence #} -{% macro write_as_sequence(field) %} +{% macro write_as_sequence(field_name, field_value, type_store) %} + {% set field_value_model = type_store[field_value] %} {# Writes `Sequence` field that is an `ArrayList` as an Ion sequence #} - writer.stepIn(IonType.{{ field.abstract_data_type["Sequence"].sequence_type | upper }}); - for ({{ field.abstract_data_type["Sequence"].element_type }} value: this.{{ field.name |camel }}) { - {% if field.abstract_data_type["Sequence"].element_type | is_built_in_type == false %} + writer.stepIn(IonType.{{ field_value_model.code_gen_type["Sequence"].sequence_type | upper }}); + for ({{ field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name }} value: this.{{ field_name |camel }}) { + {% if field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | is_built_in_type == false %} value.writeTo(writer); {% else %} - writer.write{{ field.abstract_data_type["Sequence"].element_type | upper_camel }}(value); + writer.write{{ field_value_model.code_gen_type["Sequence"].element_type | fully_qualified_type_name | replace(from="double", to="float") | replace(from="boolean", to="bool") | upper_camel }}(value); {% endif %} } writer.stepOut(); -{% endmacro %} +{% endmacro %} \ No newline at end of file diff --git a/src/bin/ion/commands/generate/utils.rs b/src/bin/ion/commands/generate/utils.rs index dca7bf4..9364768 100644 --- a/src/bin/ion/commands/generate/utils.rs +++ b/src/bin/ion/commands/generate/utils.rs @@ -245,7 +245,9 @@ impl TryFrom<&DataModelNode> for Template { AbstractDataType::Scalar(_) | AbstractDataType::WrappedScalar(_) => { Ok(Template::Scalar) } - AbstractDataType::Sequence(_) => Ok(Template::Sequence), + AbstractDataType::Sequence(_) | AbstractDataType::WrappedSequence(_) => { + Ok(Template::Sequence) + } AbstractDataType::Structure(_) => Ok(Template::Struct), } } else {