Browse Source

Repo disclosed

betaFixes
Bianca Steffes 3 years ago
commit
3ffa53d717
  1. 8
      .gitignore
  2. 21
      health_data_export/.classpath
  3. 29
      health_data_export/.project
  4. 82
      health_data_export/dependency-reduced-pom.xml
  5. BIN
      health_data_export/health_data_export-v6-shaded.jar
  6. 151
      health_data_export/pom.xml
  7. BIN
      health_data_export/res/application/RI_Logo_blau_en_short.icns
  8. BIN
      health_data_export/res/application/RI_Logo_blau_en_short.png
  9. BIN
      health_data_export/res/application/RI_Logo_weiss_en.png
  10. BIN
      health_data_export/res/application/archive.png
  11. BIN
      health_data_export/res/application/csv-grey.png
  12. BIN
      health_data_export/res/application/csv-white.png
  13. BIN
      health_data_export/res/application/csv.png
  14. BIN
      health_data_export/res/application/datenschutz.png
  15. BIN
      health_data_export/res/application/dreieck.png
  16. BIN
      health_data_export/res/application/dreieck2.png
  17. BIN
      health_data_export/res/application/dsgvo.png
  18. BIN
      health_data_export/res/application/filepath.png
  19. BIN
      health_data_export/res/application/help.png
  20. BIN
      health_data_export/res/application/icon.ico
  21. BIN
      health_data_export/res/application/inspect.png
  22. BIN
      health_data_export/res/application/internet.png
  23. BIN
      health_data_export/res/application/result.png
  24. BIN
      health_data_export/res/application/select.png
  25. BIN
      health_data_export/res/application/start2.png
  26. 253
      health_data_export/res/application/style.css
  27. BIN
      health_data_export/res/application/txt-grey.png
  28. BIN
      health_data_export/res/application/txt-white.png
  29. BIN
      health_data_export/res/application/txt.png
  30. 22
      health_data_export/src/application/Main.java
  31. 17
      health_data_export/src/application/SuperMain.java
  32. 1717
      health_data_export/src/application/UICoordinator.java
  33. 426
      health_data_export/src/application/customviews/EntryView.java
  34. 94
      health_data_export/src/application/customviews/FileView.java
  35. 130
      health_data_export/src/application/customviews/SubEntryView.java
  36. 59
      health_data_export/src/application/enums/BiologicalSex.java
  37. 84
      health_data_export/src/application/enums/BloodType.java
  38. 75
      health_data_export/src/application/enums/EntryType.java
  39. 52
      health_data_export/src/application/enums/NavState.java
  40. 82
      health_data_export/src/application/enums/SkinType.java
  41. 97
      health_data_export/src/application/helpers/ChangeViewCallback.java
  42. 144
      health_data_export/src/application/helpers/Utils.java
  43. 89
      health_data_export/src/application/helpers/WorkWhileProgressThread.java
  44. 45
      health_data_export/src/application/helpers/wrappers/AttributeDataInfoTuple.java
  45. 49
      health_data_export/src/application/helpers/wrappers/Element.java
  46. 61
      health_data_export/src/application/helpers/wrappers/GeneralSaveInfo.java
  47. 62
      health_data_export/src/application/helpers/wrappers/MeSaveInfo.java
  48. 59
      health_data_export/src/application/helpers/wrappers/Occupation.java
  49. 47
      health_data_export/src/application/helpers/wrappers/SubElement.java
  50. 50
      health_data_export/src/application/helpers/wrappers/WrappedException.java
  51. 138
      health_data_export/src/application/helpers/wrappers/WriteSelection.java
  52. 217
      health_data_export/src/application/parsing/FileWrapper.java
  53. 150
      health_data_export/src/application/parsing/ParsingWrapper.java
  54. 287
      health_data_export/src/application/parsing/ReadHandler.java
  55. 632
      health_data_export/src/application/parsing/WriteHandler.java
  56. 30
      health_data_export/src/application/res/Backgrounds.java
  57. 24
      health_data_export/src/application/res/Colors.java
  58. 575
      health_data_export/src/application/res/Occupations.java
  59. 50
      health_data_export/src/application/res/PrivacyStatementText.java
  60. 155
      health_data_export/src/application/res/Text.java
  61. 5
      health_data_export/target/.gitignore
  62. BIN
      health_data_export/target/health_data_export-7.jar
  63. 5
      health_data_export/target/maven-archiver/pom.properties
  64. BIN
      health_data_export/target/original-health_data_export-7.jar

8
.gitignore

@ -0,0 +1,8 @@
.metadata/
health_data_export/.settings/
health_data_export/bin/
health_data_export/target/classes/
health_data_export/target/generated-sources/
health_data_export/target/maven_archiver/
health_data_export/target/maven-status/
health_data_export/target/test-classes/

21
health_data_export/.classpath

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry including="**/*.java" kind="src" output="target/classes" path="src">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="res"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

29
health_data_export/.project

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>health_data_export</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.xtext.ui.shared.xtextBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.xtext.ui.shared.xtextNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

82
health_data_export/dependency-reduced-pom.xml

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>health_data_export</groupId>
<artifactId>health_data_export</artifactId>
<version>7</version>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.cbi.maven.plugins</groupId>
<artifactId>eclipse-dmg-packager</artifactId>
<version>1.1.3</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.5</version>
<configuration>
<mainClass>health_data_export/application.SuperMain</mainClass>
</configuration>
</plugin>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>false</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer>
<mainClass>application.SuperMain</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx</artifactId>
<version>11</version>
<type>pom</type>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

BIN
health_data_export/health_data_export-v6-shaded.jar

151
health_data_export/pom.xml

@ -0,0 +1,151 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>health_data_export</groupId>
<artifactId>health_data_export</artifactId>
<version>7</version>
<packaging>jar</packaging>
<repositories>
<repository>
<id>eclipse cbi</id>
<url>https://repo.eclipse.org/content/repositories/cbi-releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>eclipse cbi</id>
<url>https://repo.eclipse.org/content/repositories/cbi-releases</url>
</pluginRepository>
</pluginRepositories>
<build>
<sourceDirectory>src</sourceDirectory>
<resources>
<resource>
<directory>src</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
</resources>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.eclipse.cbi.maven.plugins</groupId>
<artifactId>eclipse-dmg-packager</artifactId>
<version>1.3.0</version>
</plugin>
</plugins>
</pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.cbi.maven.plugins</groupId>
<artifactId>eclipse-dmg-packager</artifactId>
<version>1.3.0</version>
</plugin>
<plugin>
<groupId>org.openjfx</groupId>
<artifactId>javafx-maven-plugin</artifactId>
<version>0.0.5</version>
<configuration>
<mainClass>health_data_export/application.SuperMain</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>false</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.3</version>
<executions>
<!-- Run shade goal on package phase -->
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<!-- add Main-Class to manifest file -->
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>application.SuperMain</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>11</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>11</version>
</dependency>
<dependency>
<groupId>org.eclipse.cbi.maven.plugins</groupId>
<artifactId>eclipse-dmg-packager</artifactId>
<version>1.3.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.xml.parsers/jaxp-api -->
<dependency>
<groupId>javax.xml.parsers</groupId>
<artifactId>jaxp-api</artifactId>
<version>1.4.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.openjfx/javafx -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx</artifactId>
<version>11</version>
<type>pom</type>
</dependency>
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
</dependencies>
</project>

BIN
health_data_export/res/application/RI_Logo_blau_en_short.icns

BIN
health_data_export/res/application/RI_Logo_blau_en_short.png

After

Width: 789  |  Height: 803  |  Size: 12 KiB

BIN
health_data_export/res/application/RI_Logo_weiss_en.png

After

Width: 2870  |  Height: 827  |  Size: 61 KiB

BIN
health_data_export/res/application/archive.png

After

Width: 64  |  Height: 64  |  Size: 820 B

BIN
health_data_export/res/application/csv-grey.png

After

Width: 96  |  Height: 96  |  Size: 2.2 KiB

BIN
health_data_export/res/application/csv-white.png

After

Width: 96  |  Height: 96  |  Size: 1.6 KiB

BIN
health_data_export/res/application/csv.png

After

Width: 96  |  Height: 96  |  Size: 2.1 KiB

BIN
health_data_export/res/application/datenschutz.png

After

Width: 32  |  Height: 32  |  Size: 417 B

BIN
health_data_export/res/application/dreieck.png

After

Width: 24  |  Height: 24  |  Size: 317 B

BIN
health_data_export/res/application/dreieck2.png

After

Width: 24  |  Height: 24  |  Size: 392 B

BIN
health_data_export/res/application/dsgvo.png

After

Width: 50  |  Height: 50  |  Size: 870 B

BIN
health_data_export/res/application/filepath.png

After

Width: 64  |  Height: 64  |  Size: 977 B

BIN
health_data_export/res/application/help.png

After

Width: 32  |  Height: 32  |  Size: 622 B

BIN
health_data_export/res/application/icon.ico

BIN
health_data_export/res/application/inspect.png

After

Width: 52  |  Height: 52  |  Size: 960 B

BIN
health_data_export/res/application/internet.png

After

Width: 64  |  Height: 64  |  Size: 2.5 KiB

BIN
health_data_export/res/application/result.png

After

Width: 64  |  Height: 64  |  Size: 1.1 KiB

BIN
health_data_export/res/application/select.png

After

Width: 80  |  Height: 80  |  Size: 269 B

BIN
health_data_export/res/application/start2.png

After

Width: 52  |  Height: 52  |  Size: 786 B

253
health_data_export/res/application/style.css

@ -0,0 +1,253 @@
#frameheader {
-fx-background-color: #01283f;
-fx-padding: 10;
-fx-alignment: center-left;
}
#framefooter {
-fx-background-color: #01283f;
-fx-padding: 10;
-fx-alignment: center-left;
}
#framefooter .label{
-fx-text-fill: white;
-fx-font-size: 10px;
-fx-alignment: center-left;
-fx-text-alignment: left;
}
#frame {
-fx-alignment: center-left;
}
#frameheader .label{
-fx-text-fill: white;
-fx-font-size: 20px;
-fx-alignment: center-left;
-fx-text-alignment: left;
}
#container {
-fx-fill-width: true;
-fx-alignment: center;
}
#container .label{
-fx-padding: 10;
}
#scroll {
-fx-alignment: center;
}
#savedOverview {
-fx-background-color:#e6e6e6;
-fx-padding: 3;
}
#buttonProblems {
-fx-font-size: 12px;
-fx-text-fill:#919191;
}
.label {
-fx-font-size: 15px;
-fx-text-alignment: left;
-fx-wrap-text: true;
-fx-font-family: "Arial";
}
.button {
-fx-font-size: 15px;
}
#datacontainer {
-fx-padding: 3;
}
.check-box{
-fx-alignment:center_left;
-fx-padding: 3;
}
#goToPriv {
-fx-padding: 5;
-fx-spacing: 3;
}
#goToPriv BorderPane{
-fx-background-color: #919191;
-fx-padding: 5;
}
#goToPriv BorderPane .label{
-fx-text-fill: white;
}
#selectPath {
-fx-border-color: grey;
-fx-border-insets: 5;
-fx-border-width: 1;
-fx-padding: 3;
-fx-border-style: dashed;
-fx-fill-height: true;
-fx-text-fill: grey;
-fx-text-alignment: center;
-fx-alignment: center;
-fx-font-size: 15px;
}
#ddTarget {
-fx-border-color: grey;
-fx-border-insets: 5;
-fx-border-width: 1;
-fx-padding: 3;
-fx-border-style: dashed;
-fx-alignment: baseline-center;
-fx-fill-height: true;
}
#ddLabel {
-fx-text-fill: grey;
-fx-text-alignment: center;
-fx-alignment: center;
-fx-font-size: 15px;
}
#selectButtons {
-fx-padding: 5;
-fx-spacing: 3;
}
#selectButtons BorderPane{
-fx-background-color: #919191;
-fx-padding: 5;
}
#selectButtons BorderPane .label{
-fx-text-fill: white;
}
#nextcloudHelp {
-fx-padding: 5;
-fx-spacing: 3;
}
#nextcloudHelp VBox {
-fx-spacing: 3;
}
#goToNextcloud {
-fx-background-color: #919191;
-fx-padding: 5;
}
#dragToServer {
-fx-background-color: #919191;
-fx-padding: 5;
}
#openFileInFileBrowser {
-fx-background-color: #bebebe;
-fx-padding: 5;
}
.text-area{
-fx-background-color: #00000000, #00000000;
}
.text-area .content {
-fx-background-color: #00000000;
}
.text-area:focused .content {
-fx-background-color: #00000000;
}
#nextcloudHelp BorderPane .label{
-fx-text-fill: white;
}
#nextcloudHelp VBox BorderPane .label{
-fx-text-fill: white;
}
#error {
-fx-font-size: 12px;
}
#jobInfo {
-fx-allignment:center;
}
#jobInfo .label{
-fx-font-size: 12px;
}
#jobInfo BorderPane{
-fx-padding: 3;
}
#savedAt {
-fx-font-size: 15px;
-fx-text-fill: grey;
}
EntryView {
-fx-alignment: center;
-fx-background-color:#e6e6e6;
}
EntryView .check-box{
-fx-alignment: center;
}
EntryView AnchorPane .label{
-fx-alignment: center-left;
-fx-text-alignment: left;
-fx-padding: 5;
-fx-font-size: 15px;
}
EntryView BorderPane{
-fx-background-color: #bebebe;
-fx-padding: 5;
}
#help {
-fx-background-color: #919191;
-fx-pref-width: 50;
-fx-alignment: center;
}
#open {
-fx-background-color: #919191;
-fx-pref-width: 50;
-fx-alignment: center;
}
OccupationListelementView {
-fx-background-color: #bebebe;
-fx-pref-height: 20;
}
#OccupationListElemeText{
-fx-background-color: #bebe44;
-fx-font-size: 12px;
}
#help ImageView{
-fx-alignment: center;
}
FileView {
-fx-background-color:#e6e6e6;
}

BIN
health_data_export/res/application/txt-grey.png

After

Width: 96  |  Height: 96  |  Size: 1.4 KiB

BIN
health_data_export/res/application/txt-white.png

After

Width: 96  |  Height: 96  |  Size: 1.2 KiB

BIN
health_data_export/res/application/txt.png

After

Width: 96  |  Height: 96  |  Size: 1.4 KiB

22
health_data_export/src/application/Main.java

@ -0,0 +1,22 @@
package application;
import javafx.application.Application;
import javafx.stage.Stage;
public class Main extends Application
{
@Override
public void start(Stage primaryStage)
{
UICoordinator coord = new UICoordinator();
coord.setStage(primaryStage);
coord.setHostServices(getHostServices());
}
public static void main(String[] args)
{
launch(args);
}
}

17
health_data_export/src/application/SuperMain.java

@ -0,0 +1,17 @@
package application;
/**
* This class is needed so the jar works with javafx (17.03.2021, 15:05Uhr):
* https://stackoverflow.com/questions/57019143/build-executable-jar-with-javafx11-from-maven
* @author Bianca
*
*/
public class SuperMain
{
public static void main(String[] args)
{
Main.main(args);
}
}

1717
health_data_export/src/application/UICoordinator.java
File diff suppressed because it is too large
View File

426
health_data_export/src/application/customviews/EntryView.java

@ -0,0 +1,426 @@
package application.customviews;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import application.UICoordinator;
import application.enums.EntryType;
import application.helpers.wrappers.Element;
import application.helpers.wrappers.SubElement;
import application.res.Backgrounds;
import application.res.Text;
import javafx.beans.binding.Bindings;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
/**
* This class represents one element in the select view where the user can
* select the data he / she wishes to contribute. The elements can contain
* {@link SubEntryView}s with sub elements. Every EntryView can contain an info
* view and may be opened for the sub elements (if they exist).
*
* @author Bianca
*
*/
public class EntryView extends VBox implements ChangeListener<Boolean>
{
/**
* The {@link Element} represented by this {@link EntryView}.
*/
private Element elem;
/**
* The header of the element. Is always visible.
*/
private AnchorPane header;
/**
* Contains the detailed information for the element as defined in its
* {@link EntryType}.
*/
private BorderPane details;
/**
* Contains the sub elements if they exist. If there are no subElements, the
* view will never be visible and there won't be a button to show it.
*/
private BorderPane subItem;
/**
* Indicates whether the information part is currently shown.
*/
private boolean infoOpen = false;
/**
* Indicates whether the sub elements are currently shown.
*/
private boolean subOpen = false;
/**
* Indicates whether the whole element with all its sub elements is selected.
*/
private BooleanProperty selected;
/**
* Represents the button to open the information view of the element.
*/
private BorderPane help;
/**
* Represents the button to show the sub elements.
*/
private BorderPane open;
/**
* Number of all sub elements.
*/
private int subElemCount = 0;
/**
* Number of all selected sub elements.
*/
private int selectedSubElemCount = 0;
/**
* Sets up an {@link EntryView} without sub elements.
*
* @param elem The data of this entry.
*/
public EntryView(Element elem)
{
super();
this.elem = elem;
selected = new SimpleBooleanProperty(false);
header = new AnchorPane();
details = new BorderPane();
details.setId("Details");
subItem = new BorderPane();
subItem.setId("SubItem");
details.setCenter(new Label(elem.getType().getDesc()));
header.minWidthProperty().bind(Bindings.createDoubleBinding(() -> this.getWidth(), this.widthProperty()));
header.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> this.getWidth(), this.widthProperty()));
// right side (help button for showing further information)
help = new BorderPane();
help.setId("help");
ImageView helpImage = new ImageView(new Image(UICoordinator.class.getResourceAsStream("help.png")));
help.setCenter(helpImage);
AnchorPane.setRightAnchor(help, 0.0);
AnchorPane.setTopAnchor(help, 0.0);
AnchorPane.setBottomAnchor(help, 0.0);
help.setOnMouseClicked(new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent arg0)
{
// always close sub elements first
if (subOpen)
{
closeSub();
}
if (infoOpen)
{
closeInfo();
}
else
{
getChildren().add(1, details);
infoOpen = true;
help.setBackground(Backgrounds.greyBackground);
}
}
});
// middle (name and value etc)
Label text = new Label(String.format(Text.F_ME_SAVE, elem.getType().getValue(), elem.getValue()));
AnchorPane.setLeftAnchor(text, 50.0);
AnchorPane.setTopAnchor(text, 0.0);
AnchorPane.setBottomAnchor(text, 0.0);
AnchorPane.setRightAnchor(text, 50.0);
header.getChildren().add(text);
text.minWidthProperty().bind(
Bindings.createDoubleBinding(() -> header.getWidth() - help.getWidth() * 2, header.widthProperty()));
text.setOnMouseClicked(new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
setSelected(!getSelected(), true);
}
});
header.getChildren().add(help);
getChildren().add(header);
}
/**
* Sets up an {@link EntryView} with sub elements.
*
* @param elem The data of this entry.
* @param subEntries The sub elements which belong to this element
*/
public EntryView(Element elem, HashMap<String, Integer> subEntries)
{
this(elem);
VBox subContainer = new VBox();
for (String entry : subEntries.keySet())
{
subElemCount++;
SubEntryView sub = new SubEntryView(new SubElement(entry, subEntries.get(entry)));
sub.addCheckListener(this);
subContainer.getChildren().add(sub);
}
subItem.setCenter(subContainer);
// left side
open = new BorderPane();
ImageView openImage = new ImageView(new Image(UICoordinator.class.getResourceAsStream("dreieck2.png")));
open.setId("open");
AnchorPane.setLeftAnchor(open, 0.0);
AnchorPane.setTopAnchor(open, 0.0);
AnchorPane.setBottomAnchor(open, 0.0);
open.setCenter(openImage);
openImage.setRotate(180);
open.setOnMouseClicked(new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent arg0)
{
// always close info first
if (infoOpen)
{
closeInfo();
}
if (subOpen)
{
closeSub();
}
else
{
openSub();
}
}
});
header.getChildren().add(open);
}
/**
* Returns the value / count of this entry.
*
* @return The described value.
*/
public String getValue()
{
return elem.getValue();
}
/**
* Closes the detailed information view if it is open
*/
private void closeInfo()
{
getChildren().remove(details);
infoOpen = false;
help.setBackground(Backgrounds.darkGreyBackground);
}
/**
* Shows the sub elements if they are not open yet.
*/
private void openSub()
{
getChildren().add(subItem);
subOpen = true;
open.setBackground(Backgrounds.greyBackground);
}
/**
* Closes the sub elements view if it is not closed yet.
*/
private void closeSub()
{
getChildren().remove(subItem);
subOpen = false;
open.setBackground(Backgrounds.darkGreyBackground);
}
/**
* Sets the value of the selection. If wanted it can also adjust the states of
* the {@link SubEntryView}s
*
* @param value New value of the selection.
* @param all If true then the {@link SubEntryView}s will also be adjusted.
* Otherwise they won't be changed.
*/
public void setSelected(boolean value, boolean all)
{
this.selected.set(value);
// adjust color of the entry
if (!value)
{
header.setBackground(null);
}
else
{
header.setBackground(Backgrounds.greenBackground);
}
// adjust the sub elements
if (all && subItem.getCenter() != null)
{
// there are subitems
for (Node child : ((VBox) subItem.getCenter()).getChildren())
{
SubEntryView sub = (SubEntryView) child;
sub.setSelected(value);
}
}
}
/**
* Gets the selection.
*
* @return {@code True} if the entry or any sub entry is selected. {@code False}
* otherwise.
*/
public boolean getSelected()
{
return selectedSubElemCount > 0 || selected.getValue();
}
/**
* Allows for a listener to be added to the selection value.
*
* @param listen Listener to be added.
*/
public void addCheckListener(ChangeListener<Boolean> listen)
{
selected.addListener(listen);
}
/**
* Returns the {@link EntryType} of this {@link EntryView}.
*
* @return The described value.
*/
public EntryType getEntryType()
{
return this.elem.getType();
}
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
{
// Here the sub elements are watched and the selected ones counted
if (newValue)
{
selectedSubElemCount++;
}
else
{
selectedSubElemCount--;
}
// if any sub element was selected then of course the whole
// element also needs to be selected.
if (selectedSubElemCount > 0)
{
this.selected.set(true);
}
else
{
this.selected.set(false);
}
// this adjusts the color of the element but not the selection of the sub
// elements
if (selectedSubElemCount == subElemCount)
{
setSelected(true, false);
}
else if (selectedSubElemCount > 0)
{
header.setBackground(Backgrounds.lightGreenBackground);
}
else
{
setSelected(false, false);
}
}
/**
* Returns a list of readable names of all sub elements or an empty list if
* their are no sub elements.
*
* @return The described value.
*/
public List<String> getSelectedSubElementNames()
{
List<String> res = new ArrayList<>();
if (subItem.getCenter() != null)
{
// there are subitems
for (Node child : ((VBox) subItem.getCenter()).getChildren())
{
SubEntryView sub = (SubEntryView) child;
if (sub.isSelected())
{
res.add(sub.getName());
}
}
}
return res;
}
/**
* Returns all {@link SubEntryView}s of this entry in a list or an empty lift if
* there are now sub elements.
*
* @return The described value.
*/
public List<SubEntryView> getSelectedSubElements()
{
List<SubEntryView> res = new ArrayList<>();
if (subItem.getCenter() != null)
{
// there are sub elements
for (Node child : ((VBox) subItem.getCenter()).getChildren())
{
SubEntryView sub = (SubEntryView) child;
if (sub.isSelected())
{
res.add(sub);
}
}
}
return res;
}
public void selectSubelement(String elementName)
{
if (subItem.getCenter() != null)
{
// there are subitems
for (Node child : ((VBox) subItem.getCenter()).getChildren())
{
SubEntryView sub = (SubEntryView) child;
if (sub.getName().contains(elementName))
{
sub.setSelected(true);
}
}
}
}
}

94
health_data_export/src/application/customviews/FileView.java

@ -0,0 +1,94 @@
package application.customviews;
import java.io.File;
import java.net.MalformedURLException;
import application.UICoordinator;
import application.helpers.Utils;
import application.res.Text;
import javafx.application.HostServices;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.HBox;
public class FileView extends HBox
{
/**
* Set height of all FileViews
*/
private static final int HEIGHT = 62;
/**
* Set width of all FileViews
*/
public static final int WIDTH = 300;
/**
* Set border width of all FileViews
*/
public static final double BORDER_WIDTH = Utils.darkGreyBorderBR.getInsets().getBottom();
/**
* Set height of the icon of all FileViews
*/
private static final int PIC_HEIGHT = HEIGHT - 5;
/**
* Initializes the view and sets up the clickHandler
* @param file File which will be represented with this view
* @param desc The user readable name of the file to be displayed to the user.
* @param hs The hostServices needed to open the document in host application.
*/
public FileView(File file, String desc, HostServices hs)
{
this.setMaxHeight(HEIGHT);
this.setMinHeight(HEIGHT);
this.setMaxWidth(WIDTH);
this.setMinWidth(WIDTH);
ImageView image;
this.setBorder(Utils.darkGreyBorderBR);
if (file.getName().endsWith(".csv"))
{
image = new ImageView(new Image(UICoordinator.class.getResourceAsStream("csv-grey.png")));
} else
{
Image i = new Image(UICoordinator.class.getResourceAsStream("txt-grey.png"));
image = new ImageView(i);
}
image.setFitWidth(PIC_HEIGHT);
image.setFitHeight(PIC_HEIGHT);
Label name = new Label(desc);
name.setMinHeight(HEIGHT);
name.setMaxHeight(HEIGHT);
this.getChildren().add(image);
this.getChildren().add(name);
setOnMouseClicked(new EventHandler<MouseEvent>()
{ // Click on the fileView should open the file in a system native application.
// Otherwise a tooltip will appear to inform the user about failing to open the
// file.
@Override
public void handle(MouseEvent event)
{
try
{
hs.showDocument(file.toURI().toURL().toExternalForm());
name.setTooltip(null);
} catch (MalformedURLException e)
{
Point2D p = name.localToScreen(name.getLayoutBounds().getCenterX(),
name.getLayoutBounds().getCenterY());
Tooltip t = new Tooltip(Text.NO_OPENING_FILES);
name.setTooltip(t);
t.show(name, p.getX(), p.getY());
t.autoHideProperty().set(true);
}
}
});
}
}

130
health_data_export/src/application/customviews/SubEntryView.java

@ -0,0 +1,130 @@
package application.customviews;
import application.helpers.Utils;
import application.helpers.wrappers.SubElement;
import application.res.Colors;
import application.res.Text;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.event.EventHandler;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.shape.Circle;
/**
* This class represents one of the sub elements of the select view.
* {@link EntryView}s can contain sub elements like the information concerning
* the date of birth which is represented in this class. They can be selected on
* their own and the selection state is indicated by a circle on the left side
* of the sub element.
*
* @author Bianca
*
*/
public class SubEntryView extends HBox
{
/**
* Circle which indicates the selection state. Invisible / grey if not selected,
* green if selected.
*/
private Circle c;
/**
* Property which indicates whether the sub element is selected.
*/
private BooleanProperty selected;
/**
* Data of this sub element.
*/
private SubElement elem;
/**
* Initializes all attributes, sets up the view and draws the initial circle.
* Starts in the unselected state.
*
* @param elem The data of the sub element.
*/
public SubEntryView(SubElement elem)
{
this.elem=elem;
selected = new SimpleBooleanProperty(false);
BorderPane circleContainer = new BorderPane();
c = new Circle(10, Colors.grey);
circleContainer.setCenter(c);
getChildren().add(circleContainer);
getChildren().add(new Label(String.format(Text.F_ME_SAVE, Utils.shortenHKStrings(getName()), getValue())));
setOnMouseClicked(new EventHandler<MouseEvent>()
{
@Override
public void handle(MouseEvent event)
{
setSelected(!isSelected());
}
});
}
/**
* Returns the name / identifier of the sub element.
*
* @return The described value.
*/
public String getName()
{
return elem.getType();
}
/**
* Returns the value / entrycount of the sub element.
*
* @return The described value.
*/
public int getValue()
{
return elem.getValue();
}
/**
* Allows for a listener to be attached to the selected property of the sub
* element.
*
* @param listen Listener to add to the property
*/
public void addCheckListener(ChangeListener<Boolean> listen)
{
selected.addListener(listen);
}
/**
* Shows whether the element is selected or not.
*
* @return The describes value.
*/
public boolean isSelected()
{
return selected.get();
}
/**
* Changes the value of the selection of this element to the given selection and
* adjusts the circles color.
*
* @param value The value the selection should take.
*/
public void setSelected(boolean value)
{
if (value) //select
{
c.setFill(Colors.green);
}
else //remove selection
{
c.setFill(Colors.grey);
}
this.selected.set(value);
}
}

59
health_data_export/src/application/enums/BiologicalSex.java

@ -0,0 +1,59 @@
package application.enums;
import application.res.*;
public enum BiologicalSex
{
MALE("male", "HKBiologicalSexMale"),
FEMALE("female","HKBiologicalSexFemale"),
OTHER("other","HKBiologicalSexOther"),
NOT_SET(Text.NOT_SET,"HKBiologicalSexNotSet"),
NOT_PARSED("","");
//Those two are used to match the found values with the enum
/** A word which could be contained**/
private final String shortTag;
/** The exact value the attribute should have, but I don't know if they are correct **/
private final String exactTag;
BiologicalSex(String shor, String exact)
{
this.shortTag=shor;
this.exactTag=exact;
}
/**
* Returns a short string which can be used to identify the value. Readable for users.
* @return the respective String
*/
public String getReadableValue()
{
return shortTag;
}
/**
* For the input string returns the matching enum value
* @param input String to compare
* @return matched enum value
*/
public static BiologicalSex getValue(String input)
{
if(input.equalsIgnoreCase(FEMALE.exactTag)|| input.contains(FEMALE.shortTag))
{
return FEMALE;
}
if(input.equalsIgnoreCase(MALE.exactTag)|| input.contains(MALE.shortTag))
{
return MALE;
}
if(input.equalsIgnoreCase(OTHER.exactTag)|| input.contains(OTHER.shortTag))
{
return OTHER;
}
if(input.equalsIgnoreCase(NOT_SET.exactTag)|| input.contains(NOT_SET.shortTag))
{
return NOT_SET;
}
return NOT_PARSED;
}
}

84
health_data_export/src/application/enums/BloodType.java

@ -0,0 +1,84 @@
package application.enums;
import application.res.*;
public enum BloodType
{
A_POSITIVE("A+", "HKBloodTypeAPositive"),
A_NEGATIVE("A-", "HKBloodTypeANegative"),
B_POSITIVE("B+", "HKBloodTypeBPositive"),
B_NEGATIVE("B-", "HKBloodTypeBNegative"),
AB_POSITIVE("AB+", "HKBloodTypeABPositive"),
AB_NEGATIVE("AB-", "HKBloodTypeABNegative"),
O_POSITIVE("O+", "HKBloodTypeOPositive"),
O_NEGATIVE("O-", "HKBloodTypeONegative"),
NOT_SET(Text.NOT_SET,"HKBloodTypeNotSet"),
NOT_PARSED("","");
//Those two are used to match the found values with the enum
/** A word which could be contained**/
private final String shortTag;
/** The exact value the attribute should have, but I don't know if they are correct **/
private final String exactTag;
BloodType(String shor, String exact)
{
this.shortTag=shor;
this.exactTag=exact;
}
/**
* Returns a short string which can be used to identify the value. Readable for users.
* @return the respective String
*/
public String getReadableValue()
{
return shortTag;
}
/**
* For the input string returns the matching enum value
* @param input String to compare
* @return matched enum value
*/
public static BloodType getValue(String input)
{
if(input.equalsIgnoreCase(A_POSITIVE.exactTag)|| input.contains(A_POSITIVE.shortTag))
{
return A_POSITIVE;
}
if(input.equalsIgnoreCase(A_NEGATIVE.exactTag)|| input.contains(A_NEGATIVE.shortTag))
{
return A_NEGATIVE;
}
if(input.equalsIgnoreCase(B_POSITIVE.exactTag)|| input.contains(B_POSITIVE.shortTag))
{
return B_POSITIVE;
}
if(input.equalsIgnoreCase(B_NEGATIVE.exactTag)|| input.contains(B_NEGATIVE.shortTag))
{
return B_NEGATIVE;
}
if(input.equalsIgnoreCase(AB_POSITIVE.exactTag)|| input.contains(AB_POSITIVE.shortTag))
{
return AB_POSITIVE;
}
if(input.equalsIgnoreCase(AB_NEGATIVE.exactTag)|| input.contains(AB_NEGATIVE.shortTag))
{
return AB_NEGATIVE;
}
if(input.equalsIgnoreCase(O_POSITIVE.exactTag)|| input.contains(O_POSITIVE.shortTag))
{
return O_POSITIVE;
}
if(input.equalsIgnoreCase(O_NEGATIVE.exactTag)|| input.contains(O_NEGATIVE.shortTag))
{
return O_NEGATIVE;
}
if(input.equalsIgnoreCase(NOT_SET.exactTag)|| input.contains(NOT_SET.shortTag))
{
return NOT_SET;
}
return NOT_PARSED;
}
}

75
health_data_export/src/application/enums/EntryType.java

@ -0,0 +1,75 @@
package application.enums;
import application.res.*;
public enum EntryType
{
EXPORT_DATE("ExportDate", Text.DESC_EXPORT_DATE), JOB("Occupation", ""),
DATE_OF_BIRTH("DateOfBirth", Text.DESC_DATE_OF_BIRTH), BIOLOGICAL_SEX("BiologicalSex", Text.DESC_BIOLOGICAL_SEX),
SKIN_TYPE("SkinType", Text.DESC_SKIN_TYPE), BLOOD_TYPE("BloodType", Text.DESC_BLOOD_TYPE),
RECORD("Record", Text.DESC_RECORDS), REGION_CODE("RegionCode", Text.DESC_REGION_CODE),
// CORRELATION("Correlation", Text.DESC_CORRELATIONS), //excluded to reduce complexity.
WORKOUT("Workout", Text.DESC_WORKOUT), ACTIVITY_SUMMARY("ActivitySummary", Text.DESC_ACTIVITY_SUM),
// CLINICAL_RECORD("ClinicalRecord", Text.DESC_CLIN_RECORD), //excluded because it might be too explicit
// AUDIOGRAM("Audiogram", Text.DESC_AUDIOGRAM), //excluded because of missing documentation.
// SENSITIVITY_POINT("SensitivityPoint",Text.DESC_SENS_POINT); //excluded because of missing documentation.
;
/** All values which will produce own files, might have subelements **/
public static EntryType[] ALL_SEPARATE_ONES =
{ RECORD, WORKOUT, ACTIVITY_SUMMARY };
/**
* All values which will be written in the ME File, each contains only one value
* (except date of birth which was split)
**/
public static EntryType[] ALL_ME_ONES =
{ EXPORT_DATE, REGION_CODE,DATE_OF_BIRTH, BIOLOGICAL_SEX, SKIN_TYPE, BLOOD_TYPE };
/**
* These values will be written into the ME file and originate from the me tag
**/
public static EntryType[] ALL_ORIGINAL_ME_ONES =
{ DATE_OF_BIRTH, BIOLOGICAL_SEX, SKIN_TYPE, BLOOD_TYPE };
/**
* These values will be written into the ME file, but they don't originate from
* the me tag
**/
public static EntryType[] ALL_TEMPERED_ME_ONES =
{ EXPORT_DATE, REGION_CODE };
/** Value of the enum **/
private final String value;
/** Description String **/
private final String desc;
EntryType(String value, String desc)
{
this.value = value;
this.desc = desc;
}
/**
* Returns the value or name of the element. Is readable for Users and can be
* used for XML mapping(not for ME infos and regionCode).
*
* @return The said String.
*/
public String getValue()
{
return this.value;
}
/**
* Returns the description for the element which is a text that explains the
* element and its value. It's the help text.
*
* @return The said String.
*/
public String getDesc()
{
return this.desc;
}
}

52
health_data_export/src/application/enums/NavState.java

@ -0,0 +1,52 @@
package application.enums;
/**
* This enum is used to differentiate the different states the navigation (and
* the tool) can be in.
*
* @author Bianca
*
*/
public enum NavState
{
/** Start of the application **/
INIT,
/**
* In the start view the data import was given and the privacy policy was
* checked
**/
IMPORT_GIVEN,
/**
* The start state of the view where the user can select which data he/she
* wishes to contribute
**/
SELECT_DATA,
/**
* In the select view, at least one data object was selected to contribute and
* the savep ath was set
**/
DATA_AND_PATH_SELECTED,
/**
* Return to the first view where a data import can be done, but the path to the
* previously given file is still set and the privacy policy checked
**/
START_AGAIN,
/**
* The view which shows all created files and allows the user to open these for
* inspection
**/
INSPECT,
/**
* The view where the user should be able to inspect the created files. But as
* there was an error in writing / creating these files, there will only be an
* error message shown for him / her
**/
INSPECT_WITH_ERROR,
/**
* Last view of the tool. Gives an overview over all included data and helps the
* user to upload these to nextcloud
**/
RESULT,
/** This view contains the privacy policy and can be accessed from all other views.**/
PRIVACY_STATEMENT;
}

82
health_data_export/src/application/enums/SkinType.java

@ -0,0 +1,82 @@
package application.enums;
import application.res.*;
//https://developer.apple.com/documentation/healthkit/hkfitzpatrickskintype
public enum SkinType
{
CASE_I("pale white skin", "HKFitzpatrickSkinTypeI"),
//Pale white skin that always burns easily in the sun and never tans.
CASE_II("white skin", "HKFitzpatrickSkinTypeII"),
//White skin that burns easily and tans minimally.
CASE_III("white to light brown skin", "HKFitzpatrickSkinTypeIII"),
//White to light brown skin that burns moderately and tans uniformly.
CASE_IV("beige-olive skin", "HKFitzpatrickSkinTypeIV"),
//Beige-olive, lightly tanned skin that burns minimally and tans moderately.
CASE_V("brown skin", "HKFitzpatrickSkinTypeV"),
//Brown skin that rarely burns and tans profusely.
CASE_VI("dark brown to black skin", "HKFitzpatrickSkinTypeVI"),
//Dark brown to black skin that never burns and tans profusely.
NOT_SET(Text.NOT_SET,"HKFitzpatrickSkinTypeNotSet"),
NOT_PARSED("","");
//Those two are used to match the found values with the enum
/** A word which could be contained**/
private final String shortTag;
/** The exact value the attribute should have, but I don't know if they are correct **/
private final String exactTag;
SkinType(String shor, String exact)
{
this.shortTag=shor;
this.exactTag=exact;
}
/**
* Returns a short string which can be used to identify the value. Readable for users.
* @return the respective String
*/
public String getReadableValue()
{
return shortTag;
}
/**
* For the input string returns the matching enum value
* @param input String to compare
* @return matched enum value
*/
public static SkinType getValue(String input)
{
if(input.equalsIgnoreCase(CASE_I.exactTag)|| input.contains(CASE_I.shortTag))
{
return CASE_I;
}
if(input.equalsIgnoreCase(CASE_II.exactTag)|| input.contains(CASE_II.shortTag))
{
return CASE_II;
}
if(input.equalsIgnoreCase(CASE_III.exactTag)|| input.contains(CASE_III.shortTag))
{
return CASE_II;
}
if(input.equalsIgnoreCase(CASE_IV.exactTag)|| input.contains(CASE_IV.shortTag))
{
return CASE_IV;
}
if(input.equalsIgnoreCase(CASE_V.exactTag)|| input.contains(CASE_V.shortTag))
{
return CASE_V;
}
if(input.equalsIgnoreCase(CASE_VI.exactTag)|| input.contains(CASE_VI.shortTag))
{
return CASE_VI;
}
if(input.equalsIgnoreCase(NOT_SET.exactTag)|| input.contains(NOT_SET.shortTag))
{
return NOT_SET;
}
return NOT_PARSED;
}
}

97
health_data_export/src/application/helpers/ChangeViewCallback.java

@ -0,0 +1,97 @@
package application.helpers;
import application.UICoordinator;
import application.enums.NavState;
import application.helpers.wrappers.WrappedException;
/**
* Callback for the communication between the thread
* {@link WorkWhileProgressThread} and the {@link UICoordinator}
*
* @author Bianca
*
*/
public class ChangeViewCallback
{
/**
* Coordinator for accessing
*/
private UICoordinator coord;
public ChangeViewCallback(UICoordinator coord)
{
this.coord = coord;
}
/**
* Calls the {@code fillDataList} method of the {@link UICoordinator}. Reads in
* the health data.
*
* @throws WrappedException if there was an error reading the file
*/
public void callFillDataList() throws WrappedException
{
this.coord.fillDataList();
}
/**
* Calls the {@code createTempFiles} method of the {@link UICoordinator}.
* Creates the temp files.
*
* @throws WrappedException If there was an error creating or writing to the
* files
*/
public void callCreateTempFiles() throws WrappedException
{
this.coord.createTempFiles();
}
/**
* Changes to the overview view
*/
public void callOverview()
{
this.coord.changeView(this.coord.getOverviewView());
this.coord.updateNav(NavState.SELECT_DATA);
}
/**
* In case an error occurred while the health data was read, the
* {@link UICoordinator} will be informed.
*
* @param text The user readable information about the error.
*/
public void callErrorReadingFile(String text)
{
this.coord.setErrorReadingFile(text);
}
/**
* In case an error occurred while the files were written, the
* {@link UICoordinator} will be informed.
*
* @param text The user readable information about the error.
*/
public void callErrorWritingTemFiles(String text)
{
this.coord.setErrorWritingTempFiles(text);
}
/**
* Changes to the inspect view
*/
public void callInspect()
{
this.coord.changeView(this.coord.getInspectView());
this.coord.updateNav(NavState.INSPECT);
}
/**
* Changes to the inspect view after an error occurred.
*/
public void callInspectWithError()
{
this.coord.changeView(this.coord.getInspectView());
this.coord.updateNav(NavState.INSPECT_WITH_ERROR);
}
}

144
health_data_export/src/application/helpers/Utils.java

@ -0,0 +1,144 @@
package application.helpers;
import java.text.SimpleDateFormat;
import application.res.Colors;
import application.res.Text;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.BorderStrokeStyle;
import javafx.scene.layout.BorderWidths;
/**
* Gathers some of the data structures an methods more widely used.
*
* @author Bianca
*
*/
public class Utils
{
/**
* Attributes of an activity summary row (10 in total)
*/
public static final String[] ACTIV_SUMM_ATTR = new String[]
{ "dateComponents", "activeEnergyBurned", "activeEnergyBurnedGoal", "activeEnergyBurnedUnit", "appleExerciseTime",
"appleExerciseTimeGoal", "appleStandHours", "appleStandHoursGoal", "appleMoveMinutes",
"appleMoveMinutesGoal"
// appleMoveMinutes /-Goal couldn't be tested due to missing test data
};
/**
* Attributes of a workout row (12 attributes +WorkoutEvents) Important: nested attributes need to be at the end of the array!
*/
public static final String[] workoutAttributes = new String[]
{ "workoutActivityType", "duration", "durationUnit", "totalDistance", "totalDistanceUnit", "totalEnergyBurned",
"totalEnergyBurnedUnit", //"sourceName", left out because of privacy
"sourceVersion", "creationDate", "startDate", "endDate", "device",
Text.TAG_NAME_WORKOUT_EVENT //, Text.TAG_NAME_META_DATA_ENTRY, Text.TAG_NAME_WORKOUT_ROUTE left out because of privacy
};
/**
* Attributes of a record row (8 attributes + HRMetaList(+IBperMinute)
* +MetaData) Important: nested attributes need to be at the end of the array!
*/
public static final String[] recordAttributes = new String[]
{ "type", "unit", "value", // "sourceName", left out because of privacy
"sourceVersion", "device", "creationDate", "startDate", "endDate",// Text.TAG_NAME_META_DATA_ENTRY, left out due to privacy
Text.TAG_NAME_HR_LIST };
/**
* Attributes of a meta data entry (2 attributes in total)
*/
public static String[] META_DATA_ATTR = new String[]
{ "key", "value" };
/**
* Attributes of a workout event entry (4 attributes in total)
*/
public static String[] WORKOUT_EVENT_ATTR = new String[]
{ "type", "date", "duration", "durationUnit" };
/**
* Attributes of a workout route entry (6 attributes + MetaData)
* (Currently excluded due to privacy)
*/
public static String[] WORKOUT_ROUTE_ATTR = new String[]
{ "sourceName", "sourceVersion", "device", "creationDate", "startDate", "endDate", Text.TAG_NAME_META_DATA_ENTRY };
/**
* Attributes of a hr_list entry (only ib_per_minutes entries)
*/
public static String[] HR_LIST_ATTR = new String[]
{ Text.TAG_NAME_IB_PER_MINUTES };
/**
* Attributes of a ib per mintes entry (2 attributes in total)
*/
public static String[] IB_PER_MINUTES_ATTR = new String[]
{ "bpm", "time" };
/**
* This format is according to the format the date of birth is saved in the
* health data
*/
public static final SimpleDateFormat formatDateOfBirth = new SimpleDateFormat("yyyy-MM-dd");
/**
* Especially sub workouts / records start with something like 'HK...' and
* aren't very readable for the user. This method tries to remove the
* unnecessary parts to make it more readable. Based on https://developer.apple.com/documentation/healthkit/data_types
*
* @param input The string to shorten
* @return the shortened result
*/
public static String shortenHKStrings(String input)
{
// Subclasses
// class HKCharacteristicType
// A type that represents data that does not typically change over time.
String res = input.replace("HKCharacteristicType", "");
// class HKQuantityType
// A type that identifies samples that store numerical values.
res = res.replace("HKQuantityType", "");
// class HKCategoryType
// A type that identifies samples that contain a value from a small set of possible values.
res = res.replace("HKCategoryType", "");
// class HKCorrelationType
// A type that identifies samples that group multiple subsamples.
res = res.replace("HKCorrelationType", "");
// class HKActivitySummaryType
// A type that identifies activity summary objects.
res = res.replace("HKActivitySummaryType", "");
// class HKAudiogramSampleType
// A type that identifies samples that contain audiogram data.
res = res.replace("HKAudiogramSampleType", "");
// class HKElectrocardiogramType
// A type that identifies samples containing electrocardiogram data.
res = res.replace("HKElectrocardiogramType", "");
// class HKSeriesType
// A type that indicates the data stored in a series sample.
res = res.replace("HKSeriesType", "");
// class HKClinicalType
// A type that identifies samples that contain clinical record data.
res = res.replace("HKClinicalType", "");
// class HKWorkoutType
// A type that identifies samples that store information about a workout.
res = res.replace("HKWorkoutType", "");
res = res.replace("HKWorkoutActivityType", "");
res = res.replace("Identifier", "");
return res;
}
public static Border darkBlueBorder = new Border(new BorderStroke(Colors.darkBlue, Colors.darkBlue, Colors.darkBlue,
Colors.darkBlue, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID,
BorderStrokeStyle.SOLID, null, new BorderWidths(2), null));
public static Border redBorder = new Border(new BorderStroke(Colors.red, Colors.red, Colors.red, Colors.red,
BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, null,
new BorderWidths(2), null));
public static Border darkGreyBorderBR = new Border(new BorderStroke(null, Colors.darkGrey, Colors.darkGrey, null,
BorderStrokeStyle.NONE, BorderStrokeStyle.SOLID, BorderStrokeStyle.SOLID, BorderStrokeStyle.NONE, null,
new BorderWidths(1), null));
}

89
health_data_export/src/application/helpers/WorkWhileProgressThread.java

@ -0,0 +1,89 @@
package application.helpers;
import application.helpers.wrappers.WrappedException;
import javafx.application.Platform;
/**
* This thread is used to enable working in the background while the progress
* view is displayed
*
* @author Bianca
*
*/
public class WorkWhileProgressThread extends Thread
{
/**
* The callback used to change the view after the work is done
*/
private ChangeViewCallback cb;
/**
* There are 2 possible workloads: reading the file for the overview
* ({@code True}) or creating the files for being inspected ({@code False})
*/
private boolean overview;
/**
*
* @param cb The callback
* @param overView {@code True} if the initial file needs to be read or
* {@code False} if the files for being inspected need to be
* created
*/
public WorkWhileProgressThread(ChangeViewCallback cb, boolean overView)
{
this.cb = cb;
this.overview = overView;
}
@Override
public void run()
{
//read the file
if (overview)
{
try
{
this.cb.callFillDataList();
}
catch (WrappedException e)
{
cb.callErrorReadingFile(e.getReason());
}
Platform.runLater(new Runnable()
{
@Override
public void run()
{
cb.callOverview();
}
});
}
else
{ //create the selected files
try
{
this.cb.callCreateTempFiles();
Platform.runLater(new Runnable()
{
@Override
public void run()
{
cb.callInspect();
}
});
}
catch (WrappedException e)
{
this.cb.callErrorWritingTemFiles(e.getReason());
Platform.runLater(new Runnable()
{
@Override
public void run()
{
cb.callInspectWithError();
}
});
}
}
}
}

45
health_data_export/src/application/helpers/wrappers/AttributeDataInfoTuple.java

@ -0,0 +1,45 @@
package application.helpers.wrappers;
/**
* This class is needed for the {@link WriteHandler}. It wraps a StringBuilder for a
* nested attribute together with its sub attribute names.
* @author Bianca
*
*/
public class AttributeDataInfoTuple
{
/** The StringBuilder which will contain the sub attribute values **/
private StringBuilder text;
/** The names of the sub attributes**/
private String[] attributeList;
/**
* Initializes the attributes. Creates a new StringBuilder for the values
* @param attributeList Names of the sub attributes
*/
public AttributeDataInfoTuple( String[] attributeList)
{
this.text=new StringBuilder();
this.attributeList=attributeList;
}
/**
* Returns the Builder which contains the sub attribute values
* @return The described value.
*/
public StringBuilder getBuilder()
{
return text;
}
/**
* Returns the sub attribute names
* @return The described value.
*/
public String[] getSubAttributes()
{
return attributeList;
}
}

49
health_data_export/src/application/helpers/wrappers/Element.java

@ -0,0 +1,49 @@
package application.helpers.wrappers;
import application.enums.EntryType;
/**
* This class represents an element of the level of {@link EntryType}s as it
* saves the {@link EntryType} and the corresponding value (e.g. de_DE for
* region code) or the count of data entries of this type (e.g. 10 for records).
*
* @author Bianca
*
*/
public class Element
{
/**
* The {@link EntryType} of this element.
*/
private EntryType type;
/**
* The value or count of this element.
*/
private String value;
public Element(EntryType type, String value)
{
this.type = type;
this.value = value;
}
/**
* Gets the {@link EntryType} of this element.
*
* @return The described value.
*/
public EntryType getType()
{
return this.type;
}
/**
* Get the value or count of this element.
*
* @return The described value.
*/
public String getValue()
{
return this.value;
}
}

61
health_data_export/src/application/helpers/wrappers/GeneralSaveInfo.java

@ -0,0 +1,61 @@
package application.helpers.wrappers;
import application.enums.EntryType;
import application.parsing.FileWrapper;
/**
* This class wraps the information saved in one line of all files except the Me
* File.
*
* @author Bianca
*
*/
public class GeneralSaveInfo
{
/**
* A readable name of the file where these values should be saved
*/
private String readableFilename;
/** The values to be saved **/
private String[] values;
/**
* Initializes the attributes
* @param values The values to be saved
* @param type The {@link EntryType} of this info.
* @param subCategory The subCategory as specifier for the file (e.g subRecord name)
*/
public GeneralSaveInfo(String[] values, EntryType type, String subCategory)
{
this.values = values;
this.readableFilename = FileWrapper.createReadableFileName(type.getValue(), subCategory);
}
/**
* Initializes the attributes
* @param values The values to be saved
* @param type The {@link EntryType} of this info.
*/
public GeneralSaveInfo(String[] values, EntryType type)
{
this(values, type, "");
}
/**
* Returns the filename of the file where this data should be saved
* @return The described value.
*/
public String getReadableFilename()
{
return readableFilename;
}
/**
* Returns the values which should be saved.
* @return The described values.
*/
public String[] getValues()
{
return values;
}
}

62
health_data_export/src/application/helpers/wrappers/MeSaveInfo.java

@ -0,0 +1,62 @@
package application.helpers.wrappers;
import application.enums.EntryType;
/**
* This class wraps the information saved in one line of the ME File
*
* @author Bianca
*
*/
public class MeSaveInfo
{
/** The value which should be saved **/
private String value;
/**
* The key to write this information.
**/
private String saveKey;
/**
* Initializes the values.
* @param entry The {@code EntryType} of this information
* @param extension The name extension for the type
* @param value The value of this information
*/
public MeSaveInfo(EntryType entry, String extension, String value)
{
this.value = value;
this.saveKey=entry.getValue() + " "+extension;
}
/**
* Initializes the values.
* @param entry The {@code EntryType} of this information
* @param value The value of this information
*/
public MeSaveInfo(EntryType entry, String value)
{
this(entry,"",value);
}
/**
* Returns the value of this information
*
* @return The described value.
*/
public String getSaveValue()
{
return value;
}
/**
* Returns the description / name of this infor (combination of the
* {@link EntryType} and the extension
*
* @return The described value.
*/
public String getSaveKey()
{
return saveKey;
}
}

59
health_data_export/src/application/helpers/wrappers/Occupation.java

@ -0,0 +1,59 @@
package application.helpers.wrappers;
/**
* This class wraps the occupation given by the user.
* @author Bianca
*
*/
public class Occupation
{
private String levelOne;
private String levelTwo;
private String levelThree;
private String levelFour;
public Occupation(String levelOne)
{
setLevelOne(levelOne);
}
public String getLevelOne()
{
return levelOne;
}
public void setLevelOne(String levelOne)
{
this.levelOne = levelOne;
}
public String getLevelTwo()
{
return levelTwo;
}
public void setLevelTwo(String levelTwo)
{
this.levelTwo = levelTwo;
}
public String getLevelThree()
{
return levelThree;
}
public void setLevelThree(String levelThree)
{
this.levelThree = levelThree;
}
public String getLevelFour()
{
return levelFour;
}
public void setLevelFour(String levelFour)
{
this.levelFour = levelFour;
}
}

47
health_data_export/src/application/helpers/wrappers/SubElement.java

@ -0,0 +1,47 @@
package application.helpers.wrappers;
import application.enums.EntryType;
/**
* This class represents a sub element under the level of {@link EntryType}s as it
* saves the sub type name and the corresponding value (e.g. 2020 for year
* of birth) or the count of data entries of this type (e.g. 10 for StepCount).
*
* @author Bianca
*
*/
public class SubElement
{
/**
* The type of this sub element.
*/
private String type;
/**
* The value or count of this sub element.
*/
private int value;
public SubElement(String type, int value)
{
this.type=type;
this.value=value;
}
/**
* Gets the type of this sub element.
* @return The described value.
*/
public String getType()
{
return this.type;
}
/**
* Get the value or count of this element.
* @return The described value.
*/
public int getValue()
{
return this.value;
}
}

50
health_data_export/src/application/helpers/wrappers/WrappedException.java

@ -0,0 +1,50 @@
package application.helpers.wrappers;
/**
* This class wraps exceptions of any kind and saves a user readable reason to
* the exceptions
*
* @author Bianca
*
*/
public class WrappedException extends Exception
{
private static final long serialVersionUID = 1L;
/**
* The wrapped exception
*/
private Exception exc;
/**
* The reason
*/
private String reason;
/**
*
* @param e The exception to wrap
* @param reason A user readable reason.
*/
public WrappedException(Exception e, String reason)
{
this.exc = e;
this.reason = reason;
}
/**
* Return the reason for the exception
* @return the described value
*/
public String getReason()
{
return reason;
}
/**
* Returns the exception wrapped here.
* @return The described value
*/
public Exception getException()
{
return exc;
}
}

138
health_data_export/src/application/helpers/wrappers/WriteSelection.java

@ -0,0 +1,138 @@
package application.helpers.wrappers;
import java.util.ArrayList;
import java.util.List;
import org.xml.sax.Attributes;
import application.customviews.EntryView;
import application.enums.EntryType;
import application.res.Text;
import javafx.scene.Node;
import javafx.scene.layout.VBox;
/**
* This class wraps the selection of the user regarding which data should be
* included in the created files
*
* @author Bianca
*
*/
public class WriteSelection
{
/** All selected EntryTypes (even if only one sub element is selected)**/
private List<EntryType> select = new ArrayList<>();
/** All sub records selected **/
private List<String> recordSelected = new ArrayList<>();
/** All sub workouts selected**/
private List<String> workoutSelected = new ArrayList<>();
/** All date of birth parts selected**/
private List<String> dateOfBirthSelected = new ArrayList<>();
/**
* Constructor which extracts the selection from the {@link EntryView}s
* @param datalist The list containing all the {@link EntryView}s
*/
public WriteSelection(VBox datalist)
{
for(Node n: datalist.getChildren())
{
if(n instanceof EntryView)
{
EntryView i = (EntryView)n;
if (i.getSelected())
{
if(i.getEntryType().equals(EntryType.WORKOUT))
{
workoutSelected=i.getSelectedSubElementNames();
}
if(i.getEntryType().equals(EntryType.DATE_OF_BIRTH))
{
dateOfBirthSelected=i.getSelectedSubElementNames();
}
select.add(i.getEntryType());
if(i.getEntryType().equals(EntryType.RECORD))
{
recordSelected=i.getSelectedSubElementNames();
}
}
}
}
}
/**
* Determines whether the given date of birth part is selected
* @param birthPart part to check (Year, month or day)
* @return {@code True} if it is selected, {@code False} otherwise
*/
public boolean birthPartSelected(String birthPart)
{
return dateOfBirthSelected.contains(birthPart);
}
/**
* Determines whether the workout of these attributes is selected
* @param attributes The attributes of the workout which also contain the type
* @return {@code True} if it is selected, {@code False} otherwise
*/
public boolean workoutSelected(Attributes attributes)
{
return workoutSelected.contains(attributes.getValue(Text.TAG_ATTR_WORKOUT_TYPE));
}
/**
* Returns all selected sub workouts
* @return The described value
*/
public List<String> getWorkouts()
{
return workoutSelected;
}
/**
* Determines whether the record of these attributes is selected
* @param attributes The attributes of the record which also contain the type
* @return {@code True} if it is selected, {@code False} otherwise
*/
public boolean recordSelected(Attributes attributes)
{
return recordSelected.contains(attributes.getValue(Text.TAG_ATTR_RECORD_TYPE));
}
/**
* Returns all selected sub records
* @return The described value
*/
public List<String> getRecords()
{
return recordSelected;
}
/**
* Determines whether the {@link EntryType} of these attributes is selected
* @param entry The entry to compare
* @return {@code True} if it is selected, {@code False} otherwise
*/
public boolean entrySelected(EntryType entry)
{
return select.contains(entry);
}
/**
* Returns all selected date of birth parts
* @return The described value
*/
public List<String> getBirthParts()
{
return dateOfBirthSelected;
}
/**
* Returns all selected {@link EntryType}s
* @return The described value
*/
public List<EntryType> getEntryTypes()
{
return select;
}
}

217
health_data_export/src/application/parsing/FileWrapper.java

@ -0,0 +1,217 @@
package application.parsing;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import application.helpers.Utils;
import application.helpers.wrappers.WrappedException;
import application.res.Text;
/**
* This class wraps a file together with its writer and readable name together,
* so the writer can be written on repeatedly without having to be opened each
* and every time.
*
* @author Bianca
*
*/
public class FileWrapper
{
/**
* The file to be wrapped
*/
private File file;
/**
* The filename in a readable format so it can be displayed to the user.
*/
private String readableFileName;
/**
* A writer for the file
*/
private BufferedWriter bout;
/**
* Creates a file in temp directory for this filename and creates the writer.
*
* @param filename Exact filename ending
* @param readableFilename First part of the readable name
* @param readableSubFilename Second part of the readable name
* @throws WrappedException If there was an error creating the file or the
* writer
*/
private FileWrapper(String filename, String readableFilename, String readableSubFilename) throws WrappedException
{
try
{
file = Files.createTempFile("health_data", "__" + filename).toFile();
readableFileName = createReadableFileName(readableFilename, readableSubFilename);
file.deleteOnExit();
bout = new BufferedWriter(new FileWriter(file, true));
}
catch (IOException e)
{
throw new WrappedException(null, Text.E_CREATE_TEMP_FILES);
}
}
/**
* Creates a file in temp directory for this filename and creates the writer.
*
* @param filename Exact filename ending
* @param readableFilename Readable name
* @throws WrappedException If there was an error creating the file or the
* writer
*/
public FileWrapper(String filename, String readableFilename) throws WrappedException
{
this(filename, readableFilename, null);
}
/**
* Creates a file in temp directory for this filename and creates the writer.
* The header will be written with the given header coloumns.
*
* @param filename Exact filename ending
* @param readableFilename First part of the readable name
* @param readableSubFilename Second part of the readable name
* @param headerColoums The header coloumn names
* @throws WrappedException If there was an error creating the file or the
* writer
*/
public FileWrapper(String filename, String readableFilename, String readableSubFilename, String[] headerColoums)
throws WrappedException
{
this(filename, readableFilename, readableSubFilename);
try
{
writeHeaderColumn(headerColoums, true);
bout.newLine();
}
catch (IOException e)
{
throw new WrappedException(null, Text.E_CREATE_TEMP_FILES);
}
}
/**
* Combines the two parts of the readable filename into on.
*
* @param partOne The first (main) part of the filename.
* @param partTwo The second (additional) part of the filename.
* @return The described value.
*/
public static String createReadableFileName(String partOne, String partTwo)
{
if (partTwo == null)
{
return String.format(Text.F_FILE_DESC, partOne, "");
}
return String.format(Text.F_FILE_DESC, partOne, partTwo);
}
/**
* Creates a readable filename.
*
* @param name The intended filename.
* @return The described value.
*/
public static String createReadableFileName(String name)
{
return createReadableFileName(name, null);
}
/**
* Writes the header column in the files (names of attributes and sub attributes
* (in lists))
*
* @param attributes names of the attributes
* @param top attributes can be nested. If this is {@code True}, then
* this is the top level attribute. If this is {@code False}
* than it's a sub attribute.
* @throws IOException if there was an error writing
*/
private void writeHeaderColumn(String[] attributes, boolean top) throws IOException
{
for (int i = 0; i < attributes.length; i++)
{
// write attributeName
bout.write(attributes[i]);
// in case of sub attributes write sested elements
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_META_DATA_ENTRY))
{
bout.write("[[");
writeHeaderColumn(Utils.META_DATA_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_WORKOUT_EVENT))
{
bout.write("[[");
writeHeaderColumn(Utils.WORKOUT_EVENT_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_WORKOUT_ROUTE))
{
bout.write("[[");
writeHeaderColumn(Utils.WORKOUT_ROUTE_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_HR_LIST))
{
bout.write("[[");
writeHeaderColumn(Utils.HR_LIST_ATTR, false);
bout.write("]*]");
}
if (attributes[i].equalsIgnoreCase(Text.TAG_NAME_IB_PER_MINUTES))
{
bout.write("[[");
writeHeaderColumn(Utils.IB_PER_MINUTES_ATTR, false);
bout.write("]*]");
}
//Elements are separated with ';' on top level and with ',' on all other levels
if (i < attributes.length - 1)
{
if (top)
{
bout.write(";");
}
else
{
bout.write(",");
}
}
}
}
/**
* Returns the writer for the file.
* @return the described value.
*/
public BufferedWriter getBufferedWriter()
{
return bout;
}
/**
* Returns the file as an object.
* @return The described value.
*/
public File getFile()
{
return file;
}
/**
* Returns the readable filename.
* @return The described value.
*/
public String getReadableFilename()
{
return readableFileName;
}
}

150
health_data_export/src/application/parsing/ParsingWrapper.java

@ -0,0 +1,150 @@
package application.parsing;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import application.helpers.wrappers.Occupation;
import application.helpers.wrappers.WrappedException;
import application.helpers.wrappers.WriteSelection;
import application.res.Text;
/**
* This class coordinates the work with the parser (Reading and Writing).
* @author Bianca
*
*/
public class ParsingWrapper
{
/** The {@link ReadHandler} who will give an overview of the health data given**/
private ReadHandler read;
/** factory needed for the parser**/
private SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
/** the parser**/
private SAXParser saxParser;
/** The health data file**/
private File srcFile;
/**
* Constructor tries to get access to the health data file
*
* @param file file object of the data
* @throws Exception in case there was an error while getting access
*/
public ParsingWrapper(File file) throws Exception
{
srcFile=file;
saxParser = saxParserFactory.newSAXParser();
read = new ReadHandler();
saxParser.parse(srcFile, read);
if(read.getExportDate()==null) //wrong file / error reading
{
throw new Exception();
}
}
/**
* Gives access to the read data
* @return
*/
public ReadHandler getRead()
{
return read;
}
/**
* Writes the chosen data to the temp-directory
* @param selection The users selection on what data to use
* @param job The given job of the user
* @return the created files. Keys are the readable filenames
* @throws WrappedException If there was an error with the writing to files
*/
public HashMap<String,File> writeSelectedDataToFiles(WriteSelection selection, Occupation job) throws WrappedException
{
try
{
WriteHandler write = new WriteHandler(selection, job);
saxParser.parse(srcFile, write);
if(write.getErrorMessages()!=null)
{
throw new WrappedException(null, write.getErrorMessages());
}
return write.getAllFiles();
}
catch (Exception e)
{
e.printStackTrace();
throw new WrappedException(e, Text.E_WRITE_TO_TEMP_FILES);
}
}
/**
* Wraps the files from the temp directory into a zip file and places it in the given directory.
* @param files The files to wrap up
* @param targetDir The directory on where to save them
* @return The created zip file or {@code null} if writing / creating didn't work
*/
public File writeToZipFile(HashMap<String,File> files, File targetDir)
{
File target = new File(targetDir, "Export.zip");
int c=0;
//file with the same name could already exist
while(target.exists())
{
c++;
target = new File(targetDir, "Export("+c+").zip");
}
boolean created=false;
try
{
created=target.createNewFile();
}
catch(Exception e)
{
e.printStackTrace();
//file couldn't be created due to exception
}
if(!created)
{ //file couldn't be created, so writing impossible
return null;
}
try(FileOutputStream fos = new FileOutputStream(target);
ZipOutputStream zipOut = new ZipOutputStream(fos))
{
for (String key : files.keySet())
{
File srcFile=files.get(key);
if(srcFile!=null)
{
try(FileInputStream fis = new FileInputStream(srcFile))
{
String filename= srcFile.getName();
filename=filename.substring(filename.indexOf("__")+2);
ZipEntry zipEntry = new ZipEntry(filename);
zipOut.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while((length = fis.read(bytes)) >= 0)
{
zipOut.write(bytes, 0, length);
}
}
}
}
}
catch(Exception e)
{
e.printStackTrace();
//Problem with the file streams, Nothing a user could change so:
return null;
}
return target;
}
}

287
health_data_export/src/application/parsing/ReadHandler.java

@ -0,0 +1,287 @@
package application.parsing;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import application.enums.BiologicalSex;
import application.enums.BloodType;
import application.enums.EntryType;
import application.enums.SkinType;
import application.helpers.Utils;
import application.res.Text;
/**
* This class is in charge of reading in the data of the file and getting an overview of the data.
* @author Bianca
*
*/
public class ReadHandler extends DefaultHandler
{
/** The date the health data was exported**/
private String exportDate;
/** the biological sex given in the health data **/
private BiologicalSex bSex;
/** the blood type given in the health data **/
private BloodType bloodType;
/** the skin type given in the health data **/
private SkinType skinType;
/** the region code given in the health data **/
private String regionCode;
/** the date of birth as a string given in the health data**/
private String dateOfBirthString;
/** the date of birth saved in a calendar**/
private Calendar dateOfBirth;
/** contains as a key the name of tags (e.g EntryTypes) and as value the number of occurrences**/
private HashMap<String, Integer> names=new HashMap<>();
/** contains as a key the name of the sub workout and as value the number of occurrences **/
private HashMap<String, Integer> subWorkouts=new HashMap<>();
/** contains as a key the name of the sub records and as value the number of occurrences **/
private HashMap<String, Integer> subRecords=new HashMap<>();
/** keeps track of the tag layer. With every new element we increase the layer by one**/
private int overallLayer=0;
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
//increase layer
if(overallLayer>0)
{
overallLayer++;
}
if (qName.equalsIgnoreCase(Text.TAG_NAME_HEALTH_DATA))
{ //outermost tag (overallLayer==0)
overallLayer++;
regionCode=attributes.getValue(Text.TAG_ATTR_REGION_CODE);
}
else if (qName.equalsIgnoreCase(EntryType.EXPORT_DATE.getValue()))
{
exportDate=attributes.getValue(Text.TAG_ATTR_EXPORT_DATE);
}
else if (qName.equalsIgnoreCase(Text.TAG_NAME_ME_INFO))
{
bSex = BiologicalSex.getValue(attributes.getValue(Text.TAG_ATTR_BIOLOGICAL_SEX));
dateOfBirthString = attributes.getValue(Text.TAG_ATTR_DATE_OF_BIRTH);
bloodType = BloodType.getValue(attributes.getValue(Text.TAG_ATTR_BLOOD_TYPE));
skinType = SkinType.getValue(attributes.getValue(Text.TAG_ATTR_SKIN_TYPE));
if(dateOfBirthString!=null&&!dateOfBirthString.equals(""))
{
try
{
dateOfBirth=new GregorianCalendar();
dateOfBirth.setTime(Utils.formatDateOfBirth.parse(dateOfBirthString));
} catch (ParseException e)
{
dateOfBirth=null;
e.printStackTrace();
//do nth if parsing error
}
}
}
else if (qName.equalsIgnoreCase(EntryType.RECORD.getValue()))
{
String subRecord=attributes.getValue(Text.TAG_ATTR_RECORD_TYPE);
Integer j = subRecords.get(subRecord);
if (j == null)
{
j = 0;
}
subRecords.put(subRecord, ++j);
}
else if (qName.equalsIgnoreCase(EntryType.WORKOUT.getValue()))
{
String subWorkout=attributes.getValue(Text.TAG_ATTR_WORKOUT_TYPE);
Integer j = subWorkouts.get(subWorkout);
if (j == null)
{
j = 0;
}
subWorkouts.put(subWorkout, ++j);
}
if(overallLayer==2) //layer 2 means direct descendants of the document root (HealthData-tag)
{
Integer v=names.get(qName);
if (v==null)
{
v=0;
}
names.put(qName, ++v);
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException
{
//decrease layer
if(overallLayer>0)
{
overallLayer--;
}
}
/**
* Returns the selected value from ME or ExportDate.
*
* @param type Value to find the info for.
* @return Found value if it was a valid entry of ME or the export Date,
* {@code null} otherwise
*/
public String getMeInfo(EntryType type)
{
if (type.equals(EntryType.BIOLOGICAL_SEX))
{
return bSex.getReadableValue();
} else if (type.equals(EntryType.DATE_OF_BIRTH))
{
return getDateOfBirth();
}
if (type.equals(EntryType.BLOOD_TYPE))
{
return bloodType.getReadableValue();
}
if (type.equals(EntryType.SKIN_TYPE))
{
return skinType.getReadableValue();
}
return null;
}
/**
* Returns the day the health data was exported
* @return the described value.
*/
public String getExportDate()
{
return exportDate;
}
/**
* Returns the region code given in the file.
* @return the described value.
*/
public String getRegionCode()
{
return regionCode;
}
/**
* Finds out how many Entries of the given EntryTypes can be found in the
* document
*
* @param type EntryType to find the count to
* @return 0 if there are none, otherwise the correct number of entries
*/
public int getTypeNumber(EntryType type)
{
if (names.get(type.getValue()) == null)
{
return 0;
}
return names.get(type.getValue());
}
/**
* Returns the year of birth if the data was given in the file. Otherwise returns -1.
* @return the described value.
*/
public int getDateOfBirthYear()
{
if(dateOfBirth!=null)
{
return dateOfBirth.get(Calendar.YEAR);
}
return -1;
}
/**
* Returns the month of birth if the data was given in the file. Otherwise returns -1.
* @return the described value.
*/
public int getDateOfBirthMonth()
{
if(dateOfBirth!=null)
{
return dateOfBirth.get(Calendar.MONTH)+1;
}
return -1;
}
/**
* Returns the day of birth if the data was given in the file. Otherwise returns -1.
* @return the described value.
*/
public int getDateOfBirthDay()
{
if(dateOfBirth!=null)
{
return dateOfBirth.get(Calendar.DAY_OF_MONTH);
}
return -1;
}
/**
* Returns the SubWorkouts with their respective occurrences
* @return The described value
*/
public HashMap<String, Integer> getSubWorkouts()
{
return subWorkouts;
}
/**
* Returns the SubRecords with their respective occurrences
* @return The described value
*/
public HashMap<String, Integer> getSubRecords()
{
return subRecords;
}
/**
* Returns the date of birth as the String read in the file
* @return The described value.
*/
public String getDateOfBirth()
{
return dateOfBirthString;
}
/**
* Returns the date of birth split into 3 parts (day, month, year)
* @return the described value
*/
public HashMap<String, Integer> getSplitDateOfBirth()
{
HashMap<String, Integer> res= new HashMap<>();
if(getDateOfBirthDay()>0)
{
res.put(Text.DAY, getDateOfBirthDay());
}
if(getDateOfBirthMonth()>0)
{
res.put(Text.MONTH, getDateOfBirthMonth());
}
if(getDateOfBirthYear()>0)
{
res.put(Text.YEAR, getDateOfBirthYear());
}
return res;
}
/**
* Returns the biological sex as given in the file
* @return the described value
*/
public BiologicalSex getBSex()
{
return bSex;
}
}

632
health_data_export/src/application/parsing/WriteHandler.java

@ -0,0 +1,632 @@
package application.parsing;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import application.enums.BiologicalSex;
import application.enums.BloodType;
import application.enums.EntryType;
import application.enums.SkinType;
import application.helpers.Utils;
import application.helpers.wrappers.AttributeDataInfoTuple;
import application.helpers.wrappers.GeneralSaveInfo;
import application.helpers.wrappers.MeSaveInfo;
import application.helpers.wrappers.Occupation;
import application.helpers.wrappers.WrappedException;
import application.helpers.wrappers.WriteSelection;
import application.res.Text;
public class WriteHandler extends DefaultHandler
{
/**
* Contains the StringBuilders for the nested attributes which need to be added
* to the complete Row. Key is the tag name of the nested attribute
*/
private HashMap<String, AttributeDataInfoTuple> nestedAttributes;
/** The text to be written as one row into the files **/
private StringBuilder completeRow;
/** the selection of the user **/
private WriteSelection selection;
/** all files which will be written. Key is the readable name **/
private HashMap<String, FileWrapper> allFiles;
/**
* In case of records and workouts there will be sub elements. The name will be
* saved here.
**/
private String subElementName = null;
/**
* All error messages will be appended here as {@link DefaultHandler} does not
* permit other exceptions in the startElement and endElement methods
**/
private StringBuilder errorMessages;
/**
* Initializes the variables and creates all files needed for the selected data
*
* @param selection The selection of the user
* @param job The job given by the user
*/
public WriteHandler(WriteSelection selection, Occupation job)
{
this.selection = selection;
this.errorMessages = new StringBuilder();
resetSubAttributes();
// figure out what is selected
// create files for the selected
allFiles = new HashMap<>();
try
{
boolean me = false;
for (EntryType meElem : EntryType.ALL_ME_ONES)
{
if (this.selection.entrySelected(meElem))
{
me = true;
}
}
if (!job.equals(Text.NOT_SET))
{ // job given
me = true;
}
if (me)
{
FileWrapper fw = new FileWrapper("me_info.txt", "MainInfo");
allFiles.put(fw.getReadableFilename(), fw);
if (!job.getLevelOne().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_ONE, job.getLevelOne()));
if (!job.getLevelTwo().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_TWO, job.getLevelTwo()));
if (!job.getLevelThree().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_THREE, job.getLevelThree()));
if (!job.getLevelFour().equals(Text.NOT_SET))
{
writeToMeFile(new MeSaveInfo(EntryType.JOB, Text.OCCUPATION_LEVEL_FOUR, job.getLevelFour()));
}
}
}
}
}
if (this.selection.entrySelected(EntryType.ACTIVITY_SUMMARY))
{
FileWrapper fw = new FileWrapper("activity_summary.csv", EntryType.ACTIVITY_SUMMARY.getValue(), null,
Utils.ACTIV_SUMM_ATTR);
allFiles.put(fw.getReadableFilename(), fw);
}
if (this.selection.entrySelected(EntryType.WORKOUT))
{
for (String selectedType : selection.getWorkouts())
{
FileWrapper fw = new FileWrapper("workout_" + selectedType + ".csv", EntryType.WORKOUT.getValue(),
Utils.shortenHKStrings(selectedType), Utils.workoutAttributes);
allFiles.put(fw.getReadableFilename(), fw);
}
}
if (this.selection.entrySelected(EntryType.RECORD))
{
for (String selectedType : selection.getRecords())
{
FileWrapper fw = new FileWrapper("record_" + selectedType + ".csv", EntryType.RECORD.getValue(),
Utils.shortenHKStrings(selectedType), Utils.recordAttributes);
allFiles.put(fw.getReadableFilename(), fw);
}
}
}
catch (WrappedException e)
{
errorMessages.append(e.getReason());
errorMessages.append("\n");
e.getException().printStackTrace();
}
catch (Exception e)
{
errorMessages.append(Text.E_CREATE_TEMP_FILES);
errorMessages.append("\n");
e.printStackTrace();
}
}
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException
{
try
{
// ME Entries not of the me tag
if (selection.entrySelected(EntryType.REGION_CODE) && qName.equalsIgnoreCase(Text.TAG_NAME_HEALTH_DATA))
{
writeToMeFile(new MeSaveInfo(EntryType.REGION_CODE, attributes.getValue(Text.TAG_ATTR_REGION_CODE)));
}
else if (selection.entrySelected(EntryType.EXPORT_DATE)
&& qName.equalsIgnoreCase(EntryType.EXPORT_DATE.getValue()))
{
writeToMeFile(new MeSaveInfo(EntryType.EXPORT_DATE, attributes.getValue(Text.TAG_ATTR_EXPORT_DATE)));
}
// ME Tag
else if (qName.equalsIgnoreCase(Text.TAG_NAME_ME_INFO))
{
for (EntryType me : EntryType.ALL_ORIGINAL_ME_ONES)
{
if (selection.entrySelected(me))
{
String dateOfBirthString = attributes.getValue(Text.TAG_ATTR_DATE_OF_BIRTH);
// first the simple ones
if (me != EntryType.DATE_OF_BIRTH)
{
String value = "";
if (me == EntryType.BIOLOGICAL_SEX)
{
value = BiologicalSex.getValue(attributes.getValue(Text.TAG_ATTR_BIOLOGICAL_SEX))
.getReadableValue();
}
else if (me == EntryType.BLOOD_TYPE)
{
value = BloodType.getValue(attributes.getValue(Text.TAG_ATTR_BLOOD_TYPE))
.getReadableValue();
}
else if (me == EntryType.SKIN_TYPE)
{
value = SkinType.getValue(attributes.getValue(Text.TAG_ATTR_SKIN_TYPE))
.getReadableValue();
}
writeToMeFile(new MeSaveInfo(me, value));
}
else if (dateOfBirthString != null && !dateOfBirthString.equals(""))
{// now the date of birth with individually selected parts
try
{
Calendar dateOfBirth = new GregorianCalendar();
dateOfBirth.setTime(Utils.formatDateOfBirth.parse(dateOfBirthString));
if (selection.birthPartSelected(Text.YEAR))
{
writeToMeFile(new MeSaveInfo(EntryType.DATE_OF_BIRTH, Text.YEAR,
dateOfBirth.get(Calendar.YEAR) + ""));
}
if (selection.birthPartSelected(Text.MONTH))
{
writeToMeFile(new MeSaveInfo(EntryType.DATE_OF_BIRTH, Text.MONTH,
"" + (dateOfBirth.get(Calendar.MONTH) + 1)));
}
if (selection.birthPartSelected(Text.DAY))
{
writeToMeFile(new MeSaveInfo(EntryType.DATE_OF_BIRTH, Text.DAY,
dateOfBirth.get(Calendar.DAY_OF_MONTH) + ""));
}
}
catch (ParseException e)
{
e.printStackTrace();
// bad luck. Do nothing if parsing error
}
}
}
}
}
// activity summaries: only one file and no special cases
else if (selection.entrySelected(EntryType.ACTIVITY_SUMMARY)
&& qName.equalsIgnoreCase(EntryType.ACTIVITY_SUMMARY.getValue()))
{
writeToFile(new GeneralSaveInfo(getValuesToAttributes(Utils.ACTIV_SUMM_ATTR, attributes),
EntryType.ACTIVITY_SUMMARY));
}
// The following ones might need completion in the endElement-method
else if (qName.equalsIgnoreCase(EntryType.WORKOUT.getValue()) && selection.workoutSelected(attributes))
{
subElementName = Utils.shortenHKStrings(attributes.getValue(Text.TAG_ATTR_WORKOUT_TYPE));
completeRow.append(getNonNestedAttributeValues(
getValuesToAttributes(Utils.workoutAttributes, attributes), Utils.workoutAttributes, true));
}
else if (qName.equalsIgnoreCase(EntryType.RECORD.getValue()) && selection.recordSelected(attributes))
{
subElementName = Utils.shortenHKStrings(attributes.getValue(Text.TAG_ATTR_RECORD_TYPE));
completeRow.append(getNonNestedAttributeValues(
getValuesToAttributes(Utils.recordAttributes, attributes), Utils.recordAttributes, true));
}
// These are now nested elements with nested attributes
else if (subElementName != null && isNestedAttribute(qName))
{
for (String tagName : nestedAttributes.keySet())
{
if (qName.equals(tagName))
{ // all entries in attributes
AttributeDataInfoTuple tuple = nestedAttributes.get(tagName);
if (tagName.equals(Text.TAG_NAME_HR_LIST))
{ // this one is completely nested
tuple.getBuilder().append("[");
}
else
{
tuple.getBuilder()
.append(getNonNestedAttributeValues(
getValuesToAttributes(tuple.getSubAttributes(), attributes),
tuple.getSubAttributes(), false));
if (!tagName.equals(Text.TAG_NAME_WORKOUT_ROUTE))
{ // workout route is nested again and isn't complete yet
tuple.getBuilder().append(",");
}
}
}
}
}
}
catch (WrappedException e)
{
errorMessages.append(e.getReason());
errorMessages.append("\n");
// TODO: alle prints entfernen
e.getException().printStackTrace();
}
}
/**
* Writes all attributes of an element up to the first nested attribute
*
* @param values attribute values to be written
* @param names attribute names to check for nesting
* @param top indicates whether this is already a nested attribute
* ({@code False}) or whether we are at top level ({@code True}
* @return The complete string for the element if there are no nested values,
* otherwise the incomplete beginning which needs to be finished with
* {@code addAllNestedAttributes}
*/
private String getNonNestedAttributeValues(String[] values, String[] names, boolean top)
{
StringBuilder res = new StringBuilder();
// Start element with a bracket
res.append("[");
for (int i = 0; i < names.length; i++)
{
if (isNestedAttribute(names[i]))
{ // stop if attribute is nested, then we need to finish at another point
// here is no bracket at the end!
return res.toString();
}
res.append(values[i]);
if (top)
{
res.append(";");
}
else
{
res.append(",");
}
}
// replace last char (, or ;) with bracket
res = res.replace(res.length() - 1, res.length(), "]");
return res.toString();
}
/**
* Determines for an attribute name whether it is a nested attribute
*
* @param attr the name to check
* @return {@code True} if it is a nested attribute, {@code False} otherwise.
*/
private boolean isNestedAttribute(String attr)
{
for (String nestedAttribute : nestedAttributes.keySet())
{
if (attr.equals(nestedAttribute))
{
return true;
}
}
return false;
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException
{
if (subElementName != null)
{ // we have a selected value which is nested / has nested attributes
if (isNestedAttribute(qName))
{ // nested case: either hr_list or workout_route
if (qName.equals(Text.TAG_NAME_HR_LIST))
{ // all entries in IBperMinutes
appendSubEntry(Text.TAG_NAME_HR_LIST, Text.TAG_NAME_IB_PER_MINUTES);
nestedAttributes.put(Text.TAG_NAME_IB_PER_MINUTES,
new AttributeDataInfoTuple(Utils.IB_PER_MINUTES_ATTR));
nestedAttributes.get(Text.TAG_NAME_HR_LIST).getBuilder().append("],");
}
if (qName.equals(Text.TAG_NAME_WORKOUT_ROUTE))
{ // last entry not in attributes! (MetaDataEntry)
appendSubEntry(Text.TAG_NAME_WORKOUT_ROUTE, Text.TAG_NAME_META_DATA_ENTRY);
nestedAttributes.put(Text.TAG_NAME_META_DATA_ENTRY,
new AttributeDataInfoTuple(Utils.META_DATA_ATTR));
nestedAttributes.get(Text.TAG_NAME_WORKOUT_ROUTE).getBuilder().append("],");
}
}
else
{ // this can only be a record or workout
// non nested ones were written in the start method, so only add the nested ones
// now
if (qName.equalsIgnoreCase(EntryType.WORKOUT.getValue()))
{
addAllNestedAttributes(completeRow, Utils.workoutAttributes);
}
else
{
addAllNestedAttributes(completeRow, Utils.recordAttributes);
}
completeRow = removeOuterBrackets(completeRow);
FileWrapper fw = allFiles.get(FileWrapper.createReadableFileName(qName, subElementName));
if (fw != null)
{
try
{
fw.getBufferedWriter().append(completeRow);
fw.getBufferedWriter().newLine();
}
catch (IOException e)
{
errorMessages.append(Text.E_WRITE_TO_TEMP_FILES);
errorMessages.append("\n");
e.printStackTrace();
}
}
resetSubAttributes();
}
}
}
/**
* Used for removing the outer Brackets in a StringBuilder, but in truth just
* removes the first and last char of it.... So be careful, it doesn't check for
* brackets!
*
* @param sb The StringBuilder to be shortened
* @return The shortened StringBuilder
*/
private StringBuilder removeOuterBrackets(StringBuilder sb)
{
sb = sb.replace(0, 1, "");
return sb.replace(sb.length() - 1, sb.length(), "");
}
/**
* Nested Attributes (which are ALLWAYS at the end of attribute lists) are here
* appended at the end of the current row
*
* @param toAppend Contains the current row which should be appended
* @param attributes The whole attribute list of the current row. But only the
* nested ones will be appended thus the non nested ones need
* to already be in the StringBuilder
*/
private void addAllNestedAttributes(StringBuilder toAppend, String[] attributes)
{
for (int i = 0; i < attributes.length; i++)
{
for (String tagName : nestedAttributes.keySet())
{
if (attributes[i].equals(tagName))
{
appendSubEntry(toAppend, tagName);
toAppend.append(";");
}
}
}
toAppend = toAppend.replace(toAppend.length() - 1, toAppend.length(), "]");
}
/**
* Appends the text of a sub entry to its containing row. All sub entries (even
* if they are empty) will be surrounded by brackets [].
*
* @param toAppendTo The containing row which should be appended
* @param nestedInfo The name of the attribute containing the sub entry
* information
*/
private void appendSubEntry(StringBuilder toAppendTo, String nestedInfo)
{
toAppendTo.append("[");
StringBuilder toAppendWith = nestedAttributes.get(nestedInfo).getBuilder();
if (toAppendWith.length() > 0) // there is an element here, so there is also a ',' too much
{
toAppendTo.append(toAppendWith.substring(0, toAppendWith.length() - 1));
}
toAppendTo.append("]");
}
/**
* Appends the text of a sub entry to its containing row. All sub entries (even
* if they are empty) will be surrounded by brackets [].
*
* @param topLevelInfo The name of the attribute which should be appended with a
* sub attribute
* @param nestedInfo The name of the attribute containing the sub entry
* information
*/
private void appendSubEntry(String topLevelInfo, String nestedInfo)
{
appendSubEntry(nestedAttributes.get(topLevelInfo).getBuilder(), nestedInfo);
}
/**
* /** Writes one line of information to a file (but not the ME File)
*
* @param info The information about what to write.
* @throws WrappedException if the file could not be written
*/
private void writeToFile(GeneralSaveInfo info) throws WrappedException
{
FileWrapper fw = allFiles.get(info.getReadableFilename());
if (fw != null)
{
BufferedWriter bout = fw.getBufferedWriter();
try
{
writeNormalRow(bout, info.getValues(), true);
bout.newLine();
}
catch (IOException e)
{
e.printStackTrace();
throw new WrappedException(e, Text.E_WRITE_TO_TEMP_FILES);
}
}
}
/**
* Writes one line of information to the me file
*
* @param info The information about what to write.
* @throws WrappedException if the file could not be written
*/
private void writeToMeFile(MeSaveInfo info) throws WrappedException
{
FileWrapper fw = allFiles.get(FileWrapper.createReadableFileName(Text.ME_FILE_DESC));
if (fw != null)
{
BufferedWriter bout = fw.getBufferedWriter();
try
{
bout.write(String.format(Text.F_ME_SAVE, info.getSaveKey(), info.getSaveValue()));
bout.newLine();
}
catch (IOException e)
{
e.printStackTrace();
throw new WrappedException(e, Text.E_WRITE_TO_TEMP_FILES);
}
}
}
/**
* Initializes the HashMap containing the nested attributes info, as well as the
* completeRow and sets the subElementname to {@code null}
*/
private void resetSubAttributes()
{
nestedAttributes = new HashMap<>();
nestedAttributes.put(Text.TAG_NAME_META_DATA_ENTRY, new AttributeDataInfoTuple(Utils.META_DATA_ATTR));
nestedAttributes.put(Text.TAG_NAME_WORKOUT_EVENT, new AttributeDataInfoTuple(Utils.WORKOUT_EVENT_ATTR));
nestedAttributes.put(Text.TAG_NAME_WORKOUT_ROUTE, new AttributeDataInfoTuple(Utils.WORKOUT_ROUTE_ATTR));
nestedAttributes.put(Text.TAG_NAME_HR_LIST, new AttributeDataInfoTuple(Utils.HR_LIST_ATTR));
nestedAttributes.put(Text.TAG_NAME_IB_PER_MINUTES, new AttributeDataInfoTuple(Utils.IB_PER_MINUTES_ATTR));
completeRow = new StringBuilder();
subElementName = null;
}
/**
* Gets the values for the given attributes and returns them in an array where
* the values are saved at the index corresponding to their keys.
*
* @param attributeName Keys / names of the attributes.
* @param values The object containing the values
* @return The described array
*/
private String[] getValuesToAttributes(String[] attributeName, Attributes values)
{
String[] res = new String[attributeName.length];
for (int i = 0; i < res.length; i++)
{
String v = values.getValue(attributeName[i]);
if(attributeName[i].equals("device")&&v!=null)
{
//Entry left out due to privacy
String device="<HKDevice:";
String actualDeviceSubstring=v.substring(v.indexOf(device),v.indexOf(">")+1);
v=v.replace(actualDeviceSubstring, "device_anonymized");
}
if (v == null)
{
res[i] = "";
}
else if (v.contains(",") || v.contains(";"))
{
res[i] = "\"" + v + "\"";
}
else
{
res[i] = v;
}
}
return res;
}
/**
* Writes a non nested row into a file
*
* @param bout The writer of the file
* @param values the values in correct order needed to fit with the header row
* which should be written
* @param top shows whether this is a "nested row" / nested attribute entry
* @throws IOException if there was an error writing
*/
private void writeNormalRow(Writer bout, String[] values, boolean top) throws IOException
{
for (int i = 0; i < values.length; i++) // for every attribute value
{
bout.write(values[i]);
if (i < values.length - 1)
{
if (top)
{
bout.write(";");
}
else
{
bout.write(",");
}
}
}
}
/**
* Returns all files written in temp directory with the readable name as key and
* the {@link File} as value. It also closes the writers and thus can only be
* used after the end of writing.
*
* @return The described value.
*/
public HashMap<String, File> getAllFiles()
{
HashMap<String, File> res = new HashMap<>();
for (String key : allFiles.keySet())
{
FileWrapper fw = allFiles.get(key);
try
{
fw.getBufferedWriter().close();
}
catch (IOException e)
{
// Stream couldn't be closed. Bad luck.
}
res.put(key, fw.getFile());
}
return res;
}
/**
* Returns all accumulated error messages.
*
* @return The described value or {@code null} if there is no error message.
*/
public String getErrorMessages()
{
if (errorMessages.length()==0)
{
return null;
}
return errorMessages.toString();
}
}

30
health_data_export/src/application/res/Backgrounds.java

@ -0,0 +1,30 @@
package application.res;
import javafx.geometry.Insets;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
public class Backgrounds
{
private static CornerRadii cr = CornerRadii.EMPTY;
private static Insets i = Insets.EMPTY;
public static Background lightGreenBackground =new Background(new BackgroundFill(Colors.lightGreen,
cr , i));
public static Background greenBackground =new Background(new BackgroundFill(Colors.green,
cr , i));
public static Background darkBlueBackground =new Background(new BackgroundFill(Colors.darkBlue,
cr , i));
public static Background greyBackground = new Background(new BackgroundFill(Colors.grey,
cr, i));
public static Background lightGreyBackground = new Background(new BackgroundFill(Colors.lightGrey,
cr, i));
public static Background darkGreyBackground = new Background(new BackgroundFill(Colors.darkGrey, cr, i));
}

24
health_data_export/src/application/res/Colors.java

@ -0,0 +1,24 @@
package application.res;
import javafx.scene.paint.Color;
public class Colors
{
/** used to indicate elements with selected sub elements (but not all sub elements are selected)**/
public static Color lightGreen= Color.valueOf("#a2c69f");
/** Used to indicate selected elements**/
public static Color green= Color.valueOf("#668c63");
/** Main color**/
public static Color darkBlue= Color.valueOf("#01283f");
/** A very slight grey for light accents**/
public static Color lightGrey= Color.valueOf("#e6e6e6");
/** A medium grey, for normal accents**/
public static Color grey= Color.valueOf("#bebebe");
/** Accent color for the navigation**/
public static Color red= Color.valueOf("#c82254");
/** A dark grey which can't be differentiated from black or the main color much. Should be used sparsely **/
public static Color darkGrey= Color.valueOf("#919191");
/** A bright blue which can be used for links **/
public static Color blue= Color.valueOf("#004877");
}

575
health_data_export/src/application/res/Occupations.java

@ -0,0 +1,575 @@
package application.res;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
/**
* This class holds the list of occupations according to ISOC-08.
*
* @author Bianca
*
*/
public class Occupations
{
public static ObservableList<String> JOBS_LEVEL_1 =
FXCollections.observableArrayList(Text.NOT_SET,"1 Managers", "2 Professionals",
"3 Technicians and Associate Professionals",
"4 Clerical Support Workers","5 Services And Sales Workers",
"6 Skilled Agricultural, Forestry and Fishery Workers",
"7 Craft and Related Trades Workers",
"8 Plant and Machine Operators and Assemblers",
"9 Elementary Occupations", "0 Armed Forces Occupations"
);
public static ObservableList<String> JOBS_LEVEL_2 =
FXCollections.observableArrayList(Text.NOT_SET,
"11 Chief Executives, Senior Officials and Legislators",
"12 Administrative and Commercial Managers",
"13 Production and Specialized Services Managers",
"14 Hospitality, Retail and Other Services Managers",
"21 Science and Engineering Professionals",
"22 Health Professionals",
"23 Teaching Professionals",
"24 Business and Administration Professionals",
"25 Information and Communications Technology Professionals",
"26 Legal, Social and Cultural Professionals",
"31 Science and Engineering Associate Professionals",
"32 Health Associate Professionals",
"33 Business and Administration Associate Professionals",
"34 Legal, Social, Cultural and Related Associate Professionals",
"35 Information and Communications Technicians",
"41 General and Keyboard Clerks",
"42 Customer Services Clerks",
"43 Numerical and Material Recording Clerks",
"44 Other Clerical Support Workers",
"51 Personal Services Workers", "52 Sales Workers",
"53 Personal Care Workers", "54 Protective Services Workers",
"61 Market-oriented Skilled Agricultural Workers",
"62 Market-oriented Skilled Forestry, Fishery and Hunting Workers",
"63 Subsistence Farmers, Fishers, Hunters and Gatherers",
"71 Building and Related Trades Workers (excluding Electricians)",
"72 Metal, Machinery and Related Trades Workers",
"73 Handicraft and Printing Workers",
"74 Electrical and Electronics Trades Workers",
"75 Food Processing, Woodworking, Garment and Other Craft and Related Trades Workers",
"81 Stationary Plant and Machine Operators",
"82 Assemblers", "83 Drivers and Mobile Plant Operators",
"91 Cleaners and Helpers",
"92 Agricultural, Forestry and Fishery Labourers",
"93 Labourers in Mining, Construction, Manufacturing and Transport",
"94 Food Preparation Assistants",
"95 Street and Related Sales and Services Workers",
"96 Refuse Workers and Other Elementary Workers",
"01 Commissioned Armed Forces Officers",
"02 Non-commissioned Armed Forces Officers",
"03 Armed Forces Occupations, Other Ranks"
);
public static ObservableList<String> JOBS_LEVEL_3 =
FXCollections.observableArrayList(Text.NOT_SET,
"111 Legislators and Senior Officials",
"112 Managing Directors and Chief Executives",
"121 Business Services and Administration Managers",
"122 Sales, Marketing and Development Managers",
"131 Production Managers in Agriculture, Forestry and Fisheries",
"132 Manufacturing, Mining, Construction and Distribution Managers",
"133 Information and Communications Technology Services Managers",
"134 Professional Services Managers",
"141 Hotel and Restaurant Managers",
"142 Retail and Wholesale Trade Managers",
"143 Other Services Managers",
"211 Physical and Earth Science Professionals",
"212 Mathematicians, Actuaries and Statisticians",
"213 Life Science Professionals",
"214 Engineering Professionals (excluding Electrotechnology)",
"215 Electrotechnology Engineers",
"216 Architects, Planners, Surveyors and Designers",
"221 Medical Doctors", "222 Nursing and Midwifery Professionals",
"223 Traditional and Complementary Medicine Professionals",
"224 Paramedical Practitioners", "225 Veterinarians",
"226 Other Health Professionals",
"231 University and Higher Education Teachers",
"232 Vocational Education Teachers",
"233 Secondary Education Teachers",
"234 Primary School and Early Childhood Teachers",
"235 Other Teaching Professionals",
"241 Finance Professionals",
"242 Administration Professionals",
"243 Sales, Marketing and Public Relations Professionals",
"251 Software and Applications Developers and Analysts",
"252 Database and Network Professionals",
"261 Legal Professionals",
"262 Librarians, Archivists and Curators",
"263 Social and Religious Professionals",
"264 Authors, Journalists and Linguists",
"265 Creative and Performing Artists",
"311 Physical and Engineering Science Technicians",
"312 Mining, Manufacturing and Construction Supervisors",
"313 Process Control Technicians",
"314 Life Science Technicians and Related Associate Professionals",
"315 Ship and Aircraft Controllers and Technicians",
"321 Medical and Pharmaceutical Technicians",
"322 Nursing and Midwifery Associate Professionals",
"323 Traditional and Complementary Medicine Associate Professionals",
"324 Veterinary Technicians and Assistants",
"325 Other Health Associate Professionals",
"331 Financial and Mathematical Associate Professionals",
"332 Sales and Purchasing Agents and Brokers",
"333 Business Services Agents",
"334 Administrative and Specialized Secretaries",
"335 Government Regulatory Associate Professionals",
"341 Legal, Social and Religious Associate Professionals",
"342 Sports and Fitness Workers",
"343 Artistic, Cultural and Culinary Associate Professionals",
"351 Information and Communications Technology Operations and User Support Technicians",
"352 Telecommunications and Broadcasting Technicians", "411 General Office Clerks",
"412 Secretaries (general)",
"413 Keyboard Operators",
"421 Tellers, Money Collectors and Related Clerks",
"422 Client Information Workers",
"431 Numerical Clerks",
"432 Material Recording and Transport Clerks",
"441 Other Clerical Support Workers",
"511 Travel Attendants, Conductors and Guides",
"512 Cooks", "513 Waiters and Bartenders",
"514 Hairdressers, Beauticians and Related Workers",
"515 Building and Housekeeping Supervisors",
"516 Other Personal Services Workers",
"521 Street and Market Salespersons",
"522 Shop Salespersons",
"523 Cashiers and Ticket Clerks",
"524 Other Sales Workers",
"531 Child Care Workers and Teachers' Aides",
"532 Personal Care Workers in Health Services",
"541 Protective Services Workers",
"611 Market Gardeners and Crop Growers",
"612 Animal Producers",
"613 Mixed Crop and Animal Producers",
"621 Forestry and Related Workers",
"622 Fishery Workers, Hunters and Trappers",
"631 Subsistence Crop Farmers",
"632 Subsistence Livestock Farmers",
"633 Subsistence Mixed Crop and Livestock Farmers",
"634 Subsistence Fishers, Hunters, Trappers and Gatherers",
"711 Building Frame and Related Trades Workers",
"712 Building Finishers and Related Trades Workers",
"713 Painters, Building Structure Cleaners and Related Trades Workers",
"721 Sheet and Structural Metal Workers, Moulders and Welders, and Related Workers",
"722 Blacksmiths, Toolmakers and Related Trades Workers",
"723 Machinery Mechanics and Repairers",
"731 Handicraft Workers", "732 Printing Trades Workers",
"741 Electrical Equipment Installers and Repairers",
"742 Electronics and Telecommunications Installers and Repairers",
"751 Food Processing and Related Trades Workers",
"752 Wood Treaters, Cabinet-makers and Related Trades Workers",
"753 Garment and Related Trades Workers",
"754 Other Craft and Related Workers",
"811 Mining and Mineral Processing Plant Operators",
"812 Metal Processing and Finishing Plant Operators",
"813 Chemical and Photographic Products Plant and Machine Operators",
"814 Rubber, Plastic and Paper Products Machine Operators",
"815 Textile, Fur and Leather Products Machine Operators",
"816 Food and Related Products Machine Operators",
"817 Wood Processing and Papermaking Plant Operators",
"818 Other Stationary Plant and Machine Operators",
"821 Assemblers",
"831 Locomotive Engine Drivers and Related Workers",
"832 Car, Van and Motorcycle Drivers",
"833 Heavy Truck and Bus Drivers",
"834 Mobile Plant Operators",
"835 Ships' Deck Crews and Related Workers",
"911 Domestic, Hotel and Office Cleaners and Helpers",
"912 Vehicle, Window, Laundry and Other Hand Cleaning Workers",
"921 Agricultural, Forestry and Fishery Labourers",
"931 Mining and Construction Labourers",
"932 Manufacturing Labourers",
"933 Transport and Storage Labourers",
"941 Food Preparation Assistants",
"951 Street and Related Services Workers",
"952 Street Vendors (excluding Food)",
"961 Refuse Workers",
"962 Other Elementary Workers",
"011 Commissioned Armed Forces Officers",
"021 Non-commissioned Armed Forces Officers",
"031 Armed Forces Occupations, Other Ranks"
);
public static ObservableList<String> JOBS_LEVEL_4 =
FXCollections.observableArrayList(Text.NOT_SET,
"1111 Legislators",
"1112 Senior Government Officials",
"1113 Traditional Chiefs and Heads of Villages",
"1114 Senior Officials of Special-interest Organizations",
"1120 Managing Directors and Chief Executives",
"1211 Finance Managers", "1212 Human Resource Managers",
"1213 Policy and Planning Managers",
"1219 Business Services and Administration Managers Not Elsewhere Classified",
"1221 Sales and Marketing Managers",
"1222 Advertising and Public Relations Managers",
"1223 Research and Development Managers",
"1311 Agricultural and Forestry Production Managers",
"1312 Aquaculture and Fisheries Production Managers",
"1321 Manufacturing Managers", "1322 Mining Managers",
"1323 Construction Managers",
"1324 Supply, Distribution and Related Managers",
"1330 Information and Communications Technology Services Managers",
"1341 Child Care Services Managers",
"1342 Health Services Managers", "1343 Aged Care Services Managers",
"1344 Social Welfare Managers", "1345 Education Managers",
"1346 Financial and Insurance Services Branch Managers",
"1349 Professional Services Managers Not Elsewhere Classified",
"1411 Hotel Managers", "1412 Restaurant Managers",
"1420 Retail and Wholesale Trade Managers",
"1431 Sports, Recreation and Cultural Centre Managers",
"1439 Services Managers Not Elsewhere Classified",
"2111 Physicists and Astronomers",
"2112 Meteorologists", "2113 Chemists",
"2114 Geologists and Geophysicists",
"2120 Mathematicians, Actuaries and Statisticians",
"2131 Biologists, Botanists, Zoologists and Related Professionals",
"2132 Farming, Forestry and Fisheries Advisers",
"2133 Environmental Protection Professionals",
"2141 Industrial and Production Engineers",
"2142 Civil Engineers", "2143 Environmental Engineers",
"2144 Mechanical Engineers", "2145 Chemical Engineers",
"2146 Mining Engineers, Metallurgists and Related Professionals",
"2149 Engineering Professionals Not Elsewhere Classified",
"2151 Electrical Engineers", "2152 Electronics Engineers",
"2153 Telecommunications Engineers",
"2161 Building Architects", "2162 Landscape Architects",
"2163 Product and Garment Designers",
"2164 Town and Traffic Planners", "2165 Cartographers and Surveyors",
"2166 Graphic and Multimedia Designers",
"2211 Generalist Medical Practitioners",
"2212 Specialist Medical Practitioners",
"2221 Nursing Professionals", "2222 Midwifery Professionals",
"2230 Traditional and Complementary Medicine Professionals",
"2240 Paramedical Practitioners", "2250 Veterinarians",
"2261 Dentists", "2262 Pharmacists",
"2263 Environmental and Occupational Health and Hygiene Professionals",
"2264 Physiotherapists", "2265 Dieticians and Nutritionists",
"2266 Audiologists and Speech Therapists",
"2267 Optometrists and Ophthalmic Opticians",
"2269 Health Professionals Not Elsewhere Classified",
"2310 University and Higher Education Teachers",
"2320 Vocational Education Teachers",
"2330 Secondary Education Teachers",
"2341 Primary School Teachers", "2342 Early Childhood Educators",
"2351 Education Methods specialists",
"2352 Special Needs Teachers", "2353 Other Language Teachers",
"2354 Other Music Teachers", "2355 Other Arts Teachers",
"2356 Information Technology Trainers",
"2359 Teaching Professionals Not Elsewhere Classified",
"2411 Accountants",
"2412 Financial and Investment Advisers",
"2413 Financial Analysts",
"2421 Management and Organization Analysts",
"2422 Policy Administration Professionals",
"2423 Personnel and Careers Professionals",
"2424 Training and Staff Development Professionals",
"2431 Advertising and Marketing Professionals",
"2432 Public Relations Professionals",
"2433 Technical and Medical Sales Professionals (excluding ICT)",
"2434 Information and Communications Technology Sales Professionals",
"2511 Systems Analysts","2512 Software Developers",
"2513 Web and Multimedia Developers",
"2514 Applications Programmers",
"2519 Software and Applications Developers and Analysts Not Elsewhere Classified",
"2521 Database Designers and Administrators",
"2522 Systems Administrators", "2523 Computer Network Professionals",
"2529 Database and Network Professionals Not Elsewhere Classified",
"2611 Lawyers", "2612 Judges",
"2619 Legal Professionals Not Elsewhere Classified",
"2621 Archivists and Curators",
"2622 Librarians and Related Information Professionals",
"2631 Economists",
"2632 Sociologists, Anthropologists and Related Professionals",
"2633 Philosophers, Historians and Political Scientists",
"2634 Psychologists",
"2635 Social Work and Counselling Professionals",
"2636 Religious Professionals", "2641 Authors and Related Writers",
"2642 Journalists",
"2643 Translators, Interpreters and Other Linguists",
"2651 Visual Artists",
"2652 Musicians, Singers and Composers",
"2653 Dancers and Choreographers",
"2654 Film, Stage and Related Directors and Producers",
"2655 Actors", "2656 Announcers on Radio, Television and Other Media",
"2659 Creative and Performing Artists Not Elsewhere Classified",
"3111 Chemical and Physical Science Technicians",
"3112 Civil Engineering Technicians",
"3113 Electrical Engineering Technicians",
"3114 Electronics Engineering Technicians",
"3115 Mechanical Engineering Technicians",
"3116 Chemical Engineering Technicians",
"3117 Mining and Metallurgical Technicians",
"3118 Draughtspersons",
"3119 Physical and Engineering Science Technicians Not Elsewhere Classified",
"3121 Mining Supervisors", "3122 Manufacturing Supervisors",
"3123 Construction Supervisors",
"3131 Power Production Plant Operators",
"3132 Incinerator and Water Treatment Plant Operators",
"3133 Chemical Processing Plant Controllers",
"3134 Petroleum and Natural Gas Refining Plant Operators",
"3135 Metal Production Process Controllers",
"3139 Process Control Technicians Not Elsewhere Classified",
"3141 Life Science Technicians (excluding Medical)",
"3142 Agricultural Technicians", "3143 Forestry Technicians",
"3151 Ships' Engineers", "3152 Ships' Deck Officers and Pilots",
"3153 Aircraft Pilots and Related Associate Professionals",
"3154 Air Traffic Controllers",
"3155 Air Traffic Safety Electronics Technicians",
"3211 Medical Imaging and Therapeutic Equipment Technicians",
"3212 Medical and Pathology Laboratory Technicians",
"3213 Pharmaceutical Technicians and Assistants",
"3214 Medical and Dental Prosthetic Technicians",
"3221 Nursing Associate Professionals",
"3222 Midwifery Associate Professionals",
"3230 Traditional and Complementary Medicine Associate Professionals",
"3240 Veterinary Technicians and Assistants",
"3251 Dental Assistants and Therapists",
"3252 Medical Records and Health Information Technicians",
"3253 Community Health Workers", "3254 Dispensing Opticians",
"3255 Physiotherapy Technicians and Assistants",
"3256 Medical Assistants",
"3257 Environmental and Occupational Health Inspectors and Associates",
"3258 Ambulance Workers",
"3259 Health Associate Professionals Not Elsewhere Classified",
"3311 Securities and Finance Dealers and Brokers",
"3312 Credit and Loans Officers",
"3313 Accounting Associate Professionals",
"3314 Statistical, Mathematical and Related Associate Professionals",
"3315 Valuers and Loss Assessors",
"3321 Insurance Representatives",
"3322 Commercial Sales Representatives",
"3323 Buyers", "3324 Trade Brokers",
"3331 Clearing and Forwarding Agents",
"3332 Conference and Event Planners",
"3333 Employment Agents and Contractors",
"3334 Real Estate Agents and Property Managers",
"3339 Business Services Agents Not Elsewhere Classified",
"3341 Office Supervisors", "3342 Legal Secretaries",
"3343 Administrative and Executive Secretaries",
"3344 Medical Secretaries","3351 Customs and Border Inspectors",
"3352 Government Tax and Excise Officials",
"3353 Government Social Benefits Officials",
"3354 Government Licensing Officials",
"3355 Police Inspectors and Detectives",
"3359 Government Regulatory Associate Professionals Not Elsewhere Classified",
"3411 Legal and Related Associate Professionals",
"3412 Social Work Associate Professionals",
"3413 Religious Associate Professionals",
"3421 Athletes and Sports Players",
"3422 Sports Coaches, Instructors and Officials",
"3423 Fitness and Recreation Instructors and Programme Leaders",
"3431 Photographers", "3432 Interior Designers and Decorators",
"3433 Gallery, Museum and Library Technicians",
"3434 Chefs",
"3435 Other Artistic and Cultural Associate Professionals",
"3511 Information and Communications Technology Operations Technicians",
"3512 Information and Communications Technology User Support Technicians",
"3513 Computer Network and Systems Technicians",
"3514 Web Technicians",
"3521 Broadcasting and Audiovisual Technicians",
"3522 Telecommunications Engineering Technicians",
"4110 General Office Clerks", "4120 Secretaries (general)",
"4131 Typists and Word Processing Operators",
"4132 Data Entry Clerks", "4211 Bank Tellers and Related Clerks",
"4212 Bookmakers, Croupiers and Related Gaming Workers",
"4213 Pawnbrokers and Money-lenders",
"4214 Debt Collectors and Related Workers",
"4221 Travel Consultants and Clerks",
"4222 Contact Centre Information Clerks",
"4223 Telephone Switchboard Operators",
"4224 Hotel Receptionists","4225 Inquiry Clerks",
"4226 Receptionists (general)",
"4227 Survey and Market Research Interviewers",
"4229 Client Information Workers Not Elsewhere Classified",
"4311 Accounting and Bookkeeping Clerks",
"4312 Statistical, Finance and Insurance Clerks",
"4313 Payroll Clerks", "4321 Stock Clerks",
"4322 Production Clerks", "4323 Transport Clerks",
"4411 Library Clerks", "4412 Mail Carriers and Sorting Clerks",
"4413 Coding, Proofreading and Related Clerks",
"4414 Scribes and Related Workers", "4415 Filing and Copying Clerks",
"4416 Personnel Clerks",
"4419 Clerical Support Workers Not Elsewhere Classified",
"5111 Travel Attendants and Travel Stewards",
"5112 Transport Conductors", "5113 Travel Guides",
"5120 Cooks", "5131 Waiters", "5132 Bartenders",
"5141 Hairdressers", "5142 Beauticians and Related Workers",
"5151 Cleaning and Housekeeping Supervisors in Offices, Hotels and Other Establishments",
"5152 Domestic Housekeepers", "5153 Building Caretakers",
"5161 Astrologers, Fortune-tellers and Related Workers",
"5162 Companions and Valets", "5163 Undertakers and Embalmers",
"5164 Pet Groomers and Animal Care Workers",
"5165 Driving Instructors",
"5169 Personal Services Workers Not Elsewhere Classified",
"5211 Stall and Market Salespersons",
"5212 Street Food Salespersons", "5221 Shopkeepers",
"5222 Shop Supervisors", "5223 Shop Sales Assistants",
"5230 Cashiers and Ticket Clerks",
"5241 Fashion and Other Models", "5242 Sales Demonstrators",
"5243 Door-to-door Salespersons",
"5244 Contact Centre Salespersons",
"5245 Service Station Attendants",
"5246 Food Service Counter Attendants",
"5249 Sales Workers Not Elsewhere Classified",
"5311 Child Care Workers", "5312 Teachers' Aides",
"5321 Health Care Assistants",
"5322 Home-based Personal Care Workers",
"5329 Personal Care Workers in Health Services Not Elsewhere Classified",
"5411 Firefighters", "5412 Police Officers", "5413 Prison Guards",
"5414 Security Guards",
"5419 Protective Services Workers Not Elsewhere Classified",
"6111 Field Crop and Vegetable Growers",
"6112 Tree and Shrub Crop Growers",
"6113 Gardeners; Horticultural and Nursery Growers",
"6114 Mixed Crop Growers", "6121 Livestock and Dairy Producers",
"6122 Poultry Producers",
"6123 Apiarists and Sericulturists",
"6129 Animal Producers Not Elsewhere Classified",
"6130 Mixed Crop and Animal Producers",
"6210 Forestry and Related Workers",
"6221 Aquaculture Workers",
"6222 Inland and Coastal Waters Fishery Workers",
"6223 Deep-sea Fishery Workers", "6224 Hunters and Trappers",
"6310 Subsistence Crop Farmers",
"6320 Subsistence Livestock Farmers",
"6330 Subsistence Mixed Crop and Livestock Farmers",
"6340 Subsistence Fishers, Hunters, Trappers and Gatherers",
"7111 House Builders",
"7112 Bricklayers and Related Workers",
"7113 Stonemasons, Stone Cutters, Splitters and Carvers",
"7114 Concrete Placers, Concrete Finishers and Related Workers",
"7115 Carpenters and Joiners",
"7119 Building Frame and Related Trades Workers Not Elsewhere Classified",
"7121 Roofers", "7122 Floor Layers and Tile Setters",
"7123 Plasterers", "7124 Insulation Workers", "7125 Glaziers",
"7126 Plumbers and Pipe Fitters",
"7127 Air Conditioning and Refrigeration Mechanics",
"7131 Painters and Related Workers",
"7132 Spray Painters and Varnishers",
"7133 Building Structure Cleaners",
"7211 Metal Moulders and Coremakers",
"7212 Welders and Flame Cutters",
"7213 Sheet Metal Workers",
"7214 Structural Metal Preparers and Erectors",
"7215 Riggers and Cable Splicers",
"7221 Blacksmiths, Hammersmiths and Forging Press Workers",
"7222 Toolmakers and Related Workers",
"7223 Metal Working Machine Tool Setters and Operators",
"7224 Metal Polishers, Wheel Grinders and Tool Sharpeners",
"7231 Motor Vehicle Mechanics and Repairers",
"7232 Aircraft Engine Mechanics and Repairers",
"7233 Agricultural and Industrial Machinery Mechanics and Repairers",
"7234 Bicycle and Related Repairers",
"7311 Precision-instrument Makers and Repairers",
"7312 Musical Instrument Makers and Tuners",
"7313 Jewellery and Precious Metal Workers",
"7314 Potters and Related Workers",
"7315 Glass Makers, Cutters, Grinders and Finishers",
"7316 Signwriters, Decorative Painters, Engravers and Etchers",
"7317 Handicraft Workers in Wood, Basketry and Related Materials",
"7318 Handicraft Workers in Textile, Leather and Related Materials",
"7319 Handicraft Workers Not Elsewhere Classified",
"7321 Pre-press Technicians","7322 Printers",
"7323 Print Finishing and Binding Workers",
"7411 Building and Related Electricians",
"7412 Electrical Mechanics and Fitters",
"7413 Electrical Line Installers and Repairers",
"7421 Electronics Mechanics and Servicers",
"7422 Information and Communications Technology Installers and Servicers",
"7511 Butchers, Fishmongers and Related Food Preparers",
"7512 Bakers, Pastry-cooks and Confectionery Makers",
"7513 Dairy Products Makers",
"7514 Fruit, Vegetable and Related Preservers",
"7515 Food and Beverage Tasters and Graders",
"7516 Tobacco Preparers and Tobacco Products Makers",
"7521 Wood Treaters",
"7522 Cabinet-makers and Related Workers",
"7523 Woodworking Machine Tool Setters and Operators",
"7531 Tailors, Dressmakers, Furriers and Hatters",
"7532 Garment and Related Patternmakers and Cutters",
"7533 Sewing, Embroidery and Related Workers",
"7534 Upholsterers and Related Workers",
"7535 Pelt Dressers, Tanners and Fellmongers",
"7536 Shoemakers and Related Workers",
"7541 Underwater Divers",
"7542 Shotfirers and Blasters",
"7543 Product Graders and Testers (excluding Foods and Beverages)",
"7544 Fumigators and Other Pest and Weed Controllers",
"7549 Craft and Related Workers Not Elsewhere Classified",
"8111 Miners and Quarriers",
"8112 Mineral and Stone Processing Plant Operators",
"8113 Well Drillers and Borers and Related Workers",
"8114 Cement, Stone and Other Mineral Products Machine Operators",
"8121 Metal Processing Plant Operators",
"8122 Metal Finishing, Plating and Coating Machine Operators",
"8131 Chemical Products Plant and Machine Operators",
"8132 Photographic Products Machine Operators",
"8141 Rubber Products Machine Operators",
"8142 Plastic Products Machine Operators",
"8143 Paper Products Machine Operators",
"8151 Fibre Preparing, Spinning and Winding Machine Operators",
"8152 Weaving and Knitting Machine Operators",
"8153 Sewing Machine Operators",
"8154 Bleaching, Dyeing and Fabric Cleaning Machine Operators",
"8155 Fur and Leather Preparing Machine Operators",
"8156 Shoemaking and Related Machine Operators",
"8157 Laundry Machine Operators",
"8159 Textile, Fur and Leather Products Machine Operators Not Elsewhere Classified",
"8160 Food and Related Products Machine Operators",
"8171 Pulp and Papermaking Plant Operators",
"8172 Wood Processing Plant Operators",
"8181 Glass and Ceramics Plant Operators",
"8182 Steam Engine and Boiler Operators",
"8183 Packing, Bottling and Labelling Machine Operators",
"8189 Stationary Plant and Machine Operators Not Elsewhere Classified",
"8211 Mechanical Machinery Assemblers",
"8212 Electrical and Electronic Equipment Assemblers",
"8219 Assemblers Not Elsewhere Classified",
"8311 Locomotive Engine Drivers",
"8312 Railway Brake, Signal and Switch Operators",
"8321 Motorcycle Drivers",
"8322 Car, Taxi and Van Drivers",
"8331 Bus and Tram Drivers",
"8332 Heavy Truck and Lorry Drivers",
"8341 Mobile Farm and Forestry Plant Operators",
"8342 Earthmoving and Related Plant Operators",
"8343 Crane, Hoist and Related Plant Operators",
"8344 Lifting Truck Operators",
"8350 Ships' Deck Crews and Related Workers",
"9111 Domestic Cleaners and Helpers",
"9112 Cleaners and Helpers in Offices, Hotels and Other Establishments",
"9121 Hand Launderers and Pressers",
"9122 Vehicle Cleaners", "9123 Window Cleaners",
"9129 Other Cleaning Workers","9211 Crop Farm Labourers",
"9212 Livestock Farm Labourers",
"9213 Mixed Crop and Livestock Farm Labourers",
"9214 Garden and Horticultural Labourers",
"9215 Forestry Labourers", "9216 Fishery and Aquaculture Labourers",
"9311 Mining and Quarrying Labourers",
"9312 Civil Engineering Labourers",
"9313 Building Construction Labourers", "9321 Hand Packers",
"9329 Manufacturing Labourers Not Elsewhere Classified",
"9331 Hand and Pedal Vehicle Drivers",
"9332 Drivers of Animal-drawn Vehicles and Machinery",
"9333 Freight Handlers", "9334 Shelf Fillers",
"9411 Fast Food Preparers", "9412 Kitchen Helpers",
"9510 Street and Related Services Workers",
"9520 Street Vendors (excluding Food)",
"9611 Garbage and Recycling Collectors",
"9612 Refuse Sorters",
"9613 Sweepers and Related Labourers",
"9621 Messengers, Package Deliverers and Luggage Porters",
"9622 Odd-job Persons",
"9623 Meter Readers and Vending-machine Collectors",
"9624 Water and Firewood Collectors",
"9629 Elementary Workers Not Elsewhere Classified",
"0110 Commissioned Armed Forces Officers",
"0210 Non-commissioned Armed Forces Officers",
"0310 Armed Forces Occupations, Other Ranks"
);
}

50
health_data_export/src/application/res/PrivacyStatementText.java

@ -0,0 +1,50 @@
package application.res;
public class PrivacyStatementText
{
public static final String heading="Privacy Statement";
public static final String responsibleParty="Responsible party:\n"
+ "Chair of legal informatics\n"
+ "Saarland University\n"
+ "Campus C3.1\n"
+ "66123 Saarbrücken\n"
+ "Contact person: Prof. Dr.-Ing. Christoph Sorge (lehrstuhl.sorge@uni-saarland.de)";
public static final String introText="The protection of your privacy is of great importance to us. Therefore, we would like to inform you about the specifics regarding the processing of your data.";
public static final String headingSec1="1. Data collection, processing, and storage";
public static final String contentSec1="You can use our tool without having to disclose any information regarding your person. But the tool enables you to disclose personal data which may in particular include biometric data and health data. Among others, it is possible to give information about your heart rate, weight, height, type and amount of walking activities (flights climbing, walking, running), or menstrual cycle. Additionally, you may inform us about your occupation or the given choice closest to your actual occupation. The decision on which data you wish to share with us is completely up to you. We will only collect the data you have actively selected. Because of the design of the tool, it will be impossible for us to identify you from the collected data. The only possible way for us to infer your identity will be by using the IP address assigned to you at the moment you upload the data. This IP address will only be stored for a limited amount of time. After that period has passed it will not be possible to identify your data anymore. Furthermore, organizational measures prevent processors of your data from accessing the documented IP addresses.\n\n"
+ "The data will be used for the purpose of testing the efficiency and robustness of technical methods for anonymization and pseudonymization. Please be aware that during this research it might be possible to identify your data set, but it still will not be possible to identify you as a person. That means that after the initial anonymization process it will not be possible to identify you with the collected data. After the anonymization process, your data will not be personal data according to Art. 4 Nr. 1 GDPR anymore.\n\n"
+ "We are aware that the collected data may contain biometric data and health data in line of Art. 4 Nr. 14 and 15 GDPR and thus may contain especially sensitive data. This sensitive data will be addressed with the appropriate care and diligence.";
public static final String headingSec2="2. Data transfers";
public static final String contentSec2="The data will only be used for the purpose of scientific research. Regarding the transfer it is e.g., planned to share some of the anonymized data with an Indian university in the course of a research cooperation. Additionally, the data will be stored for future research projects (e.g., bachelor or master thesis).\n\n"
+ "Non anonymized data will never be transferred or disclosed.";
public static final String headingSec3="3. Lawfulness of processing";
public static final String contentSec3="The processing is based on Art. 9 (2) j) GDPR in combination with § 23 (1) SaarlDSG. The data will be anonymized in line of § 23 (1) SaarlDSG.";
public static final String headingSec4="4. Your rights";
public static final String contentSec4="As a data subject you have the following rights regarding your personal data:\n\n"
+ "You have the right of access on whether or even which of your data is processed (Art. 15 GDPR). You have the right of rectification and / or completion of your data in case it is incorrect or incomplete (Art. 16 GDPR). According to Art. 17 GDPR you have the right to erasure of your data as well as according to Art. 18 GDPR the right to restriction of processing. In line of Art. 20 GDPR you have the right to data portability. You also have the right to object the processing (Art. 21 GDPR).\n\n"
+ "Additionally, you have the right to submit a complaint to a supervisory authority in case that you believe the processing of your data to violate the regulations of the GDPR.\n\n"
+ "Please be aware that your data will be anonymized a short amount of time after the collection and thus will not be personal data any longer. It will only be possible to exercise your rights as a data subject in the time your data is not yet completely anonymized.";
public static final String headingSec5="5. Contacts";
public static final String contentSec5="You may file complaints at the data protection officer of Saarland University:\n"
+ "Barbara Partzsch\n"
+ "datenschutz@uni-saarland.de\n"
+ "Postal address:\n"
+ "Facility Meerwiesertalweg\n"
+ "Post-office box 15 11 50\n"
+ "66041 Saarbrücken\n"
+ "\n"
+ "Alternatively, you may directly contact Prof. Dr.-Ing. Christoph Sorge (lehrstuhl.sorge@uni-saarland.de).";
}

155
health_data_export/src/application/res/Text.java

@ -0,0 +1,155 @@
package application.res;
public class Text
{
public static final String TITLE = "Contribute Health Data";
public static final String FOOTER_TEXT = "This tool was created in cooperation with "
+ "the Chair of Legal Informatics of the Saarland University\n" + "Source of icons: Icons8.de";
public static final String NOT_SET = "not set";
public static final String F_ME_SAVE = "%s : %s";
public static final String ME_FILE_DESC = "MainInfo";
public static final String TAG_NAME_META_DATA_ENTRY = "MetadataEntry";
public static final String TAG_NAME_WORKOUT_EVENT = "WorkoutEvent";
public static final String TAG_NAME_WORKOUT_ROUTE = "WorkoutRoute";
public static final String TAG_NAME_HR_LIST = "HeartRateVariabilityMetadataList";
public static final String TAG_NAME_IB_PER_MINUTES = "InstantaneousBeatsPerMinute";
public static final String YEAR = "Year";
public static final String MONTH = "Month";
public static final String DAY = "Day";
public static final String F_FILE_DESC = "%s\n%s";
public static final String TAG_NAME_HEALTH_DATA = "HealthData";
public static final String TAG_ATTR_REGION_CODE = "locale";
public static final String TAG_ATTR_EXPORT_DATE = "value";
public static final String TAG_NAME_ME_INFO = "Me";
public static final String TAG_ATTR_BIOLOGICAL_SEX = "HKCharacteristicTypeIdentifierBiologicalSex";
public static final String TAG_ATTR_BLOOD_TYPE = "HKCharacteristicTypeIdentifierBloodType";
public static final String TAG_ATTR_SKIN_TYPE = "HKCharacteristicTypeIdentifierFitzpatrickSkinType";
public static final String TAG_ATTR_DATE_OF_BIRTH = "HKCharacteristicTypeIdentifierDateOfBirth";
public static final String TAG_ATTR_WORKOUT_TYPE = "workoutActivityType";
public static final String TAG_ATTR_RECORD_TYPE = "type";
public static final String MENU_PRIVACY_POLICY ="Privacy policy";
public static final String MENU_DATA_IMPORT ="Data import";
public static final String MENU_DATA_SELECTION ="Data selection";
public static final String MENU_REVIEW ="Review selection";
public static final String MENU_DATA_UPLOAD ="Data upload";
// error Messages
public static final String E_CREATE_TEMP_FILES = "The files for your selected data couldn't be created.";
public static final String E_READ_ZIP_FILE = "Your zip file couldn't be read. You might want to try "
+ "and insert the unzipped file.";
public static final String E_READ_XML_FILE = "Your xml file couldn't be read.";
public static final String E_WRONG_FILE = "There wasn't any health data found in your file. Please check if you inserted the right file.";
public static final String E_WRITE_TO_TEMP_FILES = "The data couldn't be written to "
+ "the files. Maybe there is a problem with the permissions of your system " + "for this application.";
// Intro page
public static final String MAIN_INFO = "This tool allows iPhone users to analyse and export selected parts of their phones health data export. "
+ "It's intended to be used for gathering health data for scientific research in cooperation with the Chair of Legal Informatics from Saarland University.\n\n"
+ "By merely using this tool, no data will be disclosed to us. "
+ "To contribute your data, you will have to uploaded the file created by using this tool to our nextcloud server. "
+ "Your data will be anonymized and only containing the information you selected. You can check that by inspecting the created zip file or the source code.\n\n"
+ "To get started, you have to insert your health file in the field below "
+ "and agree to our privacy policy. Then use the navigation on the left"
+ " to continue. If you need help exporting your health data you may look at the information from apple support linked below.";
public static final String SELECT_FILE = "Drag health file to me!";
public static final String FOUND_FILE = "The following file was found: \n%s";
public static final String NO_VALID_FILE = "You might have selected the wrong file: You can either insert the ziped export or the xml file!";
public static final String WIP_READING_FILE = "Reading file...";
public static final String PRIVACY_STATEMENT_ACCEPTED = "I read the privacy statement and "
+ "agree to the processing my data under these terms.";
public static final String GO_TO_PRIVACY_STATEMENT = "(Privacy policy? I didn't see a thing... "
+ "Please take me there!)";
public static final String APPLE_LINK_EXPORT = "https://support.apple.com/guide/iphone/share-health-and-fitness-data-iph27f6325b2/ios";
// Overview page
public static final String DESC_WORKOUT = "Workouts contain information about "
+ "physical exercises and activities. All entries belong to a specific "
+ "activity like running, swimming, hunting or even basketball.\n"
+ "Typical information stored here are the duration of the activity and " + "how much energy was burned.";
public static final String DESC_ACTIVITY_SUM = "Activity Summaries summarise all your "
+ "activites of a certain day.\n" + "Typical information stored here contains the duration of exercises, "
+ "the burned energy and durations of you moving or standing.";
public static final String DESC_CLIN_RECORD = "Clinical Records can contain "
+ "information about allergic reaction, a lab result, or a medical procedure.\n"
+ "Typical information stored here is an idententifier for the record. "
+ "All further information about the content of the record is stored in a "
+ "separate file which will not be accessed by this tool.";
public static final String DESC_RECORDS = "Records are the most common entries "
+ "in health files. They don't just contain information about physical "
+ "activities but also other health related data like height, heart rate or your step count.\n"
+ "This data is typically stored with the measured values und matching units.";
public static final String DESC_REGION_CODE = "The region code shows information about the "
+ "region as well as the language you set in your phone.";
public static final String SPECIFIED_IN_PHONE = "as specified in your phone.";
public static final String DESC_EXPORT_DATE = "This is the date you exported your data from your phone.";
public static final String DESC_DATE_OF_BIRTH = "This shows your date of birth " + SPECIFIED_IN_PHONE;
public static final String DESC_BIOLOGICAL_SEX = "This shows your gender " + SPECIFIED_IN_PHONE;
public static final String DESC_BLOOD_TYPE = "This shows your blood type " + SPECIFIED_IN_PHONE;
public static final String DESC_SKIN_TYPE = "This shows your skin type " + SPECIFIED_IN_PHONE;
public static final String SELECT_INTRO= "Now it's time for you to select the data "
+ "you wish to contribute. You may contribute the data from your file and additonally your occupation. Let's start with that one.";
public static final String SELECT_HOW_TO="Now let's continue with the data from your health file. "
+ "To select the categories you from your file, "
+ "tap on the corresponding items in the list at the bottom of this page. You can also choose to ony select a "
+ "subset of these categories in case of your date of birth, workouts and records. "
+ "You can see the existing categories by clicking on the arrows on the left. "
+ "Information about the categories can be found on the right. "
+ "\n\nYou may also use the buttons below for specific selections. We provided you with two preselections you may choose which are according to our research interest."
+ "To continue to the next step you need to select at least one item from the list at the bottom to contribute.";
public static final String SELECT_CATEGORIES = "Now you may choose the data to contribute. Additionally to the data in your file, you may also contribute your occupation. "
+ "To select the categories you from your health file, tap on the corresponding items in the list at the bottom of this page. "
+ "You can also choose to ony select a subset of these categories in case of your date of birth, workouts and records. You can see the existing categories by clicking on the arrows on the left. "
+ "Information about the categories can be found on the right. \n\nYou may also use the buttons below for specific selections. We provided you with two preselections you may choose which are according to our research interest. "
+ "To continue to the next step you need to select at least one item from the list at the bottom to contribute. ";
public static final String ADD_JOB = "In the box on the left you may select your occupation (or the most similar "
+ "one to your occupation). From top to bottom you will get more specific selections. This is completely optional but of great interest for our research.";
public static final String SELECT_ALL = "Select all";
public static final String SELECT_BASIC_FIELD_OF_INTEREST = "Select basic research interests";
public static final String SELECT_ADVANCED_FIELD_OF_INTEREST = "Select advanced research interests";
public static final String DESELECT_ALL = "Deselect all";
public static final String F_LABEL_SAVE_PATH = "We will save your data at your home directory, which is %s. If you wish to choose another directory, just click here.";
public static final String F_SAVE_AT = "You chose to save your data at %s. Click again if you still wish to change it.";
public static final String WIP_CREATE_FILE = "Creating files...";
public static final String ERROR_FILE_NO_DATA = "Sorry, we couldn't find your health "
+ "data in the file. Maybe you inserted the wrong file or there was an error " + "reading the file";
// Inspect page
public static final String PLEASE_INSPECT = "Now you can inspect the files which will "
+ "be created by your selection. Just click on the files and read "
+ "to your hearts content. \n\nIf you want to change something, just return "
+ "to the previous step and adjust your selection. If everything is fine then "
+ "just continue to the next step.";
public static final String NO_OPENING_FILES = "Sorry, somehow the file couldn't be "
+ "opened.\nIf you would still like to inspect this file, you may inspect "
+ "it in the resulting zip file.";
// Result page
public static final String SUCCESS = "You have succesfully created the data. "
+ "To contribute to our research database you need to upload it to our nextcloud server. For easy access you"
+ " can use the buttons below.\n"
+ "If you have any more questions concerning our research or how your data "
+ "will be stored, feel free to write us at lehrstuhl.sorge@uni-saarland.de."
;
public static final String NEXTCLOUD_ADDRESS = "https://kingsx.cs.uni-saarland.de/owncloud/index.php/s/bF4Y4inWoDGXTBS";
public static final String NEXTCLOUD = "Open nextcloud web page!";
public static final String F_NEXTCLOUD_BROWSER_WONT_OPEN = "Opening your browser didn't work.\n"
+ "\nClick here to copy server adress to clipboard!)";
public static final String SERVER_ADDRESS_IN_CLIPBOARD = "Server address was saved in clipboard";
public static final String FILE_PATH_IN_CLIPBOARD = "Filepath was saved in clipboard";
public static final String OPEN_FILE_IN_BROWSER = "Open file in filebrowser!";
public static final String F_RAW_UPLOAD_DATA = "The buttons aren't working for you? Then you might need the following information:\nSaved Data: %s\nNextcloud server: %s";
public static final String DRAG_FILE = "Drag me to nextcloud server!";
public static final String DRAG_IN_ACTION = "Started dragging the file";
public static final String F_FILE_IS_SAVED_AT = "Just a small reminder: Your file was saved as %s";
public static final String DATA_OVERVIEW = "You included the following data in your zip file";
public static final String F_SAVED_DATA_VALUE = "%s : %s";
public static final String F_SAVED_DATA_VALUE_SUBCATEGORY = "%s - %s : %s";
public static final String F_SAVED_DATA_ENTRIES = "%s, Number of entries: %s";
public static final String F_SAVED_DATA_VALUE_SUBCATEGORY_ENTRIES = "%s - %s, Number of entries : %s";
public static final String OCCUPATION_LEVEL_ONE = "Major group";
public static final String OCCUPATION_LEVEL_TWO = "Sub-major group";
public static final String OCCUPATION_LEVEL_THREE = "Minor group";
public static final String OCCUPATION_LEVEL_FOUR = "Unit group";
}

5
health_data_export/target/.gitignore

@ -0,0 +1,5 @@
/java-runtime/
*.0.dmg
/HealthTool-1.0.pkg
/healthtool_1.0-1_amd64.deb
/HealthTool-1.0.msi

BIN
health_data_export/target/health_data_export-7.jar

5
health_data_export/target/maven-archiver/pom.properties

@ -0,0 +1,5 @@
#Generated by Maven
#Fri Apr 30 10:46:45 CEST 2021
groupId=health_data_export
artifactId=health_data_export
version=7

BIN
health_data_export/target/original-health_data_export-7.jar

Loading…
Cancel
Save