diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9356e88
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,113 @@
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Maven template
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+.idea/
+clusters/
diff --git a/designs/cluster_node_icon.svg b/designs/cluster_node_icon.svg
new file mode 100644
index 0000000..6dbe761
--- /dev/null
+++ b/designs/cluster_node_icon.svg
@@ -0,0 +1,78 @@
+
+
+
+
diff --git a/designs/shard_node_icon.svg b/designs/shard_node_icon.svg
new file mode 100644
index 0000000..24fc994
--- /dev/null
+++ b/designs/shard_node_icon.svg
@@ -0,0 +1,78 @@
+
+
+
+
diff --git a/pom.xml b/pom.xml
index f5ff024..3926ebb 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,5 +48,18 @@
provided
true
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.7.2
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.7.0
+ test
+
\ No newline at end of file
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java b/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java
index 045a046..7d06dba 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/CrystalKeep.java
@@ -7,17 +7,13 @@ import javafx.stage.Stage;
import nl.andrewlalis.crystalkeep.control.MainViewController;
import nl.andrewlalis.crystalkeep.model.Cluster;
import nl.andrewlalis.crystalkeep.model.Model;
-import nl.andrewlalis.crystalkeep.model.Shard;
import nl.andrewlalis.crystalkeep.model.serialization.ClusterLoader;
-import nl.andrewlalis.crystalkeep.model.serialization.ClusterSerializer;
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URL;
import java.time.LocalDateTime;
-import java.util.Arrays;
public class CrystalKeep extends Application {
public static void main(String[] args) {
@@ -41,10 +37,10 @@ public class CrystalKeep extends Application {
System.out.println("Loaded existing root cluster.");
} catch (IOException e) {
rootCluster = new Cluster("Root");
- rootCluster.addShard(new TextShard(rootCluster, "Example Shard", LocalDateTime.now(), "Hello world!"));
- rootCluster.addShard(new LoginCredentialsShard(rootCluster, "Netflix", LocalDateTime.now(), "user", "secret password"));
+ rootCluster.addShard(new TextShard("Example Shard", LocalDateTime.now(), "Hello world!"));
+ rootCluster.addShard(new LoginCredentialsShard("Netflix", LocalDateTime.now(), "user", "secret password"));
for (int i = 0; i < 100; i++) {
- rootCluster.addShard(new TextShard(rootCluster, "test " + i, LocalDateTime.now(), "value: " + i));
+ rootCluster.addShard(new TextShard("test " + i, LocalDateTime.now(), "value: " + i));
}
clusterLoader.saveDefault(rootCluster);
System.out.println("Saved root cluster on first load.");
@@ -57,30 +53,4 @@ public class CrystalKeep extends Application {
controller.init(model);
System.out.println(rootCluster);
}
-
- public static void test() throws IOException {
- Cluster c = new Cluster("Test");
- Shard s = new TextShard(c, "sample", LocalDateTime.now(), "Hello world!");
- Shard s2 = new LoginCredentialsShard(c, "logs", LocalDateTime.now().plusHours(3), "andrew", "testing");
- c.addShard(s);
- c.addShard(s2);
- Cluster c2 = new Cluster("Test2");
- Shard s3 = new TextShard(c2, "another sample", LocalDateTime.now().plusMinutes(3), "Testing this stuff....");
- c2.addShard(s3);
- Cluster parent = new Cluster("Parent");
- parent.addCluster(c);
- parent.addCluster(c2);
-
- System.out.println(parent);
- long start = System.currentTimeMillis();
- byte[] cBytes = ClusterSerializer.toBytes(parent);
- long dur = System.currentTimeMillis() - start;
- System.out.println("Duration: " + dur + " milliseconds");
- System.out.println(Arrays.toString(cBytes));
-
- Cluster cLoaded = ClusterSerializer.clusterFromBytes(new ByteArrayInputStream(cBytes), null);
- System.out.println(cLoaded);
- System.out.println(Arrays.toString(ClusterSerializer.toBytes(cLoaded)));
- System.out.println(Arrays.equals(cBytes, ClusterSerializer.toBytes(cLoaded)));
- }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
index cd9ed4e..72fa186 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
@@ -27,7 +27,6 @@ public class Cluster implements Comparable, CrystalItem {
public void addShard(Shard shard) {
this.shards.add(shard);
- shard.setCluster(this);
}
public void addCluster(Cluster cluster) {
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
index c5de7de..a73f159 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
@@ -19,15 +19,12 @@ import java.util.Objects;
*/
@Getter
public abstract class Shard implements Comparable, CrystalItem {
- @Setter
- private Cluster cluster;
@Setter
private String name;
private final LocalDateTime createdAt;
private final ShardType type;
- public Shard(Cluster cluster, String name, LocalDateTime createdAt, ShardType type) {
- this.cluster = cluster;
+ public Shard(String name, LocalDateTime createdAt, ShardType type) {
this.name = name;
this.createdAt = createdAt;
this.type = type;
@@ -45,12 +42,12 @@ public abstract class Shard implements Comparable, CrystalItem {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Shard shard = (Shard) o;
- return getCluster().equals(shard.getCluster()) && getName().equals(shard.getName()) && getType() == shard.getType();
+ return getName().equals(shard.getName()) && getType() == shard.getType();
}
@Override
public int hashCode() {
- return Objects.hash(getCluster(), getName(), getType());
+ return Objects.hash(getName(), getType());
}
@Override
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterLoader.java b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterLoader.java
index 4f5e55f..d38d5e0 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterLoader.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterLoader.java
@@ -5,6 +5,7 @@ import nl.andrewlalis.crystalkeep.model.Cluster;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -14,12 +15,13 @@ public class ClusterLoader {
public Cluster loadDefault() throws IOException {
InputStream is = new FileInputStream(DEFAULT_CLUSTER.toFile());
- return ClusterSerializer.clusterFromBytes(is, null);
+ return ClusterSerializer.readCluster(is, null);
}
public void saveDefault(Cluster cluster) throws IOException {
Files.createDirectories(CLUSTER_PATH);
- byte[] bytes = ClusterSerializer.toBytes(cluster);
- Files.write(DEFAULT_CLUSTER, bytes);
+ OutputStream os = Files.newOutputStream(DEFAULT_CLUSTER);
+ ClusterSerializer.writeCluster(cluster, os);
+ os.close();
}
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java
index fc89090..de9e58d 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializer.java
@@ -6,9 +6,9 @@ import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
import nl.andrewlalis.crystalkeep.model.shards.ShardType;
import nl.andrewlalis.crystalkeep.model.shards.TextShard;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@@ -33,31 +33,28 @@ public class ClusterSerializer {
/**
* Serializes a cluster to a byte array, including all shards and nested
* clusters.
- * TODO: Use output stream instead of byte array.
* @param cluster The cluster to serialize.
- * @return The byte array representing the cluster.
+ * @param os The output stream to write to.
* @throws IOException If an error occurs while writing the cluster.
*/
- public static byte[] toBytes(Cluster cluster) throws IOException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ByteUtils.writeLengthPrefixed(cluster.getName(), bos);
+ public static void writeCluster(Cluster cluster, OutputStream os) throws IOException {
+ ByteUtils.writeLengthPrefixed(cluster.getName(), os);
- bos.write(ByteUtils.toBytes(cluster.getClusters().size()));
+ os.write(ByteUtils.toBytes(cluster.getClusters().size()));
Cluster[] children = new Cluster[cluster.getClusters().size()];
cluster.getClusters().toArray(children);
Arrays.sort(children);
for (Cluster child : children) {
- bos.write(toBytes(child));
+ writeCluster(child, os);
}
- bos.write(ByteUtils.toBytes(cluster.getShards().size()));
+ os.write(ByteUtils.toBytes(cluster.getShards().size()));
Shard[] shards = new Shard[cluster.getShards().size()];
cluster.getShards().toArray(shards);
Arrays.sort(shards);
for (Shard shard : shards) {
- bos.write(toBytes(shard));
+ writeShard(shard, os);
}
- return bos.toByteArray();
}
/**
@@ -67,16 +64,16 @@ public class ClusterSerializer {
* @return The cluster that was read.
* @throws IOException If data could not be read from the stream.
*/
- public static Cluster clusterFromBytes(InputStream is, Cluster parent) throws IOException {
+ public static Cluster readCluster(InputStream is, Cluster parent) throws IOException {
String name = ByteUtils.readLengthPrefixedString(is);
Cluster cluster = new Cluster(name, new HashSet<>(), new HashSet<>(), parent);
int childCount = toInt(is.readNBytes(4));
for (int i = 0; i < childCount; i++) {
- cluster.addCluster(clusterFromBytes(is, cluster));
+ cluster.addCluster(readCluster(is, cluster));
}
int shardCount = toInt(is.readNBytes(4));
for (int i = 0; i < shardCount; i++) {
- cluster.addShard(shardFromBytes(is, cluster));
+ cluster.addShard(readShard(is));
}
return cluster;
}
@@ -86,29 +83,26 @@ public class ClusterSerializer {
* standard shard information, then using a specific {@link ShardSerializer}
* to get the bytes that represent the body of the shard.
* @param shard The shard to serialize.
- * @return A byte array representing the shard.
+ * @param os The output stream to write to.
* @throws IOException If byte array stream could not be written to.
*/
@SuppressWarnings({"unchecked", "rawtypes"})
- public static byte[] toBytes(Shard shard) throws IOException {
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- ByteUtils.writeLengthPrefixed(shard.getName().getBytes(StandardCharsets.UTF_8), bos);
- ByteUtils.writeLengthPrefixed(shard.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).getBytes(StandardCharsets.UTF_8), bos);
- bos.write(ByteUtils.toBytes(shard.getType().getValue()));
+ public static void writeShard(Shard shard, OutputStream os) throws IOException {
+ ByteUtils.writeLengthPrefixed(shard.getName().getBytes(StandardCharsets.UTF_8), os);
+ ByteUtils.writeLengthPrefixed(shard.getCreatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME).getBytes(StandardCharsets.UTF_8), os);
+ os.write(ByteUtils.toBytes(shard.getType().getValue()));
ShardSerializer serializer = serializers.get(shard.getType());
- bos.write(serializer.serialize(shard));
- return bos.toByteArray();
+ os.write(serializer.serialize(shard));
}
/**
* Deserializes a shard from a byte array that's being read by the given
* input stream.
* @param is The input stream to read from.
- * @param cluster The cluster that the shard should belong to.
* @return The shard that was deserialized.
* @throws IOException If an error occurs while reading bytes.
*/
- public static Shard shardFromBytes(InputStream is, Cluster cluster) throws IOException {
+ public static Shard readShard(InputStream is) throws IOException {
String name = ByteUtils.readLengthPrefixedString(is);
LocalDateTime createdAt = LocalDateTime.parse(ByteUtils.readLengthPrefixedString(is), DateTimeFormatter.ISO_LOCAL_DATE_TIME);
ShardType type = ShardType.valueOf(ByteUtils.toInt(is.readNBytes(4)));
@@ -116,6 +110,6 @@ public class ClusterSerializer {
if (serializer == null) {
throw new IOException("Unsupported shard type.");
}
- return serializer.deserialize(is, cluster, name, createdAt);
+ return serializer.deserialize(is, name, createdAt);
}
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ShardSerializer.java b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ShardSerializer.java
index 3ce2b85..b789351 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ShardSerializer.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/serialization/ShardSerializer.java
@@ -1,9 +1,7 @@
package nl.andrewlalis.crystalkeep.model.serialization;
-import nl.andrewlalis.crystalkeep.model.Cluster;
import nl.andrewlalis.crystalkeep.model.Shard;
-import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.time.LocalDateTime;
@@ -11,5 +9,5 @@ import java.time.LocalDateTime;
public interface ShardSerializer {
byte[] serialize(T shard) throws IOException;
- T deserialize(InputStream bis, Cluster cluster, String name, LocalDateTime createdAt) throws IOException;
+ T deserialize(InputStream is, String name, LocalDateTime createdAt) throws IOException;
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
index e433948..baa3720 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
@@ -2,7 +2,6 @@ package nl.andrewlalis.crystalkeep.model.shards;
import lombok.Getter;
import lombok.Setter;
-import nl.andrewlalis.crystalkeep.model.Cluster;
import nl.andrewlalis.crystalkeep.model.Shard;
import nl.andrewlalis.crystalkeep.model.serialization.ByteUtils;
import nl.andrewlalis.crystalkeep.model.serialization.ShardSerializer;
@@ -17,8 +16,8 @@ public class LoginCredentialsShard extends Shard {
private String username;
private String password;
- public LoginCredentialsShard(Cluster cluster, String name, LocalDateTime createdAt, String username, String password) {
- super(cluster, name, createdAt, ShardType.LOGIN_CREDENTIALS);
+ public LoginCredentialsShard(String name, LocalDateTime createdAt, String username, String password) {
+ super(name, createdAt, ShardType.LOGIN_CREDENTIALS);
this.username = username;
this.password = password;
}
@@ -36,10 +35,10 @@ public class LoginCredentialsShard extends Shard {
}
@Override
- public LoginCredentialsShard deserialize(InputStream is, Cluster cluster, String name, LocalDateTime createdAt) throws IOException {
+ public LoginCredentialsShard deserialize(InputStream is, String name, LocalDateTime createdAt) throws IOException {
String username = ByteUtils.readLengthPrefixedString(is);
String password = ByteUtils.readLengthPrefixedString(is);
- return new LoginCredentialsShard(cluster, name, createdAt, username, password);
+ return new LoginCredentialsShard(name, createdAt, username, password);
}
}
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
index 6fade21..7ab6c08 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
@@ -14,8 +14,8 @@ import java.time.LocalDateTime;
public class TextShard extends Shard {
private String text;
- public TextShard(Cluster cluster, String name, LocalDateTime createdAt, String text) {
- super(cluster, name, createdAt, ShardType.TEXT);
+ public TextShard(String name, LocalDateTime createdAt, String text) {
+ super(name, createdAt, ShardType.TEXT);
this.text = text;
}
@@ -31,9 +31,9 @@ public class TextShard extends Shard {
}
@Override
- public TextShard deserialize(InputStream is, Cluster cluster, String name, LocalDateTime createdAt) throws IOException {
+ public TextShard deserialize(InputStream is, String name, LocalDateTime createdAt) throws IOException {
String text = ByteUtils.readLengthPrefixedString(is);
- return new TextShard(cluster, name, createdAt, text);
+ return new TextShard(name, createdAt, text);
}
}
}
diff --git a/src/test/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializerTest.java b/src/test/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializerTest.java
new file mode 100644
index 0000000..67fcb49
--- /dev/null
+++ b/src/test/java/nl/andrewlalis/crystalkeep/model/serialization/ClusterSerializerTest.java
@@ -0,0 +1,54 @@
+package nl.andrewlalis.crystalkeep.model.serialization;
+
+import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
+import nl.andrewlalis.crystalkeep.model.shards.TextShard;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class ClusterSerializerTest {
+ private static List testShardIOData() {
+ return List.of(
+ new TextShard("a", LocalDateTime.now(), "Hello world!"),
+ new TextShard("Another", LocalDateTime.now(), "Testing"),
+ new TextShard("", LocalDateTime.now(), ""),
+ new LoginCredentialsShard("login", LocalDateTime.now(), "andrew", "password"),
+ new LoginCredentialsShard("test", LocalDateTime.now(), "", "")
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("testShardIOData")
+ public void testShardIO(Shard s1) throws IOException {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ ClusterSerializer.writeShard(s1, bos);
+ byte[] data = bos.toByteArray();
+ assertNotEquals(0, data.length, "Serialized shard should never result in empty bytes.");
+ assertEquals(
+ s1.getName().length(),
+ ByteUtils.toInt(Arrays.copyOfRange(data, 0, 4)),
+ "Serialized shard name length does not match expected."
+ );
+ assertEquals(
+ s1.getName(),
+ new String(Arrays.copyOfRange(data, 4, 4 + s1.getName().length())),
+ "First letter of shard name does not match expected."
+ );
+
+ Shard loadedS1 = ClusterSerializer.readShard(new ByteArrayInputStream(data));
+ assertEquals(s1, loadedS1, "Loaded shard should equal original shard.");
+ bos.reset();
+ ClusterSerializer.writeShard(loadedS1, bos);
+ byte[] data2 = bos.toByteArray();
+ assertArrayEquals(data, data2, "Serialized data from a shard should not change.");
+ }
+}
diff --git a/src/test/java/nl/andrewlalis/crystalkeep/package-info.java b/src/test/java/nl/andrewlalis/crystalkeep/package-info.java
new file mode 100644
index 0000000..da5f043
--- /dev/null
+++ b/src/test/java/nl/andrewlalis/crystalkeep/package-info.java
@@ -0,0 +1,4 @@
+/**
+ * This package contains all tests for CrystalKeep.
+ */
+package nl.andrewlalis.crystalkeep;
\ No newline at end of file