Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add model flattern #900

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.rosetta.model.lib.flatten;

import com.rosetta.model.lib.RosettaModelObject;
import com.rosetta.model.lib.RosettaModelObjectBuilder;
import com.rosetta.model.lib.meta.FieldWithMeta;
import com.rosetta.model.lib.path.RosettaPath;
import com.rosetta.model.lib.path.RosettaPathValue;
import com.rosetta.model.lib.process.AttributeMeta;
import com.rosetta.model.lib.process.BuilderProcessor;

import java.util.*;
import java.util.stream.Collectors;

/**
* Flattens a {@link RosettaModelObject} into a list of {@link RosettaPathValue} objects.
* This effectively transforms a nested object structure into a flat list of path-value pairs,
* omitting metadata fields.
*/
public class ModelObjectFlattener {

/**
* Flattens the provided RosettaModelObject.
*
* @param modelObject The RosettaModelObject to flatten.
* @return A list of RosettaPathValue objects representing the flattened object.
*/
public List<RosettaPathValue> flatten(RosettaModelObject modelObject) {
FlattenerBuilderProcessor processor = new FlattenerBuilderProcessor();
modelObject.toBuilder().process(RosettaPath.valueOf(modelObject.getType().getSimpleName()), processor);

List<RosettaPath> metaPaths = processor.getMetaPaths();
List<RosettaPathValue> pathValues = processor.getRosettaPathValue();

return removeAllMetaPaths(metaPaths, pathValues);
}

/**
* Removes all metadata paths from the provided list of RosettaPathValues.
*
* @param metaPaths The list of metadata paths to remove.
* @param pathValues The list of RosettaPathValues to filter.
* @return A new list of RosettaPathValues with metadata paths removed.
*/
private List<RosettaPathValue> removeAllMetaPaths(List<RosettaPath> metaPaths, List<RosettaPathValue> pathValues) {
return pathValues.stream()
.map(pathValue -> new RosettaPathValue(removeAllMetaPaths(metaPaths, pathValue.getPath()), pathValue.getValue()))
.collect(Collectors.toList());
}

/**
* Removes all metadata path segments from the provided RosettaPath.
*
* @param metaPaths The list of metadata paths to use for filtering.
* @param path The RosettaPath to filter.
* @return A new RosettaPath with metadata segments removed.
*/
private RosettaPath removeAllMetaPaths(List<RosettaPath> metaPaths, RosettaPath path) {
LinkedList<RosettaPath.Element> elements = path.allElements();
metaPaths.stream()
.filter(path::startsWith)
.map(RosettaPath::allElements)
.map(LinkedList::size)
.map(i -> i - 1)
.distinct()
.sorted(Comparator.reverseOrder())
.forEach(i -> elements.remove((int) i));

RosettaPath newPath = RosettaPath.createPathFromElements(elements);
return newPath.trimFirst();
}

/**
* A {@link BuilderProcessor} implementation used to extract path-value pairs and metadata paths
* during the flattening process.
*/
private static class FlattenerBuilderProcessor implements BuilderProcessor {

private final List<RosettaPathValue> rosettaPathValues = new ArrayList<>();
private final List<RosettaPath> metaPaths = new ArrayList<>();

/**
* Returns the list of identified metadata paths.
* @return The list of metadata paths.
*/
public List<RosettaPath> getMetaPaths() {
return metaPaths;
}

/**
* Returns the list of RosettaPathValue objects, excluding metadata paths.
* @return The list of RosettaPathValue objects.
*/
public List<RosettaPathValue> getRosettaPathValue() {
return rosettaPathValues;
}

@Override
public <R extends RosettaModelObject> boolean processRosetta(RosettaPath path, Class<R> rosettaType, RosettaModelObjectBuilder builder, RosettaModelObjectBuilder parent, AttributeMeta... metas) {
if (builder != null && parent instanceof FieldWithMeta) {
metaPaths.add(path);
}
return true;
}

@Override
public <R extends RosettaModelObject> boolean processRosetta(RosettaPath path, Class<R> rosettaType, List<? extends RosettaModelObjectBuilder> builders, RosettaModelObjectBuilder parent, AttributeMeta... metas) {
int i = 0;
for (RosettaModelObjectBuilder builder : builders) {
processRosetta(path.withIndex(i++), rosettaType, builder, parent, metas);
}
return true;
}

@Override
public <T> void processBasic(RosettaPath path, Class<T> rosettaType, T instance, RosettaModelObjectBuilder parent, AttributeMeta... metas) {
if (instance != null) {
if (parent instanceof FieldWithMeta) {
rosettaPathValues.add(new RosettaPathValue(path.getParent(), instance));
} else {
rosettaPathValues.add(new RosettaPathValue(path, instance));
}
}
}

@Override
public <T> void processBasic(RosettaPath path, Class<T> rosettaType, Collection<? extends T> instances, RosettaModelObjectBuilder parent, AttributeMeta... metas) {
int i = 0;
for (T instance : instances) {
processBasic(path.withIndex(i++), rosettaType, instance, parent, metas);
}
}

@Override
public Report report() {
return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,25 @@ public boolean containsPath(RosettaPath subPath) {
if (parent==null) return false;
return parent.containsPath(subPath);
}

public boolean startsWith(RosettaPath other) {
LinkedList<RosettaPath.Element> list = this.allElements();
LinkedList<RosettaPath.Element> prefix = other.allElements();
if (prefix == null || prefix.isEmpty()) {
return true;
}
if (list == null || list.size() < prefix.size()) {
return false;
}

for (int i = 0; i < prefix.size(); i++) {
if (!list.get(i).equals(prefix.get(i))) {
return false;
}
}

return true;
}

@Override
public boolean equals(Object o) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.rosetta.model.lib.path;

public class RosettaPathValue {
private final RosettaPath path;
private final Object value;

public RosettaPathValue(RosettaPath path, Object value) {
this.path = path;
this.value = value;
}

@Override
public String toString() {
return "FieldValue{" +
"path=" + path +
", value=" + value +
'}';
}

public RosettaPath getPath() {
return path;
}

public String getArrowPath() {
return String.join(" -> ", path.allElementPaths());
}

public Object getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.rosetta.model.lib.flatten;


import com.google.inject.Guice;
import com.google.inject.Injector;
import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper;
import com.rosetta.model.lib.RosettaModelObject;

import javax.inject.Inject;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;

/**
* Creates instances of Rosetta model objects from Rosetta code.
* This class uses the {@link CodeGeneratorTestHelper} to generate and compile
* Rosetta code, then uses reflection to invoke the generated "evaluate" method
* to create an instance of the model object.
*/
public class ModelInstanceCreator {

@Inject
private CodeGeneratorTestHelper helper;

/**
* Creates a RosettaModelObject instance from the provided Rosetta code file.
*
* @param loadFunction The fully qualified name of the static "evaluate" method
* that creates the model object instance. This method should
* be generated by the Rosetta code generator.
* @param rosettaFilePath The path to the Rosetta code file.
* @return An instance of the RosettaModelObject defined in the Rosetta code.
* @throws RuntimeException if there are any errors during code generation, compilation,
* or instantiation of the model object.
*/
public RosettaModelObject create(String loadFunction, Path rosettaFilePath) {
try {
String model = Files.readString(rosettaFilePath);
Map<String, String> code = helper.generateCode(model);
Map<String, Class<?>> classes = helper.compileToClasses(code);
Class<?> func = classes.get(String.format(loadFunction));
Injector injector = Guice.createInjector();
Object instance = injector.getInstance(func);
Method evaluate = instance.getClass().getMethod("evaluate");
return (RosettaModelObject) evaluate.invoke(instance);
} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException |
IOException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.rosetta.model.lib.flatten;

import com.regnosys.rosetta.tests.RosettaTestInjectorProvider;
import com.regnosys.rosetta.tests.util.CodeGeneratorTestHelper;
import com.rosetta.model.lib.RosettaModelObject;
import com.rosetta.model.lib.path.RosettaPathValue;
import org.eclipse.xtext.testing.InjectWith;
import org.eclipse.xtext.testing.extensions.InjectionExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import javax.inject.Inject;

import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;

import static com.google.common.collect.ImmutableMap.of;
import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(InjectionExtension.class)
@InjectWith(RosettaTestInjectorProvider.class)
class ModelObjectFlattenerTest {

@Inject
private CodeGeneratorTestHelper helper;

@Inject
private ModelInstanceCreator creator;
private ModelObjectFlattener modelObjectFlattener;

@BeforeEach
void setUp() {
modelObjectFlattener = new ModelObjectFlattener();
}

@Test
void flattenTest1() {
RosettaModelObject instance = creator.create("test.tabulator.functions.Data",
Path.of("src/test/resources/model-object-flattener-test/test-1.rosetta"));

List<RosettaPathValue> table = modelObjectFlattener.flatten(instance);
Map<String, Object> tableMap = table.stream()
.collect(Collectors.toMap(x -> x.getPath().buildPath(), RosettaPathValue::getValue));

assertEquals("VALUE1", tableMap.get("r1"));
assertEquals("VALUE2", tableMap.get("foo.f1"));
assertEquals("VALUE3", tableMap.get("foo.bar.b1"));
assertEquals("VALUE4", tableMap.get("foo.bar.b3(0)"));
assertEquals("VALUE5", tableMap.get("bar(0).b1"));
assertEquals("VALUE6", tableMap.get("bar(0).b2(0)"));
assertEquals("VALUE7", tableMap.get("bar(0).b2(1)"));
assertEquals("VALUE8", tableMap.get("bar(1).b1"));
assertEquals("VALUE9", tableMap.get("bar(1).b2(0)"));
assertEquals("VALUE10", tableMap.get("bar(1).b2(1)"));
assertEquals("VALUE11", tableMap.get("bar(1).b3(0)"));
assertEquals("VALUE12", tableMap.get("bar(1).b3(1)"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
namespace test.tabulator

type Root:
r1 string (0..1)
foo Foo (0..1)
bar Bar (0..*)

type Foo:
f1 string (0..1)
bar Bar (0..1)
[metadata reference]

type Bar:
[metadata key]
b1 string (0..1)
b2 string (0..*)
b3 string (0..*)
[metadata scheme]

func Data:
output:
result Root (1..1)

set result:
Root {
r1: "VALUE1",
foo: Foo {
f1: "VALUE2",
bar: Bar {
b1: "VALUE3",
b3: ["VALUE4"],
...
}},
bar: [
Bar {
b1: "VALUE5",
b2: ["VALUE6", "VALUE7"],
...
},
Bar {
b1: "VALUE8",
b2: ["VALUE9", "VALUE10"],
b3: ["VALUE11", "VALUE12"],
}
]
}
Loading