diff --git a/src/main/java/ch/geowerkstatt/lk2dxf/DxfWriter.java b/src/main/java/ch/geowerkstatt/lk2dxf/DxfWriter.java index fbce5df..09f9be1 100644 --- a/src/main/java/ch/geowerkstatt/lk2dxf/DxfWriter.java +++ b/src/main/java/ch/geowerkstatt/lk2dxf/DxfWriter.java @@ -288,6 +288,7 @@ public void writeText(String layerName, String textStyle, String text, String hA var isDefaultAlignment = hAlignmentValue == 0 && vAlignmentValue == 0; orientation = convertOrientation(orientation); + text = text.replace("\n", "\\U+000A").replace("\r", "\\U+000D").replace("\t", "\\U+0009"); writeElement(0, "TEXT"); writeElement(5, getNextHandle()); diff --git a/src/main/java/ch/geowerkstatt/lk2dxf/Main.java b/src/main/java/ch/geowerkstatt/lk2dxf/Main.java index d0354a6..550cf53 100644 --- a/src/main/java/ch/geowerkstatt/lk2dxf/Main.java +++ b/src/main/java/ch/geowerkstatt/lk2dxf/Main.java @@ -55,11 +55,17 @@ public static void main(String[] args) { private static void processFiles(LK2DxfOptions options) { Optional perimeter = options.parsePerimeter(); - try (var dxfWriter = new DxfWriter(options.dxfFile(), 3, ObjectMapper.getLayerMappings(), "lk2dxf " + Main.VERSION)) { + ObjectMapper objectMapper; + try { + objectMapper = new ObjectMapper(); + } catch (Exception e) { + throw new RuntimeException("Failed to read layer mappings.", e); + } + + try (var dxfWriter = new DxfWriter(options.dxfFile(), 3, objectMapper.getLayerMappings(), "lk2dxf " + Main.VERSION)) { for (String xtfFile : options.xtfFiles()) { try (XtfStreamReader reader = new XtfStreamReader(new File(xtfFile))) { - ObjectMapper mapper = new ObjectMapper(); - Stream objects = mapper.mapObjects(reader.readObjects()); + Stream objects = objectMapper.mapObjects(reader.readObjects()); if (perimeter.isPresent()) { objects = objects.filter(o -> perimeter.get().intersects(o.geometry())); diff --git a/src/main/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapper.java b/src/main/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapper.java index ac78477..5e63b42 100644 --- a/src/main/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapper.java +++ b/src/main/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapper.java @@ -33,30 +33,37 @@ public final class ObjectMapper { private static final String MODELS_RESOURCE = "/models"; - private static final List LAYER_MAPPINGS; - private static final TransferDescription TRANSFER_DESCRIPTION; + private final List layerMappings; + private final TransferDescription transferDescription; - private static final Map, Set> CACHE_REQUIREMENTS = new HashMap, Set>(); - private static final List FILTERS; + private final Map, Set> cacheRequirements = new HashMap<>(); + private final List filters = new ArrayList<>(); - static { - try { - LAYER_MAPPINGS = MappingReader.readMappings(); - TRANSFER_DESCRIPTION = getTransferDescription(); - FILTERS = analyzeLayerMappings(); - } catch (Exception e) { - throw new RuntimeException("Failed to read layer mappings.", e); - } + /** + * Create a new {@link ObjectMapper} with the default mappings. + */ + public ObjectMapper() throws IOException, URISyntaxException, Ili2cException { + this(MappingReader.readMappings()); } - private final Map objectCache = new HashMap<>(); - private final Set objectsWithUnresolvedRef = new HashSet<>(); + /** + * Create a new {@link ObjectMapper} with the given layer mappings. + * + * @param layerMappings The layer mappings to use. + */ + public ObjectMapper(List layerMappings) throws IOException, URISyntaxException, Ili2cException { + this.layerMappings = layerMappings; + transferDescription = getTransferDescription(layerMappings); + analyzeLayerMappings(); + } - private static List analyzeLayerMappings() { - var mappers = new ArrayList(); - for (LayerMapping layerMapping : ObjectMapper.LAYER_MAPPINGS) { + /** + * Analyze the layer mappings and populate the {@link #filters} and {@link #cacheRequirements}. + */ + private void analyzeLayerMappings() { + for (LayerMapping layerMapping : layerMappings) { for (String objectClass : layerMapping.objectClass()) { - var element = TRANSFER_DESCRIPTION.getElement(objectClass); + var element = transferDescription.getElement(objectClass); if (element == null) { throw new IllegalArgumentException("No element found for object with id \"" + objectClass + "\"."); } @@ -70,10 +77,10 @@ private static List analyzeLayerMappings() { for (var baseAttributeName : layerMapping.mapping().keySet()) { var values = layerMapping.mapping().get(baseAttributeName); var pathElements = getTranslatedPath(classDef, Arrays.asList(baseAttributeName.split("->"))); - analyzeCacheRequirements(pathElements, CACHE_REQUIREMENTS); + analyzeCacheRequirements(pathElements, cacheRequirements); var type = ((AttributeDef) pathElements.getLast().element).getDomainResolvingAliases(); if (!(type instanceof EnumerationType enumerationType)) { - throw new IllegalArgumentException("Only enumeratino types supported" + baseAttributeName); + throw new IllegalArgumentException("Only enumeration types supported: " + baseAttributeName); } var attrFilter = new PathMatcher(pathElements, values.stream().map(v -> getTranslatedEnumValue(enumerationType, v)).toList()); @@ -84,37 +91,36 @@ private static List analyzeLayerMappings() { var mapper = switch (layerMapping.output()) { case SURFACE, LINE -> new Mapper(filter, layerMapping, - getTranslatedPath(classDef, layerMapping.geometry()), + getAndAnalyzeTranslatedPath(classDef, layerMapping.geometry()), null, null, null, null); case TEXT -> new Mapper(filter, layerMapping, - getTranslatedPath(classDef, layerMapping.geometry()), - getTranslatedPath(classDef, layerMapping.orientation()), - getTranslatedPath(classDef, layerMapping.vAlign()), - getTranslatedPath(classDef, layerMapping.hAlign()), - getTranslatedPath(classDef, layerMapping.text())); + getAndAnalyzeTranslatedPath(classDef, layerMapping.geometry()), + getAndAnalyzeTranslatedPath(classDef, layerMapping.orientation()), + getAndAnalyzeTranslatedPath(classDef, layerMapping.vAlign()), + getAndAnalyzeTranslatedPath(classDef, layerMapping.hAlign()), + getAndAnalyzeTranslatedPath(classDef, layerMapping.text())); case POINT -> new Mapper(filter, layerMapping, - getTranslatedPath(classDef, layerMapping.geometry()), - getTranslatedPath(classDef, layerMapping.orientation()), + getAndAnalyzeTranslatedPath(classDef, layerMapping.geometry()), + getAndAnalyzeTranslatedPath(classDef, layerMapping.orientation()), null, null, null); }; - mappers.add(mapper); + filters.add(mapper); } } - return mappers; } /** * Get the layer mappings as an immutable list. */ - public static List getLayerMappings() { - return Collections.unmodifiableList(LAYER_MAPPINGS); + public List getLayerMappings() { + return Collections.unmodifiableList(layerMappings); } private static boolean matchesEnumSubValue(List allowedValues, String attrValue) { @@ -180,7 +186,7 @@ private static Value resolve(IomObject iomObject, List path, Map path, Map, Set> cacheRequirements) { for (int i = 0; i < path.size(); i++) { @@ -197,10 +203,13 @@ private static void analyzeCacheRequirements(List path, Map getTranslatedPath(AbstractClassDef viewable, String basePathElements) { - return getTranslatedPath(viewable, Arrays.asList(basePathElements.split("->"))); + private List getAndAnalyzeTranslatedPath(AbstractClassDef viewable, String basePathElements) { + var path = getTranslatedPath(viewable, Arrays.asList(basePathElements.split("->"))); + analyzeCacheRequirements(path, cacheRequirements); + return path; } /** @@ -249,12 +258,13 @@ private static List getTranslatedPath(AbstractClassDef viewable, private static AbstractLeafElement getTranslatedAttributeOrRole(Viewable viewable, String attributeBaseName) { for (ExtendableContainer extension : viewable.getExtensions()) { for (var it = ((Viewable) extension).getAttributesAndRoles2(); it.hasNext();) { - var result = switch (it.next().obj) { + var element = it.next().obj; + var result = switch (element) { case AttributeDef attributeDef -> (attributeDef.getTranslationOfOrSame().getName().equals(attributeBaseName) ? attributeDef : null); case RoleDef roleDef -> (roleDef.getTranslationOfOrSame().getName().equals(attributeBaseName) ? roleDef : null); - default -> throw new IllegalStateException("Unexpected value: " + it.next().obj); + default -> throw new IllegalStateException("Unexpected value: " + element); }; if (result != null) { @@ -294,8 +304,8 @@ private static String getTranslatedEnumValue(EnumerationType type, String enumer /** * Get the {@link TransferDescription} with all models used in the layerMappings. */ - private static TransferDescription getTransferDescription() throws Ili2cException, IOException, URISyntaxException { - var requiredModels = ObjectMapper.LAYER_MAPPINGS.stream() + private static TransferDescription getTransferDescription(List layerMappings) throws Ili2cException, IOException, URISyntaxException { + var requiredModels = layerMappings.stream() .map(LayerMapping::objectClass) .flatMap(Collection::stream) .map(c -> c.substring(0, c.indexOf('.'))) @@ -341,11 +351,14 @@ private static TransferDescription getTransferDescription() throws Ili2cExceptio * @return A stream of mapped objects. */ public Stream mapObjects(Stream iomObjects) { + final Map objectCache = new HashMap<>(); + final Set objectsWithUnresolvedRef = new HashSet<>(); + // Combine streams using flatMap instead of concat to process objectsWithRef // after all objects have been processed by the first stream. var combinedStream = Stream.>>>of( - () -> iomObjects.map(b -> mapObject(b, true)), - () -> objectsWithUnresolvedRef.stream().map(b -> mapObject(b, false)) + () -> iomObjects.map(b -> mapObject(b, objectCache, objectsWithUnresolvedRef, true)), + () -> objectsWithUnresolvedRef.stream().map(b -> mapObject(b, objectCache, objectsWithUnresolvedRef, false)) ).flatMap(Supplier::get); return combinedStream @@ -353,8 +366,8 @@ public Stream mapObjects(Stream iomObjects) { .map(Optional::get); } - private Optional mapObject(IomObject iomObject, boolean unresolvedReferencesAllowed) { - var element = TRANSFER_DESCRIPTION.getElement(iomObject.getobjecttag()); + private Optional mapObject(IomObject iomObject, Map objectCache, Set objectsWithUnresolvedRef, boolean unresolvedReferencesAllowed) { + var element = transferDescription.getElement(iomObject.getobjecttag()); if (element == null) { System.out.println("No element found for object with id \"" + iomObject.getobjectoid() + "\"."); return Optional.empty(); @@ -365,7 +378,7 @@ private Optional mapObject(IomObject iomObject, boolean unresolved } // cache part of the object if necessary - var pathElements = CACHE_REQUIREMENTS.get(classDef); + var pathElements = cacheRequirements.get(classDef); if (pathElements != null) { IomObject cacheObject = new Iom_jObject(iomObject.getobjecttag(), iomObject.getobjectoid()); for (var pathElement : pathElements) { @@ -382,29 +395,37 @@ private Optional mapObject(IomObject iomObject, boolean unresolved objectCache.put(iomObject.getobjectoid(), cacheObject); } - return FILTERS.stream() - .filter(filter -> filter.filter.stream().allMatch((f) -> - switch (f.matches(iomObject, objectCache)) { - case UNRESOLVED_REF -> { - if (unresolvedReferencesAllowed) { - objectsWithUnresolvedRef.add(iomObject); - yield false; - } else { - throw new IllegalStateException("Unresolved reference in object with id \"" + iomObject.getobjectoid() + "\"."); - } - } - case MATCH -> true; - case NO_MATCH -> false; - })) - .findFirst() - .map(filter -> new MappedObject( - iomObject.getobjectoid(), - Optional.ofNullable(resolve(iomObject, filter.geometry(), objectCache).getComplexObjects()).map(Collection::iterator).map(Iterator::next).orElse(null), - Optional.ofNullable(resolve(iomObject, filter.orientation(), objectCache).getValue()).map(Double::parseDouble).orElse(null), - resolve(iomObject, filter.vAlign(), objectCache).getValue(), - resolve(iomObject, filter.hAlign(), objectCache).getValue(), - resolve(iomObject, filter.text(), objectCache).getValue(), - filter.mapping())); + mapperLoop: + for (var mapper : filters) { + for (var filter : mapper.filter) { + switch (filter.matches(iomObject, objectCache)) { + case UNRESOLVED_REF -> { + if (unresolvedReferencesAllowed) { + objectsWithUnresolvedRef.add(iomObject); + return Optional.empty(); + } else { + throw new IllegalStateException("Unresolved reference in object with id \"" + iomObject.getobjectoid() + "\"."); + } + } + case NO_MATCH -> { + continue mapperLoop; + } + default -> { } // MATCH, continue with next filter + } + } + return Optional.of(new MappedObject( + iomObject.getobjectoid(), + Optional.ofNullable(resolve(iomObject, mapper.geometry(), objectCache).getComplexObjects()).map(Collection::iterator).map(Iterator::next).orElse(null), + Optional.ofNullable(resolve(iomObject, mapper.orientation(), objectCache).getValue()).map(Double::parseDouble).orElse(null), + resolve(iomObject, mapper.vAlign(), objectCache).getValue(), + resolve(iomObject, mapper.hAlign(), objectCache).getValue(), + resolve(iomObject, mapper.text(), objectCache).getValue(), + mapper.mapping())); + } + + // no match found + System.out.println("No match found for object with id \"" + iomObject.getobjectoid() + "\"."); + return Optional.empty(); } private interface Filter { diff --git a/src/test/java/ch/geowerkstatt/lk2dxf/DxfWriterTest.java b/src/test/java/ch/geowerkstatt/lk2dxf/DxfWriterTest.java index 6c3299f..8cf0467 100644 --- a/src/test/java/ch/geowerkstatt/lk2dxf/DxfWriterTest.java +++ b/src/test/java/ch/geowerkstatt/lk2dxf/DxfWriterTest.java @@ -174,6 +174,15 @@ public void writeTextOrientation() throws Exception { } } + @Test + public void writeMultiLineText() throws Exception { + try (var dxfWriter = new DxfWriter(testOutputWriter, 3, createTestLayerMappings(), null)) { + stringWriter.getBuffer().setLength(0); + dxfWriter.writeText("Test", "arial", "Multiline Text\r\nNext line with Tab <\t>", "Left", "Base", 90, 1.25, IomObjectHelper.createCoord("0", "0")); + assertEquals("0\nTEXT\n5\n1E\n100\nAcDbEntity\n8\nTest\n100\nAcDbText\n7\narial\n10\n0\n20\n0\n40\n1.25\n1\nMultiline Text\\U+000D\\U+000ANext line with Tab <\\U+0009>\n100\nAcDbText\n", stringWriter.toString()); + } + } + private IomObject[] createArcTestSegments(double pointRadius, double midPointRadius, int segmentCount) { var segments = new IomObject[segmentCount + 1]; segments[0] = IomObjectHelper.createCoord(Double.toString(pointRadius), "0"); diff --git a/src/test/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapperTest.java b/src/test/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapperTest.java index ed21d04..bf44ad7 100644 --- a/src/test/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapperTest.java +++ b/src/test/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapperTest.java @@ -1,14 +1,20 @@ package ch.geowerkstatt.lk2dxf.mapping; +import ch.geowerkstatt.lk2dxf.MappedObject; import ch.geowerkstatt.lk2dxf.XtfStreamReader; import ch.interlis.iom.IomObject; +import ch.interlis.iom_j.Iom_jObject; import org.junit.jupiter.api.Test; import java.io.File; import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public final class ObjectMapperTest { private static final String TEST_FILE = "src/test/data/MapperTest/MapWithText.xtf"; @@ -28,4 +34,220 @@ public void mapObject() throws Exception { assertArrayEquals(expected, layerMappings); } } + + @Test + public void mappingContainsNonexistentPath() { + var layerMappings = createTextLayerMapping(Map.of("NonExistent_LOGREMAT", List.of("NonExistent_RENAPHIP"))); + var exception = assertThrows(IllegalArgumentException.class, () -> new ObjectMapper(layerMappings)); + assertEquals("Attribute or role not found: NonExistent_LOGREMAT", exception.getMessage()); + } + + @Test + public void mappingContainsNonExistentValue() { + var layerMappings = createTextLayerMapping(Map.of("Plantyp", List.of("NonExistent_RENAPHIP"))); + var exception = assertThrows(IllegalArgumentException.class, () -> new ObjectMapper(layerMappings)); + assertEquals("Enumeration value not found: NonExistent_RENAPHIP", exception.getMessage()); + } + + @Test + public void mappingContainsTextAttributeFilter() { + var layerMappings = createTextLayerMapping(Map.of("Bemerkung", List.of("SULTIOND"))); + var exception = assertThrows(IllegalArgumentException.class, () -> new ObjectMapper(layerMappings)); + assertEquals("Only enumeration types supported: Bemerkung", exception.getMessage()); + } + + @Test + public void pathEndsAtReference() { + var layerMappings = createTextLayerMapping(Map.of("LKObjektRef", List.of("Abwasser"))); + var exception = assertThrows(IllegalArgumentException.class, () -> new ObjectMapper(layerMappings)); + assertEquals("Expected the path to continue but it ended at: [LKObjektRef]", exception.getMessage()); + } + + @Test + public void pathEndsAtStruct() { + var layerMappings = createTextLayerMapping(Map.of("LKObjektRef->Metaattribute", List.of("Abwasser"))); + var exception = assertThrows(IllegalArgumentException.class, () -> new ObjectMapper(layerMappings)); + assertEquals("Expected the path to continue but it ended at: [Metaattribute]", exception.getMessage()); + } + + @Test + public void pathContinuesAfterAttribute() { + var layerMappings = createTextLayerMapping(Map.of("LKObjektRef->Objektart->UnexpectedAttribute", List.of("Abwasser"))); + var exception = assertThrows(IllegalArgumentException.class, () -> new ObjectMapper(layerMappings)); + assertEquals("Expected the path to end at an attribute, but the path goes on: [Objektart, UnexpectedAttribute]", exception.getMessage()); + } + + @Test + public void mappingContainsEnumFilter() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("Plantyp", List.of("Leitungskataster"))); + var objectMapper = new ObjectMapper(layerMappings); + + var objA = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "obj_A", + o -> o.addattrvalue("Plantyp", "Leitungskataster")); + var objB = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "obj_B", + o -> o.addattrvalue("Plantyp", "Werkplan")); + + String[] actual = getMappedLayers(objectMapper, objA, objB); + assertArrayEquals(new String[] {"Test", "CatchAllText"}, actual); + } + + @Test + public void mappingContainsEnumFilterWithMultipleOptions() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("Plantyp", List.of("Werkplan", "Leitungskataster"))); + var objectMapper = new ObjectMapper(layerMappings); + + var objA = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "obj_A", + o -> o.addattrvalue("Plantyp", "Leitungskataster")); + var objB = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "obj_B", + o -> o.addattrvalue("Plantyp", "Werkplan")); + + String[] actual = getMappedLayers(objectMapper, objA, objB); + assertArrayEquals(new String[] {"Test", "Test"}, actual); + } + + @Test + public void mapTranslatedObject() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("Plantyp", List.of("Werkplan", "Uebersichtsplan.UeP10"))); + var objectMapper = new ObjectMapper(layerMappings); + + var objA = createIomObject("SIA405_LKMap_2015_f_LV95.SIA405_LKMap_f.LKOBJET_Texte", "obj_A", + o -> o.addattrvalue("TYPE_DE_PLAN", "plan_de_reseau")); + var objB = createIomObject("SIA405_LKMap_2015_f_LV95.SIA405_LKMap_f.LKOBJET_Texte", "obj_B", + o -> o.addattrvalue("TYPE_DE_PLAN", "plan_d_ensemble.pe10")); + + String[] actual = getMappedLayers(objectMapper, objA, objB); + assertArrayEquals(new String[] {"Test", "Test"}, actual); + } + + @Test + public void mapSubEnum() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("Plantyp", List.of("Uebersichtsplan"))); + var objectMapper = new ObjectMapper(layerMappings); + + var objA = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "obj_A", + o -> o.addattrvalue("Plantyp", "Uebersichtsplan.UeP5")); + var objB = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "obj_B", + o -> o.addattrvalue("Plantyp", "Werkplan")); + + String[] actual = getMappedLayers(objectMapper, objA, objB); + assertArrayEquals(new String[] {"Test", "CatchAllText"}, actual); + } + + @Test + public void mapObjectWithForwardRef() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("LKObjektRef->Objektart", List.of("Elektrizitaet"))); + var objectMapper = new ObjectMapper(layerMappings); + + var textObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "textObj", + o -> o.addattrobj("LKObjektRef", createIomObject("REF", null, + r -> r.setobjectrefoid("punktObj")))); + + var pointObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKPunkt", "punktObj", + o -> o.addattrvalue("Objektart", "Elektrizitaet")); + + String[] actual = getMappedLayers(objectMapper, textObj, pointObj); + assertArrayEquals(new String[] {"Test"}, actual); + } + + @Test + public void mapObjectWithBackwardRef() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("LKObjektRef->Objektart", List.of("Elektrizitaet"))); + var objectMapper = new ObjectMapper(layerMappings); + + var textObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "textObj", + o -> o.addattrobj("LKObjektRef", createIomObject("REF", null, + r -> r.setobjectrefoid("punktObj")))); + + var pointObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKPunkt", "punktObj", + o -> o.addattrvalue("Objektart", "Elektrizitaet")); + + String[] actual = getMappedLayers(objectMapper, pointObj, textObj); + assertArrayEquals(new String[] {"Test"}, actual); + } + + @Test + public void mapObjectWithMissingRef() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("LKObjektRef->Objektart", List.of("Elektrizitaet"))); + var objectMapper = new ObjectMapper(layerMappings); + + var textObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "textObj", + o -> o.addattrobj("LKObjektRef", createIomObject("REF", null, + r -> r.setobjectrefoid("punktObj")))); + + var exception = assertThrows(IllegalStateException.class, () -> objectMapper.mapObjects(Stream.of(textObj)).toList()); + assertEquals("Unresolved reference in object with id \"textObj\".", exception.getMessage()); + } + + @Test + public void mapObjectWithEmptyRef() throws Exception { + var layerMappings = createTextLayerMapping(Map.of("LKObjektRef->Objektart", List.of("Elektrizitaet"))); + var objectMapper = new ObjectMapper(layerMappings); + + var textObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "textObj"); + + String[] actual = getMappedLayers(objectMapper, textObj); + assertArrayEquals(new String[] {"CatchAllText"}, actual); + } + + @Test + public void resolvePathWithStruct() throws Exception { + var layerMappings = List.of( + new LayerMapping("Test", List.of("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text"), LayerMapping.OutputType.TEXT, + "TextPos", 1, "TextOri", "TextVAli", "TextHAli", "LKObjektRef->Metaattribute->Datenlieferant", + "TestSymbol", "", 0.25, 1.25, "arial", Map.of("LKObjektRef->Objektart", List.of("Elektrizitaet")))); + var objectMapper = new ObjectMapper(layerMappings); + + var textObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", "textObj", + o -> o.addattrobj("LKObjektRef", createIomObject("REF", null, + r -> r.setobjectrefoid("punktObj")))); + var pointObj = createIomObject("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKPunkt", "punktObj", + o -> o.addattrvalue("Objektart", "Elektrizitaet"), + o -> o.addattrobj("Metaattribute", createIomObject("Metaattribute", null, + m -> m.addattrvalue("Datenlieferant", "MINATERI")))); + + List output = objectMapper.mapObjects(Stream.of(textObj, pointObj)).toList(); + assertEquals(1, output.size()); + assertEquals("MINATERI", output.getFirst().text()); + } + + private List createTextLayerMapping(Map> mapping) { + return List.of( + createTextLayerMapping("Test", mapping), + createTextLayerMapping("CatchAllText", Map.of()) + ); + } + + private LayerMapping createTextLayerMapping(String layerName, Map> mapping) { + return new LayerMapping(layerName, + List.of("SIA405_LKMap_2015_LV95.SIA405_LKMap.LKObjekt_Text", + "SIA405_LKMap_2015_f_LV95.SIA405_LKMap_f.LKOBJET_Texte"), + LayerMapping.OutputType.TEXT, + "TextPos", + 1, + "TextOri", + "TextVAli", + "TextHAli", + "Textinhalt", + "TestSymbol", + "", + 0.25, + 1.25, + "arial", + mapping); + } + + private String[] getMappedLayers(ObjectMapper objectMapper, IomObject... objects) { + return objectMapper.mapObjects(Stream.of(objects)) + .map(o -> o.layerMapping().layer()) + .toArray(String[]::new); + } + + @SafeVarargs + private static IomObject createIomObject(String tag, String oid, Consumer... attributeSetters) { + var iomObject = new Iom_jObject(tag, oid); + for (Consumer attributeSetter : attributeSetters) { + attributeSetter.accept(iomObject); + } + return iomObject; + } }