From 45d40f9d66fbb49447837f1ca54e1cc307f889e4 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 22 May 2024 16:34:15 -0400 Subject: [PATCH 1/3] Support multi-level polymorphism --- .../models/OperationDetails.java | 3 +- .../implementation/models/DotFish.java | 3 +- .../implementation/models/Fish.java | 3 +- .../implementation/models/MyBaseType.java | 3 +- .../azure/autorest/mapper/ModelMapper.java | 489 ++++++++++-------- .../model/clientmodel/ClientModel.java | 28 + .../ConvenienceMethodTemplateBase.java | 85 +-- .../autorest/template/ModelTemplate.java | 142 ++--- .../StreamSerializationModelTemplate.java | 42 +- .../azure/autorest/util/ClientModelUtil.java | 6 +- .../com/azure/autorest/TypeSpecPlugin.java | 9 +- .../implementation/models/AbstractModel.java | 3 +- .../cadl/armresourceprovider/models/Dog.java | 3 +- .../java/com/cadl/naming/models/Data.java | 3 +- .../implementation/JsonMergePatchHelper.java | 15 + .../main/java/com/cadl/patch/models/Fish.java | 3 +- .../java/com/cadl/patch/models/SawShark.java | 211 ++++++++ .../java/com/cadl/patch/models/Shark.java | 111 +++- .../enumdiscriminator/models/Dog.java | 3 +- .../enumdiscriminator/models/Snake.java | 3 +- .../nesteddiscriminator/models/Fish.java | 9 +- .../models/GoblinShark.java | 7 + .../nesteddiscriminator/models/SawShark.java | 7 + .../nesteddiscriminator/models/Shark.java | 8 +- .../singlediscriminator/models/Bird.java | 3 +- .../singlediscriminator/models/Dinosaur.java | 3 +- ...nownAdditionalPropertiesDiscriminated.java | 3 +- ...nownAdditionalPropertiesDiscriminated.java | 3 +- typespec-tests/tsp/patch.tsp | 8 + .../fixtures/bodycomplex/models/DotFish.java | 3 +- .../fixtures/bodycomplex/models/Fish.java | 3 +- .../bodycomplex/models/MyBaseType.java | 3 +- .../discriminatorenum/models/Dog.java | 3 +- .../models/MetricAlertCriteria.java | 3 +- .../models/MetricAlertCriteria.java | 3 +- .../noflatten/models/MetricAlertCriteria.java | 3 +- .../models/MetricAlertCriteria.java | 3 +- .../implementation/models/Dog.java | 3 +- .../models/MetricAlertCriteria.java | 3 +- .../models/DotFish.java | 3 +- .../streamstyleserialization/models/Fish.java | 3 +- .../models/MyBaseType.java | 3 +- .../models/DotFish.java | 3 +- .../models/Fish.java | 3 +- .../models/MyBaseType.java | 3 +- .../models/DotFish.java | 3 +- .../models/Fish.java | 3 +- .../models/MyBaseType.java | 3 +- 48 files changed, 839 insertions(+), 437 deletions(-) create mode 100644 typespec-tests/src/main/java/com/cadl/patch/models/SawShark.java diff --git a/azure-dataplane-tests/src/main/java/com/azure/ai/formrecognizer/documentanalysis/implementation/models/OperationDetails.java b/azure-dataplane-tests/src/main/java/com/azure/ai/formrecognizer/documentanalysis/implementation/models/OperationDetails.java index ea9285a555..d9423dac2f 100644 --- a/azure-dataplane-tests/src/main/java/com/azure/ai/formrecognizer/documentanalysis/implementation/models/OperationDetails.java +++ b/azure-dataplane-tests/src/main/java/com/azure/ai/formrecognizer/documentanalysis/implementation/models/OperationDetails.java @@ -22,7 +22,7 @@ public class OperationDetails implements JsonSerializable { /* * Type of operation. */ - private String kind; + private String kind = "OperationDetails"; /* * Operation ID @@ -73,7 +73,6 @@ public class OperationDetails implements JsonSerializable { * Creates an instance of OperationDetails class. */ public OperationDetails() { - this.kind = "OperationDetails"; } /** diff --git a/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/DotFish.java b/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/DotFish.java index 1f9e0aa36b..74987437b1 100644 --- a/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/DotFish.java +++ b/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/DotFish.java @@ -19,7 +19,7 @@ public class DotFish implements JsonSerializable { /* * The fish.type property. */ - private String fishType; + private String fishType = "DotFish"; /* * The species property. @@ -30,7 +30,6 @@ public class DotFish implements JsonSerializable { * Creates an instance of DotFish class. */ public DotFish() { - this.fishType = "DotFish"; } /** diff --git a/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/Fish.java b/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/Fish.java index d8fb6e558c..22ac112064 100644 --- a/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/Fish.java +++ b/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/Fish.java @@ -20,7 +20,7 @@ public class Fish implements JsonSerializable { /* * The fishtype property. */ - private String fishtype; + private String fishtype = "Fish"; /* * The species property. @@ -41,7 +41,6 @@ public class Fish implements JsonSerializable { * Creates an instance of Fish class. */ public Fish() { - this.fishtype = "Fish"; } /** diff --git a/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/MyBaseType.java b/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/MyBaseType.java index c7b50d36ea..3c45135e04 100644 --- a/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/MyBaseType.java +++ b/customization-tests/src/main/java/fixtures/bodycomplex/implementation/models/MyBaseType.java @@ -19,7 +19,7 @@ public class MyBaseType implements JsonSerializable { /* * The kind property. */ - private MyKind kind; + private MyKind kind = MyKind.fromString("MyBaseType"); /* * The propB1 property. @@ -35,7 +35,6 @@ public class MyBaseType implements JsonSerializable { * Creates an instance of MyBaseType class. */ public MyBaseType() { - this.kind = MyKind.fromString("MyBaseType"); } /** diff --git a/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java b/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java index f5b295bf58..ba34631214 100644 --- a/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java +++ b/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java @@ -59,284 +59,319 @@ public ClientModel map(ObjectSchema compositeType) { ClassType modelType = objectMapper.map(compositeType); String modelName = modelType.getName(); ClientModel result = serviceModels.getModel(modelType.getName()); - if (result == null && !ObjectMapper.isPlainObject(compositeType)) { - Set usages = SchemaUtil.mapSchemaContext(compositeType.getUsage()); - if (isPredefinedModel(modelType)) { - // TODO (weidxu): a more consistent handling of external model for all data-plane - if (settings.isDataPlaneClient()) { - usages = new HashSet<>(usages); - usages.add(ImplementationDetails.Usage.EXTERNAL); - } else { - // abort handling external model, if not DPG - // vanilla and fluent currently does not have mechanism to handle model that not to be outputted. - return result; - } - } - ClientModel.Builder builder = createModelBuilder() - .name(modelName) - .packageName(modelType.getPackage()) - .type(modelType) - .stronglyTypedHeader(compositeType.isStronglyTypedHeader()) - .usedInXml(SchemaUtil.treatAsXml(compositeType)) - .serializationFormats(compositeType.getSerializationFormats()) - .implementationDetails(new ImplementationDetails.Builder() - .usages(usages) - .build()); - - boolean isPolymorphic = compositeType.getDiscriminator() != null || compositeType.getDiscriminatorValue() != null; - builder.polymorphic(isPolymorphic); - - HashSet modelImports = new HashSet<>(); - - String parentModelName = null; - boolean hasAdditionalProperties = false; - List parentsNeedFlatten = Collections.emptyList(); - if (compositeType.getParents() != null && compositeType.getParents().getImmediate() != null) { - hasAdditionalProperties = compositeType.getParents().getImmediate().stream() - .anyMatch(s -> s instanceof DictionarySchema); - - ParentSchemaInfo parentSchemaInfo = getParentSchemaInfo(compositeType); - if (parentSchemaInfo.hasParentSchema()) { - parentsNeedFlatten = parentSchemaInfo.getFlattenedParentSchemas(); + if (result != null || ObjectMapper.isPlainObject(compositeType)) { + return result; + } - ClassType parentType = objectMapper.map(parentSchemaInfo.getParentSchema()); - parentModelName = parentType.getName(); - modelImports.add(parentType.getPackage() + "." + parentModelName); - } + Set usages = SchemaUtil.mapSchemaContext(compositeType.getUsage()); + if (isPredefinedModel(modelType)) { + // TODO (weidxu): a more consistent handling of external model for all data-plane + if (settings.isDataPlaneClient()) { + usages = new HashSet<>(usages); + usages.add(ImplementationDetails.Usage.EXTERNAL); + } else { + // abort handling external model, if not DPG + // vanilla and fluent currently does not have mechanism to handle model that not to be outputted. + return result; + } + } + + ClientModel.Builder builder = createModelBuilder() + .name(modelName) + .packageName(modelType.getPackage()) + .type(modelType) + .stronglyTypedHeader(compositeType.isStronglyTypedHeader()) + .usedInXml(SchemaUtil.treatAsXml(compositeType)) + .serializationFormats(compositeType.getSerializationFormats()) + .implementationDetails(new ImplementationDetails.Builder() + .usages(usages) + .build()); + + boolean isPolymorphic = compositeType.getDiscriminator() != null || compositeType.getDiscriminatorValue() != null; + builder.polymorphic(isPolymorphic); + + HashSet modelImports = new HashSet<>(); + + String parentModelName = null; + boolean hasAdditionalProperties = false; + List parentsNeedFlatten = Collections.emptyList(); + if (compositeType.getParents() != null && compositeType.getParents().getImmediate() != null) { + hasAdditionalProperties = compositeType.getParents().getImmediate().stream() + .anyMatch(s -> s instanceof DictionarySchema); + + ParentSchemaInfo parentSchemaInfo = getParentSchemaInfo(compositeType); + if (parentSchemaInfo.hasParentSchema()) { + parentsNeedFlatten = parentSchemaInfo.getFlattenedParentSchemas(); + + ClassType parentType = objectMapper.map(parentSchemaInfo.getParentSchema()); + parentModelName = parentType.getName(); + modelImports.add(parentType.getPackage() + "." + parentModelName); } - builder.parentModelName(parentModelName); - - List compositeTypeProperties = compositeType.getProperties() - .stream().filter(p -> !p.isIsDiscriminator()).collect(Collectors.toList()); - if (!parentsNeedFlatten.isEmpty()) { - // Take properties from base class of multiple inheritance as properties of this class. - for (ObjectSchema parent : parentsNeedFlatten) { - compositeTypeProperties.addAll(parent.getProperties().stream() + } + builder.parentModelName(parentModelName); + + List compositeTypeProperties = compositeType.getProperties() + .stream().filter(p -> !p.isIsDiscriminator()).collect(Collectors.toList()); + if (!parentsNeedFlatten.isEmpty()) { + // Take properties from base class of multiple inheritance as properties of this class. + for (ObjectSchema parent : parentsNeedFlatten) { + compositeTypeProperties.addAll(parent.getProperties().stream() + .filter(p -> !p.isIsDiscriminator()) + .collect(Collectors.toList())); + if (parent.getParents() != null) { + compositeTypeProperties.addAll(parent.getParents().getAll().stream() + .filter(s -> s instanceof ObjectSchema) + .flatMap(s -> ((ObjectSchema) s).getProperties().stream()) .filter(p -> !p.isIsDiscriminator()) .collect(Collectors.toList())); - if (parent.getParents() != null) { - compositeTypeProperties.addAll(parent.getParents().getAll().stream() - .filter(s -> s instanceof ObjectSchema) - .flatMap(s -> ((ObjectSchema) s).getProperties().stream()) - .filter(p -> !p.isIsDiscriminator()) - .collect(Collectors.toList())); - } } } - for (Property autoRestProperty : compositeTypeProperties) { - IType propertyType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()); - if (!autoRestProperty.isRequired()) { - propertyType = propertyType.asNullable(); - } - propertyType.addImportsTo(modelImports, false); - - IType propertyClientType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()).getClientType(); - propertyClientType.addImportsTo(modelImports, false); + } + for (Property autoRestProperty : compositeTypeProperties) { + IType propertyType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()); + if (!autoRestProperty.isRequired()) { + propertyType = propertyType.asNullable(); } + propertyType.addImportsTo(modelImports, false); - boolean compositeTypeUsedWithXml = SchemaUtil.treatAsXml(compositeType); - if (!compositeTypeProperties.isEmpty()) { - if (compositeTypeUsedWithXml) { - modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement"); + IType propertyClientType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()).getClientType(); + propertyClientType.addImportsTo(modelImports, false); + } - if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema() instanceof ArraySchema)) { - modelImports.add(ArrayList.class.getName()); - } + boolean compositeTypeUsedWithXml = SchemaUtil.treatAsXml(compositeType); + if (!compositeTypeProperties.isEmpty()) { + if (compositeTypeUsedWithXml) { + modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement"); - if (compositeTypeProperties.stream().anyMatch(p -> { - if (p.getSchema().getSerialization() == null || p.getSchema().getSerialization().getXml() == null) { - return false; - } + if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema() instanceof ArraySchema)) { + modelImports.add(ArrayList.class.getName()); + } - XmlSerializationFormat xmlSchema = p.getSchema().getSerialization().getXml(); - return xmlSchema.isAttribute() || xmlSchema.getNamespace() != null; - })) { - modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty"); + if (compositeTypeProperties.stream().anyMatch(p -> { + if (p.getSchema().getSerialization() == null || p.getSchema().getSerialization().getXml() == null) { + return false; } - if (compositeTypeProperties.stream().anyMatch(p -> { - if (p.getSchema().getSerialization() == null || p.getSchema().getSerialization().getXml() == null) { - return false; - } - - return p.getSchema().getSerialization().getXml().isText(); - })) { - modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText"); - } + XmlSerializationFormat xmlSchema = p.getSchema().getSerialization().getXml(); + return xmlSchema.isAttribute() || xmlSchema.getNamespace() != null; + })) { + modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty"); + } - if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema().getSerialization() == null - || p.getSchema().getSerialization().getXml() == null || !p.getSchema().getSerialization() - .getXml().isAttribute())) { - modelImports.add(JsonProperty.class.getName()); + if (compositeTypeProperties.stream().anyMatch(p -> { + if (p.getSchema().getSerialization() == null || p.getSchema().getSerialization().getXml() == null) { + return false; } - if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema().getSerialization() != null - && p.getSchema().getSerialization().getXml() != null && p.getSchema().getSerialization().getXml().isWrapped())) { - modelImports.add(JsonCreator.class.getName()); - } + return p.getSchema().getSerialization().getXml().isText(); + })) { + modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText"); + } - } else { + if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema().getSerialization() == null + || p.getSchema().getSerialization().getXml() == null || !p.getSchema().getSerialization() + .getXml().isAttribute())) { modelImports.add(JsonProperty.class.getName()); } - } - if (hasAdditionalProperties) { - for (Property property : compositeTypeProperties) { - if (property.getLanguage().getJava().getName().equals(PROPERTY_NAME_ADDITIONAL_PROPERTIES)) { - property.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES + "Property"); - } + + if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema().getSerialization() != null + && p.getSchema().getSerialization().getXml() != null && p.getSchema().getSerialization().getXml().isWrapped())) { + modelImports.add(JsonCreator.class.getName()); } - } - String summary = compositeType.getSummary(); - String description = compositeType.getLanguage().getJava() == null ? null : compositeType.getLanguage().getJava().getDescription(); - if (CoreUtils.isNullOrEmpty(summary) && CoreUtils.isNullOrEmpty(description)) { - builder.description(String.format("The %s model.", compositeType.getLanguage().getJava().getName())); } else { - builder.description(SchemaUtil.mergeSummaryWithDescription(summary, description)); + modelImports.add(JsonProperty.class.getName()); } - - String modelSerializedName = compositeType.getDiscriminatorValue(); - if (modelSerializedName == null && compositeType.getLanguage().getDefault() != null) { - modelSerializedName = compositeType.getLanguage().getDefault().getName(); - } - builder.serializedName(modelSerializedName); - - List derivedTypes = new ArrayList<>(); - boolean hasChildren = compositeType.getChildren() != null && compositeType.getChildren().getImmediate() != null; - if (hasChildren) { - for (Schema childSchema : compositeType.getChildren().getImmediate()) { - if (childSchema instanceof ObjectSchema) { - ClientModel model = this.map((ObjectSchema) childSchema); - derivedTypes.add(model); - } else { - throw new RuntimeException("Wait what? How? Child is not an object but a " + childSchema.getClass() + "?"); - } + } + if (hasAdditionalProperties) { + for (Property property : compositeTypeProperties) { + if (property.getLanguage().getJava().getName().equals(PROPERTY_NAME_ADDITIONAL_PROPERTIES)) { + property.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES + "Property"); } } - builder.derivedModels(derivedTypes); - - // Only configure XML information if XML is listed as one of the serialization formats in the ObjectSchema. - if (SchemaUtil.treatAsXml(compositeType)) { - boolean hasXmlFormat = compositeType.getSerialization() != null - && compositeType.getSerialization().getXml() != null; - if (hasXmlFormat) { - final XmlSerializationFormat xml = compositeType.getSerialization().getXml(); - String xmlName = CoreUtils.isNullOrEmpty(xml.getName()) - ? compositeType.getLanguage().getDefault().getName() - : xml.getName(); - builder.xmlName(xmlName); - builder.xmlNamespace(xml.getNamespace()); + } + + String summary = compositeType.getSummary(); + String description = compositeType.getLanguage().getJava() == null ? null : compositeType.getLanguage().getJava().getDescription(); + if (CoreUtils.isNullOrEmpty(summary) && CoreUtils.isNullOrEmpty(description)) { + builder.description(String.format("The %s model.", compositeType.getLanguage().getJava().getName())); + } else { + builder.description(SchemaUtil.mergeSummaryWithDescription(summary, description)); + } + + String modelSerializedName = compositeType.getDiscriminatorValue(); + if (modelSerializedName == null && compositeType.getLanguage().getDefault() != null) { + modelSerializedName = compositeType.getLanguage().getDefault().getName(); + } + builder.serializedName(modelSerializedName); + + List derivedTypes = new ArrayList<>(); + boolean hasChildren = compositeType.getChildren() != null && compositeType.getChildren().getImmediate() != null; + if (hasChildren) { + for (Schema childSchema : compositeType.getChildren().getImmediate()) { + if (childSchema instanceof ObjectSchema) { + ClientModel model = this.map((ObjectSchema) childSchema); + derivedTypes.add(model); } else { - builder.xmlName(compositeType.getLanguage().getDefault().getName()); + throw new RuntimeException("Wait what? How? Child is not an object but a " + childSchema.getClass() + "?"); } } + } + builder.derivedModels(derivedTypes); + + // Only configure XML information if XML is listed as one of the serialization formats in the ObjectSchema. + if (SchemaUtil.treatAsXml(compositeType)) { + boolean hasXmlFormat = compositeType.getSerialization() != null + && compositeType.getSerialization().getXml() != null; + if (hasXmlFormat) { + final XmlSerializationFormat xml = compositeType.getSerialization().getXml(); + String xmlName = CoreUtils.isNullOrEmpty(xml.getName()) + ? compositeType.getLanguage().getDefault().getName() + : xml.getName(); + builder.xmlName(xmlName); + builder.xmlNamespace(xml.getNamespace()); + } else { + builder.xmlName(compositeType.getLanguage().getDefault().getName()); + } + } - List properties = new ArrayList<>(); + List properties = new ArrayList<>(); - boolean needsFlatten = false; - if (settings.getModelerSettings().isFlattenModel() // enabled by modelerfour - && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.TYPE) { - needsFlatten = hasFlattenedProperty(compositeType, parentsNeedFlatten); - } + boolean needsFlatten = false; + if (settings.getModelerSettings().isFlattenModel() // enabled by modelerfour + && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.TYPE) { + needsFlatten = hasFlattenedProperty(compositeType, parentsNeedFlatten); + } - String polymorphicDiscriminator = null; - if (isPolymorphic) { - String discriminatorSerializedName = SchemaUtil.getDiscriminatorSerializedName(compositeType); - // Only escape the discriminator if the model will be flattened. - polymorphicDiscriminator = needsFlatten - ? discriminatorSerializedName.replace(".", "\\\\.") - : discriminatorSerializedName; - - final String finalPolymorphicDiscriminator = polymorphicDiscriminator; - ClientModelProperty discriminatorProperty = createDiscriminatorProperty( - settings, hasChildren, compositeType, - annotationArgs -> annotationArgs.replace(discriminatorSerializedName, finalPolymorphicDiscriminator), - polymorphicDiscriminator); - - if (discriminatorProperty != null) { - properties.add(discriminatorProperty); - - if (!settings.isStreamStyleSerialization()) { - modelImports.add(JsonTypeId.class.getName()); - } + String polymorphicDiscriminator = null; + if (isPolymorphic) { + String discriminatorSerializedName = SchemaUtil.getDiscriminatorSerializedName(compositeType); + // Only escape the discriminator if the model will be flattened. + polymorphicDiscriminator = needsFlatten + ? discriminatorSerializedName.replace(".", "\\\\.") + : discriminatorSerializedName; + + final String finalPolymorphicDiscriminator = polymorphicDiscriminator; + ClientModelProperty discriminatorProperty = createDiscriminatorProperty( + settings, hasChildren, compositeType, + annotationArgs -> annotationArgs.replace(discriminatorSerializedName, finalPolymorphicDiscriminator), + polymorphicDiscriminator); + + if (discriminatorProperty != null) { + properties.add(discriminatorProperty); + + if (!settings.isStreamStyleSerialization()) { + modelImports.add(JsonTypeId.class.getName()); } - - builder.polymorphicDiscriminatorName(polymorphicDiscriminator) - .polymorphicDiscriminator(discriminatorProperty); } - builder.needsFlatten(needsFlatten); - builder.imports(new ArrayList<>(modelImports)); + builder.polymorphicDiscriminatorName(polymorphicDiscriminator) + .polymorphicDiscriminator(discriminatorProperty); + } + + builder.needsFlatten(needsFlatten); + builder.imports(new ArrayList<>(modelImports)); - final boolean mutablePropertyAsOptional = usages.contains(ImplementationDetails.Usage.JSON_MERGE_PATCH) && settings.isStreamStyleSerialization(); - List propertyReferences = new ArrayList<>(); - for (Property property : compositeTypeProperties) { - ClientModelProperty modelProperty = Mappers.getModelPropertyMapper().map(property, mutablePropertyAsOptional); - if (Objects.equals(polymorphicDiscriminator, modelProperty.getSerializedName())) { - // Discriminator is defined both as the discriminator and a property in the model. - // Make the discriminator property required if the property is required. But don't add the property - // again as it would result in two properties for the same serialized name. - properties.get(0).setRequired(modelProperty.isRequired()); - - // If the model has children models, copy the requirement logic to the children models with the same - // polymorphic discriminator. - // Passing from the parent is performed instead of children checking the parent as children will - // complete mapping before the parent. So, the parent is last to complete and the children models - // will be fully defined. If the inverse was done, children checking the parent, the parent would - // be null or an infinite loop would happen. - if (!CoreUtils.isNullOrEmpty(derivedTypes)) { - for (ClientModel derivedType : derivedTypes) { - if (Objects.equals(derivedType.getPolymorphicDiscriminator().getSerializedName(), polymorphicDiscriminator)) { - derivedType.getPolymorphicDiscriminator().setRequired(modelProperty.isRequired()); - } + final boolean mutablePropertyAsOptional = usages.contains(ImplementationDetails.Usage.JSON_MERGE_PATCH) && settings.isStreamStyleSerialization(); + List propertyReferences = new ArrayList<>(); + for (Property property : compositeTypeProperties) { + ClientModelProperty modelProperty = Mappers.getModelPropertyMapper().map(property, mutablePropertyAsOptional); + if (Objects.equals(polymorphicDiscriminator, modelProperty.getSerializedName())) { + // Discriminator is defined both as the discriminator and a property in the model. + // Make the discriminator property required if the property is required. But don't add the property + // again as it would result in two properties for the same serialized name. + properties.get(0).setRequired(modelProperty.isRequired()); + + // If the model has children models, copy the requirement logic to the children models with the same + // polymorphic discriminator. + // Passing from the parent is performed instead of children checking the parent as children will + // complete mapping before the parent. So, the parent is last to complete and the children models + // will be fully defined. If the inverse was done, children checking the parent, the parent would + // be null or an infinite loop would happen. + if (!CoreUtils.isNullOrEmpty(derivedTypes)) { + for (ClientModel derivedType : derivedTypes) { + if (Objects.equals(derivedType.getPolymorphicDiscriminator().getSerializedName(), polymorphicDiscriminator)) { + derivedType.getPolymorphicDiscriminator().setRequired(modelProperty.isRequired()); } } - - continue; } - properties.add(modelProperty); - - if (modelProperty.getClientFlatten() && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.NONE) { - propertyReferences.addAll(collectPropertiesFromFlattenedModel(compositeType, property, modelProperty, propertyReferences)); - } + continue; } - if (hasAdditionalProperties) { - DictionarySchema schema = (DictionarySchema) compositeType.getParents().getImmediate().stream() - .filter(s -> s instanceof DictionarySchema) - .findFirst() - .orElseThrow(() -> new IllegalStateException( - "Unable to find DictionarySchema for additional properties property.")); - Property additionalProperties = new Property(); - additionalProperties.setReadOnly(false); - additionalProperties.setSchema(schema); - additionalProperties.setSerializedName(""); - - additionalProperties.setLanguage(new Languages()); - additionalProperties.getLanguage().setJava(new Language()); - additionalProperties.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES); - String additionalPropertiesDescription = schema.getLanguage().getJava().getDescription(); - if (CoreUtils.isNullOrEmpty(additionalPropertiesDescription)) { - additionalPropertiesDescription = "Additional properties"; - } - additionalProperties.getLanguage().getJava().setDescription(additionalPropertiesDescription); + properties.add(modelProperty); - properties.add(Mappers.getModelPropertyMapper().map(additionalProperties)); + if (modelProperty.getClientFlatten() && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.NONE) { + propertyReferences.addAll(collectPropertiesFromFlattenedModel(compositeType, property, modelProperty, propertyReferences)); } + } + + if (hasAdditionalProperties) { + DictionarySchema schema = (DictionarySchema) compositeType.getParents().getImmediate().stream() + .filter(s -> s instanceof DictionarySchema) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "Unable to find DictionarySchema for additional properties property.")); + Property additionalProperties = new Property(); + additionalProperties.setReadOnly(false); + additionalProperties.setSchema(schema); + additionalProperties.setSerializedName(""); + + additionalProperties.setLanguage(new Languages()); + additionalProperties.getLanguage().setJava(new Language()); + additionalProperties.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES); + String additionalPropertiesDescription = schema.getLanguage().getJava().getDescription(); + if (CoreUtils.isNullOrEmpty(additionalPropertiesDescription)) { + additionalPropertiesDescription = "Additional properties"; + } + additionalProperties.getLanguage().getJava().setDescription(additionalPropertiesDescription); + + properties.add(Mappers.getModelPropertyMapper().map(additionalProperties)); + } + + builder.properties(properties); + builder.propertyReferences(propertyReferences); + builder.crossLanguageDefinitionId(compositeType.getCrossLanguageDefinitionId()); + + result = builder.build(); - builder.properties(properties); - builder.propertyReferences(propertyReferences); - builder.crossLanguageDefinitionId(compositeType.getCrossLanguageDefinitionId()); + if (isPolymorphic && !CoreUtils.isNullOrEmpty(derivedTypes)) { + // Walk the polymorphic hierarchy finding places where the parent model and child model have different + // polymorphic discriminators. When this case is found add the parent polymorphic discriminator as a parent + // polymorphic discriminator to the child model. This is necessary to ensure that the child model generates + // the correct serialization in multi-level polymorphic structures. + for (ClientModel derivedType : derivedTypes) { + if (!Objects.equals(polymorphicDiscriminator, derivedType.getPolymorphicDiscriminatorName())) { + ClientModelProperty parentDiscriminator = result.getPolymorphicDiscriminator().newBuilder() + .defaultValue(derivedType.getPolymorphicDiscriminator().getDefaultValue()) + .build(); - result = builder.build(); - serviceModels.addModel(result); + passPolymorphicDiscriminatorToChildren(parentDiscriminator, derivedType); + } + } } + serviceModels.addModel(result); + return result; } + private static void passPolymorphicDiscriminatorToChildren(ClientModelProperty parentDiscriminator, + ClientModel child) { + // Due to the execution order of ModelMapper, where children models complete mapping before the parent model, + // the parent polymorphic discriminator needs to be added at index 0. Reason, given an example where there are + // three models, where model #1 is the root parent with discriminator type, model #2 is a child of model #2 with + // discriminator kind, and model #3 is a child of model #3 with discriminator form. The order if this running + // will have model #2 add its discriminator to model #3 before model #1 runs adding its discriminator to #2 and + // #3. We want #3 to have the ordering of [type, kind], to represent the ordering of the parent models. + child.getParentPolymorphicDiscriminators().add(0, parentDiscriminator); + + for (ClientModel derived : child.getDerivedModels()) { + passPolymorphicDiscriminatorToChildren(parentDiscriminator, derived); + } + } + private static class ParentSchemaInfo { private final ObjectSchema parentSchema; private final List flattenedParentSchemas; diff --git a/javagen/src/main/java/com/azure/autorest/model/clientmodel/ClientModel.java b/javagen/src/main/java/com/azure/autorest/model/clientmodel/ClientModel.java index b9c240b5de..166e85bcaa 100644 --- a/javagen/src/main/java/com/azure/autorest/model/clientmodel/ClientModel.java +++ b/javagen/src/main/java/com/azure/autorest/model/clientmodel/ClientModel.java @@ -41,6 +41,10 @@ public class ClientModel { * Get whether this model is a parent in a polymorphic hierarchy. */ private final boolean isPolymorphicParent; + /** + * Get the properties used by parent models as polymorphic discriminators. + */ + private final List parentPolymorphicDiscriminators; /** * Get the property that determines which polymorphic model type to create. */ @@ -136,6 +140,7 @@ public class ClientModel { * @param implementationDetails The implementation details for the model. * @param usedInXml Whether the model is used in XML serialization. * @param crossLanguageDefinitionId The cross language definition id for the model. + * */ protected ClientModel(String packageKeyword, String name, List imports, String description, boolean isPolymorphic, ClientModelProperty polymorphicDiscriminator, String polymorphicDiscriminatorName, @@ -151,6 +156,7 @@ protected ClientModel(String packageKeyword, String name, List imports, this.description = description; this.isPolymorphic = isPolymorphic; this.isPolymorphicParent = isPolymorphic && !CoreUtils.isNullOrEmpty(derivedModels); + this.parentPolymorphicDiscriminators = new ArrayList<>(); this.polymorphicDiscriminator = polymorphicDiscriminator; this.polymorphicDiscriminatorName = polymorphicDiscriminatorName; this.serializedName = serializedName; @@ -241,6 +247,28 @@ public final boolean isPolymorphicParent() { return isPolymorphicParent; } + /** + * Gets the properties used by parent models as polymorphic discriminators. + *

+ * The only time this will return a non-empty list is when this model is used in a multi-level polymorphic + * hierarchy. Or, as an example, if the root model uses a polymorphic discriminator of {@code kind} and this model + * uses a polymorphic discriminator of {@code type} this will have a single property where the serialized name is + * {@code kind} and the default value for it will be what to root model uses to determine that this is the model + * that should be deserialized. Continuing this example, the third level models, or those that are determined by the + * {@code type} value, will also have a single property where the serialized name is {@code kind} and the default + * value will be what the second level model uses to determine that this is the model that should be deserialized. + * This is because the {@code kind} property will always need to be present for these models. If there are even + * deeper levels of polymorphism, the same pattern will continue. So, if in the third level there is a model that + * introduces another polymorphic discriminator of {@code format} that model would have two properties in this list, + * one with {@code kind} with a default that determined the second level model and one with {@code type} with a + * default that determined the third level model. The fourth level model would then have both as well. + * + * @return The properties used by parent models as polymorphic discriminators. + */ + public final List getParentPolymorphicDiscriminators() { + return parentPolymorphicDiscriminators; + } + /** * Gets the property that determines which polymorphic model type to create. * diff --git a/javagen/src/main/java/com/azure/autorest/template/ConvenienceMethodTemplateBase.java b/javagen/src/main/java/com/azure/autorest/template/ConvenienceMethodTemplateBase.java index 0b0a5c7a32..bcab7ffad8 100644 --- a/javagen/src/main/java/com/azure/autorest/template/ConvenienceMethodTemplateBase.java +++ b/javagen/src/main/java/com/azure/autorest/template/ConvenienceMethodTemplateBase.java @@ -68,26 +68,27 @@ public void write(ConvenienceMethod convenienceMethodObj, JavaClass classBlock, ClientMethod protocolMethod = convenienceMethodObj.getProtocolMethod(); convenienceMethodObj.getConvenienceMethods().stream() - .filter(this::isMethodIncluded) - .forEach(convenienceMethod -> { - // javadoc - classBlock.javadocComment(comment -> { - ClientMethodTemplate.generateJavadoc(convenienceMethod, comment, convenienceMethod.getProxyMethod(), false); - }); + .filter(this::isMethodIncluded) + .forEach(convenienceMethod -> { + // javadoc + classBlock.javadocComment(comment -> ClientMethodTemplate.generateJavadoc(convenienceMethod, comment, + convenienceMethod.getProxyMethod(), false)); - addGeneratedAnnotation(classBlock); - TemplateUtil.writeClientMethodServiceMethodAnnotation(convenienceMethod, classBlock); + addGeneratedAnnotation(classBlock); + TemplateUtil.writeClientMethodServiceMethodAnnotation(convenienceMethod, classBlock); - JavaVisibility methodVisibility = convenienceMethod.getMethodVisibilityInWrapperClient(); + JavaVisibility methodVisibility = convenienceMethod.getMethodVisibilityInWrapperClient(); - // convenience method - String methodDeclaration = String.format("%1$s %2$s(%3$s)", convenienceMethod.getReturnValue().getType(), getMethodName(convenienceMethod), convenienceMethod.getParametersDeclaration()); - classBlock.method(methodVisibility, null, methodDeclaration, methodBlock -> { - methodBlock.line("// Generated convenience method for " + getMethodName(protocolMethod)); + // convenience method + String methodDeclaration = String.format("%1$s %2$s(%3$s)", + convenienceMethod.getReturnValue().getType(), getMethodName(convenienceMethod), + convenienceMethod.getParametersDeclaration()); + classBlock.method(methodVisibility, null, methodDeclaration, methodBlock -> { + methodBlock.line("// Generated convenience method for " + getMethodName(protocolMethod)); - writeMethodImplementation(protocolMethod, convenienceMethod, methodBlock, typeReferenceStaticClasses); - }); + writeMethodImplementation(protocolMethod, convenienceMethod, methodBlock, typeReferenceStaticClasses); }); + }); } /** @@ -112,12 +113,14 @@ protected void writeMethodImplementation( // parameter transformation if (!CoreUtils.isNullOrEmpty(convenienceMethod.getMethodTransformationDetails())) { - convenienceMethod.getMethodTransformationDetails().forEach(d -> writeParameterTransformation(d, convenienceMethod, protocolMethod, methodBlock, parametersMap)); + convenienceMethod.getMethodTransformationDetails() + .forEach(d -> writeParameterTransformation(d, convenienceMethod, protocolMethod, methodBlock, parametersMap)); } writeValidationForVersioning(convenienceMethod, parametersMap.keySet(), methodBlock); - boolean isJsonMergePatchOperation = protocolMethod != null && protocolMethod.getProxyMethod() != null && "application/merge-patch+json".equalsIgnoreCase(protocolMethod.getProxyMethod().getRequestContentType()); + boolean isJsonMergePatchOperation = protocolMethod != null && protocolMethod.getProxyMethod() != null + && "application/merge-patch+json".equalsIgnoreCase(protocolMethod.getProxyMethod().getRequestContentType()); Map parameterExpressionsMap = new HashMap<>(); for (Map.Entry entry : parametersMap.entrySet()) { MethodParameter parameter = entry.getKey(); @@ -141,41 +144,47 @@ protected void writeMethodImplementation( writeQueryParam(parameter, methodBlock); break; - case BODY: { + case BODY: Consumer writeLine = javaBlock -> { IType parameterType = parameter.getClientMethodParameter().getClientType(); - String expression = expressionConvertToBinaryData(parameter.getName(), parameter.getClientMethodParameter().getWireType(), protocolMethod.getProxyMethod().getRequestContentType()); - if (isJsonMergePatchOperation && ClientModelUtil.isClientModel(parameterType) && ClientModelUtil.isJsonMergePatchModel(ClientModelUtil.getClientModel(((ClassType) parameterType).getName()))) { + String expression = expressionConvertToBinaryData(parameter.getName(), + parameter.getClientMethodParameter().getWireType(), + protocolMethod.getProxyMethod().getRequestContentType()); + + if (isJsonMergePatchOperation + && ClientModelUtil.isClientModel(parameterType) + && ClientModelUtil.isJsonMergePatchModel(ClientModelUtil.getClientModel(((ClassType) parameterType).getName()), JavaSettings.getInstance())) { String variableName = writeParameterConversionExpressionWithJsonMergePatchEnabled(javaBlock, parameterType.toString(), parameter.getName(), expression); - javaBlock.line(String.format("requestOptions.setBody(%s);", variableName)); + javaBlock.line("requestOptions.setBody(" + variableName + ");"); } else { - javaBlock.line(String.format("requestOptions.setBody(%s);", expression)); + javaBlock.line("requestOptions.setBody(" + expression + ");"); } }; if (!parameter.getClientMethodParameter().isRequired()) { - methodBlock.ifBlock(String.format("%s != null", parameter.getName()), writeLine); + methodBlock.ifBlock(parameter.getName() + " != null", writeLine); } else { writeLine.accept(methodBlock); } - } - break; + break; } } } // invocation with protocol method parameters and RequestOptions String invocationExpression = protocolMethod.getMethodInputParameters().stream() - .map(p -> { - String parameterName = p.getName(); - String expression = parameterExpressionsMap.get(parameterName); - IType parameterRawType = p.getRawType(); - if (isJsonMergePatchOperation && ClientModelUtil.isClientModel(parameterRawType) && RequestParameterLocation.BODY.equals(p.getRequestParameterLocation()) && ClientModelUtil.isJsonMergePatchModel(ClientModelUtil.getClientModel(((ClassType) parameterRawType).getName()))) { - return writeParameterConversionExpressionWithJsonMergePatchEnabled(methodBlock, parameterRawType.toString(), parameterName, expression); - } else { - return expression == null ? parameterName : expression; - } - }) - .collect(Collectors.joining(", ")); + .map(p -> { + String parameterName = p.getName(); + String expression = parameterExpressionsMap.get(parameterName); + IType parameterRawType = p.getRawType(); + if (isJsonMergePatchOperation && ClientModelUtil.isClientModel(parameterRawType) + && RequestParameterLocation.BODY.equals(p.getRequestParameterLocation()) + && ClientModelUtil.isJsonMergePatchModel(ClientModelUtil.getClientModel(((ClassType) parameterRawType).getName()), JavaSettings.getInstance())) { + return writeParameterConversionExpressionWithJsonMergePatchEnabled(methodBlock, parameterRawType.toString(), parameterName, expression); + } else { + return expression == null ? parameterName : expression; + } + }) + .collect(Collectors.joining(", ")); // write the invocation of protocol method, and related type conversion writeInvocationAndConversion(convenienceMethod, protocolMethod, invocationExpression, methodBlock, typeReferenceStaticClasses); @@ -193,8 +202,8 @@ protected void writeValidationForVersioning(ClientMethod convenienceMethod, Set< for (MethodParameter parameter : parameters) { if (parameter.getClientMethodParameter().getVersioning() != null && parameter.getClientMethodParameter().getVersioning().getAdded() != null) { String condition = String.format( - "!Arrays.asList(%1$s).contains(serviceClient.getServiceVersion().getVersion())", - parameter.getClientMethodParameter().getVersioning().getAdded().stream().map(ClassType.STRING::defaultValueExpression).collect(Collectors.joining(", "))); + "!Arrays.asList(%s).contains(serviceClient.getServiceVersion().getVersion())", + parameter.getClientMethodParameter().getVersioning().getAdded().stream().map(ClassType.STRING::defaultValueExpression).collect(Collectors.joining(", "))); methodBlock.ifBlock(condition, ifBlock -> { String exceptionExpression = String.format( "new IllegalArgumentException(\"Parameter %1$s is only available in api-version %2$s.\")", diff --git a/javagen/src/main/java/com/azure/autorest/template/ModelTemplate.java b/javagen/src/main/java/com/azure/autorest/template/ModelTemplate.java index 574eb79aaf..15f6d633b5 100644 --- a/javagen/src/main/java/com/azure/autorest/template/ModelTemplate.java +++ b/javagen/src/main/java/com/azure/autorest/template/ModelTemplate.java @@ -148,7 +148,7 @@ public final void write(ClientModel model, JavaFile javaFile) { addProperties(model, classBlock, settings); // add jsonMergePatch related properties and accessors - if (ClientModelUtil.isJsonMergePatchModel(model) && settings.isStreamStyleSerialization()) { + if (ClientModelUtil.isJsonMergePatchModel(model, settings)) { addJsonMergePatchRelatedPropertyAndAccessors(classBlock, model); } @@ -178,7 +178,8 @@ public final void write(ClientModel model, JavaFile javaFile) { } // getter method of discriminator property in subclass is handled differently - final boolean polymorphicDiscriminatorInSubclass = property.isPolymorphicDiscriminator() && !modelDefinesProperty(model, property); + boolean polymorphicDiscriminatorInSubclass = property.isPolymorphicDiscriminator() + && !modelDefinesProperty(model, property); if (polymorphicDiscriminatorInSubclass) { classBlock.annotation("Override"); } @@ -198,7 +199,7 @@ public final void write(ClientModel model, JavaFile javaFile) { classBlock.method(methodVisibility, null, model.getName() + " " + property.getSetterName() + "(" + propertyClientType + " " + property.getName() + ")", methodBlock -> addSetterMethod(propertyWireType, propertyClientType, property, treatAsXml, - methodBlock, settings, ClientModelUtil.isJsonMergePatchModel(model) && settings.isStreamStyleSerialization())); + methodBlock, settings, ClientModelUtil.isJsonMergePatchModel(model, settings))); } else { // If stream-style serialization is being generated or the property is the polymorphic // discriminator, some additional setters may need to be added to support read-only properties that @@ -224,7 +225,7 @@ public final void write(ClientModel model, JavaFile javaFile) { + property.getName() + ")", methodBlock -> addSetterMethod(propertyWireType, propertyWireType, property, treatAsXml, methodBlock, settings, - ClientModelUtil.isJsonMergePatchModel(model) && settings.isStreamStyleSerialization())); + ClientModelUtil.isJsonMergePatchModel(model, settings))); } } @@ -263,7 +264,7 @@ public final void write(ClientModel model, JavaFile javaFile) { methodBlock -> { methodBlock.line(String.format("super.%1$s(%2$s);", parentProperty.getSetterName(), parentProperty.getName())); - if (ClientModelUtil.isJsonMergePatchModel(model) && settings.isStreamStyleSerialization()) { + if (ClientModelUtil.isJsonMergePatchModel(model, settings)) { methodBlock.line(String.format("this.updatedProperties.add(\"%s\");", parentProperty.getName())); } methodBlock.methodReturn("this"); @@ -377,7 +378,7 @@ private void addImports(Set imports, ClientModel model, JavaSettings set model.addImportsTo(imports, settings); // add Json merge patch related imports - if (ClientModelUtil.isJsonMergePatchModel(model) && settings.isStreamStyleSerialization()) { + if (ClientModelUtil.isJsonMergePatchModel(model, settings)) { imports.add(settings.getPackage(settings.getImplementationSubpackage()) + "." + ClientModelUtil.JSON_MERGE_PATCH_HELPER_CLASS_NAME); imports.add(Set.class.getName()); imports.add(HashSet.class.getName()); @@ -539,71 +540,82 @@ protected void addXmlNamespaceConstants(ClientModel model, JavaClass classBlock) * @param settings AutoRest configuration settings. */ private void addProperties(ClientModel model, JavaClass classBlock, JavaSettings settings) { + for (ClientModelProperty parentDiscriminator : model.getParentPolymorphicDiscriminators()) { + addProperty(parentDiscriminator, model, classBlock, settings); + } + for (ClientModelProperty property : model.getProperties()) { -// if (property.isPolymorphicDiscriminator() && !modelDefinesProperty(model, property)) { -// // Only the super most parent model should have the polymorphic discriminator as a field. -// // The child models should use the parent's field. If the polymorphic property is required, it will be -// // initialized in the parent's constructor. Otherwise, it will be set using the package-private setter. -// continue; -// } + addProperty(property, model, classBlock, settings); + } + } - String propertyName = property.getName(); - IType propertyType = property.getWireType(); + private void addProperty(ClientModelProperty property, ClientModel model, JavaClass classBlock, + JavaSettings settings) { + String propertyName = property.getName(); + IType propertyType = property.getWireType(); - String fieldSignature; - if (model.isUsedInXml()) { - if (property.isXmlWrapper()) { - if (!settings.isStreamStyleSerialization()) { - String xmlWrapperClassName = getPropertyXmlWrapperClassName(property); - classBlock.staticFinalClass(JavaVisibility.PackagePrivate, xmlWrapperClassName, - innerClass -> addXmlWrapperClass(innerClass, property, xmlWrapperClassName, settings)); + String defaultValue; + if (property.isPolymorphicDiscriminator()) { + defaultValue = (property.getDefaultValue() == null) + ? property.getClientType().defaultValueExpression(model.getSerializedName()) + : property.getDefaultValue(); + } else { + defaultValue = property.getDefaultValue(); + } - fieldSignature = xmlWrapperClassName + " " + propertyName; - } else { - fieldSignature = propertyType + " " + propertyName; - } - } else if (propertyType instanceof ListType) { - fieldSignature = propertyType + " " + propertyName + " = new ArrayList<>()"; + String fieldSignature; + if (model.isUsedInXml()) { + if (property.isXmlWrapper()) { + if (!settings.isStreamStyleSerialization()) { + String xmlWrapperClassName = getPropertyXmlWrapperClassName(property); + classBlock.staticFinalClass(JavaVisibility.PackagePrivate, xmlWrapperClassName, + innerClass -> addXmlWrapperClass(innerClass, property, xmlWrapperClassName, settings)); + + fieldSignature = xmlWrapperClassName + " " + propertyName; } else { - // handle x-ms-client-default - // Only set the property to a default value if the property isn't included in the constructor. - // There can be cases with polymorphic discriminators where they have both a default value and are - // required, in which case the default value will be set in the constructor. - if (property.getDefaultValue() != null - && (!ClientModelUtil.includePropertyInConstructor(property, settings) || property.isConstant())) { - fieldSignature = propertyType + " " + propertyName + " = " + property.getDefaultValue(); - } else { - fieldSignature = propertyType + " " + propertyName; - } + fieldSignature = propertyType + " " + propertyName; } + } else if (propertyType instanceof ListType) { + fieldSignature = propertyType + " " + propertyName + " = new ArrayList<>()"; } else { - if (property.getClientFlatten() && property.isRequired() && property.getClientType() instanceof ClassType) { - // if the property of flattened model is required, initialize it - fieldSignature = propertyType + " " + propertyName + " = new " + propertyType + "()"; + // handle x-ms-client-default + // Only set the property to a default value if the property isn't included in the constructor. + // There can be cases with polymorphic discriminators where they have both a default value and are + // required, in which case the default value will be set in the constructor. + if (defaultValue != null + && (!ClientModelUtil.includePropertyInConstructor(property, settings) || property.isConstant())) { + fieldSignature = propertyType + " " + propertyName + " = " + defaultValue; } else { - // handle x-ms-client-default - // Only set the property to a default value if the property isn't included in the constructor. - // There can be cases with polymorphic discriminators where they have both a default value and are - // required, in which case the default value will be set in the constructor. - if (property.getDefaultValue() != null - && (!ClientModelUtil.includePropertyInConstructor(property, settings) || property.isConstant())) { - fieldSignature = propertyType + " " + propertyName + " = " + property.getDefaultValue(); - } else { - fieldSignature = propertyType + " " + propertyName; - } + fieldSignature = propertyType + " " + propertyName; + } + } + } else { + if (property.getClientFlatten() && property.isRequired() && property.getClientType() instanceof ClassType) { + // if the property of flattened model is required, initialize it + fieldSignature = propertyType + " " + propertyName + " = new " + propertyType + "()"; + } else { + // handle x-ms-client-default + // Only set the property to a default value if the property isn't included in the constructor. + // There can be cases with polymorphic discriminators where they have both a default value and are + // required, in which case the default value will be set in the constructor. + if (defaultValue != null + && (!ClientModelUtil.includePropertyInConstructor(property, settings) || property.isConstant())) { + fieldSignature = propertyType + " " + propertyName + " = " + defaultValue; + } else { + fieldSignature = propertyType + " " + propertyName; } } + } - classBlock.blockComment(comment -> comment.line(property.getDescription())); + classBlock.blockComment(comment -> comment.line(property.getDescription())); - addGeneratedAnnotation(classBlock); - addFieldAnnotations(model, property, classBlock, settings); + addGeneratedAnnotation(classBlock); + addFieldAnnotations(model, property, classBlock, settings); - if (ClientModelUtil.includePropertyInConstructor(property, settings)) { - classBlock.privateFinalMemberVariable(fieldSignature); - } else { - classBlock.privateMemberVariable(fieldSignature); - } + if (ClientModelUtil.includePropertyInConstructor(property, settings)) { + classBlock.privateFinalMemberVariable(fieldSignature); + } else { + classBlock.privateMemberVariable(fieldSignature); } } @@ -814,19 +826,13 @@ private void addModelConstructor(ClientModel model, JavaVisibility constructorVi // If there is a polymorphic discriminator , add a line to initialize the discriminator. ClientModelProperty polymorphicProperty = model.getPolymorphicDiscriminator(); if (polymorphicProperty != null && !polymorphicProperty.isRequired()) { - String discriminatorValue = polymorphicProperty.getDefaultValue() != null - ? polymorphicProperty.getDefaultValue() - : polymorphicProperty.getClientType().defaultValueExpression(model.getSerializedName()); + if (ClientModelUtil.isJsonMergePatchModel(model, settings)) { + for (ClientModelProperty property : model.getParentPolymorphicDiscriminators()) { + constructor.line("this.updatedProperties.add(\"" + property.getName() + "\");"); + } - if (modelDefinesProperty(model, polymorphicProperty)) { - constructor.line("this." + polymorphicProperty.getName() + " = " + discriminatorValue + ";"); - } - if (settings.isStreamStyleSerialization() && ClientModelUtil.isJsonMergePatchModel(model)) { constructor.line("this.updatedProperties.add(\"" + polymorphicProperty.getName() + "\");"); } -// else { -// constructor.line(polymorphicProperty.getSetterName() + "(" + discriminatorValue + ");"); -// } } // constant properties should already be initialized in class variable definition diff --git a/javagen/src/main/java/com/azure/autorest/template/StreamSerializationModelTemplate.java b/javagen/src/main/java/com/azure/autorest/template/StreamSerializationModelTemplate.java index cb3e07d13b..eb69a21c01 100644 --- a/javagen/src/main/java/com/azure/autorest/template/StreamSerializationModelTemplate.java +++ b/javagen/src/main/java/com/azure/autorest/template/StreamSerializationModelTemplate.java @@ -212,7 +212,7 @@ protected void writeStreamStyleSerialization(JavaClass classBlock, ClientModel m writeFromXml(classBlock, model, propertiesManager, settings, Templates.getModelTemplate()::addGeneratedAnnotation); } else { - if (ClientModelUtil.isJsonMergePatchModel(model)) { + if (ClientModelUtil.isJsonMergePatchModel(model, settings)) { writeToJson(classBlock, propertiesManager, true, Templates.getModelTemplate()::addGeneratedAnnotation); writeToJsonMergePatch(classBlock, propertiesManager, Templates.getModelTemplate()::addGeneratedAnnotation); @@ -277,6 +277,8 @@ private static void serializeJsonProperties(JavaBlock methodBlock, ClientModelPr BiConsumer serializeJsonProperty = (property, fromSuper) -> serializeJsonProperty( methodBlock, property, property.getSerializedName(), fromSuper, true, isJsonMergePatch); + propertiesManager.getModel().getParentPolymorphicDiscriminators() + .forEach(discriminator -> serializeJsonProperty.accept(discriminator, false)); propertiesManager.forEachSuperRequiredProperty(property -> serializeJsonProperty.accept(property, true)); propertiesManager.forEachSuperSetterProperty(property -> serializeJsonProperty.accept(property, true)); propertiesManager.forEachRequiredProperty(property -> serializeJsonProperty.accept(property, false)); @@ -681,12 +683,36 @@ private static void writeSuperTypeFromJson(JavaClass classBlock, ClientModel mod // Add deserialization for all child types. List childTypes = getAllChildTypes(model, new ArrayList<>()); for (ClientModel childType : childTypes) { + // Determine which serialization method to use based on whether the child type is also a polymorphic + // parent and the child shares the same polymorphic discriminator as this model. + // If the child and parent have different discriminator names then the child will need to be + // deserialized checking the multi-level polymorphic discriminator. + // Using the nested discriminator sample, there is + // Fish : kind + // - Salmon : kind + // - Shark : sharktype + // - Sawshark : sharktype + // So, if deserialization enters Fish and the "kind" is "Shark" then it needs to check the + // "sharktype" to determine if it's a Sawshark or another subtype of Shark. + boolean sameDiscimrinator = Objects.equals(childType.getPolymorphicDiscriminatorName(), + model.getPolymorphicDiscriminatorName()); + + if (!sameDiscimrinator && !Objects.equals(childType.getParentModelName(), model.getName())) { + // Child model and parent model don't share the same discriminator and the child isn't a direct + // child of the parent model, so skip this child model. This is done as the child model should + // be deserialized by the subtype that defines the different polymorphic discriminator. Using + // the sample above, Fish can't use "kind" to deserialize to a Shark subtype, it needs to use + // "sharktype". + continue; + } + + String deserializationMethod = (isSuperTypeWithDiscriminator(childType) && sameDiscimrinator) + ? ".fromJsonKnownDiscriminator(readerToUse.reset())" + : ".fromJson(readerToUse.reset())"; + ifBlock = ifOrElseIf(tryStatement, ifBlock, "\"" + childType.getSerializedName() + "\".equals(discriminatorValue)", - ifStatement -> ifStatement.methodReturn( - childType.getName() + (isSuperTypeWithDiscriminator(childType) - ? ".fromJsonKnownDiscriminator(readerToUse.reset())" - : ".fromJson(readerToUse.reset())"))); + ifStatement -> ifStatement.methodReturn(childType.getName() + deserializationMethod)); } if (ifBlock == null) { @@ -1394,9 +1420,9 @@ private static void handleSettingDeserializedValue(JavaBlock methodBlock, String } } - private static boolean isSuperTypeWithDiscriminator(ClientModel model) { - return !CoreUtils.isNullOrEmpty(model.getPolymorphicDiscriminatorName()) - && !CoreUtils.isNullOrEmpty(model.getDerivedModels()); + private static boolean isSuperTypeWithDiscriminator(ClientModel child) { + return !CoreUtils.isNullOrEmpty(child.getPolymorphicDiscriminatorName()) + && !CoreUtils.isNullOrEmpty(child.getDerivedModels()); } /** diff --git a/javagen/src/main/java/com/azure/autorest/util/ClientModelUtil.java b/javagen/src/main/java/com/azure/autorest/util/ClientModelUtil.java index ec9177ca0e..bd2c7b9588 100644 --- a/javagen/src/main/java/com/azure/autorest/util/ClientModelUtil.java +++ b/javagen/src/main/java/com/azure/autorest/util/ClientModelUtil.java @@ -458,8 +458,10 @@ public static boolean isOutputOnly(ClientModel model) { /** * Check if the model is used in json-merge-patch operation */ - public static boolean isJsonMergePatchModel(ClientModel model) { - return model.getImplementationDetails() != null && model.getImplementationDetails().getUsages() != null + public static boolean isJsonMergePatchModel(ClientModel model, JavaSettings settings) { + // JSON merge patch is only supported for stream style serialization. + return settings.isStreamStyleSerialization() + && model.getImplementationDetails() != null && model.getImplementationDetails().getUsages() != null && model.getImplementationDetails().getUsages().contains(ImplementationDetails.Usage.JSON_MERGE_PATCH); } diff --git a/typespec-extension/src/main/java/com/azure/autorest/TypeSpecPlugin.java b/typespec-extension/src/main/java/com/azure/autorest/TypeSpecPlugin.java index 3c54e1d2e3..bdcc741ec2 100644 --- a/typespec-extension/src/main/java/com/azure/autorest/TypeSpecPlugin.java +++ b/typespec-extension/src/main/java/com/azure/autorest/TypeSpecPlugin.java @@ -47,8 +47,7 @@ public class TypeSpecPlugin extends Javagen { public Client processClient(CodeModel codeModel) { // transform code model - codeModel = Preprocessor.convertOptionalConstantsToEnum(codeModel); - codeModel = new Transformer().transform(codeModel); + codeModel = new Transformer().transform(Preprocessor.convertOptionalConstantsToEnum(codeModel)); // map to client model Client client = Mappers.getClientMapper().map(codeModel); @@ -127,15 +126,15 @@ protected void writeClientModels(Client client, JavaPackage javaPackage, JavaSet // Union client.getUnionModels().stream() .filter(ModelUtil::isGeneratingModel) - .forEach(model -> javaPackage.addUnionModel(model)); + .forEach(javaPackage::addUnionModel); } @Override protected void writeHelperClasses(Client client, JavaPackage javaPackage, JavaSettings settings) { // JsonMergePatchHelper List jsonMergePatchModels = client.getModels().stream() - .filter(ModelUtil::isGeneratingModel) - .filter(ClientModelUtil::isJsonMergePatchModel).collect(Collectors.toList()); + .filter(model -> ModelUtil.isGeneratingModel(model) && ClientModelUtil.isJsonMergePatchModel(model, settings)) + .collect(Collectors.toList()); if (!jsonMergePatchModels.isEmpty()) { javaPackage.addJsonMergePatchHelper(jsonMergePatchModels); } diff --git a/typespec-tests/src/main/java/com/_specs_/azure/clientgenerator/core/access/implementation/models/AbstractModel.java b/typespec-tests/src/main/java/com/_specs_/azure/clientgenerator/core/access/implementation/models/AbstractModel.java index 7ff66964b9..868c9a2020 100644 --- a/typespec-tests/src/main/java/com/_specs_/azure/clientgenerator/core/access/implementation/models/AbstractModel.java +++ b/typespec-tests/src/main/java/com/_specs_/azure/clientgenerator/core/access/implementation/models/AbstractModel.java @@ -21,7 +21,7 @@ public class AbstractModel implements JsonSerializable { * The kind property. */ @Generated - private String kind; + private String kind = "AbstractModel"; /* * The name property. @@ -36,7 +36,6 @@ public class AbstractModel implements JsonSerializable { */ @Generated protected AbstractModel(String name) { - this.kind = "AbstractModel"; this.name = name; } diff --git a/typespec-tests/src/main/java/com/cadl/armresourceprovider/models/Dog.java b/typespec-tests/src/main/java/com/cadl/armresourceprovider/models/Dog.java index ba55edbf3d..0fc8c725a0 100644 --- a/typespec-tests/src/main/java/com/cadl/armresourceprovider/models/Dog.java +++ b/typespec-tests/src/main/java/com/cadl/armresourceprovider/models/Dog.java @@ -24,7 +24,7 @@ public class Dog { */ @JsonTypeId @JsonProperty(value = "kind", required = true) - private DogKind kind; + private DogKind kind = DogKind.fromString("Dog"); /* * Weight of the dog @@ -36,7 +36,6 @@ public class Dog { * Creates an instance of Dog class. */ public Dog() { - this.kind = DogKind.fromString("Dog"); } /** diff --git a/typespec-tests/src/main/java/com/cadl/naming/models/Data.java b/typespec-tests/src/main/java/com/cadl/naming/models/Data.java index c28af47bbf..34da609016 100644 --- a/typespec-tests/src/main/java/com/cadl/naming/models/Data.java +++ b/typespec-tests/src/main/java/com/cadl/naming/models/Data.java @@ -22,14 +22,13 @@ public class Data implements JsonSerializable { * The @data.kind property. */ @Generated - private String type; + private String type = "Data"; /** * Creates an instance of Data class. */ @Generated protected Data() { - this.type = "Data"; } /** diff --git a/typespec-tests/src/main/java/com/cadl/patch/implementation/JsonMergePatchHelper.java b/typespec-tests/src/main/java/com/cadl/patch/implementation/JsonMergePatchHelper.java index 3a13ac1c10..813b6dc4d4 100644 --- a/typespec-tests/src/main/java/com/cadl/patch/implementation/JsonMergePatchHelper.java +++ b/typespec-tests/src/main/java/com/cadl/patch/implementation/JsonMergePatchHelper.java @@ -8,6 +8,7 @@ import com.cadl.patch.models.InnerModel; import com.cadl.patch.models.Resource; import com.cadl.patch.models.Salmon; +import com.cadl.patch.models.SawShark; import com.cadl.patch.models.Shark; /** @@ -22,6 +23,8 @@ public class JsonMergePatchHelper { private static SharkAccessor sharkAccessor; + private static SawSharkAccessor sawSharkAccessor; + private static SalmonAccessor salmonAccessor; public interface ResourceAccessor { @@ -40,6 +43,10 @@ public interface SharkAccessor { Shark prepareModelForJsonMergePatch(Shark shark, boolean jsonMergePatchEnabled); } + public interface SawSharkAccessor { + SawShark prepareModelForJsonMergePatch(SawShark sawShark, boolean jsonMergePatchEnabled); + } + public interface SalmonAccessor { Salmon prepareModelForJsonMergePatch(Salmon salmon, boolean jsonMergePatchEnabled); } @@ -76,6 +83,14 @@ public static SharkAccessor getSharkAccessor() { return sharkAccessor; } + public static void setSawSharkAccessor(SawSharkAccessor accessor) { + sawSharkAccessor = accessor; + } + + public static SawSharkAccessor getSawSharkAccessor() { + return sawSharkAccessor; + } + public static void setSalmonAccessor(SalmonAccessor accessor) { salmonAccessor = accessor; } diff --git a/typespec-tests/src/main/java/com/cadl/patch/models/Fish.java b/typespec-tests/src/main/java/com/cadl/patch/models/Fish.java index db0480e59d..e5aad4feee 100644 --- a/typespec-tests/src/main/java/com/cadl/patch/models/Fish.java +++ b/typespec-tests/src/main/java/com/cadl/patch/models/Fish.java @@ -24,7 +24,7 @@ public class Fish implements JsonSerializable { * The kind property. */ @Generated - private String kind; + private String kind = "Fish"; /* * The id property. @@ -76,7 +76,6 @@ void serializeAsJsonMergePatch(boolean jsonMergePatch) { */ @Generated public Fish() { - this.kind = "Fish"; this.updatedProperties.add("kind"); } diff --git a/typespec-tests/src/main/java/com/cadl/patch/models/SawShark.java b/typespec-tests/src/main/java/com/cadl/patch/models/SawShark.java new file mode 100644 index 0000000000..a7054da7f2 --- /dev/null +++ b/typespec-tests/src/main/java/com/cadl/patch/models/SawShark.java @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.cadl.patch.models; + +import com.azure.core.annotation.Fluent; +import com.azure.core.annotation.Generated; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import com.cadl.patch.implementation.JsonMergePatchHelper; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * The third level model SawShark in polymorphic multiple levels inheritance. + */ +@Fluent +public final class SawShark extends Shark { + /* + * The kind property. + */ + @Generated + private String kind = "shark"; + + /* + * The sharktype property. + */ + @Generated + private String sharktype = "saw"; + + @Generated + private boolean jsonMergePatch; + + /** + * Stores updated model property, the value is property name, not serialized name. + */ + @Generated + private final Set updatedProperties = new HashSet<>(); + + @Generated + void serializeAsJsonMergePatch(boolean jsonMergePatch) { + this.jsonMergePatch = jsonMergePatch; + } + + static { + JsonMergePatchHelper.setSawSharkAccessor((model, jsonMergePatchEnabled) -> { + model.serializeAsJsonMergePatch(jsonMergePatchEnabled); + return model; + }); + } + + /** + * Creates an instance of SawShark class. + */ + @Generated + public SawShark() { + this.updatedProperties.add("kind"); + this.updatedProperties.add("sharktype"); + } + + /** + * Get the sharktype property: The sharktype property. + * + * @return the sharktype value. + */ + @Generated + @Override + public String getSharktype() { + return this.sharktype; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public SawShark setWeight(Integer weight) { + super.setWeight(weight); + this.updatedProperties.add("weight"); + return this; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public SawShark setAge(int age) { + super.setAge(age); + this.updatedProperties.add("age"); + return this; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public SawShark setColor(String color) { + super.setColor(color); + this.updatedProperties.add("color"); + return this; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + if (jsonMergePatch) { + return toJsonMergePatch(jsonWriter); + } else { + jsonWriter.writeStartObject(); + jsonWriter.writeStringField("kind", this.kind); + jsonWriter.writeIntField("age", getAge()); + jsonWriter.writeStringField("color", getColor()); + jsonWriter.writeNumberField("weight", getWeight()); + jsonWriter.writeStringField("sharktype", this.sharktype); + return jsonWriter.writeEndObject(); + } + } + + @Generated + private JsonWriter toJsonMergePatch(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + if (updatedProperties.contains("kind")) { + if (this.kind == null) { + jsonWriter.writeNullField("kind"); + } else { + jsonWriter.writeStringField("kind", this.kind); + } + } + jsonWriter.writeIntField("age", getAge()); + if (updatedProperties.contains("color")) { + if (getColor() == null) { + jsonWriter.writeNullField("color"); + } else { + jsonWriter.writeStringField("color", getColor()); + } + } + if (updatedProperties.contains("weight")) { + if (getWeight() == null) { + jsonWriter.writeNullField("weight"); + } else { + jsonWriter.writeNumberField("weight", getWeight()); + } + } + if (updatedProperties.contains("sharktype")) { + if (this.sharktype == null) { + jsonWriter.writeNullField("sharktype"); + } else { + jsonWriter.writeStringField("sharktype", this.sharktype); + } + } + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of SawShark from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of SawShark if the JsonReader was pointing to an instance of it, or null if it was pointing + * to JSON null. + * @throws IllegalStateException If the deserialized JSON object was missing any required properties. + * @throws IOException If an error occurs while reading the SawShark. + */ + @Generated + public static SawShark fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + String id = null; + String name = null; + int age = 0; + String color = null; + Integer weight = null; + String sharktype = "saw"; + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("id".equals(fieldName)) { + id = reader.getString(); + } else if ("name".equals(fieldName)) { + name = reader.getString(); + } else if ("age".equals(fieldName)) { + age = reader.getInt(); + } else if ("color".equals(fieldName)) { + color = reader.getString(); + } else if ("weight".equals(fieldName)) { + weight = reader.getNullable(JsonReader::getInt); + } else if ("sharktype".equals(fieldName)) { + sharktype = reader.getString(); + } else { + reader.skipChildren(); + } + } + SawShark deserializedSawShark = new SawShark(); + deserializedSawShark.setId(id); + deserializedSawShark.setName(name); + deserializedSawShark.setAge(age); + deserializedSawShark.setColor(color); + deserializedSawShark.setWeight(weight); + deserializedSawShark.sharktype = sharktype; + + return deserializedSawShark; + }); + } +} diff --git a/typespec-tests/src/main/java/com/cadl/patch/models/Shark.java b/typespec-tests/src/main/java/com/cadl/patch/models/Shark.java index d9cad26df6..076865b730 100644 --- a/typespec-tests/src/main/java/com/cadl/patch/models/Shark.java +++ b/typespec-tests/src/main/java/com/cadl/patch/models/Shark.java @@ -18,13 +18,25 @@ * The second level model in polymorphic multiple levels inheritance and it defines a new discriminator. */ @Fluent -public final class Shark extends Fish { +public class Shark extends Fish { /* * The kind property. */ @Generated private String kind = "shark"; + /* + * The sharktype property. + */ + @Generated + private String sharktype = "shark"; + + /* + * The weight property. + */ + @Generated + private Integer weight; + @Generated private boolean jsonMergePatch; @@ -52,17 +64,40 @@ void serializeAsJsonMergePatch(boolean jsonMergePatch) { @Generated public Shark() { this.updatedProperties.add("kind"); + this.updatedProperties.add("sharktype"); } /** - * Get the kind property: The kind property. + * Get the sharktype property: The sharktype property. * - * @return the kind value. + * @return the sharktype value. */ @Generated - @Override - public String getKind() { - return this.kind; + public String getSharktype() { + return this.sharktype; + } + + /** + * Get the weight property: The weight property. + * + * @return the weight value. + */ + @Generated + public Integer getWeight() { + return this.weight; + } + + /** + * Set the weight property: The weight property. + * + * @param weight the weight value to set. + * @return the Shark object itself. + */ + @Generated + public Shark setWeight(Integer weight) { + this.weight = weight; + this.updatedProperties.add("weight"); + return this; } /** @@ -97,9 +132,11 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { return toJsonMergePatch(jsonWriter); } else { jsonWriter.writeStartObject(); + jsonWriter.writeStringField("kind", this.kind); jsonWriter.writeIntField("age", getAge()); jsonWriter.writeStringField("color", getColor()); - jsonWriter.writeStringField("kind", this.kind); + jsonWriter.writeStringField("sharktype", this.sharktype); + jsonWriter.writeNumberField("weight", this.weight); return jsonWriter.writeEndObject(); } } @@ -107,6 +144,13 @@ public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { @Generated private JsonWriter toJsonMergePatch(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStartObject(); + if (updatedProperties.contains("kind")) { + if (this.kind == null) { + jsonWriter.writeNullField("kind"); + } else { + jsonWriter.writeStringField("kind", this.kind); + } + } jsonWriter.writeIntField("age", getAge()); if (updatedProperties.contains("color")) { if (getColor() == null) { @@ -115,11 +159,18 @@ private JsonWriter toJsonMergePatch(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStringField("color", getColor()); } } - if (updatedProperties.contains("kind")) { - if (this.kind == null) { - jsonWriter.writeNullField("kind"); + if (updatedProperties.contains("sharktype")) { + if (this.sharktype == null) { + jsonWriter.writeNullField("sharktype"); } else { - jsonWriter.writeStringField("kind", this.kind); + jsonWriter.writeStringField("sharktype", this.sharktype); + } + } + if (updatedProperties.contains("weight")) { + if (this.weight == null) { + jsonWriter.writeNullField("weight"); + } else { + jsonWriter.writeNumberField("weight", this.weight); } } return jsonWriter.writeEndObject(); @@ -136,12 +187,39 @@ private JsonWriter toJsonMergePatch(JsonWriter jsonWriter) throws IOException { */ @Generated public static Shark fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + String discriminatorValue = null; + try (JsonReader readerToUse = reader.bufferObject()) { + readerToUse.nextToken(); // Prepare for reading + while (readerToUse.nextToken() != JsonToken.END_OBJECT) { + String fieldName = readerToUse.getFieldName(); + readerToUse.nextToken(); + if ("sharktype".equals(fieldName)) { + discriminatorValue = readerToUse.getString(); + break; + } else { + readerToUse.skipChildren(); + } + } + // Use the discriminator value to determine which subtype should be deserialized. + if ("saw".equals(discriminatorValue)) { + return SawShark.fromJson(readerToUse.reset()); + } else { + return fromJsonKnownDiscriminator(readerToUse.reset()); + } + } + }); + } + + @Generated + static Shark fromJsonKnownDiscriminator(JsonReader jsonReader) throws IOException { return jsonReader.readObject(reader -> { String id = null; String name = null; int age = 0; String color = null; - String kind = "shark"; + String sharktype = "shark"; + Integer weight = null; while (reader.nextToken() != JsonToken.END_OBJECT) { String fieldName = reader.getFieldName(); reader.nextToken(); @@ -154,8 +232,10 @@ public static Shark fromJson(JsonReader jsonReader) throws IOException { age = reader.getInt(); } else if ("color".equals(fieldName)) { color = reader.getString(); - } else if ("kind".equals(fieldName)) { - kind = reader.getString(); + } else if ("sharktype".equals(fieldName)) { + sharktype = reader.getString(); + } else if ("weight".equals(fieldName)) { + weight = reader.getNullable(JsonReader::getInt); } else { reader.skipChildren(); } @@ -165,7 +245,8 @@ public static Shark fromJson(JsonReader jsonReader) throws IOException { deserializedShark.setName(name); deserializedShark.setAge(age); deserializedShark.setColor(color); - deserializedShark.kind = kind; + deserializedShark.sharktype = sharktype; + deserializedShark.weight = weight; return deserializedShark; }); diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Dog.java b/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Dog.java index ac0b2b191b..d4f046b81f 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Dog.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Dog.java @@ -21,7 +21,7 @@ public class Dog implements JsonSerializable { * discriminator property */ @Generated - private DogKind kind; + private DogKind kind = DogKind.fromString("Dog"); /* * Weight of the dog @@ -36,7 +36,6 @@ public class Dog implements JsonSerializable { */ @Generated public Dog(int weight) { - this.kind = DogKind.fromString("Dog"); this.weight = weight; } diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Snake.java b/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Snake.java index 74c84ff808..cea781f69a 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Snake.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/enumdiscriminator/models/Snake.java @@ -21,7 +21,7 @@ public class Snake implements JsonSerializable { * discriminator property */ @Generated - private SnakeKind kind; + private SnakeKind kind = SnakeKind.fromString("Snake"); /* * Length of the snake @@ -36,7 +36,6 @@ public class Snake implements JsonSerializable { */ @Generated public Snake(int length) { - this.kind = SnakeKind.fromString("Snake"); this.length = length; } diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Fish.java b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Fish.java index fce0019c27..6fab81a60e 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Fish.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Fish.java @@ -21,7 +21,7 @@ public class Fish implements JsonSerializable { * The kind property. */ @Generated - private String kind; + private String kind = "Fish"; /* * The age property. @@ -36,7 +36,6 @@ public class Fish implements JsonSerializable { */ @Generated public Fish(int age) { - this.kind = "Fish"; this.age = age; } @@ -99,11 +98,7 @@ public static Fish fromJson(JsonReader jsonReader) throws IOException { } // Use the discriminator value to determine which subtype should be deserialized. if ("shark".equals(discriminatorValue)) { - return Shark.fromJsonKnownDiscriminator(readerToUse.reset()); - } else if ("saw".equals(discriminatorValue)) { - return SawShark.fromJson(readerToUse.reset()); - } else if ("goblin".equals(discriminatorValue)) { - return GoblinShark.fromJson(readerToUse.reset()); + return Shark.fromJson(readerToUse.reset()); } else if ("salmon".equals(discriminatorValue)) { return Salmon.fromJson(readerToUse.reset()); } else { diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/GoblinShark.java b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/GoblinShark.java index 0fc4bc3b8c..2fa8e54b95 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/GoblinShark.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/GoblinShark.java @@ -16,6 +16,12 @@ */ @Immutable public final class GoblinShark extends Shark { + /* + * The kind property. + */ + @Generated + private String kind = "shark"; + /* * The sharktype property. */ @@ -50,6 +56,7 @@ public String getSharktype() { @Override public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStartObject(); + jsonWriter.writeStringField("kind", this.kind); jsonWriter.writeIntField("age", getAge()); jsonWriter.writeStringField("sharktype", this.sharktype); return jsonWriter.writeEndObject(); diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/SawShark.java b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/SawShark.java index ef7305ac0f..d0345243a3 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/SawShark.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/SawShark.java @@ -16,6 +16,12 @@ */ @Immutable public final class SawShark extends Shark { + /* + * The kind property. + */ + @Generated + private String kind = "shark"; + /* * The sharktype property. */ @@ -50,6 +56,7 @@ public String getSharktype() { @Override public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStartObject(); + jsonWriter.writeStringField("kind", this.kind); jsonWriter.writeIntField("age", getAge()); jsonWriter.writeStringField("sharktype", this.sharktype); return jsonWriter.writeEndObject(); diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Shark.java b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Shark.java index 0d1e3edb01..83ba5196e2 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Shark.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/nesteddiscriminator/models/Shark.java @@ -16,6 +16,12 @@ */ @Immutable public class Shark extends Fish { + /* + * The kind property. + */ + @Generated + private String kind = "shark"; + /* * The sharktype property. */ @@ -30,7 +36,6 @@ public class Shark extends Fish { @Generated public Shark(int age) { super(age); - this.sharktype = "shark"; } /** @@ -50,6 +55,7 @@ public String getSharktype() { @Override public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { jsonWriter.writeStartObject(); + jsonWriter.writeStringField("kind", this.kind); jsonWriter.writeIntField("age", getAge()); jsonWriter.writeStringField("sharktype", this.sharktype); return jsonWriter.writeEndObject(); diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Bird.java b/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Bird.java index 8cd05d6b41..6855214445 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Bird.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Bird.java @@ -21,7 +21,7 @@ public class Bird implements JsonSerializable { * The kind property. */ @Generated - private String kind; + private String kind = "Bird"; /* * The wingspan property. @@ -36,7 +36,6 @@ public class Bird implements JsonSerializable { */ @Generated public Bird(int wingspan) { - this.kind = "Bird"; this.wingspan = wingspan; } diff --git a/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Dinosaur.java b/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Dinosaur.java index c95181e5d1..5c98891e39 100644 --- a/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Dinosaur.java +++ b/typespec-tests/src/main/java/com/type/model/inheritance/singlediscriminator/models/Dinosaur.java @@ -21,7 +21,7 @@ public class Dinosaur implements JsonSerializable { * The kind property. */ @Generated - private String kind; + private String kind = "Dinosaur"; /* * The size property. @@ -36,7 +36,6 @@ public class Dinosaur implements JsonSerializable { */ @Generated protected Dinosaur(int size) { - this.kind = "Dinosaur"; this.size = size; } diff --git a/typespec-tests/src/main/java/com/type/property/additionalproperties/models/ExtendsUnknownAdditionalPropertiesDiscriminated.java b/typespec-tests/src/main/java/com/type/property/additionalproperties/models/ExtendsUnknownAdditionalPropertiesDiscriminated.java index 877f500ab7..35812dc31f 100644 --- a/typespec-tests/src/main/java/com/type/property/additionalproperties/models/ExtendsUnknownAdditionalPropertiesDiscriminated.java +++ b/typespec-tests/src/main/java/com/type/property/additionalproperties/models/ExtendsUnknownAdditionalPropertiesDiscriminated.java @@ -24,7 +24,7 @@ public class ExtendsUnknownAdditionalPropertiesDiscriminated * The discriminator */ @Generated - private String kind; + private String kind = "ExtendsUnknownAdditionalPropertiesDiscriminated"; /* * The name property @@ -47,7 +47,6 @@ public class ExtendsUnknownAdditionalPropertiesDiscriminated */ @Generated public ExtendsUnknownAdditionalPropertiesDiscriminated(String name) { - this.kind = "ExtendsUnknownAdditionalPropertiesDiscriminated"; this.name = name; } diff --git a/typespec-tests/src/main/java/com/type/property/additionalproperties/models/IsUnknownAdditionalPropertiesDiscriminated.java b/typespec-tests/src/main/java/com/type/property/additionalproperties/models/IsUnknownAdditionalPropertiesDiscriminated.java index af90ebff52..2138a40b0c 100644 --- a/typespec-tests/src/main/java/com/type/property/additionalproperties/models/IsUnknownAdditionalPropertiesDiscriminated.java +++ b/typespec-tests/src/main/java/com/type/property/additionalproperties/models/IsUnknownAdditionalPropertiesDiscriminated.java @@ -24,7 +24,7 @@ public class IsUnknownAdditionalPropertiesDiscriminated * The discriminator */ @Generated - private String kind; + private String kind = "IsUnknownAdditionalPropertiesDiscriminated"; /* * The name property @@ -47,7 +47,6 @@ public class IsUnknownAdditionalPropertiesDiscriminated */ @Generated public IsUnknownAdditionalPropertiesDiscriminated(String name) { - this.kind = "IsUnknownAdditionalPropertiesDiscriminated"; this.name = name; } diff --git a/typespec-tests/tsp/patch.tsp b/typespec-tests/tsp/patch.tsp index 340aff2f9a..c641993e12 100644 --- a/typespec-tests/tsp/patch.tsp +++ b/typespec-tests/tsp/patch.tsp @@ -58,8 +58,16 @@ model Fish { } @doc("The second level model in polymorphic multiple levels inheritance and it defines a new discriminator.") +@discriminator("sharktype") model Shark extends Fish { kind: "shark"; + sharktype: string; + weight?: int32; +} + +@doc("The third level model SawShark in polymorphic multiple levels inheritance.") +model SawShark extends Shark { + sharktype: "saw"; } @doc("The second level model in polymorphic multiple levels inheritance which contains references to other polymorphic instances.") diff --git a/vanilla-tests/src/main/java/fixtures/bodycomplex/models/DotFish.java b/vanilla-tests/src/main/java/fixtures/bodycomplex/models/DotFish.java index 477544d3d9..683268c40e 100644 --- a/vanilla-tests/src/main/java/fixtures/bodycomplex/models/DotFish.java +++ b/vanilla-tests/src/main/java/fixtures/bodycomplex/models/DotFish.java @@ -19,7 +19,7 @@ public class DotFish implements JsonSerializable { /* * The fish.type property. */ - private String fishType; + private String fishType = "DotFish"; /* * The species property. @@ -30,7 +30,6 @@ public class DotFish implements JsonSerializable { * Creates an instance of DotFish class. */ protected DotFish() { - this.fishType = "DotFish"; } /** diff --git a/vanilla-tests/src/main/java/fixtures/bodycomplex/models/Fish.java b/vanilla-tests/src/main/java/fixtures/bodycomplex/models/Fish.java index 5b78180b6d..aaa347e2e5 100644 --- a/vanilla-tests/src/main/java/fixtures/bodycomplex/models/Fish.java +++ b/vanilla-tests/src/main/java/fixtures/bodycomplex/models/Fish.java @@ -20,7 +20,7 @@ public class Fish implements JsonSerializable { /* * The fishtype property. */ - private String fishtype; + private String fishtype = "Fish"; /* * The species property. @@ -43,7 +43,6 @@ public class Fish implements JsonSerializable { * @param length the length value to set. */ public Fish(float length) { - this.fishtype = "Fish"; this.length = length; } diff --git a/vanilla-tests/src/main/java/fixtures/bodycomplex/models/MyBaseType.java b/vanilla-tests/src/main/java/fixtures/bodycomplex/models/MyBaseType.java index 4a84d6f071..860a987c5c 100644 --- a/vanilla-tests/src/main/java/fixtures/bodycomplex/models/MyBaseType.java +++ b/vanilla-tests/src/main/java/fixtures/bodycomplex/models/MyBaseType.java @@ -19,7 +19,7 @@ public class MyBaseType implements JsonSerializable { /* * The kind property. */ - private MyKind kind; + private MyKind kind = MyKind.fromString("MyBaseType"); /* * The propB1 property. @@ -35,7 +35,6 @@ public class MyBaseType implements JsonSerializable { * Creates an instance of MyBaseType class. */ protected MyBaseType() { - this.kind = MyKind.fromString("MyBaseType"); } /** diff --git a/vanilla-tests/src/main/java/fixtures/discriminatorenum/models/Dog.java b/vanilla-tests/src/main/java/fixtures/discriminatorenum/models/Dog.java index 92837a0c12..0374dbc373 100644 --- a/vanilla-tests/src/main/java/fixtures/discriminatorenum/models/Dog.java +++ b/vanilla-tests/src/main/java/fixtures/discriminatorenum/models/Dog.java @@ -19,7 +19,7 @@ public class Dog implements JsonSerializable { /* * discriminator property */ - private DogKind kind; + private DogKind kind = DogKind.fromString("Dog"); /* * Weight of the dog @@ -30,7 +30,6 @@ public class Dog implements JsonSerializable { * Creates an instance of Dog class. */ public Dog() { - this.kind = DogKind.fromString("Dog"); } /** diff --git a/vanilla-tests/src/main/java/fixtures/discriminatorflattening/clientflatten/models/MetricAlertCriteria.java b/vanilla-tests/src/main/java/fixtures/discriminatorflattening/clientflatten/models/MetricAlertCriteria.java index 022e7cc77d..2c360e8cba 100644 --- a/vanilla-tests/src/main/java/fixtures/discriminatorflattening/clientflatten/models/MetricAlertCriteria.java +++ b/vanilla-tests/src/main/java/fixtures/discriminatorflattening/clientflatten/models/MetricAlertCriteria.java @@ -21,7 +21,7 @@ public class MetricAlertCriteria implements JsonSerializable { /* * discriminator property */ - private DogKind kind; + private DogKind kind = DogKind.fromString("Dog"); /* * Weight of the dog @@ -31,7 +31,6 @@ public class Dog implements JsonSerializable { * Creates an instance of Dog class. */ public Dog() { - this.kind = DogKind.fromString("Dog"); } /** diff --git a/vanilla-tests/src/main/java/fixtures/inheritance/passdiscriminator/models/MetricAlertCriteria.java b/vanilla-tests/src/main/java/fixtures/inheritance/passdiscriminator/models/MetricAlertCriteria.java index cd9d78c1ed..4418585c41 100644 --- a/vanilla-tests/src/main/java/fixtures/inheritance/passdiscriminator/models/MetricAlertCriteria.java +++ b/vanilla-tests/src/main/java/fixtures/inheritance/passdiscriminator/models/MetricAlertCriteria.java @@ -36,7 +36,7 @@ public class MetricAlertCriteria { */ @JsonTypeId @JsonProperty(value = "odata.type", required = true) - private Odatatype odataType; + private Odatatype odataType = Odatatype.fromString("MetricAlertCriteria"); /* * The rule criteria that defines the conditions of the alert rule. @@ -48,7 +48,6 @@ public class MetricAlertCriteria { * Creates an instance of MetricAlertCriteria class. */ public MetricAlertCriteria() { - this.odataType = Odatatype.fromString("MetricAlertCriteria"); } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/DotFish.java b/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/DotFish.java index 5c68eb9870..45795f136b 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/DotFish.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/DotFish.java @@ -19,7 +19,7 @@ public class DotFish implements JsonSerializable { /* * The fish.type property. */ - private String fishType; + private String fishType = "DotFish"; /* * The species property. @@ -30,7 +30,6 @@ public class DotFish implements JsonSerializable { * Creates an instance of DotFish class. */ public DotFish() { - this.fishType = "DotFish"; } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/Fish.java b/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/Fish.java index 2c2ad32163..496c25ee23 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/Fish.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/Fish.java @@ -20,7 +20,7 @@ public class Fish implements JsonSerializable { /* * The fishtype property. */ - private String fishtype; + private String fishtype = "Fish"; /* * The species property. @@ -41,7 +41,6 @@ public class Fish implements JsonSerializable { * Creates an instance of Fish class. */ public Fish() { - this.fishtype = "Fish"; } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/MyBaseType.java b/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/MyBaseType.java index d0497a5a66..aa6d5a488e 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/MyBaseType.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserialization/models/MyBaseType.java @@ -19,7 +19,7 @@ public class MyBaseType implements JsonSerializable { /* * The kind property. */ - private MyKind kind; + private MyKind kind = MyKind.fromString("MyBaseType"); /* * The propB1 property. @@ -35,7 +35,6 @@ public class MyBaseType implements JsonSerializable { * Creates an instance of MyBaseType class. */ public MyBaseType() { - this.kind = MyKind.fromString("MyBaseType"); } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/DotFish.java b/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/DotFish.java index cf23d48873..33598a7df8 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/DotFish.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/DotFish.java @@ -19,7 +19,7 @@ public class DotFish implements JsonSerializable { /* * The fish.type property. */ - private String fishType; + private String fishType = "DotFish"; /* * The species property. @@ -30,7 +30,6 @@ public class DotFish implements JsonSerializable { * Creates an instance of DotFish class. */ public DotFish() { - this.fishType = "DotFish"; } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/Fish.java b/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/Fish.java index 140dc61cab..0858834897 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/Fish.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/Fish.java @@ -20,7 +20,7 @@ public class Fish implements JsonSerializable { /* * The fishtype property. */ - private String fishtype; + private String fishtype = "Fish"; /* * The species property. @@ -43,7 +43,6 @@ public class Fish implements JsonSerializable { * @param length the length value to set. */ public Fish(float length) { - this.fishtype = "Fish"; this.length = length; } diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/MyBaseType.java b/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/MyBaseType.java index 5f422ce9e3..dab453466a 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/MyBaseType.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserializationctorargs/models/MyBaseType.java @@ -19,7 +19,7 @@ public class MyBaseType implements JsonSerializable { /* * The kind property. */ - private MyKind kind; + private MyKind kind = MyKind.fromString("MyBaseType"); /* * The propB1 property. @@ -35,7 +35,6 @@ public class MyBaseType implements JsonSerializable { * Creates an instance of MyBaseType class. */ public MyBaseType() { - this.kind = MyKind.fromString("MyBaseType"); } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/DotFish.java b/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/DotFish.java index 5a1490d3cf..0d4b41feef 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/DotFish.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/DotFish.java @@ -19,7 +19,7 @@ public class DotFish implements JsonSerializable { /* * The fish.type property. */ - private String fishType; + private String fishType = "DotFish"; /* * The species property. @@ -30,7 +30,6 @@ public class DotFish implements JsonSerializable { * Creates an instance of DotFish class. */ protected DotFish() { - this.fishType = "DotFish"; } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/Fish.java b/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/Fish.java index c8306ed967..d7866310e8 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/Fish.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/Fish.java @@ -20,7 +20,7 @@ public class Fish implements JsonSerializable { /* * The fishtype property. */ - private String fishtype; + private String fishtype = "Fish"; /* * The species property. @@ -41,7 +41,6 @@ public class Fish implements JsonSerializable { * Creates an instance of Fish class. */ public Fish() { - this.fishtype = "Fish"; } /** diff --git a/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/MyBaseType.java b/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/MyBaseType.java index 1a841fb8d2..d45fb6c7ce 100644 --- a/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/MyBaseType.java +++ b/vanilla-tests/src/main/java/fixtures/streamstyleserializationimmutableoutput/models/MyBaseType.java @@ -19,7 +19,7 @@ public class MyBaseType implements JsonSerializable { /* * The kind property. */ - private MyKind kind; + private MyKind kind = MyKind.fromString("MyBaseType"); /* * The propB1 property. @@ -35,7 +35,6 @@ public class MyBaseType implements JsonSerializable { * Creates an instance of MyBaseType class. */ protected MyBaseType() { - this.kind = MyKind.fromString("MyBaseType"); } /** From c27f32dc3a4bda45f3e3c4ad05a8593eca9ca811 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Wed, 22 May 2024 17:33:46 -0400 Subject: [PATCH 2/3] Fix tests --- typespec-tests/pom.xml | 14 +++++++ .../java/com/cadl/builtin/ExceptionTests.java | 40 +++--------------- .../java/com/cadl/flatten/FlattenTests.java | 41 +++++++++++-------- .../inheritance/NestedDiscriminatorTests.java | 6 +-- 4 files changed, 47 insertions(+), 54 deletions(-) diff --git a/typespec-tests/pom.xml b/typespec-tests/pom.xml index d26b385a20..985b2a3074 100644 --- a/typespec-tests/pom.xml +++ b/typespec-tests/pom.xml @@ -52,6 +52,20 @@ 4.11.0 test + + + + net.bytebuddy + byte-buddy + 1.14.12 + test + + + net.bytebuddy + byte-buddy-agent + 1.14.12 + test + io.projectreactor reactor-test diff --git a/typespec-tests/src/test/java/com/cadl/builtin/ExceptionTests.java b/typespec-tests/src/test/java/com/cadl/builtin/ExceptionTests.java index af8a7b73ee..a992f960e3 100644 --- a/typespec-tests/src/test/java/com/cadl/builtin/ExceptionTests.java +++ b/typespec-tests/src/test/java/com/cadl/builtin/ExceptionTests.java @@ -4,51 +4,23 @@ package com.cadl.builtin; import com.azure.core.exception.ResourceNotFoundException; -import com.azure.core.http.HttpClient; import com.azure.core.http.HttpHeaders; -import com.azure.core.http.HttpRequest; -import com.azure.core.http.HttpResponse; -import com.azure.core.models.ResponseError; -import org.junit.jupiter.api.Assertions; +import com.azure.core.test.http.MockHttpResponse; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; public class ExceptionTests { @Test public void testExceptionValue() { - HttpClient httpClient = Mockito.mock(HttpClient.class); - HttpResponse httpResponse = Mockito.mock(HttpResponse.class); - ArgumentCaptor httpRequest = ArgumentCaptor.forClass(HttpRequest.class); - String responseStr = "{\"error\":{\"code\":\"RESOURCE_NOT_FOUND\",\"message\":\"resource not found\"}}"; - Mockito.when(httpResponse.getStatusCode()).thenReturn(404); - Mockito.when(httpResponse.getHeaders()).thenReturn(new HttpHeaders()); - Mockito - .when(httpResponse.getBody()) - .thenReturn(Flux.just(ByteBuffer.wrap(responseStr.getBytes(StandardCharsets.UTF_8)))); - Mockito - .when(httpResponse.getBodyAsByteArray()) - .thenReturn(Mono.just(responseStr.getBytes(StandardCharsets.UTF_8))); - Mockito - .when(httpClient.send(httpRequest.capture(), Mockito.any())) - .thenReturn(Mono.defer( - () -> { - Mockito.when(httpResponse.getRequest()).thenReturn(httpRequest.getValue()); - return Mono.just(httpResponse); - })); - - BuiltinAsyncClient client = new BuiltinClientBuilder() - .endpoint("http://localhost:3000") - .httpClient(httpClient) - .buildAsyncClient(); + BuiltinAsyncClient client = new BuiltinClientBuilder().endpoint("http://localhost:3000") + .httpClient(request -> Mono.just(new MockHttpResponse(request, 404, new HttpHeaders(), + responseStr.getBytes(StandardCharsets.UTF_8)))) + .buildAsyncClient(); try { client.read("q", "q").block(); @@ -57,7 +29,7 @@ public void testExceptionValue() { //org.opentest4j.AssertionFailedError: //Expected :class com.azure.core.models.ResponseError //Actual :class java.util.LinkedHashMap -// Assertions.assertEquals(ResponseError.class, e.getValue().getClass()); + // Assertions.assertEquals(ResponseError.class, e.getValue().getClass()); } } } diff --git a/typespec-tests/src/test/java/com/cadl/flatten/FlattenTests.java b/typespec-tests/src/test/java/com/cadl/flatten/FlattenTests.java index ab84e538d2..82f84f1a0c 100644 --- a/typespec-tests/src/test/java/com/cadl/flatten/FlattenTests.java +++ b/typespec-tests/src/test/java/com/cadl/flatten/FlattenTests.java @@ -3,37 +3,46 @@ package com.cadl.flatten; -import com.azure.core.http.HttpHeaders; -import com.azure.core.http.rest.SimpleResponse; +import com.azure.core.http.HttpPipeline; +import com.azure.core.http.HttpPipelineBuilder; +import com.azure.core.test.http.MockHttpResponse; import com.azure.core.util.BinaryData; +import com.azure.core.util.UrlBuilder; import com.cadl.flatten.implementation.FlattenClientImpl; import com.cadl.flatten.models.User; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import reactor.core.publisher.Mono; +import java.util.concurrent.atomic.AtomicReference; + public class FlattenTests { @Test public void testFlatten() { - FlattenClientImpl impl = Mockito.mock(FlattenClientImpl.class); - ArgumentCaptor idCaptor = ArgumentCaptor.forClass(String.class); - ArgumentCaptor payloadCaptor = ArgumentCaptor.forClass(BinaryData.class); - Mockito.when(impl.sendWithResponseAsync(idCaptor.capture(), payloadCaptor.capture(), Mockito.any())) - .thenReturn(Mono.just(new SimpleResponse<>(null, 200, new HttpHeaders(), null))); - + AtomicReference idCaptor = new AtomicReference<>(); + AtomicReference payloadCaptor = new AtomicReference<>(); + + HttpPipeline pipeline = new HttpPipelineBuilder() + .httpClient(request -> { + payloadCaptor.set(request.getBodyAsBinaryData()); + idCaptor.set(UrlBuilder.parse(request.getUrl()).getQuery().get("id")); + return Mono.just(new MockHttpResponse(request, 200)); + }) + .build(); + FlattenClientImpl impl = new FlattenClientImpl(pipeline, "https://localhost", + FlattenServiceVersion.V2022_06_01_PREVIEW); FlattenAsyncClient client = new FlattenAsyncClient(impl); - client.send("id1", "input1", new User("user1")); + client.send("id1", "input1", new User("user1")).block(); - Assertions.assertEquals("id1", idCaptor.getValue()); - Assertions.assertEquals("{\"input\":\"input1\",\"constant\":\"constant\",\"user\":{\"user\":\"user1\"}}", payloadCaptor.getValue().toString()); + Assertions.assertEquals("id1", idCaptor.get()); + Assertions.assertEquals("{\"input\":\"input1\",\"constant\":\"constant\",\"user\":{\"user\":\"user1\"}}", + payloadCaptor.get().toString()); - client.send("id2", "input2"); + client.send("id2", "input2").block(); - Assertions.assertEquals("id2", idCaptor.getValue()); - Assertions.assertEquals("{\"input\":\"input2\",\"constant\":\"constant\"}", payloadCaptor.getValue().toString()); + Assertions.assertEquals("id2", idCaptor.get()); + Assertions.assertEquals("{\"input\":\"input2\",\"constant\":\"constant\"}", payloadCaptor.get().toString()); } } diff --git a/typespec-tests/src/test/java/com/type/model/inheritance/NestedDiscriminatorTests.java b/typespec-tests/src/test/java/com/type/model/inheritance/NestedDiscriminatorTests.java index ba86ac0a39..c4e2ef844f 100644 --- a/typespec-tests/src/test/java/com/type/model/inheritance/NestedDiscriminatorTests.java +++ b/typespec-tests/src/test/java/com/type/model/inheritance/NestedDiscriminatorTests.java @@ -30,7 +30,6 @@ void getModel() { } @Test - @Disabled("The item `kind` is missing in the generated json file by the method `toJson` of the class `Shark`.") void putModel() { Shark body = new GoblinShark(1); client.putModel(body); @@ -41,14 +40,13 @@ void getRecursiveModel() { Salmon salmon = (Salmon) client.getRecursiveModel(); Assertions.assertEquals(2, salmon.getFriends().size()); Assertions.assertEquals(2, salmon.getHate().size()); - Assertions.assertEquals(Shark.class, salmon.getPartner().getClass()); + Assertions.assertEquals(SawShark.class, salmon.getPartner().getClass()); Assertions.assertEquals(1, salmon.getAge()); Assertions.assertEquals(2, (salmon.getPartner()).getAge()); } @Test - @Disabled("The item `kind` is missing in the generated json file by the method `toJson` of the class `Shark`. ") void putRecursiveModel() { Salmon salmon = new Salmon(1); salmon.setPartner(new SawShark(2)); @@ -88,4 +86,4 @@ void getWrongDiscriminator() { Fish fish = client.getWrongDiscriminator(); Assertions.assertEquals(1, fish.getAge()); } -} \ No newline at end of file +} From 1f8fbcdf2526a997e872cbd106295ad6a6ee45c9 Mon Sep 17 00:00:00 2001 From: alzimmermsft <48699787+alzimmermsft@users.noreply.github.com> Date: Thu, 23 May 2024 15:35:13 -0400 Subject: [PATCH 3/3] Clean up diff in ModelMapper --- .../azure/autorest/mapper/ModelMapper.java | 511 +++++++++--------- 1 file changed, 267 insertions(+), 244 deletions(-) diff --git a/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java b/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java index ba34631214..6d8e7bfa42 100644 --- a/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java +++ b/javagen/src/main/java/com/azure/autorest/mapper/ModelMapper.java @@ -59,300 +59,323 @@ public ClientModel map(ObjectSchema compositeType) { ClassType modelType = objectMapper.map(compositeType); String modelName = modelType.getName(); ClientModel result = serviceModels.getModel(modelType.getName()); - - if (result != null || ObjectMapper.isPlainObject(compositeType)) { - return result; - } - - Set usages = SchemaUtil.mapSchemaContext(compositeType.getUsage()); - if (isPredefinedModel(modelType)) { - // TODO (weidxu): a more consistent handling of external model for all data-plane - if (settings.isDataPlaneClient()) { - usages = new HashSet<>(usages); - usages.add(ImplementationDetails.Usage.EXTERNAL); - } else { - // abort handling external model, if not DPG - // vanilla and fluent currently does not have mechanism to handle model that not to be outputted. - return result; + if (result == null && !ObjectMapper.isPlainObject(compositeType)) { + Set usages = SchemaUtil.mapSchemaContext(compositeType.getUsage()); + if (isPredefinedModel(modelType)) { + // TODO (weidxu): a more consistent handling of external model for all data-plane + if (settings.isDataPlaneClient()) { + usages = new HashSet<>(usages); + usages.add(ImplementationDetails.Usage.EXTERNAL); + } else { + // abort handling external model, if not DPG + // vanilla and fluent currently does not have mechanism to handle model that not to be outputted. + return result; + } } - } - ClientModel.Builder builder = createModelBuilder() - .name(modelName) - .packageName(modelType.getPackage()) - .type(modelType) - .stronglyTypedHeader(compositeType.isStronglyTypedHeader()) - .usedInXml(SchemaUtil.treatAsXml(compositeType)) - .serializationFormats(compositeType.getSerializationFormats()) - .implementationDetails(new ImplementationDetails.Builder() - .usages(usages) - .build()); - - boolean isPolymorphic = compositeType.getDiscriminator() != null || compositeType.getDiscriminatorValue() != null; - builder.polymorphic(isPolymorphic); - - HashSet modelImports = new HashSet<>(); - - String parentModelName = null; - boolean hasAdditionalProperties = false; - List parentsNeedFlatten = Collections.emptyList(); - if (compositeType.getParents() != null && compositeType.getParents().getImmediate() != null) { - hasAdditionalProperties = compositeType.getParents().getImmediate().stream() - .anyMatch(s -> s instanceof DictionarySchema); - - ParentSchemaInfo parentSchemaInfo = getParentSchemaInfo(compositeType); - if (parentSchemaInfo.hasParentSchema()) { - parentsNeedFlatten = parentSchemaInfo.getFlattenedParentSchemas(); - - ClassType parentType = objectMapper.map(parentSchemaInfo.getParentSchema()); - parentModelName = parentType.getName(); - modelImports.add(parentType.getPackage() + "." + parentModelName); + ClientModel.Builder builder = createModelBuilder().name(modelName) + .packageName(modelType.getPackage()) + .type(modelType) + .stronglyTypedHeader(compositeType.isStronglyTypedHeader()) + .usedInXml(SchemaUtil.treatAsXml(compositeType)) + .serializationFormats(compositeType.getSerializationFormats()) + .implementationDetails(new ImplementationDetails.Builder().usages(usages).build()); + + boolean isPolymorphic = compositeType.getDiscriminator() != null + || compositeType.getDiscriminatorValue() != null; + builder.polymorphic(isPolymorphic); + + HashSet modelImports = new HashSet<>(); + + String parentModelName = null; + boolean hasAdditionalProperties = false; + List parentsNeedFlatten = Collections.emptyList(); + if (compositeType.getParents() != null && compositeType.getParents().getImmediate() != null) { + hasAdditionalProperties = compositeType.getParents() + .getImmediate() + .stream() + .anyMatch(s -> s instanceof DictionarySchema); + + ParentSchemaInfo parentSchemaInfo = getParentSchemaInfo(compositeType); + if (parentSchemaInfo.hasParentSchema()) { + parentsNeedFlatten = parentSchemaInfo.getFlattenedParentSchemas(); + + ClassType parentType = objectMapper.map(parentSchemaInfo.getParentSchema()); + parentModelName = parentType.getName(); + modelImports.add(parentType.getPackage() + "." + parentModelName); + } } - } - builder.parentModelName(parentModelName); - - List compositeTypeProperties = compositeType.getProperties() - .stream().filter(p -> !p.isIsDiscriminator()).collect(Collectors.toList()); - if (!parentsNeedFlatten.isEmpty()) { - // Take properties from base class of multiple inheritance as properties of this class. - for (ObjectSchema parent : parentsNeedFlatten) { - compositeTypeProperties.addAll(parent.getProperties().stream() - .filter(p -> !p.isIsDiscriminator()) - .collect(Collectors.toList())); - if (parent.getParents() != null) { - compositeTypeProperties.addAll(parent.getParents().getAll().stream() - .filter(s -> s instanceof ObjectSchema) - .flatMap(s -> ((ObjectSchema) s).getProperties().stream()) + builder.parentModelName(parentModelName); + + List compositeTypeProperties = compositeType.getProperties() + .stream() + .filter(p -> !p.isIsDiscriminator()) + .collect(Collectors.toList()); + if (!parentsNeedFlatten.isEmpty()) { + // Take properties from base class of multiple inheritance as properties of this class. + for (ObjectSchema parent : parentsNeedFlatten) { + compositeTypeProperties.addAll(parent.getProperties() + .stream() .filter(p -> !p.isIsDiscriminator()) .collect(Collectors.toList())); + if (parent.getParents() != null) { + compositeTypeProperties.addAll(parent.getParents() + .getAll() + .stream() + .filter(s -> s instanceof ObjectSchema) + .flatMap(s -> ((ObjectSchema) s).getProperties().stream()) + .filter(p -> !p.isIsDiscriminator()) + .collect(Collectors.toList())); + } } } - } - for (Property autoRestProperty : compositeTypeProperties) { - IType propertyType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()); - if (!autoRestProperty.isRequired()) { - propertyType = propertyType.asNullable(); + for (Property autoRestProperty : compositeTypeProperties) { + IType propertyType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()); + if (!autoRestProperty.isRequired()) { + propertyType = propertyType.asNullable(); + } + propertyType.addImportsTo(modelImports, false); + + IType propertyClientType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()).getClientType(); + propertyClientType.addImportsTo(modelImports, false); } - propertyType.addImportsTo(modelImports, false); - IType propertyClientType = Mappers.getSchemaMapper().map(autoRestProperty.getSchema()).getClientType(); - propertyClientType.addImportsTo(modelImports, false); - } + boolean compositeTypeUsedWithXml = SchemaUtil.treatAsXml(compositeType); + if (!compositeTypeProperties.isEmpty()) { + if (compositeTypeUsedWithXml) { + modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement"); - boolean compositeTypeUsedWithXml = SchemaUtil.treatAsXml(compositeType); - if (!compositeTypeProperties.isEmpty()) { - if (compositeTypeUsedWithXml) { - modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement"); + if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema() instanceof ArraySchema)) { + modelImports.add(ArrayList.class.getName()); + } - if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema() instanceof ArraySchema)) { - modelImports.add(ArrayList.class.getName()); - } + if (compositeTypeProperties.stream().anyMatch(p -> { + if (p.getSchema().getSerialization() == null + || p.getSchema().getSerialization().getXml() == null) { + return false; + } - if (compositeTypeProperties.stream().anyMatch(p -> { - if (p.getSchema().getSerialization() == null || p.getSchema().getSerialization().getXml() == null) { - return false; + XmlSerializationFormat xmlSchema = p.getSchema().getSerialization().getXml(); + return xmlSchema.isAttribute() || xmlSchema.getNamespace() != null; + })) { + modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty"); } - XmlSerializationFormat xmlSchema = p.getSchema().getSerialization().getXml(); - return xmlSchema.isAttribute() || xmlSchema.getNamespace() != null; - })) { - modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty"); - } + if (compositeTypeProperties.stream().anyMatch(p -> { + if (p.getSchema().getSerialization() == null + || p.getSchema().getSerialization().getXml() == null) { + return false; + } - if (compositeTypeProperties.stream().anyMatch(p -> { - if (p.getSchema().getSerialization() == null || p.getSchema().getSerialization().getXml() == null) { - return false; + return p.getSchema().getSerialization().getXml().isText(); + })) { + modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText"); } - return p.getSchema().getSerialization().getXml().isText(); - })) { - modelImports.add("com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlText"); - } + if (compositeTypeProperties.stream() + .anyMatch(p -> p.getSchema().getSerialization() == null + || p.getSchema().getSerialization().getXml() == null || !p.getSchema() + .getSerialization() + .getXml() + .isAttribute())) { + modelImports.add(JsonProperty.class.getName()); + } + + if (compositeTypeProperties.stream() + .anyMatch(p -> p.getSchema().getSerialization() != null + && p.getSchema().getSerialization().getXml() != null && p.getSchema() + .getSerialization() + .getXml() + .isWrapped())) { + modelImports.add(JsonCreator.class.getName()); + } - if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema().getSerialization() == null - || p.getSchema().getSerialization().getXml() == null || !p.getSchema().getSerialization() - .getXml().isAttribute())) { + } else { modelImports.add(JsonProperty.class.getName()); } - - if (compositeTypeProperties.stream().anyMatch(p -> p.getSchema().getSerialization() != null - && p.getSchema().getSerialization().getXml() != null && p.getSchema().getSerialization().getXml().isWrapped())) { - modelImports.add(JsonCreator.class.getName()); + } + if (hasAdditionalProperties) { + for (Property property : compositeTypeProperties) { + if (property.getLanguage().getJava().getName().equals(PROPERTY_NAME_ADDITIONAL_PROPERTIES)) { + property.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES + "Property"); + } } + } + String summary = compositeType.getSummary(); + String description = compositeType.getLanguage().getJava() == null + ? null + : compositeType.getLanguage().getJava().getDescription(); + if (CoreUtils.isNullOrEmpty(summary) && CoreUtils.isNullOrEmpty(description)) { + builder.description(String.format("The %s model.", compositeType.getLanguage().getJava().getName())); } else { - modelImports.add(JsonProperty.class.getName()); + builder.description(SchemaUtil.mergeSummaryWithDescription(summary, description)); } - } - if (hasAdditionalProperties) { - for (Property property : compositeTypeProperties) { - if (property.getLanguage().getJava().getName().equals(PROPERTY_NAME_ADDITIONAL_PROPERTIES)) { - property.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES + "Property"); + + String modelSerializedName = compositeType.getDiscriminatorValue(); + if (modelSerializedName == null && compositeType.getLanguage().getDefault() != null) { + modelSerializedName = compositeType.getLanguage().getDefault().getName(); + } + builder.serializedName(modelSerializedName); + + List derivedTypes = new ArrayList<>(); + boolean hasChildren = compositeType.getChildren() != null + && compositeType.getChildren().getImmediate() != null; + if (hasChildren) { + for (Schema childSchema : compositeType.getChildren().getImmediate()) { + if (childSchema instanceof ObjectSchema) { + ClientModel model = this.map((ObjectSchema) childSchema); + derivedTypes.add(model); + } else { + throw new RuntimeException( + "Wait what? How? Child is not an object but a " + childSchema.getClass() + "?"); + } } } - } - - String summary = compositeType.getSummary(); - String description = compositeType.getLanguage().getJava() == null ? null : compositeType.getLanguage().getJava().getDescription(); - if (CoreUtils.isNullOrEmpty(summary) && CoreUtils.isNullOrEmpty(description)) { - builder.description(String.format("The %s model.", compositeType.getLanguage().getJava().getName())); - } else { - builder.description(SchemaUtil.mergeSummaryWithDescription(summary, description)); - } - - String modelSerializedName = compositeType.getDiscriminatorValue(); - if (modelSerializedName == null && compositeType.getLanguage().getDefault() != null) { - modelSerializedName = compositeType.getLanguage().getDefault().getName(); - } - builder.serializedName(modelSerializedName); - - List derivedTypes = new ArrayList<>(); - boolean hasChildren = compositeType.getChildren() != null && compositeType.getChildren().getImmediate() != null; - if (hasChildren) { - for (Schema childSchema : compositeType.getChildren().getImmediate()) { - if (childSchema instanceof ObjectSchema) { - ClientModel model = this.map((ObjectSchema) childSchema); - derivedTypes.add(model); + builder.derivedModels(derivedTypes); + + // Only configure XML information if XML is listed as one of the serialization formats in the ObjectSchema. + if (SchemaUtil.treatAsXml(compositeType)) { + boolean hasXmlFormat = compositeType.getSerialization() != null + && compositeType.getSerialization().getXml() != null; + if (hasXmlFormat) { + final XmlSerializationFormat xml = compositeType.getSerialization().getXml(); + String xmlName = CoreUtils.isNullOrEmpty(xml.getName()) ? compositeType.getLanguage() + .getDefault() + .getName() : xml.getName(); + builder.xmlName(xmlName); + builder.xmlNamespace(xml.getNamespace()); } else { - throw new RuntimeException("Wait what? How? Child is not an object but a " + childSchema.getClass() + "?"); + builder.xmlName(compositeType.getLanguage().getDefault().getName()); } } - } - builder.derivedModels(derivedTypes); - - // Only configure XML information if XML is listed as one of the serialization formats in the ObjectSchema. - if (SchemaUtil.treatAsXml(compositeType)) { - boolean hasXmlFormat = compositeType.getSerialization() != null - && compositeType.getSerialization().getXml() != null; - if (hasXmlFormat) { - final XmlSerializationFormat xml = compositeType.getSerialization().getXml(); - String xmlName = CoreUtils.isNullOrEmpty(xml.getName()) - ? compositeType.getLanguage().getDefault().getName() - : xml.getName(); - builder.xmlName(xmlName); - builder.xmlNamespace(xml.getNamespace()); - } else { - builder.xmlName(compositeType.getLanguage().getDefault().getName()); - } - } - - List properties = new ArrayList<>(); - boolean needsFlatten = false; - if (settings.getModelerSettings().isFlattenModel() // enabled by modelerfour - && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.TYPE) { - needsFlatten = hasFlattenedProperty(compositeType, parentsNeedFlatten); - } + List properties = new ArrayList<>(); - String polymorphicDiscriminator = null; - if (isPolymorphic) { - String discriminatorSerializedName = SchemaUtil.getDiscriminatorSerializedName(compositeType); - // Only escape the discriminator if the model will be flattened. - polymorphicDiscriminator = needsFlatten - ? discriminatorSerializedName.replace(".", "\\\\.") - : discriminatorSerializedName; - - final String finalPolymorphicDiscriminator = polymorphicDiscriminator; - ClientModelProperty discriminatorProperty = createDiscriminatorProperty( - settings, hasChildren, compositeType, - annotationArgs -> annotationArgs.replace(discriminatorSerializedName, finalPolymorphicDiscriminator), - polymorphicDiscriminator); - - if (discriminatorProperty != null) { - properties.add(discriminatorProperty); - - if (!settings.isStreamStyleSerialization()) { - modelImports.add(JsonTypeId.class.getName()); - } + boolean needsFlatten = false; + if (settings.getModelerSettings().isFlattenModel() // enabled by modelerfour + && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.TYPE) { + needsFlatten = hasFlattenedProperty(compositeType, parentsNeedFlatten); } - builder.polymorphicDiscriminatorName(polymorphicDiscriminator) - .polymorphicDiscriminator(discriminatorProperty); - } + String polymorphicDiscriminator = null; + if (isPolymorphic) { + String discriminatorSerializedName = SchemaUtil.getDiscriminatorSerializedName(compositeType); + // Only escape the discriminator if the model will be flattened. + polymorphicDiscriminator = needsFlatten + ? discriminatorSerializedName.replace(".", "\\\\.") + : discriminatorSerializedName; - builder.needsFlatten(needsFlatten); - builder.imports(new ArrayList<>(modelImports)); + final String finalPolymorphicDiscriminator = polymorphicDiscriminator; + ClientModelProperty discriminatorProperty = createDiscriminatorProperty(settings, hasChildren, + compositeType, annotationArgs -> annotationArgs.replace(discriminatorSerializedName, + finalPolymorphicDiscriminator), polymorphicDiscriminator); - final boolean mutablePropertyAsOptional = usages.contains(ImplementationDetails.Usage.JSON_MERGE_PATCH) && settings.isStreamStyleSerialization(); - List propertyReferences = new ArrayList<>(); - for (Property property : compositeTypeProperties) { - ClientModelProperty modelProperty = Mappers.getModelPropertyMapper().map(property, mutablePropertyAsOptional); - if (Objects.equals(polymorphicDiscriminator, modelProperty.getSerializedName())) { - // Discriminator is defined both as the discriminator and a property in the model. - // Make the discriminator property required if the property is required. But don't add the property - // again as it would result in two properties for the same serialized name. - properties.get(0).setRequired(modelProperty.isRequired()); - - // If the model has children models, copy the requirement logic to the children models with the same - // polymorphic discriminator. - // Passing from the parent is performed instead of children checking the parent as children will - // complete mapping before the parent. So, the parent is last to complete and the children models - // will be fully defined. If the inverse was done, children checking the parent, the parent would - // be null or an infinite loop would happen. - if (!CoreUtils.isNullOrEmpty(derivedTypes)) { - for (ClientModel derivedType : derivedTypes) { - if (Objects.equals(derivedType.getPolymorphicDiscriminator().getSerializedName(), polymorphicDiscriminator)) { - derivedType.getPolymorphicDiscriminator().setRequired(modelProperty.isRequired()); - } + if (discriminatorProperty != null) { + properties.add(discriminatorProperty); + + if (!settings.isStreamStyleSerialization()) { + modelImports.add(JsonTypeId.class.getName()); } } - continue; + builder.polymorphicDiscriminatorName(polymorphicDiscriminator) + .polymorphicDiscriminator(discriminatorProperty); } - properties.add(modelProperty); + builder.needsFlatten(needsFlatten); + builder.imports(new ArrayList<>(modelImports)); - if (modelProperty.getClientFlatten() && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.NONE) { - propertyReferences.addAll(collectPropertiesFromFlattenedModel(compositeType, property, modelProperty, propertyReferences)); - } - } + final boolean mutablePropertyAsOptional = usages.contains(ImplementationDetails.Usage.JSON_MERGE_PATCH) + && settings.isStreamStyleSerialization(); + List propertyReferences = new ArrayList<>(); + for (Property property : compositeTypeProperties) { + ClientModelProperty modelProperty = Mappers.getModelPropertyMapper() + .map(property, mutablePropertyAsOptional); + if (Objects.equals(polymorphicDiscriminator, modelProperty.getSerializedName())) { + // Discriminator is defined both as the discriminator and a property in the model. + // Make the discriminator property required if the property is required. But don't add the property + // again as it would result in two properties for the same serialized name. + properties.get(0).setRequired(modelProperty.isRequired()); + + // If the model has children models, copy the requirement logic to the children models with the same + // polymorphic discriminator. + // Passing from the parent is performed instead of children checking the parent as children will + // complete mapping before the parent. So, the parent is last to complete and the children models + // will be fully defined. If the inverse was done, children checking the parent, the parent would + // be null or an infinite loop would happen. + if (!CoreUtils.isNullOrEmpty(derivedTypes)) { + for (ClientModel derivedType : derivedTypes) { + if (Objects.equals(derivedType.getPolymorphicDiscriminator().getSerializedName(), + polymorphicDiscriminator)) { + derivedType.getPolymorphicDiscriminator().setRequired(modelProperty.isRequired()); + } + } + } - if (hasAdditionalProperties) { - DictionarySchema schema = (DictionarySchema) compositeType.getParents().getImmediate().stream() - .filter(s -> s instanceof DictionarySchema) - .findFirst() - .orElseThrow(() -> new IllegalStateException( - "Unable to find DictionarySchema for additional properties property.")); - Property additionalProperties = new Property(); - additionalProperties.setReadOnly(false); - additionalProperties.setSchema(schema); - additionalProperties.setSerializedName(""); - - additionalProperties.setLanguage(new Languages()); - additionalProperties.getLanguage().setJava(new Language()); - additionalProperties.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES); - String additionalPropertiesDescription = schema.getLanguage().getJava().getDescription(); - if (CoreUtils.isNullOrEmpty(additionalPropertiesDescription)) { - additionalPropertiesDescription = "Additional properties"; - } - additionalProperties.getLanguage().getJava().setDescription(additionalPropertiesDescription); + continue; + } - properties.add(Mappers.getModelPropertyMapper().map(additionalProperties)); - } + properties.add(modelProperty); - builder.properties(properties); - builder.propertyReferences(propertyReferences); - builder.crossLanguageDefinitionId(compositeType.getCrossLanguageDefinitionId()); + if (modelProperty.getClientFlatten() + && settings.getClientFlattenAnnotationTarget() == JavaSettings.ClientFlattenAnnotationTarget.NONE) { + propertyReferences.addAll( + collectPropertiesFromFlattenedModel(compositeType, property, modelProperty, + propertyReferences)); + } + } - result = builder.build(); + if (hasAdditionalProperties) { + DictionarySchema schema = (DictionarySchema) compositeType.getParents() + .getImmediate() + .stream() + .filter(s -> s instanceof DictionarySchema) + .findFirst() + .orElseThrow(() -> new IllegalStateException( + "Unable to find DictionarySchema for additional properties property.")); + Property additionalProperties = new Property(); + additionalProperties.setReadOnly(false); + additionalProperties.setSchema(schema); + additionalProperties.setSerializedName(""); + + additionalProperties.setLanguage(new Languages()); + additionalProperties.getLanguage().setJava(new Language()); + additionalProperties.getLanguage().getJava().setName(PROPERTY_NAME_ADDITIONAL_PROPERTIES); + String additionalPropertiesDescription = schema.getLanguage().getJava().getDescription(); + if (CoreUtils.isNullOrEmpty(additionalPropertiesDescription)) { + additionalPropertiesDescription = "Additional properties"; + } + additionalProperties.getLanguage().getJava().setDescription(additionalPropertiesDescription); - if (isPolymorphic && !CoreUtils.isNullOrEmpty(derivedTypes)) { - // Walk the polymorphic hierarchy finding places where the parent model and child model have different - // polymorphic discriminators. When this case is found add the parent polymorphic discriminator as a parent - // polymorphic discriminator to the child model. This is necessary to ensure that the child model generates - // the correct serialization in multi-level polymorphic structures. - for (ClientModel derivedType : derivedTypes) { - if (!Objects.equals(polymorphicDiscriminator, derivedType.getPolymorphicDiscriminatorName())) { - ClientModelProperty parentDiscriminator = result.getPolymorphicDiscriminator().newBuilder() - .defaultValue(derivedType.getPolymorphicDiscriminator().getDefaultValue()) - .build(); + properties.add(Mappers.getModelPropertyMapper().map(additionalProperties)); + } - passPolymorphicDiscriminatorToChildren(parentDiscriminator, derivedType); + builder.properties(properties); + builder.propertyReferences(propertyReferences); + builder.crossLanguageDefinitionId(compositeType.getCrossLanguageDefinitionId()); + + result = builder.build(); + + if (isPolymorphic && !CoreUtils.isNullOrEmpty(derivedTypes)) { + // Walk the polymorphic hierarchy finding places where the parent model and child model have different + // polymorphic discriminators. When this case is found add the parent polymorphic discriminator as a parent + // polymorphic discriminator to the child model. This is necessary to ensure that the child model generates + // the correct serialization in multi-level polymorphic structures. + for (ClientModel derivedType : derivedTypes) { + if (!Objects.equals(polymorphicDiscriminator, derivedType.getPolymorphicDiscriminatorName())) { + ClientModelProperty parentDiscriminator = result.getPolymorphicDiscriminator() + .newBuilder() + .defaultValue(derivedType.getPolymorphicDiscriminator().getDefaultValue()) + .build(); + + passPolymorphicDiscriminatorToChildren(parentDiscriminator, derivedType); + } } } - } - serviceModels.addModel(result); + serviceModels.addModel(result); + } return result; }