"...00 Introduction.adoc" did not exist on "12c03ccfad75edbd87b652103e2b07aaae577668"
Commit b8bac3e6 authored by Luna Riegel's avatar Luna Riegel
Browse files

Merge branch 'dev' into 'master'

CityDoctor Version 3.18.2

See merge request !40
parents 947020ff 4b27a7e4
Pipeline #12385 passed with stage
in 2 minutes and 4 seconds
......@@ -13,4 +13,18 @@
<option name="Make" enabled="true" />
</method>
</configuration>
<configuration default="false" name="CityDoctorGUIStarter" type="Application" factoryName="Application" nameIsGenerated="true">
<option name="MAIN_CLASS_NAME" value="de.hft.stuttgart.citydoctor2.gui.CityDoctorGUIStarter" />
<module name="CityDoctorGUI" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/CityDoctorParent/Extensions/CityDoctorGUI" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="de.hft.stuttgart.citydoctor2.gui.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
\ No newline at end of file
......@@ -5,6 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [3.18.2] (2026-02-24)
### Added
- CLI parameter "-db_location" for changing the location of the database-file of the embedded database. This parameter will
override the location settings of the DB-settings file.
- CLI parameter "-db_settings" for giving a path to a DB-settings .properties file.
- Added -db_location pointing to the system's temp directory to the start scripts for the binaries
- Added buttons for (un)checking all Geometric or Semantic requirements
- Added a Hyperlink to CityDoctor's homepage with detailed explanations for each requirement
- The GUI will now show a loading spinner while waiting for Schematron to finish.
### Fixes
- Connected CityDoctorHealer to the embedded database
- Various small fixes and improvements to performance
## [3.18.1] (2025-12-10)
### Hotfix
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.18.1</version>
<version>3.18.2</version>
</parent>
<artifactId>CityDoctorCheckResult</artifactId>
<dependencies>
......
......@@ -22,6 +22,7 @@ import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
......@@ -65,7 +66,11 @@ public class CheckReport {
private Map<String, FeatureReport> featureReports;
public static CheckReport load(String file) throws CheckReportParseException {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
return load(Path.of(file));
}
public static CheckReport load(Path path) throws CheckReportParseException {
try (BufferedReader reader = new BufferedReader(new FileReader(path.toFile()))) {
CheckReport report = (CheckReport) getContext().createUnmarshaller().unmarshal(new InputSource(reader));
enrichReportStructure(report);
return report;
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.18.1</version>
<version>3.18.2</version>
</parent>
<artifactId>CityDoctorEdge</artifactId>
<dependencies>
......
......@@ -41,7 +41,7 @@ public class IntersectionErrorsTest {
Geometry geom = new Geometry(GeometryType.SOLID, Lod.LOD1, Orientation.OUTWARD);
ConcretePolygon p1 = new ConcretePolygon();
geom.addPolygon(p1);
LinearRing ext1 = new LinearRing(LinearRingType.EXTERIOR);
p1.setExteriorRing(ext1);
......@@ -66,6 +66,7 @@ public class IntersectionErrorsTest {
ext1.addVertex(v1);
ConcretePolygon p2 = new ConcretePolygon();
geom.addPolygon(p2);
LinearRing ext2 = new LinearRing(LinearRingType.EXTERIOR);
p2.setExteriorRing(ext2);
......@@ -88,8 +89,8 @@ public class IntersectionErrorsTest {
ext2.addVertex(v8);
ext2.addVertex(v5);
geom.addPolygon(p1);
geom.addPolygon(p2);
geom.prepareForChecking();
......
......@@ -6,7 +6,7 @@
<parent>
<groupId>de.hft.stuttgart</groupId>
<artifactId>CityDoctorParent</artifactId>
<version>3.18.1</version>
<version>3.18.2</version>
</parent>
<properties>
<versionString>${project.version}-${git.commit.id.abbrev}</versionString>
......
......@@ -40,6 +40,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
public abstract class AbstractErrorVisitor implements ErrorVisitor {
......@@ -206,5 +207,9 @@ public abstract class AbstractErrorVisitor implements ErrorVisitor {
@Override
public void visit(SolidError err) {
}
@Override
public void visit(XMLValidationError err) {
}
}
......@@ -345,7 +345,7 @@ public abstract non-sealed class Check implements CheckableVisitor {
* @param config sometimes there are global parameters which can be used by
* checks. Those are be stored in this container
*/
public void init(Map<String, String> params, ParserConfiguration config) {
public void init(Map<CheckId, Map<String, String>> params, ParserConfiguration config) {
}
......
......@@ -83,4 +83,8 @@ public interface CheckError extends Serializable {
*/
public void report(ErrorReport report);
public default boolean isParsingError() {
return false;
}
}
......@@ -26,7 +26,7 @@ import java.util.Map;
import de.hft.stuttgart.citydoctor2.utils.CheckErrorFound;
import de.hft.stuttgart.citydoctor2.utils.visitors.CheckableErrorCollector;
import de.hft.stuttgart.citydoctor2.utils.visitors.ClearVisitorResultsVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.ClearResultsVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.ClearMetaInformationVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.ContainsAnyErrorVisitor;
import de.hft.stuttgart.citydoctor2.utils.visitors.ContainsErrorVisitor;
......@@ -221,18 +221,23 @@ public abstract class Checkable implements Serializable {
}
/**
* Clears the checkResults list of this checkable.
* Clears the checkResults of this checkable unless the error occured during parsing.
*/
public void clearCheckResults() {
setValidated(false);
checkResults.clear();
checkResults.entrySet().removeIf(e -> {
if (e.getValue().getError() == null) {
return true;
}
return !e.getValue().getError().isParsingError();
});
setValidated(!checkResults.isEmpty());
}
/**
* Clears the checkResults list of this checkable and all child objects in its datastructure.
*/
public final void clearAllContainedCheckResults() {
this.accept(new ClearVisitorResultsVisitor());
this.accept(new ClearResultsVisitor());
}
/**
......
......@@ -66,6 +66,7 @@ public record ErrorId(String name) implements Serializable {
public static final ErrorId SE_BS_UNFRAGMENTED = new ErrorId("SE_BS_UNFRAGMENTED");
public static final ErrorId GE_P_DEGENERATED_RING = new ErrorId("GE_P_DEGENERATED_POLYGON");
public static final ErrorId SE_POLYGON_WITHOUT_SURFACE = new ErrorId("SE_POLYGON_WITHOUT_SURFACE");
public static final ErrorId SC_SCHEMA_VALIDATION = new ErrorId("SC_SCHEMA_VALIDATION");
......
......@@ -58,6 +58,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
/**
* Visitor pattern interface to access errors. This is to determine the type of
......@@ -149,5 +150,7 @@ public interface ErrorVisitor {
public void visit(CheckError err);
public void visit(SolidError err);
public void visit(XMLValidationError err);
}
......@@ -51,6 +51,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
/**
* Visitor pattern interface used in the healing procedures. The modification
......@@ -199,6 +200,10 @@ public interface HealingMethod {
default boolean visit(PolygonWithoutSurfaceError err, ModificationListener l) {
return false;
}
default boolean visit(XMLValidationError err, ModificationListener l) {
return false;
}
public HealingMethod createNew();
......
......@@ -58,6 +58,7 @@ import de.hft.stuttgart.citydoctor2.check.error.SolidSelfIntError;
import de.hft.stuttgart.citydoctor2.check.error.SurfaceUnfragmentedError;
import de.hft.stuttgart.citydoctor2.check.error.TooFewPolygonsError;
import de.hft.stuttgart.citydoctor2.check.error.UnknownCheckError;
import de.hft.stuttgart.citydoctor2.check.error.XMLValidationError;
import de.hft.stuttgart.citydoctor2.datastructure.Edge;
import de.hft.stuttgart.citydoctor2.datastructure.LinearRing;
import de.hft.stuttgart.citydoctor2.datastructure.Polygon;
......@@ -403,5 +404,10 @@ public class QualityAdeErrorVisitor extends AbstractErrorVisitor {
public void visit(PolygonWithoutSurfaceError err) {
// not translated
}
@Override
public void visit(XMLValidationError err) {
// not translated
}
}
package de.hft.stuttgart.citydoctor2.check.error;
import java.io.Serial;
import de.hft.stuttgart.citydoctor2.check.CheckError;
import de.hft.stuttgart.citydoctor2.check.ErrorId;
import de.hft.stuttgart.citydoctor2.check.ErrorReport;
import de.hft.stuttgart.citydoctor2.check.ErrorType;
import de.hft.stuttgart.citydoctor2.check.ErrorVisitor;
import de.hft.stuttgart.citydoctor2.check.HealingMethod;
import de.hft.stuttgart.citydoctor2.check.ModificationListener;
import de.hft.stuttgart.citydoctor2.datastructure.GmlElement;
public class XMLValidationError implements CheckError {
@Serial
private static final long serialVersionUID = 4002356100680460598L;
private String message;
public XMLValidationError(String message) {
this.message = message;
}
@Override
public ErrorType getType() {
return ErrorType.ERROR;
}
@Override
public ErrorId getErrorId() {
return ErrorId.SC_SCHEMA_VALIDATION;
}
@Override
public GmlElement getFeature() {
return null;
}
@Override
public void accept(ErrorVisitor errorVisitor) {
errorVisitor.visit(this);
}
@Override
public boolean accept(HealingMethod method, ModificationListener l) {
return method.visit(this, l);
}
public String getMessage() {
return message;
}
@Override
public void report(ErrorReport report) {
report.add("message", message);
}
@Override
public boolean isParsingError() {
return true;
}
}
......@@ -3,6 +3,7 @@ package de.hft.stuttgart.citydoctor2.database;
import de.hft.stuttgart.citydoctor2.datastructure.CityObject;
import de.hft.stuttgart.citydoctor2.datastructure.GmlId;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
......@@ -21,6 +22,8 @@ public interface CityObjectCache {
*/
CityObject get(GmlId id);
Collection<CityObject> getAll(List<GmlId> ids);
/**
* Replaces a CityObject with another one
* @param id GmlID of the CityObject that is to be replaced
......
package de.hft.stuttgart.citydoctor2.database;
import com.github.benmanes.caffeine.cache.CacheLoader;
import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException;
import org.jspecify.annotations.NonNull;
import java.util.Map;
import java.util.Set;
public class DatabaseCacheLoader<K, V> implements CacheLoader<K, V> {
private final Unmarshaller<K, V> unmarshaller;
private final BatchUnmarshaller<K, V> batchUnmarshaller;
public DatabaseCacheLoader(Unmarshaller<K,V> unmarshaller, BatchUnmarshaller<K, V> batchUnmarshaller){
this.unmarshaller = unmarshaller;
this.batchUnmarshaller = batchUnmarshaller;
}
@Override
public @NonNull V load(@NonNull K key) throws EmbeddedDatabaseHandlerException {
return unmarshaller.unmarshall(key);
}
@Override
@NonNull
public Map<K, V> loadAll(@NonNull Set<? extends K> keys) throws EmbeddedDatabaseHandlerException {
return batchUnmarshaller.unmarshall(keys);
}
public interface Unmarshaller<K, V> {
V unmarshall(K key) throws EmbeddedDatabaseHandlerException;
}
public interface BatchUnmarshaller<K, V> {
Map<K, V> unmarshall(Set<? extends K> keys) throws EmbeddedDatabaseHandlerException;
}
}
package de.hft.stuttgart.citydoctor2.database;
import de.hft.stuttgart.citydoctor2.exceptions.EmbeddedDatabaseHandlerException;
import de.hft.stuttgart.citydoctor2.utils.Localization;
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.Path;
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 = new Properties();
private DatabaseSettings() {}
static {
File propFile = new File("DBSettings.properties");
if (propFile.exists()) {
loadPropertiesFromFile(propFile);
}
}
public static void loadPropertiesFromFile(File file) throws EmbeddedDatabaseHandlerException {
try (BufferedReader bis = new BufferedReader(new FileReader(file))) {
props.load(bis);
} catch (IOException e) {
throw new EmbeddedDatabaseHandlerException(e);
}
}
public static void setDBLocation(String dbLocation) throws EmbeddedDatabaseHandlerException{
Path dbPath = Paths.get(dbLocation).toAbsolutePath();
File dbLocationFile = dbPath.toFile();
if (!dbLocationFile.isFile()) {
dbPath = dbPath.resolve("cd_db");
}
Path dbParentDirectoryPath = dbPath.getParent();
if (dbParentDirectoryPath.toFile().mkdirs()){
logger.trace("Created parent directories for database location");
}
if (!dbParentDirectoryPath.toFile().canWrite()) {
throw new EmbeddedDatabaseHandlerException("Missing write permissions for database location:" + dbLocation);
}
props.setProperty("database.name", dbPath.getFileName().toString());
props.setProperty("database.directory", dbParentDirectoryPath.toAbsolutePath().toString());
}
public static EmbeddedDatabaseConfiguration getConfig() {
EmbeddedDatabaseConfiguration defaultConfig = new EmbeddedDatabaseConfiguration();
if (props.isEmpty()) {
logger.trace("No database settings found or specified, using default config");
return defaultConfig;
}
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);
boolean debugMode = Boolean.parseBoolean(debugModeString);
boolean tempMode = Boolean.parseBoolean(tempModeString);
int poolSize = defaultConfig.connectionPoolSize();
if (poolSizeString != null && !poolSizeString.isBlank()){
try {
int size = Integer.parseInt(poolSizeString);
if (size < 1) {
logger.warn(Localization.getText("DatabaseSettings.poolSizeLessThanOne"));
} else poolSize = size;
} catch (NumberFormatException e) {
logger.warn(Localization.getText("DatabaseSettings.poolSizeNaN"));
}
}
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
logger.warn(Localization.getText("DatabaseSettings.invalidPath"));
name = defaultConfig.databaseName();
directory = defaultConfig.databaseDirectory();
}
}
return new EmbeddedDatabaseConfiguration(name, directory, poolSize, inMemoryMode, attemptFallback, debugMode, tempMode);
}
}
package de.hft.stuttgart.citydoctor2.database;
import java.io.File;
import java.nio.file.Paths;
import java.util.StringJoiner;
/**
* 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.
* @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 tempFileMode) {
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";
/**
* Instantiates the default configuration for the embedded database.
*/
EmbeddedDatabaseConfiguration() {
this("cd_db", File.separator + "database"+ File.separator, Runtime.getRuntime().availableProcessors(),
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 wrapUrlWithH2JdbcDriver(String url){
return JDBC_DRIVER_PREFIX + url + getH2ParametersSuffix();
}
public String getInMemoryH2Url() {
return wrapUrlWithH2JdbcDriver(IN_MEMORY_PREFIX + databaseName);
}
public String getFallbackH2Url(){
return getInMemoryH2Url() + ";" + KEEP_ALIVE_PARAMETER;
}
public String getH2FileUrl(){
return wrapUrlWithH2JdbcDriver(Paths.get(databaseDirectory).resolve(databaseName).toString());
}
public String getH2ParametersSuffix(){
StringJoiner joiner = new StringJoiner(";",";","");
if (debugMode){
joiner.add(AUTO_SERVER_PARAMETER);
}
if (inMemoryMode){
joiner.add(KEEP_ALIVE_PARAMETER);
}
return joiner.toString();
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment