Skip to content

Commit

Permalink
Add ObjectMapper tests (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrickackermann authored Nov 20, 2024
2 parents 0d5946c + 833d3f2 commit eb839b3
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 70 deletions.
1 change: 1 addition & 0 deletions src/main/java/ch/geowerkstatt/lk2dxf/DxfWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/ch/geowerkstatt/lk2dxf/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,17 @@ public static void main(String[] args) {
private static void processFiles(LK2DxfOptions options) {
Optional<Geometry> 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<MappedObject> objects = mapper.mapObjects(reader.readObjects());
Stream<MappedObject> objects = objectMapper.mapObjects(reader.readObjects());

if (perimeter.isPresent()) {
objects = objects.filter(o -> perimeter.get().intersects(o.geometry()));
Expand Down
155 changes: 88 additions & 67 deletions src/main/java/ch/geowerkstatt/lk2dxf/mapping/ObjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,30 +33,37 @@

public final class ObjectMapper {
private static final String MODELS_RESOURCE = "/models";
private static final List<LayerMapping> LAYER_MAPPINGS;
private static final TransferDescription TRANSFER_DESCRIPTION;
private final List<LayerMapping> layerMappings;
private final TransferDescription transferDescription;

private static final Map<AbstractClassDef<?>, Set<PathElement>> CACHE_REQUIREMENTS = new HashMap<AbstractClassDef<?>, Set<PathElement>>();
private static final List<Mapper> FILTERS;
private final Map<AbstractClassDef<?>, Set<PathElement>> cacheRequirements = new HashMap<>();
private final List<Mapper> 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<String, IomObject> objectCache = new HashMap<>();
private final Set<IomObject> objectsWithUnresolvedRef = new HashSet<>();
/**
* Create a new {@link ObjectMapper} with the given layer mappings.
*
* @param layerMappings The layer mappings to use.
*/
public ObjectMapper(List<LayerMapping> layerMappings) throws IOException, URISyntaxException, Ili2cException {
this.layerMappings = layerMappings;
transferDescription = getTransferDescription(layerMappings);
analyzeLayerMappings();
}

private static List<Mapper> analyzeLayerMappings() {
var mappers = new ArrayList<Mapper>();
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 + "\".");
}
Expand All @@ -70,10 +77,10 @@ private static List<Mapper> 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());
Expand All @@ -84,37 +91,36 @@ private static List<Mapper> 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<LayerMapping> getLayerMappings() {
return Collections.unmodifiableList(LAYER_MAPPINGS);
public List<LayerMapping> getLayerMappings() {
return Collections.unmodifiableList(layerMappings);
}

private static boolean matchesEnumSubValue(List<String> allowedValues, String attrValue) {
Expand Down Expand Up @@ -180,7 +186,7 @@ private static Value resolve(IomObject iomObject, List<PathElement> path, Map<St
}

/**
* Analyze a path for references that need a referenced object in the cache. Update the {@link #CACHE_REQUIREMENTS} accordingly.
* Analyze a path for references that need a referenced object in the cache. Update the {@link #cacheRequirements} accordingly.
*/
private static void analyzeCacheRequirements(List<PathElement> path, Map<AbstractClassDef<?>, Set<PathElement>> cacheRequirements) {
for (int i = 0; i < path.size(); i++) {
Expand All @@ -197,10 +203,13 @@ private static void analyzeCacheRequirements(List<PathElement> path, Map<Abstrac
}

/**
* Get the translated path elements for the given attribute path. Analyzes the path for cache requirements.
* @see #getTranslatedPath(AbstractClassDef, List)
*/
private static List<PathElement> getTranslatedPath(AbstractClassDef<?> viewable, String basePathElements) {
return getTranslatedPath(viewable, Arrays.asList(basePathElements.split("->")));
private List<PathElement> getAndAnalyzeTranslatedPath(AbstractClassDef<?> viewable, String basePathElements) {
var path = getTranslatedPath(viewable, Arrays.asList(basePathElements.split("->")));
analyzeCacheRequirements(path, cacheRequirements);
return path;
}

/**
Expand Down Expand Up @@ -249,12 +258,13 @@ private static List<PathElement> 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) {
Expand Down Expand Up @@ -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<LayerMapping> layerMappings) throws Ili2cException, IOException, URISyntaxException {
var requiredModels = layerMappings.stream()
.map(LayerMapping::objectClass)
.flatMap(Collection::stream)
.map(c -> c.substring(0, c.indexOf('.')))
Expand Down Expand Up @@ -341,20 +351,23 @@ private static TransferDescription getTransferDescription() throws Ili2cExceptio
* @return A stream of mapped objects.
*/
public Stream<MappedObject> mapObjects(Stream<IomObject> iomObjects) {
final Map<String, IomObject> objectCache = new HashMap<>();
final Set<IomObject> 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.<Supplier<Stream<Optional<MappedObject>>>>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
.filter(Optional::isPresent)
.map(Optional::get);
}

private Optional<MappedObject> mapObject(IomObject iomObject, boolean unresolvedReferencesAllowed) {
var element = TRANSFER_DESCRIPTION.getElement(iomObject.getobjecttag());
private Optional<MappedObject> mapObject(IomObject iomObject, Map<String, IomObject> objectCache, Set<IomObject> 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();
Expand All @@ -365,7 +378,7 @@ private Optional<MappedObject> 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) {
Expand All @@ -382,29 +395,37 @@ private Optional<MappedObject> 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 {
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/ch/geowerkstatt/lk2dxf/DxfWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Loading

0 comments on commit eb839b3

Please sign in to comment.