Skip to content

Commit

Permalink
implement RenamedClass token functionality (finos#2351)
Browse files Browse the repository at this point in the history
  • Loading branch information
mateusz-klatt authored Oct 10, 2023
1 parent cdda855 commit 9e9d3f4
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 0 deletions.
1 change: 1 addition & 0 deletions legend-engine-xts-changetoken/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For those kind of detected operations user confirmation should be required, in o

## Change Token Types
- **AddedClass** / **RemovedClass** - current no-op - should be emitted if a class of entity was added/removed.
- **RenamedClass** - token describing that a class was renamed or moved to another package.
- **AddField** / **RemoveField** - token describing that a field was added or removed to specific entity class.
This token should provide **fieldName**, **fieldType** and **class** properties together with **defaultValue**, of e.g. **ConstValue** **@type**.
The **value** should be of expected primitive or nested object type (in case of object type **@type** property is mandatory).
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// Copyright 2023 Goldman Sachs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.finos.legend.engine.changetoken.generation;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;

import static org.junit.Assert.assertThrows;

public class GenerateCastRenamedClassTest extends GenerateCastTestBase
{
@BeforeClass
public static void setupSuite() throws IOException, ClassNotFoundException
{
setupSuiteFromJson("{\n" +
" \"@type\": \"meta::pure::changetoken::Versions\",\n" +
" \"versions\": [\n" +
" {\n" +
" \"@type\": \"meta::pure::changetoken::Version\",\n" +
" \"version\": \"ftdm:abcdefg123\"\n" +
" },\n" +
" {\n" +
" \"@type\": \"meta::pure::changetoken::Version\",\n" +
" \"version\": \"ftdm:abcdefg456\",\n" +
" \"prevVersion\": \"ftdm:abcdefg123\",\n" +
" \"changeTokens\": [\n" +
" {\n" +
" \"@type\": \"meta::pure::changetoken::RenamedClass\",\n" +
" \"newName\": \"meta::pure::changetoken::tests::nested::NewSampleClass\",\n" +
" \"class\": \"meta::pure::changetoken::tests::SampleClass\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}\n");
}

@Test
public void testUpcast() throws JsonProcessingException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
Map<String,Object> jsonNode = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg123\", \n" +
" \"@type\": \"meta::pure::changetoken::tests::SampleClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::SampleClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"1d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::SampleClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"2d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::SampleClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"3d\"}}]\n" +
" ],\n" +
" \"abc\": {\"@type\":\"Custom\", \"value\":\"4d\"}\n" +
"}", Map.class);
Map<String,Object> jsonNodeOut = (Map<String,Object>) compiledClass.getMethod("upcast", Map.class).invoke(null, jsonNode);

Map<String,Object> expectedJsonNodeOut = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg456\",\n" +
" \"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"1d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"2d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"3d\"}}]\n" +
" ],\n" +
" \"abc\": {\"@type\":\"Custom\", \"value\":\"4d\"}\n" +
"}", Map.class); // updated version and new default value field added
Assert.assertEquals(expectedJsonNodeOut, jsonNodeOut);
}

@Test
public void testUpcastType() throws JsonProcessingException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
Map<String,Object> jsonNode = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg123\", \n" +
" \"@type\": \"meta::pure::changetoken::tests::OtherClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"1d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"2d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"3d\"}}]\n" +
" ],\n" +
" \"abc\": {\"@type\":\"Custom\", \"value\":\"4d\"}\n" +
"}", Map.class);
Map<String,Object> jsonNodeOut = (Map<String,Object>) compiledClass.getMethod("upcast", Map.class).invoke(null, jsonNode);

Map<String,Object> expectedJsonNodeOut = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg456\",\n" +
" \"@type\": \"meta::pure::changetoken::tests::OtherClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"1d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"2d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"abc\": {\"@type\":\"Custom\", \"value\":\"3d\"}}]\n" +
" ],\n" +
" \"abc\": {\"@type\":\"Custom\", \"value\":\"4d\"}\n" +
"}", Map.class); // updated version and new default value field added
Assert.assertEquals(expectedJsonNodeOut, jsonNodeOut);
}

@Test
public void testDowncast() throws JsonProcessingException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> jsonNode = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg456\",\n" +
" \"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"4d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"3d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::nested::NewSampleClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"2d\"}}]\n" +
" ],\n" +
" \"xyz\": {\"@type\":\"Custom\", \"value\":\"1d\"}\n" +
"}", Map.class);
Map<String,Object> jsonNodeOut = (Map<String,Object>) compiledClass.getMethod("downcast", Map.class, String.class)
.invoke(null, jsonNode, "ftdm:abcdefg123");
Map<String,Object> expectedJsonNodeOut = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg123\", \n" +
" \"@type\": \"meta::pure::changetoken::tests::SampleClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::SampleClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"4d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::SampleClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"3d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::SampleClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"2d\"}}]\n" +
" ],\n" +
" \"xyz\": {\"@type\":\"Custom\", \"value\":\"1d\"}\n" +
"}", Map.class); // remove default values
Assert.assertEquals(expectedJsonNodeOut, jsonNodeOut);
}

@Test
public void testDowncastType() throws JsonProcessingException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
{
ObjectMapper mapper = new ObjectMapper();
Map<String,Object> jsonNode = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg456\",\n" +
" \"@type\": \"meta::pure::changetoken::tests::OtherClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"4d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"3d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"2d\"}}]\n" +
" ],\n" +
" \"xyz\": {\"@type\":\"Custom\", \"value\":\"1d\"}\n" +
"}", Map.class);
Map<String,Object> jsonNodeOut = (Map<String,Object>) compiledClass.getMethod("downcast", Map.class, String.class)
.invoke(null, jsonNode, "ftdm:abcdefg123");
Map<String,Object> expectedJsonNodeOut = mapper.readValue(
"{\n" +
" \"version\":\"ftdm:abcdefg123\", \n" +
" \"@type\": \"meta::pure::changetoken::tests::OtherClass\",\n" +
" \"innerObject\": {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"4d\"}},\n" +
" \"innerNestedArray\":[\n" +
" {\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"3d\"}},\n" +
" [{\"@type\": \"meta::pure::changetoken::tests::OtherClass\", \"xyz\": {\"@type\":\"Custom\", \"value\":\"2d\"}}]\n" +
" ],\n" +
" \"xyz\": {\"@type\":\"Custom\", \"value\":\"1d\"}\n" +
"}", Map.class); // remove default values
Assert.assertEquals(expectedJsonNodeOut, jsonNodeOut);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ function <<access.private>> meta::pure::changetoken::cast_generation::_generateS
t:RenameField[1]|_handleRenameFieldUpcast($class2, $t, $objVar, $typeVar, $version.version, $pathVar, $rootObjVar, $resVar),
t:AddedClass[1]|[],
t:RemovedClass[1]|[],
t:RenamedClass[1]|_handleRenamedClassUpcast($class2, $t, $objVar, $typeVar, $version.version, $pathVar, $rootObjVar, $resVar),
t:ChangeFieldType[1]|_handleChangeFieldTypeUpcast($class2, $t, $objVar, $typeVar, $version.version, $pathVar, $rootObjVar, $resVar)
]))
->fold({t,res|$res->concatenate($t)}, []);
Expand Down Expand Up @@ -343,6 +344,7 @@ function <<access.private>> meta::pure::changetoken::cast_generation::_generateS
t:RenameField[1]|_handleRenameFieldDowncast($class2, $t, $objVar, $typeVar, $version.version, $pathVar, $rootObjVar, $resVar),
t:AddedClass[1]|[],
t:RemovedClass[1]|[],
t:RenamedClass[1]|_handleRenamedClassDowncast($class2, $t, $objVar, $typeVar, $version.version, $pathVar, $rootObjVar, $resVar),
t:ChangeFieldType[1]|_handleChangeFieldTypeDowncast($class2, $t, $objVar, $typeVar, $version.version, $pathVar, $rootObjVar, $resVar)
]))
->fold({t,res|$res->concatenate($t)}, []);
Expand Down Expand Up @@ -569,6 +571,31 @@ function <<access.private>> meta::pure::changetoken::cast_generation::_handleRen
);
}

function <<access.private>> meta::pure::changetoken::cast_generation::_handleRenamedClassUpcast
(
class:meta::external::language::java::metamodel::Class[1],
token:RenamedClass[1], objVar:Code[1], typeVar:Code[1], version:String[1], pathVar:Code[1], rootObjVar:Code[1], resVar:Code[1]
):Code[1]
{
// if (type.equals(token.class))
j_if(
$typeVar->j_invoke('equals', [j_string($token.class)]),
$objVar->j_invoke('put', [j_string('@type'), j_string($token.newName)])
);
}

function <<access.private>> meta::pure::changetoken::cast_generation::_handleRenamedClassDowncast
(
class:meta::external::language::java::metamodel::Class[1],
token:RenamedClass[1], objVar:Code[1], typeVar:Code[1], version:String[1], pathVar:Code[1], rootObjVar:Code[1], resVar:Code[1]
):Code[1]
{
// if (type.equals(token.newName))
j_if(
$typeVar->j_invoke('equals', [j_string($token.newName)]),
$objVar->j_invoke('put', [j_string('@type'), j_string($token.class)])
);
}

function <<access.private>> meta::pure::changetoken::cast_generation::_handleAddFieldUpcast
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ meta::pure::changetoken::RemovedClass extends ClassChangeToken
+ ')' }: String[1];
}

Class
{doc.doc='a class was renamed in this version'}
meta::pure::changetoken::RenamedClass extends ClassChangeToken
{
newName: String[1];

toString() { 'RenamedClass('
+ 'class=' + $this.class->toString()
+ 'newName=' + $this.newName->toString()
+ ')' }: String[1];
}

Class <<typemodifiers.abstract>>
{doc.doc='root of a hierarchy of getters for a default value of a newly-added field'}
meta::pure::changetoken::FieldGetter
Expand Down

0 comments on commit 9e9d3f4

Please sign in to comment.