Skip to content

Commit

Permalink
Added "insert" methods to the data model for collections
Browse files Browse the repository at this point in the history
  • Loading branch information
EricWittmann committed Dec 2, 2024
1 parent 997b374 commit 313e4e1
Showing 10 changed files with 905 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ protected void createPropertyMethods(JavaSource<?> javaEntity, PropertyModelWith
createAddMethod(javaEntity, collectionPropertyWithOrigin);
createClearMethod(javaEntity, collectionPropertyWithOrigin);
createRemoveMethod(javaEntity, collectionPropertyWithOrigin);
createInsertMethod(javaEntity, collectionPropertyWithOrigin);
} else if (isPrimitive(property) || isPrimitiveList(property) || isPrimitiveMap(property)) {
createGetter(javaEntity, propertyWithOrigin);
createSetter(javaEntity, propertyWithOrigin);
@@ -62,6 +63,7 @@ protected void createPropertyMethods(JavaSource<?> javaEntity, PropertyModelWith
createAddMethod(javaEntity, propertyWithOrigin);
createClearMethod(javaEntity, propertyWithOrigin);
createRemoveMethod(javaEntity, propertyWithOrigin);
createInsertMethod(javaEntity, propertyWithOrigin);
} else if (isUnion(property)) {
createGetter(javaEntity, propertyWithOrigin);
createSetter(javaEntity, propertyWithOrigin);
@@ -171,7 +173,7 @@ protected void createFactoryMethod(JavaSource<?> javaEntity, PropertyType proper

/**
* Creates an "add" method for the given property. The type of the property must be a
* list of entities. The add method would accept a single entity and add it to the list.
* collection of entities. The add method will accept a single entity and add it to the collection.
* @param javaEntity
* @param propertyWithOrigin
*/
@@ -219,7 +221,7 @@ protected void createAddMethod(JavaSource<?> javaEntity, PropertyModelWithOrigin

/**
* Creates a "clear" method for the given property. The type of the property must be a
* list of entities. The clear method will remove all items from the list.
* collection of entities. The clear method will remove all items from the collection.
* @param javaEntity
* @param propertyWithOrigin
*/
@@ -237,8 +239,8 @@ protected void createClearMethod(JavaSource<?> javaEntity, PropertyModelWithOrig

/**
* Creates a "remove" method for the given property. The type of the property must be a
* list of entities. The remove method will remove one item from the list.
* @param entity
* collection of entities. The remove method will remove one item from the collection.
* @param javaEntity
* @param propertyWithOrigin
*/
protected void createRemoveMethod(JavaSource<?> javaEntity, PropertyModelWithOrigin propertyWithOrigin) {
@@ -266,6 +268,56 @@ protected void createRemoveMethod(JavaSource<?> javaEntity, PropertyModelWithOri
}
abstract protected void createRemoveMethodBody(PropertyModel property, MethodSource<?> method);

/**
* Creates an "insert" method for the given property. The type of the property must be a
* collection of entities. The insert method will add one item to the collection at
* a specific index (if possible).
* @param javaEntity
* @param propertyWithOrigin
*/
protected void createInsertMethod(JavaSource<?> javaEntity, PropertyModelWithOrigin propertyWithOrigin) {
PropertyModel property = propertyWithOrigin.getProperty();

String _package = propertyWithOrigin.getOrigin().getNamespace().fullName();
PropertyType type = property.getType().getNested().iterator().next();
String methodName = insertMethodName(singularize(property.getName()));
MethodSource<?> method;

if (type.isEntityType()) {
JavaInterfaceSource entityType = resolveJavaEntityType(_package, type);
if (entityType == null) {
error("Could not resolve entity type: " + _package + "::" + type);
return;
}

javaEntity.addImport(entityType);

method = ((MethodHolderSource<?>) javaEntity).addMethod().setPublic().setName(methodName).setReturnTypeVoid();
addAnnotations(method);
if (property.getType().isMap()) {
method.addParameter("String", "name");
}
method.addParameter(entityType.getName(), "value");
} else if (type.isPrimitiveType()) {
Class<?> primitiveType = primitiveTypeToClass(type);
javaEntity.addImport(primitiveType);

method = ((MethodHolderSource<?>) javaEntity).addMethod().setPublic().setName(methodName).setReturnTypeVoid();
addAnnotations(method);
if (property.getType().isMap()) {
method.addParameter("String", "name");
}
method.addParameter(primitiveType.getSimpleName(), "value");
} else {
warn("Type not supported for 'add' method: " + methodName + " with type: " + property.getType());
return;
}
method.addParameter("int", "atIndex");

createInsertMethodBody(javaEntity, property, method);
}
abstract protected void createInsertMethodBody(JavaSource<?> javaEntity, PropertyModel property, MethodSource<?> method);

/**
* Create factory methods for any entity types in the union. If the union is, for example, "boolean|string"
* then this will do nothing. But if the union is "Widget|string" then a factory method for Widgets will
Original file line number Diff line number Diff line change
@@ -177,6 +177,10 @@ protected String getRootNodeEntityClassFQN() {
return getState().getConfig().getRootNamespace() + ".RootNodeImpl";
}

protected String getDataModelUtilFQCN() {
return getState().getConfig().getRootNamespace() + ".util.DataModelUtil";
}

protected String getParentPropertyTypeEnumFQN() {
return getState().getConfig().getRootNamespace() + ".ParentPropertyType";
}
@@ -233,6 +237,18 @@ protected String addMethodName(String name) {
return "add" + StringUtils.capitalize(name);
}

protected String insertMethodName(EntityModel entityModel) {
return insertMethodName(entityModel.getName());
}

protected String insertMethodName(PropertyModel propertyModel) {
return insertMethodName(propertyModel.getName());
}

protected String insertMethodName(String name) {
return "insert" + StringUtils.capitalize(name);
}

protected String clearMethodName(EntityModel entityModel) {
return clearMethodName(entityModel.getName());
}
Original file line number Diff line number Diff line change
@@ -151,6 +151,34 @@ protected void createMappedNodeMethods(JavaSource<?> javaEntity, PropertyModelWi
method.setBody(body.toString());
}

// void insertItem(String name, T item, int atIndex)
{
MethodSource<?> method = ((MethodHolderSource<?>) javaEntity).addMethod().setName("insertItem").setPublic().setReturnTypeVoid();
method.addAnnotation(Override.class);
method.addParameter("String", "name");
method.addParameter(mappedNodeType, "item");
method.addParameter("int", "atIndex");

JavaClassSource dataModelUtilSource = getState().getJavaIndex().lookupClass(getDataModelUtilFQCN());
javaEntity.addImport(dataModelUtilSource);

BodyBuilder body = new BodyBuilder();
body.append("this._items = DataModelUtil.insertMapEntry(this._items, name, item, atIndex);");
if (isEntity(property)) {
JavaEnumSource parentPropertyTypeSource = getState().getJavaIndex().lookupEnum(getParentPropertyTypeEnumFQN());
javaEntity.addImport(parentPropertyTypeSource);
JavaClassSource nodeImplSource = getState().getJavaIndex().lookupClass(getNodeEntityClassFQN());
javaEntity.addImport(nodeImplSource);

body.append("if (item != null) {");
body.append(" ((NodeImpl) item)._setParentPropertyName(null);");
body.append(" ((NodeImpl) item)._setParentPropertyType(ParentPropertyType.map);");
body.append(" ((NodeImpl) item)._setMapPropertyName(name);");
body.append("}");
}
method.setBody(body.toString());
}

// T removeItem(String name)
{
MethodSource<?> method = ((MethodHolderSource<?>) javaEntity).addMethod().setName("removeItem").setPublic();
@@ -333,6 +361,69 @@ protected void createRemoveMethodBody(PropertyModel property, MethodSource<?> me
method.setBody(body.toString());
}

@Override
protected void createInsertMethodBody(JavaSource<?> javaEntity, PropertyModel property, MethodSource<?> method) {
PropertyType type = property.getType().getNested().iterator().next();
String fieldName = getFieldName(property);
String propertyName = property.getName();

BodyBuilder body = new BodyBuilder();
body.addContext("fieldName", fieldName);
body.addContext("propertyName", propertyName);

if (type.isEntityType() || type.isPrimitiveType()) {
if (property.getType().isMap()) {
JavaClassSource dataModelUtilSource = getState().getJavaIndex().lookupClass(getDataModelUtilFQCN());
javaEntity.addImport(dataModelUtilSource);
javaEntity.addImport(LinkedHashMap.class);

body.append("if (this.${fieldName} == null) {");
body.append(" this.${fieldName} = new LinkedHashMap<>();");
body.append(" this.${fieldName}.put(name, value);");
body.append("} else {");
body.append(" this.${fieldName} = DataModelUtil.insertMapEntry(this.${fieldName}, name, value, atIndex);");
body.append("}");

if (type.isEntityType()) {
JavaEnumSource parentPropertyTypeSource = getState().getJavaIndex().lookupEnum(getParentPropertyTypeEnumFQN());
javaEntity.addImport(parentPropertyTypeSource);
JavaClassSource nodeImplSource = getState().getJavaIndex().lookupClass(getNodeEntityClassFQN());
javaEntity.addImport(nodeImplSource);

body.append("if (value != null) {");
body.append(" ((NodeImpl) value)._setParentPropertyName(\"${propertyName}\");");
body.append(" ((NodeImpl) value)._setParentPropertyType(ParentPropertyType.map);");
body.append(" ((NodeImpl) value)._setMapPropertyName(name);");
body.append("}");
}
} else {
JavaClassSource dataModelUtilSource = getState().getJavaIndex().lookupClass(getDataModelUtilFQCN());
javaEntity.addImport(dataModelUtilSource);
javaEntity.addImport(ArrayList.class);

body.append("if (this.${fieldName} == null) {");
body.append(" this.${fieldName} = new ArrayList<>();");
body.append(" this.${fieldName}.add(value);");
body.append("} else {");
body.append(" this.${fieldName} = DataModelUtil.insertListEntry(this.${fieldName}, value, atIndex);");
body.append("}");
if (type.isEntityType()) {
JavaEnumSource parentPropertyTypeSource = getState().getJavaIndex().lookupEnum(getParentPropertyTypeEnumFQN());
javaEntity.addImport(parentPropertyTypeSource);
JavaClassSource nodeImplSource = getState().getJavaIndex().lookupClass(getNodeEntityClassFQN());
javaEntity.addImport(nodeImplSource);

body.append("if (value != null) {");
body.append(" ((NodeImpl) value)._setParentPropertyName(\"${propertyName}\");");
body.append(" ((NodeImpl) value)._setParentPropertyType(ParentPropertyType.array);");
body.append("}");
}
}
}

method.setBody(body.toString());
}

@Override
protected void addAnnotations(MethodSource<?> method) {
method.addAnnotation(Override.class);
Original file line number Diff line number Diff line change
@@ -131,4 +131,7 @@ protected void createClearMethodBody(PropertyModel property, MethodSource<?> met
protected void createRemoveMethodBody(PropertyModel property, MethodSource<?> method) {
}

@Override
protected void createInsertMethodBody(JavaSource<?> javaEntity, PropertyModel property, MethodSource<?> method) {
}
}
Original file line number Diff line number Diff line change
@@ -30,6 +30,7 @@ protected void doProcess() {
loadBaseClasses(
"io.apicurio.umg.base.NodeImpl",
"io.apicurio.umg.base.RootNodeImpl",
"io.apicurio.umg.base.util.DataModelUtil",
"io.apicurio.umg.base.util.JsonUtil",
"io.apicurio.umg.base.util.ReaderUtil",
"io.apicurio.umg.base.util.WriterUtil",
Original file line number Diff line number Diff line change
@@ -29,6 +29,15 @@ public interface MappedNode<T> {
*/
public void addItem(String name, T item);

/**
* Inserts a child item.
*
* @param name
* @param item
* @param atIndex
*/
public void insertItem(String name, T item, int atIndex);

/**
* Removes a child item by name and returns the deleted child or undefined if there wasn't one.
*
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.apicurio.umg.base.util;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

public class DataModelUtil {

public static <V> Map<String, V> insertMapEntry(Map<String, V> map, String key, V value, int atIndex) {
// If the new key is present, then return without modification.
if (map.containsKey(key)) {
return map;
}
// If the map isn't an LHM then ordering can't be maintained anyway
// If the atIndex is null then we're trying to undo a command that was persisted prior to this functionality being added
// If the atIndex is >= the map size it has to go at the end anyway
if (!(map instanceof LinkedHashMap) || atIndex >= map.size()){
map.put(key, value);
return map;
}

final LinkedHashMap<String, V> newMap = new LinkedHashMap<>();
// In order to maintain ordering of the elements when replacing a key in a LinkedHashMap,
// create a new instance and populate it in insertion order
int index = 0;
for (Map.Entry<String, V> entry : map.entrySet()) {
if (index++ == atIndex) {
newMap.put(key, value);
}
newMap.put(entry.getKey(), entry.getValue());
}
return newMap;
}

public static <V> List<V> insertListEntry(List<V> list, V value, int atIndex) {
if (atIndex >= list.size()) {
list.add(value);
} else if (atIndex < 0) {
list.add(0, value);
} else {
list.add(atIndex, value);
}
return list;
}

}
50 changes: 50 additions & 0 deletions generator/src/test/java/io/apicurio/umg/GeneratorTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.apicurio.umg;

import io.apicurio.umg.io.SpecificationLoader;
import io.apicurio.umg.models.spec.SpecificationModel;
import org.apache.commons.io.FileUtils;
import org.junit.Test;

import java.io.File;
import java.nio.file.Files;
import java.util.List;

public class GeneratorTest {

@Test
public void testGenerator() throws Exception {
File outputDir;

String outputDirPath = System.getenv("GENERATE_TEST_OUTPUT_DIR");
if (outputDirPath != null) {
outputDir = new File(outputDirPath);
outputDir.mkdirs();
} else {
outputDir = Files.createTempDirectory(GeneratorTest.class.getSimpleName()).toFile();
}

File umgTestOutputDir = Files.createTempDirectory(GeneratorTest.class.getSimpleName() + "-test").toFile();
UnifiedModelGeneratorConfig config = UnifiedModelGeneratorConfig.builder()
.outputDirectory(outputDir)
.testOutputDirectory(umgTestOutputDir)
.generateTestFixtures(false)
.rootNamespace("io.apicurio.umg.test").build();
// Load the specs
List<SpecificationModel> specs = List.of(
SpecificationLoader.loadSpec(GeneratorTest.class.getResource("openapi.yaml"))
);
// Create a unified model generator
UnifiedModelGenerator generator = new UnifiedModelGenerator(config, specs);
// Generate the source code into the target output directory.
try {
generator.generate();
} finally {
if (outputDirPath == null) {
FileUtils.deleteDirectory(outputDir);
FileUtils.deleteDirectory(umgTestOutputDir);
}
}

}

}
Loading

0 comments on commit 313e4e1

Please sign in to comment.