From 50d7e64fe72e6b550cee23933e498a0627ce7227 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Tue, 9 Dec 2025 16:47:32 +0100 Subject: [PATCH 01/52] Feature: Add configuration of embedded database --- .../database/DataBaseSettings.java | 92 +++++++++++++++++++ .../EmbeddedDatabaseConfiguration.java | 47 ++++++++++ .../database/EmbeddedDatabaseHandler.java | 61 ++++++++---- .../citydoctor2/database/FeatureCache.java | 12 ++- .../EmbeddedDatabaseHandlerException.java | 18 ++++ .../citygml3/Citygml3FeatureMapper.java | 2 +- .../database/EmbeddedDataBaseHandlerTest.java | 4 +- 7 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java create mode 100644 CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java create mode 100644 CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/EmbeddedDatabaseHandlerException.java diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java new file mode 100644 index 00000000..bff6243b --- /dev/null +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java @@ -0,0 +1,92 @@ +package de.hft.stuttgart.citydoctor2.database; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; +import java.util.Properties; + +public final class DataBaseSettings { + + private static final Logger logger = LogManager.getLogger(DataBaseSettings.class); + + private static final Properties props; + + private DataBaseSettings() {} + + static { + props = new Properties(); + + File propFile = new File("DBSettings.properties"); + if (propFile.exists()) { + try (BufferedReader bis = new BufferedReader(new FileReader(propFile))) { + props.load(bis); + } catch (IOException e) { + logger.error("Failed to load database settings", e); + } + } +// props.setProperty("name","cd_db"); +// props.setProperty("directory","/database/"); +// props.setProperty("poolSize","10"); +// props.setProperty("inMemoryMode","false"); +// props.setProperty("attemptFallback","true"); +// props.setProperty("debugMode","false"); + } + + public static EmbeddedDatabaseConfiguration getConfig() { + EmbeddedDatabaseConfiguration defaultConfig = new EmbeddedDatabaseConfiguration(); + if (props.isEmpty()) { + logger.debug("No database settings found, using default configuration"); + return defaultConfig; + } + String name = props.getProperty("name"); + String directory = props.getProperty("directory"); + String poolSizeString = props.getProperty("poolSize"); + String inMemoryModeString = props.getProperty("inMemoryMode"); + String attemptFallbackString = props.getProperty("attemptFallback"); + String debugModeString = props.getProperty("debugMode"); + + boolean inMemoryMode = Boolean.parseBoolean(inMemoryModeString); + boolean attemptFallback = attemptFallbackString == null || Boolean.parseBoolean(attemptFallbackString); + boolean debugMode = Boolean.parseBoolean(debugModeString); + int poolSize; + try { + poolSize = Integer.parseInt(poolSizeString); + if (poolSize < 1) { + //TODO: Localize String + logger.warn("poolSize property cannot be less than 1, using default value"); + poolSize = defaultConfig.connectionPoolSize(); + } + } catch (NumberFormatException e) { + //TODO: Localize String + logger.warn("poolSize property must be an integer, using default value"); + poolSize = defaultConfig.connectionPoolSize(); + } + + if (name == null || name.isBlank()){ + name = defaultConfig.databaseName(); + } + if (directory == null || directory.isBlank()){ + directory = defaultConfig.databaseDirectory(); + } + try { + Paths.get(directory, name); + } catch (InvalidPathException e) { + if (!inMemoryMode) { + // Only warn if in-memory mode is not being used + //TODO: Localize String + logger.warn("Properties for directory and name do not form a valid path, using default values"); + name = defaultConfig.databaseName(); + directory = defaultConfig.databaseDirectory(); + } + } + + return new EmbeddedDatabaseConfiguration(name, directory, poolSize, inMemoryMode, attemptFallback, debugMode); + } + +} diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java new file mode 100644 index 00000000..36ddc571 --- /dev/null +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java @@ -0,0 +1,47 @@ +package de.hft.stuttgart.citydoctor2.database; + +import java.io.File; + +/** + * Record containing the configuration parameters for the setup of the embedded database + * @param databaseName Name of the database + * @param databaseDirectory Directory of the database. Relative paths will be resolved from the current working directory. + * CityDoctor will create the directory if it does not exist, and will overwrite an existing + * database-file if its name matches databaseName + * @param connectionPoolSize Size of the connection pool + * @param inMemoryMode If true, CityDoctor will create the embedded database in RAM + * @param attemptFallback If true, CityDoctor will attempt to create the embedded database in RAM if creation of the + * database-file fails. Will be ignored if inMemoryMode is true + * @param debugMode If true, CityDoctor will start the embedded database in automatic mixed mode to allow access to the + * database during runtime. + */ +public record EmbeddedDatabaseConfiguration(String databaseName, String databaseDirectory, int connectionPoolSize, + boolean inMemoryMode, boolean attemptFallback, boolean debugMode) { + + private static final String IN_MEMORY_PREFIX= "jdbc:h2:mem"; + private static final String AUTO_SERVER_PARAMETER = ";AUTO_SERVER=TRUE"; + /** + * Instantiates the default configuration for the embedded database. + */ + EmbeddedDatabaseConfiguration() { + this("cd_db", File.separator + "database"+ File.separator, Runtime.getRuntime().availableProcessors(), + false, true, false); + } + + public String getInMemoryH2Url() { + return IN_MEMORY_PREFIX + databaseName; + } + + public String getH2FileUrl(){ + return databaseDirectory + databaseName; + } + + public String getH2ParametersSuffix(){ + if (debugMode){ + return AUTO_SERVER_PARAMETER; + } else { + return ";"; + } + } + +} diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java index e7820948..e32251f0 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java @@ -8,6 +8,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.CityObject; import de.hft.stuttgart.citydoctor2.datastructure.Geometry; import de.hft.stuttgart.citydoctor2.datastructure.GmlId; +import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException; import de.hft.stuttgart.citydoctor2.utils.Localization; import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationUtils; @@ -16,6 +17,7 @@ import org.apache.logging.log4j.Logger; import org.h2gis.functions.factory.H2GISDBFactory; import org.h2gis.utilities.wrapper.DataSourceWrapper; +import javax.sql.DataSource; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -34,34 +36,54 @@ import java.util.Map; public class EmbeddedDatabaseHandler { private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class); - private static final String DB_NAME = "/database/cd_db"; + private final EmbeddedDatabaseConfiguration config; private DataSourceWrapper dataSource; - public EmbeddedDatabaseHandler(){ + public EmbeddedDatabaseHandler(EmbeddedDatabaseConfiguration dbConfig){ + config = dbConfig; + String jdbcUrl; try { - String jdbcurl = H2GISDBFactory.createDataSource(DB_NAME, - true, ";AUTO_SERVER=TRUE").getConnection().getMetaData().getURL(); - - logger.debug("1] {}",jdbcurl); - HikariDataSource ds = new HikariDataSource(); - - ds.setJdbcUrl(jdbcurl); - ds.setUsername("sa"); - ds.setPassword("sa"); - int coreCount = Runtime.getRuntime().availableProcessors(); - ds.setMaximumPoolSize(coreCount*5); - ds.setMinimumIdle(coreCount*5); - - dataSource = new DataSourceWrapper(ds); - setupFeaturesTable(); - //Cleanup hook - Runtime.getRuntime().addShutdownHook(new Thread(ds::close)); + jdbcUrl = createEmbeddedDataBase(config.getH2FileUrl()); + logger.debug("1] {}",jdbcUrl); + } catch (Exception e) { logger.fatal(Localization.getText("DatabaseHandler.setupFailure")); logger.fatal(e.getMessage()); + if (config.attemptFallback()){ + logger.info("Attempting fallback to in-memory database"); + try { + jdbcUrl = createEmbeddedDataBase(config.getInMemoryH2Url()); + } catch (Exception e1) { + throw new EmbeddedDatabaseHandlerException("Fallback to in-memory database failed, embedded database could not be created", e1); + } + logger.info("Fallback to in-memory database succeeded"); + logger.warn("The in-memory database is limited by the system's available RAM. Loading big CityGML files can lead to OutOfMemory errors."); + } else { + throw new EmbeddedDatabaseHandlerException("Embedded database could not be created"); + } + } + setupHikariPool(jdbcUrl); + setupFeaturesTable(); + } + private void setupHikariPool(String jdbcUrl){ + HikariDataSource ds = new HikariDataSource(); + ds.setJdbcUrl(jdbcUrl); + ds.setUsername("sa"); + ds.setPassword("sa"); + ds.setMaximumPoolSize(config.connectionPoolSize()); + dataSource = new DataSourceWrapper(ds); + //Cleanup hook + Runtime.getRuntime().addShutdownHook(new Thread(ds::close)); + } + private String createEmbeddedDataBase(String dbUrl) throws SQLException { + DataSource source = H2GISDBFactory.createDataSource(dbUrl, + true, config.getH2ParametersSuffix()); + try (Connection con = source.getConnection()) { + return con.getMetaData().getURL(); + } } @@ -85,6 +107,7 @@ public class EmbeddedDatabaseHandler { } catch (SQLException e) { logger.fatal(Localization.getText("DatabaseHandler.tableFailure")); logger.fatal(e.getMessage()); + throw new EmbeddedDatabaseHandlerException("Could not setup features table in database", e); } } diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/FeatureCache.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/FeatureCache.java index b797f208..96d6d930 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/FeatureCache.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/FeatureCache.java @@ -42,11 +42,15 @@ public class FeatureCache implements CityObjectCache{ private final ConcurrentHashMap evictionLocks = new ConcurrentHashMap<>(); public FeatureCache() { - handler = new EmbeddedDatabaseHandler(); + this(DataBaseSettings.getConfig()); + } + + public FeatureCache(EmbeddedDatabaseConfiguration dbConfig){ + handler = new EmbeddedDatabaseHandler(dbConfig); cache = Caffeine.newBuilder().maximumSize(3000).removalListener((GmlId key, CityObject value, RemovalCause cause) -> { - if (value != null && cause.wasEvicted() && isFeatureMarshallable(value)){ - handler.marshallCityObject(value); - } + if (value != null && cause.wasEvicted() && isFeatureMarshallable(value)){ + handler.marshallCityObject(value); + } }).build(handler::unmarshallCityObject); } diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/EmbeddedDatabaseHandlerException.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/EmbeddedDatabaseHandlerException.java new file mode 100644 index 00000000..ffc6562e --- /dev/null +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/EmbeddedDatabaseHandlerException.java @@ -0,0 +1,18 @@ +package de.hft.stuttgart.citydoctor2.exceptions; + +import java.io.Serial; + +public class EmbeddedDatabaseHandlerException extends RuntimeException{ + + @Serial + private static final long serialVersionUID = -7848325704234347425L; + + + public EmbeddedDatabaseHandlerException(String message) { + super(message); + } + + public EmbeddedDatabaseHandlerException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/mapper/citygml3/Citygml3FeatureMapper.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/mapper/citygml3/Citygml3FeatureMapper.java index e27555ca..4ac8c7f9 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/mapper/citygml3/Citygml3FeatureMapper.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/mapper/citygml3/Citygml3FeatureMapper.java @@ -166,7 +166,7 @@ public class Citygml3FeatureMapper extends ObjectWalker { private static final Logger logger = LogManager.getLogger(Citygml3FeatureMapper.class); - private static FeatureCache featureCache = new FeatureCache(); + private static final FeatureCache featureCache = new FeatureCache(); private final CityDoctorModel model; private final ParserConfiguration config; private final Path directory; diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java index 06c0bb6e..a6286e01 100644 --- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java +++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java @@ -29,12 +29,10 @@ import static org.junit.Assert.fail; public class EmbeddedDataBaseHandlerTest { - private EmbeddedDatabaseHandler handler; - + private EmbeddedDatabaseHandler handler = new EmbeddedDatabaseHandler(new EmbeddedDatabaseConfiguration()); @Before public void resetDatabase() { try { - handler = new EmbeddedDatabaseHandler(); handler.setupFeaturesTable(); } catch (Exception e) { fail(e.getMessage()); -- GitLab From baac7dede9ad5bc917140a19e1fca8d0ca979cde Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Wed, 10 Dec 2025 09:19:54 +0100 Subject: [PATCH 02/52] Feature: Add tempfile-mode flag to db settings --- .../citydoctor2/database/DataBaseSettings.java | 4 +++- .../database/EmbeddedDatabaseConfiguration.java | 11 +++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java index bff6243b..9ba4d2a7 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java @@ -50,10 +50,12 @@ public final class DataBaseSettings { String inMemoryModeString = props.getProperty("inMemoryMode"); String attemptFallbackString = props.getProperty("attemptFallback"); String debugModeString = props.getProperty("debugMode"); + String tempModeString = props.getProperty("tempMode"); boolean inMemoryMode = Boolean.parseBoolean(inMemoryModeString); boolean attemptFallback = attemptFallbackString == null || Boolean.parseBoolean(attemptFallbackString); boolean debugMode = Boolean.parseBoolean(debugModeString); + boolean tempMode = Boolean.parseBoolean(tempModeString); int poolSize; try { poolSize = Integer.parseInt(poolSizeString); @@ -86,7 +88,7 @@ public final class DataBaseSettings { } } - return new EmbeddedDatabaseConfiguration(name, directory, poolSize, inMemoryMode, attemptFallback, debugMode); + return new EmbeddedDatabaseConfiguration(name, directory, poolSize, inMemoryMode, attemptFallback, debugMode, tempMode); } } diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java index 36ddc571..fb487934 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java @@ -14,9 +14,11 @@ import java.io.File; * database-file fails. Will be ignored if inMemoryMode is true * @param debugMode If true, CityDoctor will start the embedded database in automatic mixed mode to allow access to the * database during runtime. + * @param tempFileMode If true, CityDoctor will create the database in the system's temp directory. Will be ignored if + * inMemoryMode is true */ public record EmbeddedDatabaseConfiguration(String databaseName, String databaseDirectory, int connectionPoolSize, - boolean inMemoryMode, boolean attemptFallback, boolean debugMode) { + boolean inMemoryMode, boolean attemptFallback, boolean debugMode, boolean tempFileMode) { private static final String IN_MEMORY_PREFIX= "jdbc:h2:mem"; private static final String AUTO_SERVER_PARAMETER = ";AUTO_SERVER=TRUE"; @@ -25,7 +27,12 @@ public record EmbeddedDatabaseConfiguration(String databaseName, String database */ EmbeddedDatabaseConfiguration() { this("cd_db", File.separator + "database"+ File.separator, Runtime.getRuntime().availableProcessors(), - false, true, false); + false, true, false, false); + } + + public static EmbeddedDatabaseConfiguration getTestConfig() { + return new EmbeddedDatabaseConfiguration("cd_db", File.separator + "database"+ File.separator, Runtime.getRuntime().availableProcessors(), + true, true, true, false); } public String getInMemoryH2Url() { -- GitLab From 1c6652197f7810b944aaebc1ddedd427b3467268 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Wed, 10 Dec 2025 16:36:19 +0100 Subject: [PATCH 03/52] Refactor: Rename properties --- .../citydoctor2/database/DataBaseSettings.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java index 9ba4d2a7..9784e816 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/DataBaseSettings.java @@ -44,13 +44,13 @@ public final class DataBaseSettings { logger.debug("No database settings found, using default configuration"); return defaultConfig; } - String name = props.getProperty("name"); - String directory = props.getProperty("directory"); - String poolSizeString = props.getProperty("poolSize"); - String inMemoryModeString = props.getProperty("inMemoryMode"); - String attemptFallbackString = props.getProperty("attemptFallback"); - String debugModeString = props.getProperty("debugMode"); - String tempModeString = props.getProperty("tempMode"); + String name = props.getProperty("database.name"); + String directory = props.getProperty("database.directory"); + String poolSizeString = props.getProperty("database.connectionPoolSize"); + String inMemoryModeString = props.getProperty("database.inMemoryMode"); + String attemptFallbackString = props.getProperty("database.attemptFallback"); + String debugModeString = props.getProperty("database.debugMode"); + String tempModeString = props.getProperty("database.tempMode"); boolean inMemoryMode = Boolean.parseBoolean(inMemoryModeString); boolean attemptFallback = attemptFallbackString == null || Boolean.parseBoolean(attemptFallbackString); -- GitLab From 933253803142ce0d711d8f54b66963350f9cc266 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Fri, 12 Dec 2025 13:59:26 +0100 Subject: [PATCH 04/52] Fix: Fix compilation errors --- .../stuttgart/citydoctor2/healing/HealMissingSolidTest.java | 5 ++++- .../stuttgart/citydoctor2/healer/gui/HealerController.java | 5 ++--- CityDoctorParent/pom.xml | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealMissingSolidTest.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealMissingSolidTest.java index 07adf8c1..5ee9be12 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealMissingSolidTest.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealMissingSolidTest.java @@ -29,6 +29,8 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import de.hft.stuttgart.citydoctor2.database.UnconnectedCache; +import org.citygml4j.core.model.CityGMLVersion; import org.citygml4j.core.model.core.AbstractThematicSurface; import org.citygml4j.core.model.core.CityModel; import org.junit.Rule; @@ -70,7 +72,8 @@ public class HealMissingSolidTest { @Test public void testCreateSolid() throws CityDoctorWriteException, IOException, CityGmlParseException, InvalidGmlFileException { ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig(); - CityDoctorModel model = new CityDoctorModel(config.getParserConfiguration(), new File("test")); + CityDoctorModel model = new CityDoctorModel(config.getParserConfiguration(), new File("test"), new UnconnectedCache()); + model.setParsedCityGMLVersion(CityGMLVersion.v3_0); model.setCityModel(new CityModel()); Building b = new Building(); model.addBuilding(b); diff --git a/CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/java/de/hft/stuttgart/citydoctor2/healer/gui/HealerController.java b/CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/java/de/hft/stuttgart/citydoctor2/healer/gui/HealerController.java index f495692c..31627f7a 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/java/de/hft/stuttgart/citydoctor2/healer/gui/HealerController.java +++ b/CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/java/de/hft/stuttgart/citydoctor2/healer/gui/HealerController.java @@ -538,8 +538,7 @@ public class HealerController { // if (model == null || currentFeature == null) { // return; // } - - for (Building b : model.getBuildings()) { + model.getBuildings().forEach(b -> { // collect polygons Map> availablePolygons = new EnumMap<>(Lod.class); Map> existingPolygons = new EnumMap<>(Lod.class); @@ -568,7 +567,7 @@ public class HealerController { insertSolidGeometry(availablePolygons, part, existingPolygons, Lod.LOD4); } } - } + }); } diff --git a/CityDoctorParent/pom.xml b/CityDoctorParent/pom.xml index d69e27d2..ed37dea0 100644 --- a/CityDoctorParent/pom.xml +++ b/CityDoctorParent/pom.xml @@ -303,9 +303,9 @@ Extensions/CityDoctorGUI - + Extensions/CityDoctorHealer - + Extensions/CityDoctorHealerGUI \ No newline at end of file -- GitLab From 17359af885b8b595473c6341871dd6a6fa63a7bb Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Tue, 16 Dec 2025 16:37:14 +0100 Subject: [PATCH 05/52] Build: Suppress Hikari logs in Healer --- .../src/main/resources/log4j2.xml | 18 ++++++++++++++++++ .../src/main/resources/log4j2.xml | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 CityDoctorParent/Extensions/CityDoctorHealer/src/main/resources/log4j2.xml create mode 100644 CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/resources/log4j2.xml diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/resources/log4j2.xml b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/resources/log4j2.xml new file mode 100644 index 00000000..1b1a09f3 --- /dev/null +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/resources/log4j2.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/resources/log4j2.xml b/CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/resources/log4j2.xml new file mode 100644 index 00000000..1b1a09f3 --- /dev/null +++ b/CityDoctorParent/Extensions/CityDoctorHealerGUI/src/main/resources/log4j2.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file -- GitLab From 19f5db34a75b87d05ef9e6ba920385331f914faa Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Tue, 16 Dec 2025 16:38:49 +0100 Subject: [PATCH 06/52] Fix: Defer utility visitors to ConcretePolygon --- .../datastructure/LinkedPolygon.java | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/LinkedPolygon.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/LinkedPolygon.java index d915d8dc..9c858619 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/LinkedPolygon.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/datastructure/LinkedPolygon.java @@ -19,8 +19,11 @@ package de.hft.stuttgart.citydoctor2.datastructure; import de.hft.stuttgart.citydoctor2.check.Check; +import de.hft.stuttgart.citydoctor2.check.CheckError; import de.hft.stuttgart.citydoctor2.check.CheckId; import de.hft.stuttgart.citydoctor2.check.CheckResult; +import de.hft.stuttgart.citydoctor2.check.Checkable; +import de.hft.stuttgart.citydoctor2.check.CheckableUtilsVisitor; import de.hft.stuttgart.citydoctor2.check.CheckableVisitor; import de.hft.stuttgart.citydoctor2.math.Vector3d; import de.hft.stuttgart.citydoctor2.tesselation.TesselatedPolygon; @@ -62,6 +65,52 @@ public class LinkedPolygon extends Polygon { } } + @Override + public boolean hasExistingGmlId() { + return poly.hasExistingGmlId(); + } + + @Override + public void setValidated(boolean validated) { + // Linked polys themselves are not checked + } + + @Override + public boolean isValidated() { + return poly.isValidated(); + } + + //TODO: Think how to handle this, as it violates class contract if ignored + @Override + public void prepareForChecking() { + // Linked polys themselves are not checked + } + + @Override + public void clearMetaInformation() { + // Linked polys themselves are not checked + } + + @Override + public boolean hasError(CheckId checkIdentifier) { + return poly.hasError(checkIdentifier); + } + + @Override + public boolean hasAnyError() { + return poly.hasAnyError(); + } + + @Override + public void collectContainedErrors(List errors) { + poly.collectContainedErrors(errors); + } + + @Override + public void collectErrors(List errors) { + poly.collectErrors(errors); + } + @Override public CheckResult getCheckResult(Check c) { return poly.getCheckResult(c); @@ -104,8 +153,12 @@ public class LinkedPolygon extends Polygon { @Override public void accept(CheckableVisitor c) { - // linked polygons are not checked - setValidated(true); + // LinkedPolygon should ignore Checks, as their referenced ConcretePolygon must be within the same TopLevelFeature. + // Checks always start iterating from a TopLevelFeature and thus will visit the referenced ConcretePolygon. + if (!c.isValidator()){ + // Utility Visitors get passed to the ConcretePolygon, as they can start iterating from a sub-CityObject. + poly.accept(c); + } } @Override -- GitLab From 8b2f8ba8733eeaf59ea7238a1a903d05293f04ca Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Tue, 16 Dec 2025 16:40:53 +0100 Subject: [PATCH 07/52] Fix: Connect Healer with models CityObjectCache --- .../exceptions/CityObjectCacheException.java | 18 ++++++++++++++++++ .../stuttgart/citydoctor2/healer/Healer.java | 14 ++++++++++++-- .../citydoctor2/healing/TestUtil.java | 2 +- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/CityObjectCacheException.java diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/CityObjectCacheException.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/CityObjectCacheException.java new file mode 100644 index 00000000..02e1186a --- /dev/null +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/exceptions/CityObjectCacheException.java @@ -0,0 +1,18 @@ +package de.hft.stuttgart.citydoctor2.exceptions; + +import java.io.Serial; + +public class CityObjectCacheException extends RuntimeException{ + + @Serial + private static final long serialVersionUID = -460467145629964615L; + + public CityObjectCacheException(String message) { + super(message); + } + + public CityObjectCacheException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java index 06be694b..3eb54805 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import de.hft.stuttgart.citydoctor2.database.CityObjectCache; +import de.hft.stuttgart.citydoctor2.exceptions.CityObjectCacheException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.citygml4j.core.ade.ADEException; @@ -64,7 +66,7 @@ public class Healer { private Checker checker; private ModificationListener l; private HealingPlan plan; - + private CityObjectCache cache; static { // CppInitializer.initCpp(); } @@ -187,6 +189,7 @@ public class Healer { } }; } + cache = checker.getModel().getCache(); this.l = l; } @@ -208,7 +211,12 @@ public class Healer { } public void healModel(CityDoctorModel model) { - model.createFeatureStream().forEach(this::healCityObject); + try (AutoCloseable checkingContext = model.getCache().startCheckingMode()){ + model.createFeatureStream().forEach(this::healCityObject); + } catch (Exception e) { + throw new CityObjectCacheException("Healer critically failed during attempt to heal the model",e); + } + } public void healCityObject(CityObject co) { @@ -348,6 +356,8 @@ public class Healer { if (checker.getConfig().getParserConfiguration().useLowMemoryConsumption()) { co.clearMetaInformation(); } + //Reinsert object into cache and release locks + cache.put(co); } } diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestUtil.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestUtil.java index 72a2450f..cfc8063b 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestUtil.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestUtil.java @@ -31,7 +31,7 @@ public class TestUtil { } public static CityDoctorModel loadCityModel(String path, ValidationConfiguration config) throws CityGmlParseException, IOException, InvalidGmlFileException { - return CityGmlParser.parseCityGmlFile(path, config.getParserConfiguration()); + return CityGmlParser.nestedCityGmlFileParse(path, config.getParserConfiguration()); } public static Polygon getPolygonById(String gmlId, CityDoctorModel model) { -- GitLab From 8aa306c73b8f0406c28742e33c72279944d17385 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Tue, 16 Dec 2025 16:42:14 +0100 Subject: [PATCH 08/52] Test: Update Healer test cases --- .../stuttgart/citydoctor2/healing/TestHealer.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestHealer.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestHealer.java index 63ccbd05..46910bf2 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestHealer.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/TestHealer.java @@ -150,15 +150,14 @@ public class TestHealer { Checker c = new Checker(config, model); c.runChecks(); - - Geometry geom = model.getBuildings().toList().get(0).getGeometries().get(0); + Building b = model.getBuildings().toList().get(0); + Geometry geom = b.getGeometries().get(0); Healer healer = new Healer(c); - healer.setNumberOfIterations(33); - healer.healCityObject(model.getBuildings().toList().get(0)); + healer.healCityObject(b); c.runChecks(); - assertFalse(geom.containsAnyError()); + } @Test @@ -193,7 +192,6 @@ public class TestHealer { Geometry geom = model.getBuildings().toList().get(0).getGeometries().get(0); Healer healer = new Healer(c); - healer.setNumberOfIterations(83); healer.healCityObject(model.getBuildings().toList().get(0)); c.runChecks(); @@ -213,7 +211,6 @@ public class TestHealer { Geometry geom = model.getBuildings().toList().get(0).getGeometries().get(0); Healer healer = new Healer(c); - healer.setNumberOfIterations(165); healer.healCityObject(model.getBuildings().toList().get(0)); c.runChecks(); @@ -264,7 +261,6 @@ public class TestHealer { Geometry geom = model.getBuildings().toList().get(0).getGeometries().get(0); Healer healer = new Healer(c); - healer.setNumberOfIterations(45); healer.healCityObject(model.getBuildings().toList().get(0)); c.runChecks(); @@ -282,7 +278,6 @@ public class TestHealer { Healer healer = new Healer(c); - healer.setNumberOfIterations(45); healer.healModel(model); } @@ -299,7 +294,7 @@ public class TestHealer { Geometry geom = model.getBuildings().toList().get(0).getGeometries().get(0); Healer healer = new Healer(c); - healer.setNumberOfIterations(100); + healer.healCityObject(model.getBuildings().toList().get(0)); c.runChecks(); -- GitLab From 0423dc6ae6765a0c99594e2603d16b6f5faff115 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Tue, 16 Dec 2025 16:48:18 +0100 Subject: [PATCH 09/52] Build: Exclude CGAL-tests from Maven build Adds the CppTest marker-interface to exclude Healer tests that were interfacing with CGAL from the build. These tests are needed for the development of the replacement library, but shall not cause build failures in the meantime. --- .../citydoctor2/connect/edge/CppTest.java | 8 ++++++++ .../healing/HealPlanarPolygonErrorTest.java | 8 ++++++-- .../healing/HealSolidNotClosedErrorTest.java | 14 +++++++++++--- CityDoctorParent/pom.xml | 3 +++ 4 files changed, 28 insertions(+), 5 deletions(-) create mode 100644 CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/connect/edge/CppTest.java diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/connect/edge/CppTest.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/connect/edge/CppTest.java new file mode 100644 index 00000000..a7d60042 --- /dev/null +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/connect/edge/CppTest.java @@ -0,0 +1,8 @@ +package de.hft.stuttgart.citydoctor2.connect.edge; + +/** + * Marker interface for excluding testcases that use the deprecated connection to the C++ library. + * The tests are kept for the Java replacement library that is currently being developed. + */ +public interface CppTest { +} diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealPlanarPolygonErrorTest.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealPlanarPolygonErrorTest.java index 5359f2d6..a0697423 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealPlanarPolygonErrorTest.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealPlanarPolygonErrorTest.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; +import de.hft.stuttgart.citydoctor2.connect.edge.CppTest; import org.junit.Assert; import org.junit.Test; @@ -31,6 +32,7 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation; import de.hft.stuttgart.citydoctor2.healer.Healer; import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; +import org.junit.experimental.categories.Category; public class HealPlanarPolygonErrorTest { @@ -47,14 +49,14 @@ public class HealPlanarPolygonErrorTest { Assert.assertTrue(geom.containsError(CheckId.C_GE_P_NON_PLANAR)); Healer healer = new Healer(c); - healer.setNumberOfIterations(2); healer.healCityObject(model.getBuildings().toList().get(0)); Assert.assertFalse(geom.containsError(CheckId.C_GE_P_NON_PLANAR)); } - + @Test + @Category(CppTest.class) public void testNonplanarPolygon() { CppInitializer.initCpp(); Building b = new Building(); @@ -160,6 +162,7 @@ public class HealPlanarPolygonErrorTest { } @Test + @Category(CppTest.class) public void testNonplanarPolygon_Split() { CppInitializer.initCpp(); Building b = new Building(); @@ -274,6 +277,7 @@ public class HealPlanarPolygonErrorTest { } @Test + @Category(CppTest.class) public void testNonplanarPolygon_Split2() { CppInitializer.initCpp(); Building b = new Building(); diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java index 06e4d9e1..c6c57847 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java @@ -6,6 +6,7 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; +import de.hft.stuttgart.citydoctor2.connect.edge.CppTest; import org.citygml4j.xml.writer.CityGMLWriteException; import org.junit.Assert; import org.junit.Test; @@ -33,13 +34,16 @@ import de.hft.stuttgart.citydoctor2.datastructure.Geometry.Orientation; import de.hft.stuttgart.citydoctor2.healer.Healer; import de.hft.stuttgart.citydoctor2.parser.CityGmlParseException; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; +import org.junit.experimental.categories.Category; public class HealSolidNotClosedErrorTest { + /** * One polygon is split up in 3 but has not matching edges - */ + */ @Test + @Category(CppTest.class) public void testVertexCloseToEdge() { CppInitializer.initCpp(); Building b = new Building(); //Angelika: als Test eingefügt @@ -164,6 +168,7 @@ public class HealSolidNotClosedErrorTest { } @Test + @Category(CppTest.class) public void testMissingPolygon() { CppInitializer.initCpp(); Building b = new Building(); @@ -343,6 +348,7 @@ public class HealSolidNotClosedErrorTest { } @Test + @Category(CppTest.class) public void test2MissingPolygonsCpp() { CppInitializer.initCpp(); Building b = new Building(); @@ -420,7 +426,9 @@ public class HealSolidNotClosedErrorTest { assertNotNull(cr); assertEquals(ResultStatus.OK, cr.getResultStatus()); } - + + + /** * Missing Polygon has a self intersection * @throws IOException @@ -448,7 +456,7 @@ public class HealSolidNotClosedErrorTest { Assert.assertTrue(geom.containsError(CheckId.C_GE_S_NOT_CLOSED)); Healer healer = new Healer(c); - //healer.setNumberOfIterations(2); + healer.setNumberOfIterations(2); healer.healCityObject(model.getBuildings().toList().get(0)); Assert.assertFalse(geom.containsError(CheckId.C_GE_S_NOT_CLOSED)); diff --git a/CityDoctorParent/pom.xml b/CityDoctorParent/pom.xml index ed37dea0..a1287ba5 100644 --- a/CityDoctorParent/pom.xml +++ b/CityDoctorParent/pom.xml @@ -41,6 +41,9 @@ **/SolidSelfIntCheckFalsePositiveBigMeshTest.java + + de.hft.stuttgart.citydoctor2.connect.edge.CppTest + -- GitLab From 2e14765fb7f243fe1759d55fcaa559819d0686b6 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Thu, 18 Dec 2025 15:59:01 +0100 Subject: [PATCH 10/52] Feature: Implement DB configuration handling --- .../EmbeddedDatabaseConfiguration.java | 5 +++-- .../database/EmbeddedDatabaseHandler.java | 20 ++++++++++++++++- .../database/EmbeddedDataBaseHandlerTest.java | 22 +++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java index fb487934..2d07d65d 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java @@ -20,8 +20,9 @@ import java.io.File; public record EmbeddedDatabaseConfiguration(String databaseName, String databaseDirectory, int connectionPoolSize, boolean inMemoryMode, boolean attemptFallback, boolean debugMode, boolean tempFileMode) { - private static final String IN_MEMORY_PREFIX= "jdbc:h2:mem"; + private static final String IN_MEMORY_PREFIX= "mem:"; private static final String AUTO_SERVER_PARAMETER = ";AUTO_SERVER=TRUE"; + private static final String FILE_PREFIX = "file://"; /** * Instantiates the default configuration for the embedded database. */ @@ -40,7 +41,7 @@ public record EmbeddedDatabaseConfiguration(String databaseName, String database } public String getH2FileUrl(){ - return databaseDirectory + databaseName; + return FILE_PREFIX + databaseDirectory + databaseName; } public String getH2ParametersSuffix(){ diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java index e32251f0..cb0ec430 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java @@ -19,9 +19,12 @@ import org.h2gis.utilities.wrapper.DataSourceWrapper; import javax.sql.DataSource; import java.io.ByteArrayInputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InvalidClassException; +import java.nio.file.Files; +import java.nio.file.Path; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -38,12 +41,23 @@ public class EmbeddedDatabaseHandler { private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class); private final EmbeddedDatabaseConfiguration config; private DataSourceWrapper dataSource; + private File tempLoc = null; public EmbeddedDatabaseHandler(EmbeddedDatabaseConfiguration dbConfig){ config = dbConfig; String jdbcUrl; try { - jdbcUrl = createEmbeddedDataBase(config.getH2FileUrl()); + if (config.inMemoryMode()) { + jdbcUrl = createEmbeddedDataBase(config.getInMemoryH2Url()); + } else if (config.tempFileMode()){ + Path tmpDir = Files.createTempDirectory(config.databaseDirectory()); + tmpDir.toFile().deleteOnExit(); + String tempFileUrl = tmpDir.resolve(config.databaseName()).toFile().getAbsolutePath(); + jdbcUrl = createEmbeddedDataBase(tempFileUrl); + tempLoc = new File(tempFileUrl); + } else { + jdbcUrl = createEmbeddedDataBase(config.getH2FileUrl()); + } logger.debug("1] {}",jdbcUrl); } catch (Exception e) { @@ -86,6 +100,10 @@ public class EmbeddedDatabaseHandler { } } + public File getTempLoc(){ + return this.tempLoc; + } + public void setupFeaturesTable() { try (Connection con = dataSource.getConnection()) { diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java index a6286e01..c0919ea7 100644 --- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java +++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java @@ -24,7 +24,10 @@ import java.util.List; import static de.hft.stuttgart.citydoctor2.database.SerializationTest.assertCityObjectEquality; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; public class EmbeddedDataBaseHandlerTest { @@ -38,7 +41,26 @@ public class EmbeddedDataBaseHandlerTest { fail(e.getMessage()); } } + //TODO: FIX Test failures + @Test + public void testTempFileConfiguration(){ + EmbeddedDatabaseConfiguration tempFileconfig = new EmbeddedDatabaseConfiguration("database", + "inTempConfig",1, false,false, + false,true); + assertNull(handler.getTempLoc()); + EmbeddedDatabaseHandler tempHandler = new EmbeddedDatabaseHandler(tempFileconfig); + assertTrue(tempHandler.getTempLoc().exists()); + + } + @Test + public void testInMemoryConfiguration(){ + EmbeddedDatabaseConfiguration inMemoryConfig = new EmbeddedDatabaseConfiguration("database", + "irrelevant",1, true,false, + false,false); + EmbeddedDatabaseHandler memHandler = new EmbeddedDatabaseHandler(inMemoryConfig); + assertFalse(memHandler.getTempLoc().exists()); + } @Test public void testGetBboxIntersectingObjects() { -- GitLab From 182cf7937f76916bbe09fd450950cc70b13f5c9a Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Thu, 18 Dec 2025 15:59:31 +0100 Subject: [PATCH 11/52] Test: Fix test failure due to missing file --- .../healing/HealSolidNotClosedErrorTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java index c6c57847..b577700d 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/test/java/de/hft/stuttgart/citydoctor2/healing/HealSolidNotClosedErrorTest.java @@ -4,7 +4,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.File; import java.io.IOException; +import java.nio.file.Path; import de.hft.stuttgart.citydoctor2.connect.edge.CppTest; import org.citygml4j.xml.writer.CityGMLWriteException; @@ -439,20 +441,18 @@ public class HealSolidNotClosedErrorTest { */ @Test public void testMissingPolygon2() throws CityGmlParseException, IOException, InvalidGmlFileException { - String path = "src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0002-T0002.gml"; //bei diesem Modell stürzt die VM ab +// String path = "src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0002-T0002.gml"; //bei diesem Modell stürzt die VM ab // String path = "src/test/resources/SimpleSolid_SrefBS-GE-gml-SO-0002-T0001.gml"; //hier nur Fehlermeldung + String path = "src/test/resources/SimpleSolid_SrefBS-GE-gml-LR-0004-T0005.gml"; +// String path = "src/test/resources/SimpleSolid_SrefBS.gml"; ValidationConfiguration config = ValidationConfiguration.loadStandardValidationConfig(); CityDoctorModel model = TestUtil.loadCityModel(path, config); + Checker c = new Checker(config, model); c.runChecks(); Geometry geom = model.getBuildings().toList().get(0).getGeometries().get(0); - geom.updateVertices(); - for (Vertex v : geom.getVertices()) { - v.setX(v.getX() - 10); - v.setY(v.getY() - 10); - } Assert.assertTrue(geom.containsError(CheckId.C_GE_S_NOT_CLOSED)); Healer healer = new Healer(c); -- GitLab From 99763e56cecf449a4975878d5ac1ff8d5dc85304 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Wed, 14 Jan 2026 13:38:58 +0100 Subject: [PATCH 12/52] Refactor: Deduplicate error classes in log --- .../main/java/de/hft/stuttgart/citydoctor2/check/Checker.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java index bd69bb2d..50abb11b 100644 --- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java +++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java @@ -782,7 +782,7 @@ public class Checker { private List getMissedFeatures(List> futures) throws InterruptedException{ List missedList = new ArrayList<>(); - Set errors = new HashSet<>(); + Set errors = new HashSet<>(); for (Future future : futures) { try{ GmlId gmlId = future.get(); @@ -792,7 +792,7 @@ public class Checker { } catch (ExecutionException e){ logger.debug("A Task failed due to an unexpected exception", e); logger.debug(e.getCause()); - errors.add(e); + errors.add(e.getCause().toString()); } } if (!errors.isEmpty()) { -- GitLab From 430a06301a9822b29f6069b10b409be25a3aa3f6 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Wed, 14 Jan 2026 13:43:12 +0100 Subject: [PATCH 13/52] Feat: Implement in-memory and fallback of database Adds the handling and creation of the embedded database in RAM, which is used for the in-memory and fallback modes. --- .../EmbeddedDatabaseConfiguration.java | 28 ++++++++++---- .../database/EmbeddedDatabaseHandler.java | 38 ++++++++++++------- .../database/EmbeddedDataBaseHandlerTest.java | 30 ++++++++------- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java index 2d07d65d..5f5ec31f 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java @@ -1,6 +1,7 @@ package de.hft.stuttgart.citydoctor2.database; import java.io.File; +import java.util.StringJoiner; /** * Record containing the configuration parameters for the setup of the embedded database @@ -20,8 +21,10 @@ import java.io.File; public record EmbeddedDatabaseConfiguration(String databaseName, String databaseDirectory, int connectionPoolSize, boolean inMemoryMode, boolean attemptFallback, boolean debugMode, boolean tempFileMode) { - private static final String IN_MEMORY_PREFIX= "mem:"; - private static final String AUTO_SERVER_PARAMETER = ";AUTO_SERVER=TRUE"; + private static final String IN_MEMORY_PREFIX = "mem:"; + private static final String JDBC_DRIVER_PREFIX = "jdbc:h2:"; + private static final String AUTO_SERVER_PARAMETER = "AUTO_SERVER=TRUE"; + private static final String KEEP_ALIVE_PARAMETER = "DB_CLOSE_DELAY=-1"; private static final String FILE_PREFIX = "file://"; /** * Instantiates the default configuration for the embedded database. @@ -36,20 +39,31 @@ public record EmbeddedDatabaseConfiguration(String databaseName, String database true, true, true, false); } + public String wrapUrlWithH2JdbcDriver(String Url){ + return JDBC_DRIVER_PREFIX + Url + getH2ParametersSuffix(); + } + public String getInMemoryH2Url() { - return IN_MEMORY_PREFIX + databaseName; + return wrapUrlWithH2JdbcDriver(IN_MEMORY_PREFIX + databaseName); + } + + public String getFallbackH2Url(){ + return getInMemoryH2Url() + ";" + KEEP_ALIVE_PARAMETER; } public String getH2FileUrl(){ - return FILE_PREFIX + databaseDirectory + databaseName; + return wrapUrlWithH2JdbcDriver(databaseDirectory + databaseName); } public String getH2ParametersSuffix(){ + StringJoiner joiner = new StringJoiner(";",";",""); if (debugMode){ - return AUTO_SERVER_PARAMETER; - } else { - return ";"; + joiner.add(AUTO_SERVER_PARAMETER); + } + if (inMemoryMode){ + joiner.add(KEEP_ALIVE_PARAMETER); } + return joiner.toString(); } } diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java index cb0ec430..1d27f86e 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseHandler.java @@ -14,10 +14,9 @@ import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.h2gis.functions.factory.H2GISDBFactory; import org.h2gis.utilities.wrapper.DataSourceWrapper; +import org.h2gis.functions.factory.H2GISFunctions; -import javax.sql.DataSource; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; @@ -26,6 +25,7 @@ import java.io.InvalidClassException; import java.nio.file.Files; import java.nio.file.Path; import java.sql.Connection; +import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -41,20 +41,21 @@ public class EmbeddedDatabaseHandler { private static final Logger logger = LogManager.getLogger(EmbeddedDatabaseHandler.class); private final EmbeddedDatabaseConfiguration config; private DataSourceWrapper dataSource; - private File tempLoc = null; + private File tempFileDBLocation = null; public EmbeddedDatabaseHandler(EmbeddedDatabaseConfiguration dbConfig){ config = dbConfig; String jdbcUrl; try { if (config.inMemoryMode()) { - jdbcUrl = createEmbeddedDataBase(config.getInMemoryH2Url()); + jdbcUrl = setupInMemoryDataBase(config.getInMemoryH2Url()); } else if (config.tempFileMode()){ Path tmpDir = Files.createTempDirectory(config.databaseDirectory()); tmpDir.toFile().deleteOnExit(); - String tempFileUrl = tmpDir.resolve(config.databaseName()).toFile().getAbsolutePath(); - jdbcUrl = createEmbeddedDataBase(tempFileUrl); - tempLoc = new File(tempFileUrl); + String tempFileUrl = tmpDir.resolve(config.databaseName()).toFile().getCanonicalPath(); + jdbcUrl = createEmbeddedDataBase(config.wrapUrlWithH2JdbcDriver(tempFileUrl)); + tempFileDBLocation = new File(tempFileUrl+".mv.db"); + tempFileDBLocation.deleteOnExit(); } else { jdbcUrl = createEmbeddedDataBase(config.getH2FileUrl()); } @@ -66,7 +67,7 @@ public class EmbeddedDatabaseHandler { if (config.attemptFallback()){ logger.info("Attempting fallback to in-memory database"); try { - jdbcUrl = createEmbeddedDataBase(config.getInMemoryH2Url()); + jdbcUrl = setupInMemoryDataBase(config.getFallbackH2Url()); } catch (Exception e1) { throw new EmbeddedDatabaseHandlerException("Fallback to in-memory database failed, embedded database could not be created", e1); } @@ -93,15 +94,26 @@ public class EmbeddedDatabaseHandler { } private String createEmbeddedDataBase(String dbUrl) throws SQLException { - DataSource source = H2GISDBFactory.createDataSource(dbUrl, - true, config.getH2ParametersSuffix()); - try (Connection con = source.getConnection()) { +// DataSource source = H2GISDBFactory.createDataSource("file://"+dbUrl, +// true, config.getH2ParametersSuffix()); +// try (Connection con = source.getConnection()) { +// return con.getMetaData().getURL(); +// } + try (Connection con = DriverManager.getConnection(dbUrl,"sa","sa")){ + H2GISFunctions.load(con); return con.getMetaData().getURL(); } } - public File getTempLoc(){ - return this.tempLoc; + private String setupInMemoryDataBase(String dbUrl) throws SQLException{ + try (Connection con = DriverManager.getConnection(dbUrl,"sa","sa")){ + H2GISFunctions.load(con); + return con.getMetaData().getURL(); + } + } + + public File getTempFileDBLocation(){ + return this.tempFileDBLocation; } diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java index c0919ea7..371cea82 100644 --- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java +++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java @@ -16,7 +16,6 @@ import de.hft.stuttgart.citydoctor2.parser.CityGmlParser; import de.hft.stuttgart.citydoctor2.parser.InvalidGmlFileException; import de.hft.stuttgart.citydoctor2.parser.ParserConfiguration; import org.citygml4j.core.model.building.AbstractBuilding; -import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; @@ -33,37 +32,38 @@ import static org.junit.Assert.fail; public class EmbeddedDataBaseHandlerTest { private EmbeddedDatabaseHandler handler = new EmbeddedDatabaseHandler(new EmbeddedDatabaseConfiguration()); - @Before - public void resetDatabase() { - try { - handler.setupFeaturesTable(); - } catch (Exception e) { - fail(e.getMessage()); - } - } - //TODO: FIX Test failures + @Test public void testTempFileConfiguration(){ EmbeddedDatabaseConfiguration tempFileconfig = new EmbeddedDatabaseConfiguration("database", "inTempConfig",1, false,false, false,true); - assertNull(handler.getTempLoc()); EmbeddedDatabaseHandler tempHandler = new EmbeddedDatabaseHandler(tempFileconfig); - assertTrue(tempHandler.getTempLoc().exists()); + assertTrue(tempHandler.getTempFileDBLocation().exists()); } @Test - public void testInMemoryConfiguration(){ + public void testInMemoryConfiguration() throws CityGmlParseException, InvalidGmlFileException { EmbeddedDatabaseConfiguration inMemoryConfig = new EmbeddedDatabaseConfiguration("database", "irrelevant",1, true,false, false,false); EmbeddedDatabaseHandler memHandler = new EmbeddedDatabaseHandler(inMemoryConfig); - assertFalse(memHandler.getTempLoc().exists()); + assertNull(memHandler.getTempFileDBLocation()); + ParserConfiguration config = new ParserConfiguration(8, false); + CityDoctorModel model = CityGmlParser.nestedCityGmlFileParse("src/test/resources/SimpleSolid_SrefBS.gml", config); + Building b = model.getBuildings().toList().get(0); + assertEquals(0, memHandler.getFeatureCount()); + memHandler.marshallCityObject(b); + assertEquals(1, memHandler.getFeatureCount()); + CityObject co = memHandler.unmarshallCityObject(b.getGmlId()); + assertNotNull(co); + assertTrue(co instanceof Building); } @Test public void testGetBboxIntersectingObjects() { + handler.setupFeaturesTable(); Building b = new Building(); Building b2 = new Building(); b2.setGmlObject(Mockito.mock(AbstractBuilding.class)); @@ -144,6 +144,7 @@ public class EmbeddedDataBaseHandlerTest { @Test public void testGetBboxBufferIntersectingObjects() { + handler.setupFeaturesTable(); Building b = new Building(); Building b2 = new Building(); b2.setGmlObject(Mockito.mock(AbstractBuilding.class)); @@ -230,6 +231,7 @@ public class EmbeddedDataBaseHandlerTest { Building b = model.getBuildings().toList().get(0); assertNotNull(b); + assertEquals(1, handler.getFeatureCount()); handler.marshallCityObject(b); assertEquals(1, handler.getFeatureCount()); -- GitLab From 9e55508420cf8c9b4d623a53cf931412ba6f5f28 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Wed, 14 Jan 2026 13:45:59 +0100 Subject: [PATCH 14/52] Fix: Fixed NPE in stream-healing --- .../java/de/hft/stuttgart/citydoctor2/healer/Healer.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java index 3eb54805..d62e505c 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java @@ -13,6 +13,7 @@ import java.util.Map; import java.util.Objects; import de.hft.stuttgart.citydoctor2.database.CityObjectCache; +import de.hft.stuttgart.citydoctor2.database.UnconnectedCache; import de.hft.stuttgart.citydoctor2.exceptions.CityObjectCacheException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -189,7 +190,12 @@ public class Healer { } }; } - cache = checker.getModel().getCache(); + if(checker.getModel() != null){ + cache = checker.getModel().getCache(); + } else { + //Stream healing is being used, use an unconnected cache + cache = new UnconnectedCache(); + } this.l = l; } -- GitLab From 11c31eb0e587f48765fefb04d0a653f9bf28d1af Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Thu, 15 Jan 2026 15:06:22 +0100 Subject: [PATCH 15/52] Refactor: Cleanup Code --- .../database/EmbeddedDatabaseConfiguration.java | 1 - .../database/EmbeddedDataBaseHandlerTest.java | 4 +--- .../stuttgart/citydoctor2/check/Checker.java | 17 ++++++----------- .../stuttgart/citydoctor2/healer/Healer.java | 4 +--- 4 files changed, 8 insertions(+), 18 deletions(-) diff --git a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java index 5f5ec31f..be56f08a 100644 --- a/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java +++ b/CityDoctorParent/CityDoctorModel/src/main/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDatabaseConfiguration.java @@ -25,7 +25,6 @@ public record EmbeddedDatabaseConfiguration(String databaseName, String database private static final String JDBC_DRIVER_PREFIX = "jdbc:h2:"; private static final String AUTO_SERVER_PARAMETER = "AUTO_SERVER=TRUE"; private static final String KEEP_ALIVE_PARAMETER = "DB_CLOSE_DELAY=-1"; - private static final String FILE_PREFIX = "file://"; /** * Instantiates the default configuration for the embedded database. */ diff --git a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java index 371cea82..b795e50e 100644 --- a/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java +++ b/CityDoctorParent/CityDoctorModel/src/test/java/de/hft/stuttgart/citydoctor2/database/EmbeddedDataBaseHandlerTest.java @@ -23,15 +23,13 @@ import java.util.List; import static de.hft.stuttgart.citydoctor2.database.SerializationTest.assertCityObjectEquality; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; public class EmbeddedDataBaseHandlerTest { - private EmbeddedDatabaseHandler handler = new EmbeddedDatabaseHandler(new EmbeddedDatabaseConfiguration()); + private final EmbeddedDatabaseHandler handler = new EmbeddedDatabaseHandler(new EmbeddedDatabaseConfiguration()); @Test public void testTempFileConfiguration(){ diff --git a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java index 50abb11b..acb1e967 100644 --- a/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java +++ b/CityDoctorParent/CityDoctorValidation/src/main/java/de/hft/stuttgart/citydoctor2/check/Checker.java @@ -150,8 +150,8 @@ public class Checker { return; } File xmlFile = new File(xmlOutput); - if (xmlFile.getParentFile() != null) { - xmlFile.getParentFile().mkdirs(); + if (xmlFile.getParentFile() != null && xmlFile.getParentFile().mkdirs()) { + logger.trace("Xml dir created"); } Reporter reporter = new XmlValidationReporter(); try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(xmlFile.getAbsolutePath()))) { @@ -166,9 +166,10 @@ public class Checker { return; } File pdfFile = new File(pdfOutput); - if (pdfFile.getParentFile() != null) { - pdfFile.getParentFile().mkdirs(); + if (pdfFile.getParentFile() != null && pdfFile.getParentFile().mkdirs()){ + logger.trace("Pdf dir created"); } + Reporter reporter = new PdfReporter(); try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(pdfFile.getAbsolutePath()))) { reporter.writeReport(checkConfig, bos, model, config); @@ -243,7 +244,6 @@ public class Checker { private void handleSchematronResults(SvrlContentHandler handler) { CityObjectCache cache = model.getCache(); handleSchematronErrorsGlobal(handler.getGeneralErrors()); - Map featureMap = new HashMap<>(); boolean onlySchematron = execLayers.isEmpty(); if (onlySchematron) { CheckableUtilsVisitor visitor = new CheckableUtilsVisitor() { @@ -252,12 +252,7 @@ public class Checker { checkable.setValidated(true); } }; - model.createFeatureStream().forEach(f -> { - featureMap.put(f.getGmlId().getGmlString(), f); - f.accept(visitor); - }); - } else { - model.createFeatureStream().forEach(f -> featureMap.put(f.getGmlId().getGmlString(), f)); + model.createFeatureStream().forEach(co -> co.accept(visitor)); } handler.getFeatureErrors().forEach((k, v) -> { String trimmedId = k.trim(); diff --git a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java index d62e505c..f6b3c068 100644 --- a/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java +++ b/CityDoctorParent/Extensions/CityDoctorHealer/src/main/java/de/hft/stuttgart/citydoctor2/healer/Healer.java @@ -296,9 +296,7 @@ public class Healer { * * @param aco the feature to write * @return the byte array containing the feature - * @throws ADEException - * @throws CityGMLBuilderException - * @throws CityGMLWriteException + * @throws CityGMLWriteException if writing of the CityGML file fails */ private byte[] writeCityGml(AbstractCityObject aco) throws CityGMLWriteException { CityModel model = new CityModel(); -- GitLab From 3df0f7375aaf536478fbdc0434be0fe54e332e78 Mon Sep 17 00:00:00 2001 From: Luna Riegel Date: Thu, 15 Jan 2026 15:07:29 +0100 Subject: [PATCH 16/52] Test: Add run config for CityDoctorGUI --- .run/CityDoctorGUIStarter.run.xml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.run/CityDoctorGUIStarter.run.xml b/.run/CityDoctorGUIStarter.run.xml index b803b46b..b2912b34 100644 --- a/.run/CityDoctorGUIStarter.run.xml +++ b/.run/CityDoctorGUIStarter.run.xml @@ -13,4 +13,18 @@