From e3111a0ca61ce2da667cc8ec19f1b0f1912b1635 Mon Sep 17 00:00:00 2001 From: Jonathan-Casey <109082377+Jonathan-Casey@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:07:44 +0000 Subject: [PATCH] feat(map): Codegen for Java Target (#58) * feat(map): add map for javavisitor Signed-off-by: Jonathan Casey * feat(map): update snapshot Signed-off-by: Jonathan Casey * feat(map): add test cases Signed-off-by: Jonathan Casey * remove redundant ternary expression Signed-off-by: Jonathan Casey * feat(map): get type from ModelFile Signed-off-by: Jonathan Casey * chore(deps): upgrade concerto Signed-off-by: Jonathan Casey * fix(csharp):remove semicolon (;) from map property declaration (#60) fix(csharp):remove ; from map property declaration Signed-off-by: ragi.dayananda@docusign.com Signed-off-by: Jonathan Casey * chore: fix package version Signed-off-by: Jonathan Casey * chore(actions): publish v3.16.2 to npm (#61) Signed-off-by: GitHub Signed-off-by: Jonathan-Casey <109082377+Jonathan-Casey@users.noreply.github.com> Co-authored-by: Jonathan-Casey Co-authored-by: Jonathan-Casey <109082377+Jonathan-Casey@users.noreply.github.com> Signed-off-by: Jonathan Casey * feat(map): Codegen for GraphQL Target (#66) * feat(map): add graphqlvisitor Signed-off-by: Jonathan Casey * test(map): update snapshot Signed-off-by: Jonathan Casey * test(map): adds graphqlvisitor Signed-off-by: Jonathan Casey * test(map): restores sandbox Signed-off-by: Jonathan Casey --------- Signed-off-by: Jonathan Casey * feat!(codegen): remove loopback src (#59) feat(codegen): remove loopback src Signed-off-by: Jonathan Casey * chore(deps-dev): bump @babel/traverse from 7.22.11 to 7.23.2 (#67) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.11 to 7.23.2. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Jonathan Casey * test(codegen) update HR model to use multiple namespaces (#68) Signed-off-by: Dan Selman Signed-off-by: Jonathan Casey * feat(map): update snapshot Signed-off-by: Jonathan Casey * chore(deps): upgrade concerto Signed-off-by: Jonathan Casey * chore(deps): upgrade concerto Signed-off-by: Jonathan Casey * test(*): snapshot update Signed-off-by: Jonathan Casey * test(*): snapshot update Signed-off-by: Jonathan Casey * feat(map): write util import Signed-off-by: Jonathan Casey * test(*): fix regressions Signed-off-by: Jonathan Casey * test(*): add test coverage for map imports Signed-off-by: Jonathan Casey * test(*): uses isMap util fun Signed-off-by: Jonathan Casey --------- Signed-off-by: Jonathan Casey Signed-off-by: ragi.dayananda@docusign.com Signed-off-by: GitHub Signed-off-by: Jonathan-Casey <109082377+Jonathan-Casey@users.noreply.github.com> Signed-off-by: dependabot[bot] Signed-off-by: Dan Selman Co-authored-by: Ragi Dayananda <124162737+ragi-dayananda@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Jonathan-Casey Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Dan Selman --- lib/codegen/fromcto/java/javavisitor.js | 16 +++- test/codegen/__snapshots__/codegen.js.snap | 32 +++++--- .../codegen/fromcto/java/javamissingplugin.js | 14 +++- test/codegen/fromcto/java/javavisitor.js | 82 ++++++++++++++++++- 4 files changed, 129 insertions(+), 15 deletions(-) diff --git a/lib/codegen/fromcto/java/javavisitor.js b/lib/codegen/fromcto/java/javavisitor.js index cda5e914..08160891 100644 --- a/lib/codegen/fromcto/java/javavisitor.js +++ b/lib/codegen/fromcto/java/javavisitor.js @@ -169,6 +169,15 @@ class JavaVisitor { parameters.fileWriter.writeLine(0, `import ${namespace}.${typeName};` ); }); + let hasImportedJavaMap = false; + classDeclaration.getOwnProperties().forEach(p => { + if(ModelUtil.isMap(p) && !hasImportedJavaMap) { + parameters.fileWriter.writeLine(0, 'import java.util.HashMap;'); + parameters.fileWriter.writeLine(0, 'import java.util.Map;'); + hasImportedJavaMap = true; + } + }); + parameters.fileWriter.writeLine(0, 'import com.fasterxml.jackson.annotation.*;'); parameters.fileWriter.writeLine(0, ''); @@ -278,7 +287,12 @@ class JavaVisitor { parameters.fileWriter.writeLine(1, '}'); break; default: - parameters.fileWriter.writeLine(1, 'private ' + fieldType + ' ' + fieldName + ';' ); + if (ModelUtil.isMap(field)) { + const decl = field.getModelFile().getType(field.ast.type.name); + parameters.fileWriter.writeLine(1, `private Map<${this.toJavaType(decl.getKey().getType())}, ${this.toJavaType(decl.getValue().getType())}> ${field.getName()} = new HashMap<>();`); + } else { + parameters.fileWriter.writeLine(1, 'private ' + fieldType + ' ' + fieldName + ';' ); + } } } else { parameters.fileWriter.writeLine(1, 'private ' + fieldType + ' ' + fieldName + ';' ); diff --git a/test/codegen/__snapshots__/codegen.js.snap b/test/codegen/__snapshots__/codegen.js.snap index d0f07d5e..c83d6015 100644 --- a/test/codegen/__snapshots__/codegen.js.snap +++ b/test/codegen/__snapshots__/codegen.js.snap @@ -1065,17 +1065,19 @@ import concerto.Asset; import concerto.Transaction; import concerto.Participant; import concerto.Event; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.annotation.*; @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "$class") public class Company extends Concept { private String name; private Address headquarters; - private CompanyProperties companyProperties; - private EmployeeDirectory employeeDirectory; - private EmployeeTShirtSizes employeeTShirtSizes; - private EmployeeProfiles employeeProfiles; - private EmployeeSocialSecurityNumbers employeeSocialSecurityNumbers; + private Map companyProperties = new HashMap<>(); + private Map employeeDirectory = new HashMap<>(); + private Map employeeTShirtSizes = new HashMap<>(); + private Map employeeProfiles = new HashMap<>(); + private Map employeeSocialSecurityNumbers = new HashMap<>(); public String getName() { return this.name; } @@ -1253,6 +1255,8 @@ import concerto.Asset; import concerto.Transaction; import concerto.Participant; import concerto.Event; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.annotation.*; @JsonIgnoreProperties({"id"}) @@ -1272,7 +1276,7 @@ public abstract class Person extends Participant { private String ssn; private double height; private java.util.Date dob; - private NextOfKin nextOfKin; + private Map nextOfKin = new HashMap<>(); public String getEmail() { return this.email; } @@ -6613,17 +6617,19 @@ import concerto.Asset; import concerto.Transaction; import concerto.Participant; import concerto.Event; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.annotation.*; @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "$class") public class Company extends Concept { private String name; private Address headquarters; - private CompanyProperties companyProperties; - private EmployeeDirectory employeeDirectory; - private EmployeeTShirtSizes employeeTShirtSizes; - private EmployeeProfiles employeeProfiles; - private EmployeeSocialSecurityNumbers employeeSocialSecurityNumbers; + private Map companyProperties = new HashMap<>(); + private Map employeeDirectory = new HashMap<>(); + private Map employeeTShirtSizes = new HashMap<>(); + private Map employeeProfiles = new HashMap<>(); + private Map employeeSocialSecurityNumbers = new HashMap<>(); public String getName() { return this.name; } @@ -6801,6 +6807,8 @@ import concerto.Asset; import concerto.Transaction; import concerto.Participant; import concerto.Event; +import java.util.HashMap; +import java.util.Map; import com.fasterxml.jackson.annotation.*; @JsonIgnoreProperties({"id"}) @@ -6820,7 +6828,7 @@ public abstract class Person extends Participant { private String ssn; private double height; private java.util.Date dob; - private NextOfKin nextOfKin; + private Map nextOfKin = new HashMap<>(); public String getEmail() { return this.email; } diff --git a/test/codegen/fromcto/java/javamissingplugin.js b/test/codegen/fromcto/java/javamissingplugin.js index 7fed131c..8d6f3e34 100644 --- a/test/codegen/fromcto/java/javamissingplugin.js +++ b/test/codegen/fromcto/java/javamissingplugin.js @@ -20,11 +20,14 @@ const sinon = require('sinon'); const JavaVisitor = require('../../../../lib/codegen/fromcto/java/javavisitor.js'); const AbstractPlugin = require('../../../../lib/codegen/abstractplugin.js'); +const ModelUtil = require('@accordproject/concerto-core').ModelUtil; const ClassDeclaration = require('@accordproject/concerto-core').ClassDeclaration; const EnumDeclaration = require('@accordproject/concerto-core').EnumDeclaration; const FileWriter = require('@accordproject/concerto-util').FileWriter; +let sandbox = sinon.createSandbox(); + describe('JavaMissingPlugin', function () { let javaVisit; let mockFileWriter; @@ -115,6 +118,15 @@ describe('JavaMissingPlugin', function () { sinon.stub(javaVisit, 'startClassFile'); sinon.stub(javaVisit, 'endClassFile'); + + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + }); + + afterEach(() => { + sandbox.restore(); + }); it('should fail to write a class declaration and call accept on each property', () => { @@ -126,4 +138,4 @@ describe('JavaMissingPlugin', function () { }); }); -}); \ No newline at end of file +}); diff --git a/test/codegen/fromcto/java/javavisitor.js b/test/codegen/fromcto/java/javavisitor.js index a7bca99d..7ed6f004 100644 --- a/test/codegen/fromcto/java/javavisitor.js +++ b/test/codegen/fromcto/java/javavisitor.js @@ -21,6 +21,8 @@ const sinon = require('sinon'); const JavaVisitor = require('../../../../lib/codegen/fromcto/java/javavisitor.js'); const ClassDeclaration = require('@accordproject/concerto-core').ClassDeclaration; +const MapDeclaration = require('@accordproject/concerto-core').MapDeclaration; +const ModelUtil = require('@accordproject/concerto-core').ModelUtil; const EnumDeclaration = require('@accordproject/concerto-core').EnumDeclaration; const EnumValueDeclaration = require('@accordproject/concerto-core').EnumValueDeclaration; const Field = require('@accordproject/concerto-core').Field; @@ -29,12 +31,21 @@ const ModelManager = require('@accordproject/concerto-core').ModelManager; const RelationshipDeclaration = require('@accordproject/concerto-core').RelationshipDeclaration; const FileWriter = require('@accordproject/concerto-util').FileWriter; +let sandbox = sinon.createSandbox(); + describe('JavaVisitor', function () { let javaVisit; let mockFileWriter; beforeEach(() => { javaVisit = new JavaVisitor(); mockFileWriter = sinon.createStubInstance(FileWriter); + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return false; + }); + }); + + afterEach(() => { + sandbox.restore(); }); describe('visit', () => { @@ -376,6 +387,36 @@ describe('JavaVisitor', function () { acceptSpy.withArgs(javaVisit, Object.assign({},param,{mode:'setter'})).calledTwice.should.be.ok; mockEndClassFile.withArgs(mockClassDeclaration, param).calledOnce.should.be.ok; }); + + it('should write a class declaration, including imports for java.util Map & HashMap types', () => { + mockClassDeclaration.getIdentifierFieldName.returns('employeeID'); + sandbox.restore(); + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + javaVisit.visitClassDeclaration(mockClassDeclaration, param); + + mockStartClassFile.withArgs(mockClassDeclaration, param).calledOnce.should.be.ok; + param.fileWriter.writeLine.callCount.should.deep.equal(9); + param.fileWriter.writeLine.getCall(0).args.should.deep.equal([0, 'import fruit.oranges;']); + param.fileWriter.writeLine.getCall(1).args.should.deep.equal([0, 'import fruit.apples;']); + param.fileWriter.writeLine.getCall(2).args.should.deep.equal([0, 'import java.util.HashMap;']); + param.fileWriter.writeLine.getCall(3).args.should.deep.equal([0, 'import java.util.Map;']); + param.fileWriter.writeLine.getCall(4).args.should.deep.equal([0, 'import com.fasterxml.jackson.annotation.*;']); + param.fileWriter.writeLine.getCall(5).args.should.deep.equal([0, '']); + param.fileWriter.writeLine.getCall(6).args.should.deep.equal([0, 'public class Bob {']); + param.fileWriter.writeLine.getCall(7).args.should.deep.equal([1, ` + // the accessor for the identifying field + public String getID() { + return this.getEmployeeID(); + } +`]); + param.fileWriter.writeLine.getCall(8).args.should.deep.equal([0, '}']); + acceptSpy.withArgs(javaVisit, Object.assign({},param,{mode:'field'})).calledTwice.should.be.ok; + acceptSpy.withArgs(javaVisit, Object.assign({},param,{mode:'getter'})).calledTwice.should.be.ok; + acceptSpy.withArgs(javaVisit, Object.assign({},param,{mode:'setter'})).calledTwice.should.be.ok; + mockEndClassFile.withArgs(mockClassDeclaration, param).calledOnce.should.be.ok; + }); }); describe('visitField', () => { @@ -413,6 +454,45 @@ describe('JavaVisitor', function () { param.fileWriter.writeLine.withArgs(1, 'private JavaType[] Bob;').calledOnce.should.be.ok; }); + it('should write a line with a HashMap', () => { + let param = { + fileWriter: mockFileWriter, + mode: 'field' + }; + + sandbox.restore(); + sandbox.stub(ModelUtil, 'isMap').callsFake(() => { + return true; + }); + + const mockField = sinon.createStubInstance(Field); + const getType = sinon.stub(); + + mockField.ast = { type: { name: 'Dummy Value'} }; + mockField.getModelFile.returns({ getType: getType }); + + const mockMapDeclaration = sinon.createStubInstance(MapDeclaration); + const getKeyType = sinon.stub(); + const getValueType = sinon.stub(); + + mockField.getName.returns('Bob'); + mockField.getType.returns('SpecialType'); + + getType.returns(mockMapDeclaration); + getKeyType.returns('String'); + getValueType.returns('String'); + mockField.getName.returns('Map1'); + mockMapDeclaration.getName.returns('Map1'); + mockMapDeclaration.isMapDeclaration.returns(true); + mockMapDeclaration.getKey.returns({ getType: getKeyType }); + mockMapDeclaration.getValue.returns({ getType: getValueType }); + + javaVisit.visitField(mockField,param); + + param.fileWriter.writeLine.withArgs(1, 'private Map Map1 = new HashMap<>();').calledOnce.should.be.ok; + sandbox.reset(); + }); + it('should write a line defining a field', () => { let mockField = sinon.createStubInstance(Field); mockField.isField.returns(true); @@ -673,4 +753,4 @@ describe('JavaVisitor', function () { javaVisit.toJavaType('Penguin').should.deep.equal('Penguin'); }); }); -}); \ No newline at end of file +});