Added project files

This commit is contained in:
Zak Yani Star Fenton 2025-06-11 01:08:13 +10:00
commit 556474db93
63 changed files with 7638 additions and 0 deletions

73
build.xml Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="slswap" default="default" basedir=".">
<description>Builds, tests, and runs the project slswap.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar: JAR building
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="slswap-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>

3
manifest.mf Normal file
View File

@ -0,0 +1,3 @@
Manifest-Version: 1.0
X-COMMENT: Main-Class will be added automatically by build

1771
nbproject/build-impl.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
build.xml.data.CRC32=0217b7f7
build.xml.script.CRC32=5cfed110
build.xml.stylesheet.CRC32=f85dc8f2@1.112.0.48
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=0217b7f7
nbproject/build-impl.xml.script.CRC32=cb2275c5
nbproject/build-impl.xml.stylesheet.CRC32=12e0a6c2@1.112.0.48

View File

@ -0,0 +1,95 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=false
annotation.processing.processor.options=
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
# Uncomment to specify the preferred debugger connection transport:
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.modulepath=\
${run.modulepath}
debug.test.classpath=\
${run.test.classpath}
debug.test.modulepath=\
${run.test.modulepath}
# Files in build.classes.dir which should be excluded from distribution jar
dist.archive.excludes=
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=${dist.dir}/slswap.jar
dist.javadoc.dir=${dist.dir}/javadoc
dist.jlink.dir=${dist.dir}/jlink
dist.jlink.output=${dist.jlink.dir}/slswap
excludes=
includes=**
jar.compress=false
javac.classpath=
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.external.vm=true
javac.modulepath=
javac.processormodulepath=
javac.processorpath=\
${javac.classpath}
javac.source=21
javac.target=21
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.modulepath=\
${javac.modulepath}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.html5=false
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
# The jlink additional root modules to resolve
jlink.additionalmodules=
# The jlink additional command line parameters
jlink.additionalparam=
jlink.launcher=true
jlink.launcher.name=slswap
main.class=
manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=false
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
${build.classes.dir}
# Space-separated list of JVM arguments used when running the project.
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
run.jvmargs=
run.modulepath=\
${javac.modulepath}
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
run.test.modulepath=\
${javac.test.modulepath}
source.encoding=UTF-8
src.dir=src
test.src.dir=test

15
nbproject/project.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>slswap</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

View File

@ -0,0 +1,25 @@
package swap.auth;
import java.time.Instant;
public abstract class AuthService {
public abstract Session login(String username, String password);
public abstract Session validate(String sessionKey);
protected Instant getInstant() {
return Instant.now();
}
/*
protected boolean hasSessionId(String suggestedId) {
return validate(suggest)
}
protected String newSessionId() {
}
protected Session newSession(String username, int seconds) {
Instant tim = getInstant();
Instant exp = tim.plusSeconds(seconds);
}*/
}

View File

@ -0,0 +1,67 @@
package swap.auth;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public class BasicCrypto {
public static int simpleRandom() {
SecureRandom r = new SecureRandom();
return r.nextInt();
}
public static String simpleRandomHex() {
SecureRandom r = new SecureRandom();
byte[] b = new byte[16];
return hex(b);
}
public static byte[] binarySHA256(byte[] input) {
MessageDigest d;
try {
d = MessageDigest.getInstance("SHA-256");
} catch (NoSuchAlgorithmException e) {
throw new Error("System encryption problem", e);
}
return d.digest(input);
}
public static byte[] binarySHA256(String input) {
return binarySHA256(byt(input));
}
public static String hexSHA256(byte[] input) {
return hex(binarySHA256(input));
}
public static String hexSHA256(String input) {
return hexSHA256(byt(input));
}
public static byte[] byt(String s) {
if (s == null) {
return new byte[0];
}
try {
return s.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
}
public static String hex(byte[] input) {
StringBuffer result = new StringBuffer();
for (int i = 0; i < input.length; i++) {
String h = Integer.toHexString(input[i] & 0xFF);
if(h.length() < 2) {
result.append('0' + h);
} else {
result.append(h);
}
}
return result.toString();
}
}

View File

@ -0,0 +1,20 @@
package swap.auth;
import java.time.Instant;
public class Session {
private final AuthService service;
private final String username;
private final String sessionKey;
private final Instant timestamp;
private final Instant expiry;
Session(AuthService service, String username, String sessionKey, Instant timestamp, Instant expiry) {
this.service = service;
this.username = username;
this.sessionKey = sessionKey;
this.timestamp = timestamp;
this.expiry = expiry;
}
}

View File

@ -0,0 +1,71 @@
package swap.data;
public abstract class DataAbstraction {
public abstract DataString[] getKeys();
public abstract DataString getValue(DataString key);
public abstract void setValue(DataString key, DataString value);
public abstract void delete(DataString key);
public boolean has(DataString key) {
for (DataString k: getKeys()) {
if (k.equals(key)) {
return true;
}
}
return false;
}
public boolean has(String key) {
return has(DataString.ofUTF8(key));
}
public final String getUTF8(DataString key) {
DataString r = getValue(key);
if (r == null) {
return null;
} else {
return r.getUTF8();
}
}
public final DataObject getDataObject(DataString key) {
DataString r = getValue(key);
if (r == null) {
return null;
} else {
return DataObject.decodeSimple(r);
}
}
public final DataString getValue(String key) {
return getValue(DataString.ofUTF8(key));
}
public final String getUTF8(String key) {
return getUTF8(DataString.ofUTF8(key));
}
public final DataObject getDataObject(String key) {
return getDataObject(DataString.ofUTF8(key));
}
public final void setValue(String key, DataString value) {
setValue(DataString.ofUTF8(key), value);
}
public final void setValue(DataString key, String value) {
setValue(key, DataString.ofUTF8(value));
}
public final void setValue(String key, String value) {
setValue(DataString.ofUTF8(key), DataString.ofUTF8(value));
}
public final void setValue(String key, DataObject value) {
setValue(DataString.ofUTF8(key), DataObject.encodeSimple(value));
}
public final void setValue(DataString key, DataObject value) {
setValue(key, DataObject.encodeSimple(value));
}
}

115
src/swap/data/DataConv.java Normal file
View File

@ -0,0 +1,115 @@
package swap.data;
import java.util.Map;
import swap.util.JSON;
public class DataConv {
public DataConv() {
// TODO Auto-generated constructor stub
}
public static DataObject toDataObject(Object o) {
if (o == null) {
return null;
} else if (o instanceof JSON) {
return toDataObject((JSON) o);
} else if (o instanceof Boolean) {
return DataObject.of(((Boolean) o).booleanValue());
} else if (o instanceof Integer) {
return DataObject.of(((Integer) o).intValue());
} else if (o instanceof Double) {
return DataObject.of(((Double) o).doubleValue());
} else if (o instanceof String) {
return DataObject.of((String) o);
} else {
throw new Error("Not sure how to convert objects of this class: " + o.getClass() + "");
}
}
public static DataObject toDataObject(JSON o) {
switch(o.type_get()) {
case NULL:
return null;
case FALSE:
return DataObject.FALSE;
case TRUE:
return DataObject.TRUE;
case JSON_STRING:
return DataObject.of(o.rawValue().toString());
case JSON_NUMBER:
return toDataObject(o.rawValue());
case JSON_ARRAY: {
DataObject[] d = new DataObject[o.json_size()];
for (int i = 0; i < d.length; i++) {
d[i] = toDataObject(o.json_get(i));
}
return new DataObject.List(d);
}
case JSON_OBJECT: {
DataObject.Map m = new DataObject.Map();
for (Object xo: o) {
Map.Entry<String, Object> x = (Map.Entry<String, Object>)xo;
m.set(DataObject.of(x.getKey()), toDataObject(x.getValue()));
}
return m;
}
default:
throw new Error("Don't know hot to decode JSON objects of type: " + o.type_get());
}
}
public static JSON toJSON(DataObject o) {
switch (DataObject.typeOf(o)) {
case NULL:
return new JSON("null");
case BOOL:
return new JSON((((DataObject.Bool) o).value) ? "true" : "false");
case I32:
return new JSON("" + (((DataObject.I32)o).value));
case F64:
return new JSON("" + (((DataObject.F64)o).value));
case TEXT:
return JSON.rawString(((DataObject.Text)o).stringValue());
case LIST: {
String s = "[";
for (int i = 0; i < ((DataObject.List)o).count(); i++) {
if (i != 0) {
s += ",";
}
s += toJSON(((DataObject.List)o).get(i)).toString();
}
s += "]";
return new JSON(s);
}
case MAP: {
String s = "{";
DataObject.Text[] keys = ((DataObject.Map)o).keys();
for (int i = 0; i < keys.length; i++) {
if (i != 0) {
s += ",";
}
s += toJSON(keys[i]).toString() + ":";
s += toJSON(((DataObject.Map)o).get(keys[i])).toString();
}
s += "}";
return new JSON(s);
}
default:
throw new Error("Unrecognised object type: " + DataObject.typeOf(o));
}
}
// A simple test program
public static void main(String[] args) {
String test = "33";//"{\"foo\":\"bar\",\"baz\":[1,2,3]}";
JSON j = new JSON(test);
System.out.println(j);
DataObject d = toDataObject(j);
DataString s = DataObject.encodeSimple(d);
DataObject d2 = DataObject.decodeSimple(s);
JSON j2 = toJSON(d2);
System.out.println(j2);
}
}

317
src/swap/data/DataFile.java Normal file
View File

@ -0,0 +1,317 @@
package swap.data;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
public class DataFile extends DataSet {
private final File file;
public DataFile(File file) {
this.file = file;
}
public String getFilename() {
return file.getPath();
}
public String getLockFilename() {
String f = getFilename();
if (f.endsWith(".db")) {
f = f.substring(0, f.length() - 3);
}
return f + ".lock";
}
public String getBackupFilename() {
String f = getFilename();
if (f.endsWith(".db")) {
f = f.substring(0, f.length() - 3);
}
return f + ".backup";
}
/**
* This deletes the main database file from disk, as well as any lock files or backup files which have been automatically
* created for that file. If the database does not exist, the only effect should be temporarily creating the lock file and
* (after locking) removing it again. Care should be taken to avoid placing ".db" files in directories containing any other
* ".lock" or ".backup"
* files that might be accidentally deleted (or overwritten) when mistaken for files associated with a database of the
* same name.
*/
public void deleteAll() {
try {
doWithLock(getLockFilename(), new Runnable() {
@Override
public void run() {
new File(getFilename()).delete();
new File(getBackupFilename()).delete();
}
});
} catch (IOException e) {
throw new Error("Filed to delete database files", e);
}
new File(getLockFilename()).delete();
}
public static int loadInt(InputStream input) throws IOException {
int a = input.read();
int b = input.read();
int c = input.read();
int d = input.read();
if (a < 0 || b < 0 || c < 0 || d < 0) {
throw new IOException("Stream ended early, expecting a 32-bit int");
}
return a | (b << 8) | (c << 16) | (d << 24);
}
public static byte[] loadBytes(InputStream input, int nbytes) throws IOException {
byte[] b = new byte[nbytes];
for (int i = 0; i < nbytes; i++) {
int x = input.read();
if (x < 0) {
throw new IOException("Stream ended early, expected " + nbytes + " bytes but stopped at " + i);
}
b[i] = (byte) x;
}
return b;
}
public static DataString loadByteString(InputStream input, int nbytes) throws IOException {
return DataString.ofBytes(loadBytes(input, nbytes));
}
public static DataString loadByteString(InputStream input) throws IOException {
int len = loadInt(input);
return loadByteString(input, len);
}
public static String loadString(InputStream input, int nbytes) throws IOException {
return new String(loadBytes(input, nbytes), "UTF-8");
}
public static String loadString(InputStream input) throws IOException {
int len = loadInt(input);
return loadString(input, len);
}
public static HashMap<DataString, DataString> load(InputStream input) throws IOException {
HashMap<DataString,DataString> result = new HashMap<DataString, DataString>();
String magic = loadString(input);
if (!magic.equals("SimpleDB")) {
throw new IOException("Bad file, expecting SimpleDB format");
}
int ver = loadInt(input);
if (ver != 2) {
throw new IOException("Bad SimpleDB file, expecting version 2 but got " + ver);
}
int count = loadInt(input);
if (count < 0) {
throw new IOException("Bad SimpleDB file, invalid number of records: " + count);
}
int hash = loadInt(input);
for (int i = 0; i < count; i++) {
DataString k = loadByteString(input);
DataString v = loadByteString(input);
if (result.containsKey(k)) {
throw new IOException("Bad SimpleDB file, key '" + k + "' is repeated");
}
result.put(k, v);
}
String endMagic = loadString(input);
if (!endMagic.equals("DBEnd")) {
throw new IOException("Bad SimpleDB file, end marker mismatch");
}
int realHash = dataHash(result);
//System.err.println("Recorded hash:\t0b" + Integer.toBinaryString(hash));
//System.err.println("Resulting hash:\t0b" + Integer.toBinaryString(realHash));
if (hash != realHash) {
throw new IOException("Bad SimpleDB file, hash mismatch");
}
return result;
}
public static HashMap<DataString, DataString> loadOrInitialise(File f) throws IOException {
if (!f.exists()) {
store(f, new HashMap<DataString, DataString>());
}
return load(f);
}
public static HashMap<DataString,DataString> load(File f) throws IOException {
FileInputStream i = new FileInputStream(f);
try {
return load(i);
} finally {
i.close();
}
}
public static void storeInt(OutputStream output, int value) throws IOException {
output.write(value & 0xFF);
output.write((value >> 8) & 0xFF);
output.write((value >> 16) & 0xFF);
output.write((value >> 24) & 0xFF);
}
public static void storeBytes(OutputStream output, byte[] bytes) throws IOException {
storeInt(output, bytes.length);
for (int i = 0; i < bytes.length; i++) {
output.write(((int)bytes[i]) & 0xFF);
}
}
public static void storeByteString(OutputStream output, DataString str) throws IOException {
storeBytes(output, str.getBytes());
}
public static void storeString(OutputStream output, String str) throws IOException {
storeBytes(output, str.getBytes("UTF-8"));
}
public static void store(OutputStream output, HashMap<DataString,DataString> data) throws IOException {
storeString(output, "SimpleDB");
storeInt(output, 2); // Version
ArrayList<DataString> keys = sortedKeys(data);
storeInt(output, keys.size());
storeInt(output, dataHash(data));
for (DataString k: keys) {
storeByteString(output, k);
storeByteString(output, data.get(k));
}
storeString(output, "DBEnd");
}
public static ArrayList<DataString> sortedKeys(HashMap<DataString, ?> data) {
ArrayList<DataString> keys = new ArrayList<DataString>();
keys.addAll(data.keySet());
keys.sort(new Comparator<DataString>() {
@Override
public int compare(DataString o1, DataString o2) {
/*int q = o1.hashCode() - o2.hashCode();
if (q == 0) {
System.err.println("HASH CONFLICT");
return o1.compareTo(o2);
} else if (q < 0) {
return -1;
} else {
return 1;
}*/
return o1.compareTo(o2);
}
});
return keys;
}
public static int dataHash(HashMap<DataString, DataString> data) {
ArrayList<DataString> keys = sortedKeys(data);
//System.err.println("Calculating hash of " + keys.size() + " pairs");
int h = DataString.intHash(keys.size());
for (DataString k: keys) {
//System.err.println("k'" + k.getUTF8() + "'=v'" + data.get(k).getUTF8() + "'");
h = DataString.intHash((Integer.rotateRight(h, 1) ^ k.hashCode()) ^ data.get(k).hashCode());
}
//System.err.println("Calculated hash:\t0b" + Integer.toBinaryString(h));
return h;
}
public static void store(File f, HashMap<DataString,DataString> data) throws IOException {
FileOutputStream o = new FileOutputStream(f);
try {
store(o, data);
} finally {
o.close();
}
}
public static void doWithLock(String lockfileName, Runnable r) throws IOException {
//System.err.println("Locking on '" + lockfileName+ "'");
File lf = new File(lockfileName);
if (!lf.getParentFile().exists()) {
throw new IOException("Directory '" + lf.getParentFile() + "' doesn't exist");
}
lf.createNewFile();
RandomAccessFile raf = new RandomAccessFile(lf, "rw");
FileChannel ch = raf.getChannel();
FileLock l = ch.lock();
try {
r.run();
} finally {
l.close();
ch.close();
raf.close();
}
}
@Override
public synchronized void doOp(DataOp op) {
try {
doWithLock(getLockFilename(), new Runnable() {
@Override
public void run() {
try {
//System.err.println("Transaction started");
final HashMap<DataString, DataString> data = loadOrInitialise(new File(getFilename()));
DataTransaction tr = new DataTransaction() {
@Override
public void setValue(DataString key, DataString value) {
checkOperational();
data.put(key, value);
}
@Override
public DataString getValue(DataString key) {
checkOperational();
return data.get(key);
}
@Override
public DataString[] getKeys() {
ArrayList<DataString> l = sortedKeys(data);
return l.toArray(new DataString[l.size()]);
}
@Override
public void delete(DataString key) {
data.remove(key);
}
};
op.perform(tr);
if (tr.isFinished()) {
//System.err.println("Transaction finished");
Files.copy(Paths.get(file.getPath()), Paths.get(getBackupFilename()), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
try {
store(new File(getFilename()), data);
} catch (Throwable t) {
System.err.println("Write failed. Rolling back to backup.");
Files.copy(Paths.get(getBackupFilename()), Paths.get(file.getPath()), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES);
}
} else if (tr.isCancelled()) {
//System.err.println("Transaction CANCELLED");
} else {
//System.err.println("Transaction UNFINISHED");
}
} catch (IOException e) {
throw new Error("Transaction FAILED", e);
}
}
});
} catch (IOException e) {
throw new Error("Transaction FAILED", e);
}
}
}

View File

@ -0,0 +1,411 @@
package swap.data;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
/**
* A simple system for holding/encoding/decoding structured data, similar to a binary JSON format.
*
* This is built on the DataString class (to provide basic byte/text handling incl. reliable ordering of hashmap keys).
*
* @author Zak
*
*/
public abstract class DataObject {
public static DataObject.Bool of(boolean value) {
return value ? TRUE : FALSE;
}
public static DataObject.I32 of(int value) {
return new I32(value);
}
public static DataObject.F64 of(double value) {
return new F64(value);
}
public static Text of(DataString value) {
return new Text(value);
}
public static DataObject.Text of(String value) {
return new Text(DataString.ofUTF8(value));
}
public static DataObject.Text of(byte[] value) {
return new Text(DataString.ofBytes(value));
}
//public static final Null NULL = new Null();
public static final Bool FALSE = new Bool(false);
public static final Bool TRUE = new Bool(true);
public static enum Type {
NULL,
BOOL,
TEXT,
LIST,
MAP,
I32,
F64
}
public DataObject() {
// TODO Auto-generated constructor stub
}
public abstract Type getType();
public static Type typeOf(DataObject value) {
if (value == null) {
return Type.NULL;
} else {
return value.getType();
}
}
private static class Counter {
int x = 0;
}
public static DataObject decodeSimple(DataString data) {
return decodeSimple(data, new Counter());
}
public static DataObject decodeSimple(DataString data, Counter counter) {
if (data.size() < 1) {
throw new Error("Object data terminated early");
}
int first = data.get(0);
//System.out.println("Decoding type " + first);
data = data.sub(1);
counter.x++;
switch (first) {
case 0:
return null;
case 1:
return FALSE;
case 2:
return TRUE;
case 3:
counter.x += 4;
return DataObject.of(decodeI32(data));
case 4:
counter.x += 8;
return DataObject.of(decodeF64(data));
case 5: {
int len = decodeI32(data);
if (len < 0) {
throw new Error("Invalid string length: " + len);
}
counter.x += 4 + len;
DataString dat = data.sub(4, 4 + len);
return new Text(dat);
}
case 6: {
int len = decodeI32(data);
if (len < 0) {
throw new Error("Invalid list length: " + len);
}
counter.x += 4;
data = data.sub(4);
DataObject[] values = new DataObject[len];
for (int i = 0; i < len; i++) {
int oldx = counter.x;
values[i] = decodeSimple(data, counter);
data = data.sub(counter.x - oldx);
}
return new List(values);
}
case 7: {
int len = decodeI32(data);
if (len < 0) {
throw new Error("Invalid map length: " + len);
}
counter.x += 4;
data = data.sub(4);
Map m = new Map();
for (int i = 0; i < len; i++) {
int oldx = counter.x;
DataObject key = decodeSimple(data, counter);
data = data.sub(counter.x - oldx);
oldx = counter.x;
DataObject value = decodeSimple(data, counter);
data = data.sub(counter.x - oldx);
if (!(key instanceof Text)) {
throw new Error("Expected text key in map but got: " + key);
}
//System.out.println("Got k=" + key + " v=" + value);
m.set((Text) key, value);
}
return m;
}
default:
throw new Error("Invalid object data type #" + first);
}
}
public static int decodeI32(DataString data) {
if (data.size() < 4) {
throw new Error("Object data terminated early");
}
return data.get(0) | (data.get(1) << 8) | (data.get(2) << 16) | (data.get(3) << 24);
}
private static long decodeI64(DataString data) {
long lo = decodeI32(data) & 0xFFFFFFFFL;
long hi = decodeI32(data.sub(4)) & 0xFFFFFFFFL;
return lo | (hi << 32);
}
public static double decodeF64(DataString data) {
return Double.longBitsToDouble(decodeI64(data));
}
public static DataString encodeSimple(DataObject obj) {
switch (typeOf(obj)) {
case NULL:
return DataString.ofBytes(new byte[] { 0 });
case BOOL:
return DataString.ofBytes(new byte[] { (byte) (((Bool)obj).value ? 2 : 1) });
case I32:
return DataString.ofBytes(new byte[] { 3 }).cat(encodeI32(((I32)obj).value));
case F64:
return DataString.ofBytes(new byte[] { 4 }).cat(encodeF64(((F64)obj).value));
case TEXT:
return DataString.ofBytes(new byte[] { 5 }).cat(encodeI32(((Text)obj).value.size())).cat(((Text)obj).value);
case LIST:
return DataString.ofBytes(new byte[] { 6 }).cat(encodeListInner((List) obj));
case MAP:
return DataString.ofBytes(new byte[] { 7 }).cat(encodeMapInner((Map) obj));
default:
throw new Error("Unrecognised object type: " + typeOf(obj));
}
}
private static DataString encodeMapInner(Map obj) {
DataString result = encodeI32(obj.count());
Text[] keys = obj.keys();
for (int i = 0; i < keys.length; i++) {
result = result.cat(encodeSimple(keys[i])).cat(encodeSimple(obj.get(keys[i])));
}
return result;
}
private static DataString encodeListInner(List obj) {
DataString result = encodeI32(obj.count());
for (int i = 0; i < obj.count(); i++) {
result = result.cat(encodeSimple(obj.get(i)));
}
return result;
}
public static DataString encodeI32(int value) {
return DataString.ofBytes(new byte[] {
(byte) value,
(byte) (value >> 8),
(byte) (value >> 16),
(byte) (value >> 24)
});
}
private static DataString encodeI64(long value) {
return encodeI32((int) value).cat(encodeI32((int) (value >> 32)));
}
public static DataString encodeF64(double value) {
return encodeI64(Double.doubleToRawLongBits(value));
}
/*public static class Null extends DataObject {
}*/
public static class Bool extends DataObject {
public final boolean value;
Bool(boolean value) {
this.value = value;
}
@Override
public Type getType() {
return Type.BOOL;
}
}
public static class List extends DataObject {
private DataObject[] values;
public List(DataObject... values) {
this.values = values.clone();
}
public int count() {
return values.length;
}
public DataObject get(int idx) {
if (idx < 0 || idx >= values.length) {
return null;
} else {
return values[idx];
}
}
@Override
public Type getType() {
return Type.LIST;
}
}
public static class Map extends DataObject {
private HashMap<Text, DataObject> values = new HashMap<Text, DataObject>();
public int count() {
return values.size();
}
public DataObject get(Text key) {
return values.get(key);
}
public DataObject get(String key) {
return get(of(key));
}
public void set(Text key, DataObject value) {
values.put(key, value);
}
public void set(String key, DataObject value) {
set(of(key), value);
}
public void delete(Text key) {
values.remove(key);
}
public void delete(String key) {
delete(of(key));
}
public boolean has(Text key) {
return values.containsKey(key);
}
public boolean has(String key) {
return has(of(key));
}
public Text[] keys() {
ArrayList<Text> result = sortedKeys(values);
return result.toArray(new Text[result.size()]);
}
private static ArrayList<Text> sortedKeys(HashMap<Text, ?> data) {
ArrayList<Text> keys = new ArrayList<Text>();
keys.addAll(data.keySet());
keys.sort(new Comparator<Text>() {
@Override
public int compare(Text o1, Text o2) {
/*int q = o1.hashCode() - o2.hashCode();
if (q == 0) {
System.err.println("HASH CONFLICT");
return o1.compareTo(o2);
} else if (q < 0) {
return -1;
} else {
return 1;
}*/
return o1.value.compareTo(o2.value);
}
});
return keys;
}
@Override
public Type getType() {
return Type.MAP;
}
}
public static class I32 extends DataObject {
public final int value;
public I32(int value) {
this.value = value;
}
@Override
public Type getType() {
return Type.I32;
}
}
public static class F64 extends DataObject {
public final double value;
public F64(double value) {
this.value = value;
}
@Override
public Type getType() {
return Type.F64;
}
}
public static class Text extends DataObject {
public final DataString value;
public Text(DataString value) {
this.value = value;
}
public String stringValue() {
return value.getUTF8();
}
public byte[] bytesValue() {
return value.getBytes();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Text) {
return value.equals(((Text) obj).value);
} else {
return super.equals(obj);
}
}
@Override
public int hashCode() {
return value.hashCode();
}
@Override
public Type getType() {
return Type.TEXT;
}
}
}

View File

@ -0,0 +1,5 @@
package swap.data;
public interface DataOp {
void perform(DataTransaction x);
}

View File

@ -0,0 +1,52 @@
package swap.data;
public abstract class DataSet extends DataAbstraction {
public abstract void doOp(DataOp op);
@Override
public final DataString getValue(final DataString key) {
final DataVariable<DataString> v = new DataVariable<DataString>();
doOp(new DataOp() {
@Override
public void perform(DataTransaction transaction) {
v.setValue(transaction.getValue(key));
transaction.finish();
}
});
return v.getValue();
}
@Override
public final DataString[] getKeys() {
final DataVariable<DataString[]> v = new DataVariable<DataString[]>();
doOp(new DataOp() {
@Override
public void perform(DataTransaction transaction) {
v.setValue(transaction.getKeys());
transaction.finish();
}
});
return v.getValue();
}
@Override
public final void setValue(final DataString key, final DataString value) {
doOp(new DataOp() {
@Override
public void perform(DataTransaction transaction) {
transaction.setValue(key, value);
transaction.finish();
}
});
}
@Override
public void delete(final DataString key) {
doOp(new DataOp() {
@Override
public void perform(DataTransaction x) {
x.delete(key);
}
});
}
}

View File

@ -0,0 +1,139 @@
package swap.data;
import java.io.UnsupportedEncodingException;
public final class DataString implements Comparable<DataString> {
private final byte[] data;
private DataString(byte[] copiedData) {
this.data = copiedData;
}
public static DataString ofBytes(byte[] bytes) {
return new DataString(copy(bytes));
}
public static DataString ofUTF8(String string) {
try {
return new DataString(string.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
}
public String getUTF8() {
try {
return new String(data, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
}
public byte[] getBytes() {
return copy(data);
}
public int size() {
return data.length;
}
/**
* Returns the value at the given index.
* @param index The byte index starting at 0.
* @return The value in the range 0-255, or -1 if index out of range.
*/
public int get(int index) {
if (index < 0 || index >= data.length) {
return -1;
}
return ((int) data[index]) & 0xFF;
}
public static byte[] copy(byte[] data) {
byte[] copy = new byte[data.length];
for (int i = 0; i < data.length; i++) {
copy[i] = data[i];
}
return copy;
}
@Override
public int compareTo(DataString o) {
for (int i = 0; i < size() || i < o.size(); i++) {
if (i >= size()) {
return -1; // o greater
} else if (i >= o.size()) {
return 1; // this greater
} else {
int x = get(i);
int y = o.get(i);
int diff = x - y;
if (diff != 0) {
return diff;
}
}
}
return 0; // equal
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj instanceof DataString) {
return this.compareTo((DataString) obj) == 0;
} else {
return super.equals(obj);
}
}
@Override
public int hashCode() {
int h = intHash(data.length);
for (int i = 0; i < data.length; i++) {
h = intHash(Integer.rotateRight(h, 1) ^ (((int)data[i]) & 0xFF));
}
return h;
}
public static int intHash(int input) {
input += 91937;
input ^= (input << 9);
input ^= (input >>> 11);
return input;
}
public DataString cat(DataString tail) {
byte[] x = new byte[size() + tail.size()];
for (int i = 0; i < size(); i++) {
x[i] = data[i];
}
for (int i = 0; i < tail.size(); i++) {
x[data.length + i] = tail.data[i];
}
return new DataString(x);
}
public DataString sub(int firstIndex) {
return sub(firstIndex, size());
}
public DataString sub(int firstIndex, int nextIndex) {
if (firstIndex < 0) {
firstIndex = 0;
}
if (nextIndex > data.length) {
nextIndex = data.length;
}
if (nextIndex <= firstIndex) {
return new DataString(new byte[0]);
}
byte[] x = new byte[nextIndex - firstIndex];
for (int i = 0; i < x.length; i++) {
x[i] = data[firstIndex + i];
}
return new DataString(x);
}
}

View File

@ -0,0 +1,34 @@
package swap.data;
public abstract class DataTransaction extends DataAbstraction {
private boolean finished = false;
private boolean cancelled = false;
public void cancel() {
cancelled = true;
}
public void finish() {
if (!cancelled) {
finished = true;
}
}
public boolean isFinished() {
return finished && !cancelled;
}
public boolean isCancelled() {
return cancelled;
}
public boolean isOperational() {
return !(finished || cancelled);
}
public void checkOperational() {
if (!isOperational()) {
throw new Error("This transaction is no longer operational");
}
}
}

View File

@ -0,0 +1,21 @@
package swap.data;
public final class DataVariable<T> {
private T value;
public DataVariable() {
value = null;
}
public DataVariable(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}

13
src/swap/dbtool/Main.java Normal file
View File

@ -0,0 +1,13 @@
package swap.dbtool;
public class Main {
public static void main(String[] args) {
int i = 0;
for (; i < args.length; i++) {
System.out.println("Arg #" + i + " = '" + args[i] + "'");
}
}
}

View File

@ -0,0 +1,12 @@
package swap.httpd;
public interface Content {
/**
* Determines the content type.
*
* @return A suitable content type such as "text/html".
*/
public String getType();
public byte[] getPayload();
}

View File

@ -0,0 +1,5 @@
package swap.httpd;
public interface Handler {
Response handle(Request r);
}

View File

@ -0,0 +1,19 @@
package swap.httpd;
public class Header {
private final String key;
private final String value;
public Header(String key, String value) {
super();
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}

View File

@ -0,0 +1,41 @@
package swap.httpd;
import java.util.ArrayList;
public abstract class HeaderSet {
private final ArrayList<Header> headers = new ArrayList<Header>();
public void addHeader(Header header) {
headers.add(header);
}
public void addHeader(String key, String value) {
addHeader(new Header(key, value));
}
public int countHeaders() {
return headers.size();
}
public Header getHeader(int i) {
if (i < 0 || i >= headers.size()) {
return null;
} else {
return headers.get(i);
}
}
public Header getHeader(String key) {
for (int i = headers.size() - 1; i >= 0; i--) {
if (headers.get(i).getKey().equals(key)) {
return headers.get(i);
}
}
return null;
}
public boolean hasHeader(String key) {
return getHeader(key) != null;
}
}

View File

@ -0,0 +1,44 @@
package swap.httpd;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ListenerThread extends Thread {
private ServerGroup group;
private int port;
public ListenerThread(ServerGroup group, int port) {
this.group = group;
this.port = port;
}
@Override
public void run() {
try {
group.listenerStarted(this);
ServerSocket socket = createSocket();
while (!socket.isClosed()) {
Socket s = socket.accept();
group.handleNewConnection(this, s);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
group.listenerStopped(this);
}
}
protected ServerSocket createSocket() throws IOException {
return new ServerSocket(port);
}
public ServerGroup getGroup() {
return group;
}
public int getPort() {
return port;
}
}

View File

@ -0,0 +1,60 @@
package swap.httpd;
import java.net.Socket;
import swap.log.Log;
import swap.log.PrintStreamLog;
public class LoggedServerGroup extends ServerGroup {
private final Log log;
public LoggedServerGroup(Handler handler, Log log) {
super(handler);
this.log = log;
}
public LoggedServerGroup(Handler handler) {
this(handler, new PrintStreamLog());
}
@Override
protected void doConnection(ListenerThread listenerThread, Socket s) {
try {
log.line("Starting connection", this, listenerThread, s);
super.doConnection(listenerThread, s);
} finally {
log.line("Finished connection", this, listenerThread, s);
}
}
@Override
protected Response invokeHandler(Request r) {
Response result = null;
try {
log.line("Starting handler", this, r.getMethod(), r.getURL(), r.getProtocol());
result = super.invokeHandler(r);
} finally {
log.line("Finished handler", this, result);
}
return result;
}
@Override
public void startHTTP(int port) {
log.line("Attempting to start HTTP listener on port", port);
super.startHTTP(port);
}
@Override
public synchronized void listenerStarted(ListenerThread listenerThread) {
log.line("Listener started", listenerThread);
super.listenerStarted(listenerThread);
}
@Override
public synchronized void listenerStopped(ListenerThread listenerThread) {
log.line("Listener stopped", listenerThread);
super.listenerStopped(listenerThread);
}
}

View File

@ -0,0 +1,37 @@
package swap.httpd;
import java.util.ArrayList;
public class Request extends HeaderSet {
private final ListenerThread listener;
private final String method, url, protocol;
private byte[] putData;
public Request(ListenerThread listener, String method, String url, String protocol) {
this.listener = listener;
this.method = method;
this.url = url;
this.protocol = protocol;
}
public ListenerThread getListener() {
return listener;
}
public String getMethod() {
return method;
}
public String getURL() {
return url;
}
public String getProtocol() {
return protocol;
}
public void setPutData(byte[] data) {
this.putData = data;
}
public byte[] getPutData() {
return putData;
}
}

View File

@ -0,0 +1,81 @@
package swap.httpd;
import java.io.UnsupportedEncodingException;
public class Response extends HeaderSet {
private final String protocol;
private final int code;
private final String desc;
private byte[] payload = null;
public static final int OK = 200;
public static final int ERROR = 400;
public static final int UNAUTHORISED = 401;
public static final int NOTFOUND = 404;
public Response(String protocol, int code, String desc) {
this.protocol = protocol;
this.code = code;
this.desc = desc;
}
public Response(String protocol, int code) {
this(protocol, code, defaultCodeDesc(code));
}
public Response(int code) {
this("HTTP/1.1", code);
}
public String getProtocol() {
return protocol;
}
public int getCode() {
return code;
}
public String getDesc() {
return desc;
}
public byte[] getPayload() {
return payload;
}
public static String defaultCodeDesc(int code) {
switch (code) {
case OK:
return "OK";
case ERROR:
return "ERROR";
case UNAUTHORISED:
return "UNAUTHORISED";
case NOTFOUND:
return "NOTFOUND";
default:
return "Unknown";
}
}
public void setContent(Content content) {
if (!hasHeader("Content-Type")) {
addHeader("Content-Type", content.getType());
}
setContent(content.getPayload());
}
public void setContent(String string) {
try {
setContent(string.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
}
public void setContent(byte[] bytes) {
addHeader("Content-Length", "" + bytes.length);
payload = bytes;
}
}

View File

@ -0,0 +1,64 @@
package swap.httpd;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.security.KeyStore;
import javax.net.ServerSocketFactory;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
/**
* To create a new keystore try "keytool -genkey -alias hosttls -keystore testkeys -keyalg RSA" then "keytool -importkeystore -srckeystore testkeys -destkeystore testkeys -deststoretype pkcs12".
* @author Zak
*
*/
public class SecureListenerThread extends ListenerThread {
private final String keystoreName;
private final String passPhrase;
public SecureListenerThread(ServerGroup group, int port, String keystoreName, String passPhrase) {
super(group, port);
this.keystoreName = keystoreName;
this.passPhrase = passPhrase;
}
@Override
protected ServerSocket createSocket() throws IOException {
ServerSocketFactory f = getServerSocketFactory("TLS", keystoreName, passPhrase/*, alias*/);
return f.createServerSocket(getPort());
}
// Based on example code at https://docs.oracle.com/javase/10/security/sample-code-illustrating-secure-socket-connection-client-and-server.htm#JSSEC-GUID-3561ED02-174C-4E65-8BB1-5995E9B7282C
private static ServerSocketFactory getServerSocketFactory(String type, String keystoreName, String passPhrase/*, String alias*/) {
if (type.equals("TLS")) {
SSLServerSocketFactory ssf = null;
try {
// set up key manager to do server authentication
SSLContext ctx;
KeyManagerFactory kmf;
KeyStore ks;
char[] passphrase = passPhrase.toCharArray();
ctx = SSLContext.getInstance("TLS");
kmf = KeyManagerFactory.getInstance("SunX509");
ks = KeyStore.getInstance("PKCS12"/*"JKS"*/);
ks.load(new FileInputStream(keystoreName), passphrase);
kmf.init(ks, passphrase);
ctx.init(kmf.getKeyManagers(), null, null);
ssf = ctx.getServerSocketFactory();
return ssf;
} catch (Exception e) {
e.printStackTrace();
}
} else {
return ServerSocketFactory.getDefault();
}
return null;
}
}

View File

@ -0,0 +1,150 @@
package swap.httpd;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServerGroup {
private final ExecutorService executor;
private final Handler handler;
public ServerGroup(Handler handler) {
executor = Executors.newFixedThreadPool(32);
this.handler = handler;
}
public void startHTTP(int port) {
ListenerThread t = new ListenerThread(this, port);
t.start();
}
public synchronized void handleNewConnection(final ListenerThread listenerThread, final Socket s) {
executor.execute(new Runnable() {
@Override
public void run() {
doConnection(listenerThread, s);
}
});
}
public static String readHTTPLine(Socket s) throws IOException {
String r = "";
boolean wasCR = false;
boolean wasLF = false;
while (!wasLF) {
int c = s.getInputStream().read();
if (c < 0) {
throw new IOException("Socket closed early?");
}
if (c == '\r') {
wasCR = true;
wasLF = false;
} else if (c == '\n') {
if (wasCR) {
return r;
} else {
//throw new Error("Bad sequence, LF with no CR");
return r;
}
} else {
wasCR = false;
wasLF = false;
r += Character.toString((char) c);
}
}
//System.err.println("GOT LINE '" + r + "'");
//Unreachable?
return r;
}
public static byte[] readBytes(Socket s, int n) throws IOException {
byte[] result = new byte[n];
for (int i = 0; i < result.length; i++) {
result[i] = (byte) s.getInputStream().read();
}
return result;
}
protected void doConnection(ListenerThread listenerThread, Socket s) {
try {
String firstLine = readHTTPLine(s);
String[] firstLineParts = firstLine.split(" ");
if (firstLineParts.length != 3) {
throw new Error("Bad first line of HTTP request, expecting 3 parts");
}
if (!firstLineParts[2].equals("HTTP/1.1")) {
throw new Error("Bad first line of HTTP request, expecting HTTP/1.1");
}
Request r = new Request(listenerThread, firstLineParts[0], firstLineParts[1], firstLineParts[2]);
String l = null;
while (!(l = readHTTPLine(s)).equals("")) {
String[] lParts = l.split(": ");
if (lParts.length != 2) {
throw new Error("Bad HTTP header line, expected ': ' in the middle");
}
//System.err.println("GOT HEADER " + lParts[0]);
r.addHeader(new Header(lParts[0], lParts[1]));
}
if (r.getMethod().equals("PUT")) {
// TODO: Error checking when decoding header?
String lenstr = r.getHeader("Content-Length").getValue();
int len = Integer.parseInt(lenstr);
byte[] data = readBytes(s, len);
r.setPutData(data);
}
Response res = invokeHandler(r);
writeResponse(s, res);
} catch (Exception e) {
try {
writeResponse(s, new Response(400));
} catch (Exception ee) {
ee.printStackTrace();
}
} finally {
try {
s.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
protected Response invokeHandler(Request r) {
return handler.handle(r);
}
private void writeResponse(Socket s, Response r) throws IOException {
try {
s.getOutputStream().write((r.getProtocol() + " " + r.getCode() + " " + r.getDesc() + "\r\n").getBytes("UTF-8"));
for (int i = 0; i < r.countHeaders(); i++) {
Header h = r.getHeader(i);
s.getOutputStream().write((h.getKey() + ": " + h.getValue() + "\r\n").getBytes("UTF-8"));
}
s.getOutputStream().write(("\r\n").getBytes("UTF-8"));
if (r.getPayload() != null) {
s.getOutputStream().write(r.getPayload());
}
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
}
public synchronized void listenerStarted(ListenerThread listenerThread) {
}
public synchronized void listenerStopped(ListenerThread listenerThread) {
}
}

View File

@ -0,0 +1,54 @@
package swap.library;
import swap.auth.BasicCrypto;
import swap.data.DataObject.Map;
import swap.superdata.SuperDoc;
import swap.data.DataObject;
import swap.data.DataString;
public class LibraryLogin extends LibraryShelf {
public LibraryLogin(LibrarySet library, DataString title, Map config) {
super(library, title, config);
}
@Override
protected void doInitialSetup() {
SuperDoc doc = set.request(DataString.ofUTF8("admin"), null, null);
if (!doc.isNew()) {
throw new Error("Already setup");
}
String pw = BasicCrypto.simpleRandomHex();
doc.contents = DataObject.encodeSimple(generateSalty(pw));
System.out.println("Your initial admin password is '" + pw + "'");
}
public static DataObject.Map generateSalty(String pw) {
DataObject.Map result = new DataObject.Map();
String salt = BasicCrypto.simpleRandomHex();
result.set("salt", DataObject.of(salt));
result.set("pass", DataObject.of(BasicCrypto.hexSHA256(pw + salt)));
return result;
}
public static boolean validateSalty(DataObject o, String pw) {
if (!(o instanceof DataObject.Map)) {
throw new Error("Bad login data");
}
DataObject.Map m = (DataObject.Map) o;
DataObject p = m.get("pass");
if (!(p instanceof DataObject.Text)) {
throw new Error("Bad login data");
}
String pass = ((DataObject.Text) p).stringValue();
DataObject s = m.get("salt");
if (!(s instanceof DataObject.Text)) {
throw new Error("Bad login data");
}
String salt = ((DataObject.Text) s).stringValue();
return BasicCrypto.hexSHA256(pw + salt).equals(pass);
}
}

View File

@ -0,0 +1,18 @@
package swap.library;
import swap.data.DataObject;
import swap.data.DataString;
public abstract class LibrarySection {
public final LibrarySet library;
public final DataString title;
public LibrarySection(LibrarySet library, DataString title, DataObject.Map config) {
this.library = library;
this.title = title;
}
protected void doInitialSetup() {
}
}

View File

@ -0,0 +1,98 @@
package swap.library;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import swap.data.DataFile;
import swap.data.DataObject;
import swap.data.DataOp;
import swap.data.DataString;
import swap.data.DataTransaction;
import swap.data.DataObject.Map;
import swap.superdata.SuperBin;
import swap.superdata.SuperLock;
import swap.superdata.SuperSet;
public class LibrarySet {
private final String pathBase;
public final SuperLock lock;
private DataFile configDB;
private HashMap<DataString,LibrarySection> sections = new HashMap<DataString, LibrarySection>();
public LibrarySet(SuperLock lock, File path) {
this.lock = lock;
path = path.getAbsoluteFile();
if (!path.exists()) {
path.mkdirs();
}
if (!path.isDirectory()) {
throw new Error("LibrarySet path '" + path + "' is not a directory!");
}
this.pathBase = path.getAbsolutePath();
configDB = lock.open(new File(path.getPath() + "/config.db"));
configDB.doOp(new DataOp() {
@Override
public void perform(DataTransaction x) {
boolean setup = false;
if (x.getUTF8("_login") == null) {
setup = true;
x.setValue("_login", generateNewLogin());
}
DataString[] keys = x.getKeys();
for (DataString k: keys) {
DataObject o = x.getDataObject(k);
if (!(o instanceof DataObject.Map)) {
throw new Error("Data in wrong format");
}
DataObject.Map m = (DataObject.Map) o;
DataObject t = m.get("type");
if (!(t instanceof DataObject.Text)) {
throw new Error("Data in wrong format");
}
LibrarySection s;
sections.put(k, s = instantiateSection(((DataObject.Text) t).value, m));
if (setup) {
s.doInitialSetup();
}
}
x.finish();
}
});
}
protected LibrarySection instantiateSection(DataString name, DataObject.Map config) {
String n = name.getUTF8();
try {
Class<? extends LibrarySection> cl = (Class<? extends LibrarySection>) this.getClass().getClassLoader().loadClass(n);
return cl.getConstructor(LibrarySet.class, DataString.class, DataObject.Map.class).newInstance(this, name, config);
} catch (Exception e) {
throw new Error("Failed to instantiate section '" + n + "'");
}
}
public String defaultSectionPath(String name) {
return pathBase + "/" + name;
}
protected DataObject.Map generateNewLogin() {
DataObject.Map result = new DataObject.Map();
result.set("type", typeText(LibraryLogin.class));
result.set("path", DataObject.of(defaultSectionPath("_login")));
return result;
}
public static DataObject.Text typeText(Class<?> c) {
return DataObject.of(typeName(c));
}
public static DataString typeName(Class<?> c) {
return DataString.ofUTF8(c.getName());
}
}

View File

@ -0,0 +1,23 @@
package swap.library;
import java.io.File;
import swap.data.DataObject;
import swap.data.DataObject.Map;
import swap.data.DataString;
import swap.superdata.SuperSet;
public class LibraryShelf extends LibrarySection {
public final SuperSet set;
public LibraryShelf(LibrarySet library, DataString title, Map config) {
super(library, title, config);
DataObject t = config.get("path");
if (!(t instanceof DataObject.Text)) {
throw new Error("Data in wrong format");
}
String path = ((DataObject.Text)t).stringValue();
set = new SuperSet(library.lock, new File(path));
}
}

74
src/swap/log/Log.java Normal file
View File

@ -0,0 +1,74 @@
package swap.log;
import java.time.Instant;
public abstract class Log {
static final int LOW = 0;
static final int HINT = 3;
static final int DEFAULT = 4;
static final int WARNING = 5;
static final int ERROR = 7;
static final int HIGH = 10;
public static int saneLevel(int level) {
if (level < LOW) {
return LOW;
} else if (level > HIGH) {
return HIGH;
} else {
return level;
}
}
public static String stringLevel(int level) {
level = saneLevel(level);
switch(level) {
case LOW:
return "LOW";
case HINT:
return "HINT";
case DEFAULT:
return "DEFAULT";
case WARNING:
return "WARNING";
case ERROR:
return "ERROR";
case HIGH:
return "HIGH";
default:
return "LEVEL " + level;
}
}
public static String stringLine(Object[] line) {
String r = "";
boolean first = true;
for (Object o: line) {
if (first) {
first = false;
} else {
r += " ";
}
r += o;
}
return r;
}
public static String stringThread() {
return Thread.currentThread().toString();
}
public static String stringTime() {
return Instant.now().toString();
}
protected abstract void doWriteLine(int saneLevel, Object[] line);
public synchronized final void writeLine(int level, Object...line) {
doWriteLine(saneLevel(level), line);
}
public synchronized final void line(Object...line) {
doWriteLine(DEFAULT, line);
}
}

View File

@ -0,0 +1,27 @@
package swap.log;
import java.io.PrintStream;
import java.time.Instant;
public class PrintStreamLog extends Log {
private final PrintStream target;
public PrintStreamLog(PrintStream target) {
super();
this.target = target;
}
public PrintStreamLog() {
this(System.err);
}
public PrintStream getTarget() {
return target;
}
@Override
protected void doWriteLine(int saneLevel, Object[] line) {
target.println(stringTime() + " " + stringLevel(saneLevel) + " in " + stringThread() + ":\t" + stringLine(line));
}
}

View File

@ -0,0 +1,9 @@
package swap.old.othercereal;
public class Field {
public Field() {
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,9 @@
package swap.old.othercereal;
public class Image {
public Image() {
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,11 @@
package swap.old.othercereal;
public class Method {
Type type;
String name;
public Method() {
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,9 @@
package swap.old.othercereal;
public class ObjectSet {
public ObjectSet() {
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,9 @@
package swap.old.othercereal;
public class Type {
public Type() {
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,13 @@
package swap.old.othercereal;
public class TypeSet {
private boolean sealed = false;
public TypeSet() {
// TODO Auto-generated constructor stub
}
public boolean isSealed() {
return sealed;
}
}

View File

@ -0,0 +1,107 @@
package swap.old.pojocereal;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
public abstract class Boxer {
private final Brand brand;
private int position = 0;
private HashMap<String, Integer> stringPositions = new HashMap<String, Integer>();
private HashMap<Grain, Integer> grainPositions = new HashMap<Grain, Integer>();
public Boxer(Brand brand) {
this.brand = brand;
}
public Brand getBrand() {
return brand;
}
public void reset() {
position = 0;
stringPositions.clear();
grainPositions.clear();
}
public int getCurrentPosition() {
return position;
}
public abstract void writeBytes(byte[] b) throws CerealException;
/**
* Writes a byte (passed as an int for convenience, only low 8 bits written).
*
* @param b The value to be written.
* @throws CerealException
*/
public final void writeByte(int b) throws CerealException {
byte[] tmp = new byte[] {(byte)(b & 0xFF)};
writeBytes(tmp);
}
public final void writeBoolean(boolean val) throws CerealException {
writeByte(val ? 0xFF : 0x00);
}
public final void writeI8(byte val) throws CerealException {
writeByte(val);
}
public final void writeI16(short val) throws CerealException {
writeByte(val);
writeByte(val>>8);
}
public final void writeI32(int val) throws CerealException {
writeByte(val);
writeByte(val>>8);
writeByte(val>>16);
writeByte(val>>24);
}
public final void writeI64(long val) throws CerealException {
writeI32((int) val);
writeI32((int) (val >> 32));
}
/**
* Writes a string (as a UTF-8 byte sequence, preceded by it's 32-bit length).
* @param val
* @return The position of the start of the string.
* @throws CerealException
*/
public final int writeRawUTF8(String val) throws CerealException {
try {
int pos = getCurrentPosition();
byte[] b = val.getBytes("UTF-8");
writeI32(b.length);
writeBytes(b);
return pos; // Returns position of start of string
} catch (UnsupportedEncodingException e) {
throw new CerealException("Encoding problem", e);
}
}
public final int writeUTF8(String val) throws CerealException {
if (stringPositions.containsKey(val)) {
int p = stringPositions.get(val).intValue();
writeI32(-(1 + p));
return p;
} else {
int p = writeRawUTF8(val);
stringPositions.put(val, Integer.valueOf(p));
return p;
}
}
public void close() {
position = -1;
}
public final boolean isClosed() {
return position < 0;
}
}

View File

@ -0,0 +1,30 @@
package swap.old.pojocereal;
import java.util.HashMap;
public class Brand {
private HashMap<Class<? extends Grain>, Type> classTypes = new HashMap<Class<? extends Grain>, Type>();
private HashMap<Class<? extends Grain>, Type> namedTypes = new HashMap<Class<? extends Grain>, Type>();
public Type typeOf(Grain g) throws CerealException {
return typeOfClass(g.getClass());
}
public final synchronized Type typeOfClass(Class<? extends Grain> c) throws CerealException {
if (classTypes.containsKey(c)) {
return classTypes.get(c);
}
Type result = generateTypeDesc(c);
if (result != null) {
classTypes.put(c, result);
}
return result;
}
public Type generateTypeDesc(Class<? extends Grain> c) throws CerealException {
return null;
}
}

View File

@ -0,0 +1,29 @@
package swap.old.pojocereal;
public class CerealException extends Exception {
public CerealException() {
// TODO Auto-generated constructor stub
}
public CerealException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public CerealException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
public CerealException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public CerealException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,57 @@
package swap.old.pojocereal;
public class Field extends Ingredient {
public String type;
public String name;
public Field() {
// TODO Auto-generated constructor stub
}
public static enum Kind {
INVALID,
PRIMITIVE,
GRAIN,
ARRAY
}
public static enum Primitive {
BOOL,
I8,
I16,
I32,
I64,
F32,
F64,
UTF8
}
public static boolean isArray(String type) {
if (type == null) {
return false;
}
return type.endsWith("[]");
}
public static String arrayMemberType(String type) {
if (isArray(type)) {
return type.substring(0, type.length() - 2);
} else {
return null;
}
}
public static boolean isPrimitive(String type) {
if (type == null) {
return false;
}
return type.startsWith(".");
}
public static boolean isGrain(String type) {
if (type == null) {
return false;
}
return !(isArray(type) || isPrimitive(type));
}
}

View File

@ -0,0 +1,5 @@
package swap.old.pojocereal;
public abstract class Grain extends Ingredient {
}

View File

@ -0,0 +1,9 @@
package swap.old.pojocereal;
public abstract class Ingredient {
public Ingredient() {
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,13 @@
package swap.old.pojocereal;
public class Type extends Ingredient {
String name;
Type parent;
Field fields;
//Field[] fields;
public Type() {
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,43 @@
package swap.superdata;
import java.io.File;
import swap.data.DataFile;
import swap.data.DataObject;
import swap.data.DataString;
public class SuperBin {
public final SuperSet set;
public final int number;
public final boolean local;
SuperBin(SuperSet set, int number, boolean local) {
this.set = set;
this.number = number;
this.local = local;
}
SuperBin(SuperSet set, int number, DataObject.Map object) {
this.set = set;
this.number = number;
if (object.count() != 1) {
throw new Error("Invalid bin data");
}
DataObject v = object.get(DataObject.of("local"));
this.local = v.equals(DataObject.TRUE);
}
public DataObject.Map toDataObject() {
DataObject.Map m = new DataObject.Map();
m.set(DataObject.of("local"), DataObject.of(local));
return m;
}
public DataFile openIndex() {
return set.getLock().open(new File(set.binIndexPath(number)));
}
public DataString[] listAll() {
return openIndex().getKeys();
}
}

View File

@ -0,0 +1,68 @@
package swap.superdata;
import swap.data.DataObject;
import swap.data.DataString;
import swap.data.DataObject.Map;
/**
* A SuperDoc is an ordinary object which holds document state. Generally, a SuperDoc is created
* by a SuperSet object (either representing a new or existing document) and then it's contents
* (but not the other fields) are modified as necessary by the user. The version string is updated
* automatically if you store the document.
*
* @author Zak
*
*/
public class SuperDoc {
public DataString title;
public DataString version;
public DataString contents;
SuperDoc(DataString title, DataString version, DataString contents) {
this.title = title;
this.version = version;
this.contents = contents;
}
public boolean isNew() {
return version == null;
}
public boolean isDeleted() {
return contents == null && version != null;
}
public int getVersionSequence() {
if (version == null) {
return 0;
}
DataString start = version.sub(0, 4); // First 4 digits are (decimal) sequence number, mostly for sorting purposes
int x = Integer.parseInt(start.getUTF8());
return x;
}
public static String sequenceString(int n) {
n = n % 10000; // Keep it to 4 digits
String r = "" + n;
while (r.length() < 4) {
r = "0" + r;
}
return r;
}
public String nextVersionString(DataObject credentials) {
int nextSequence = getVersionSequence() + 1;
DataObject.Map m = new DataObject.Map();
m.set("title", DataObject.of(title));
m.set("version", version == null ? null : DataObject.of(version));
m.set("meta", null);
m.set("credentials", credentials);
m.set("contents", contents == null ? null : DataObject.of(contents));
int hash = DataObject.encodeSimple(m).hashCode();
String hashstr = Integer.toHexString(hash);
while (hashstr.length() < 8) {
hashstr = "0" + hashstr;
}
return sequenceString(nextSequence) + "-" + hashstr;
}
}

View File

@ -0,0 +1,46 @@
package swap.superdata;
import java.io.File;
import java.util.WeakHashMap;
import swap.data.DataFile;
/**
* This is only used to ensure that exactly/at-most one DataFile object exists within a
* single VM at a single time. This is only necessary for applications which will randomly open
* databases from multiple threads, in which case multiple SuperLock instances can be used
* if only certain databases are used by certain threads (otherwise, the default instance can
* be used). The SuperSet class will use the default SuperLock if none is passed to it, meaning
* you can create/use multiple SuperSet instances within a single VM (because the databases accessed
* within it will all be locked/shared properly).
*
* <p/> All of this is only required because the same file can't be locked from two threads of
* the same application (at least on some systems/JVMs if not more generally). Between two different
* application instances running at the same time on the same system, the internal file locking should
* be sufficient to synchronise database access (that synchronisation system is half the reason for using
* such a database - to ensure data integrity with multiple simultaneous readers/writers).
*
* @author Zak
*
*/
public class SuperLock {
private static final SuperLock instance = new SuperLock();
public static SuperLock getDefaultInstance() {
return instance;
}
private WeakHashMap<String, DataFile> databases = new WeakHashMap<String, DataFile>();
public synchronized DataFile open(File f) {
f = f.getAbsoluteFile();
String p = f.getAbsolutePath();
if (databases.containsKey(p)) {
return databases.get(p);
} else {
DataFile d = new DataFile(f);
databases.put(p, d);
return d;
}
}
}

View File

@ -0,0 +1,312 @@
package swap.superdata;
import java.io.File;
import swap.data.DataFile;
import swap.data.DataObject;
import swap.data.DataObject.Text;
import swap.data.DataOp;
import swap.data.DataString;
import swap.data.DataTransaction;
import swap.data.DataVariable;
/**
* Essentially, this class creates a larger key-value store using a set of smaller key-value stores. This
* allows somewhat-consistent read/write access from many threads without each thread having to (always)
* lock the same central database file. Individual key/value pairs are combined with metadata and stored
* together in bins based on a hash of the key, meaning that if you have 10 bins in a superset you only
* have to lock 1 bin to interact with one key/value pair - and if a second thread is accessing another key
* there's only a 1 in 10 chance it will be in the same bin.
*
* <p/> This approach guarantees (as much as possible) the integrity of each key/value pair even if accessed
* from many processes and ensures the database as a whole isn't corrupted by smaller failures (such as a single
* file being corrupted).
*
* <p/> The downside of this approach is that it's harder to lock the whole database in order to interact
* with multiple key/value pairs in synchronicity. This means that a SuperSet operates like a document
* database, i.e. each key/value pair represents a document, and synchronisation is generally at the level
* of "a version of a document" (similar to CouchDB for instance). This means that applications or other layers
* may need to do have additional synchronisation logic (as is generally the case when dealing with gigantic
* datasets).
*
* @author Zak
*
*/
public class SuperSet {
private final String pathBase;
private final SuperLock lock;
private DataFile configDB;
//private int bins;
private SuperBin[] bins;
public SuperSet(File path) {
this(SuperLock.getDefaultInstance(), path);
}
public SuperSet(SuperLock lock, File path) {
this(lock, path, 32, true);
}
public SuperSet(SuperLock lock, File path, int defaultBins, boolean defaultLocal) {
this.lock = lock;
path = path.getAbsoluteFile();
if (!path.exists()) {
path.mkdirs();
}
if (!path.isDirectory()) {
throw new Error("SuperSet path '" + path + "' is not a directory!");
}
this.pathBase = path.getAbsolutePath();
configDB = lock.open(new File(path.getPath() + "/config.db"));
configDB.doOp(new DataOp() {
@Override
public void perform(DataTransaction x) {
boolean setup = false;
String v = x.getUTF8("bins");
int nbins;
if (v == null) {
nbins = defaultBins;
setup = true;
} else {
nbins = Integer.parseInt(v);
}
if (nbins < 1 || nbins > 1000000000) {
throw new Error("Invalid number of bins: " + nbins);
}
bins = new SuperBin[nbins];
/* Each bin has a record in the config database, in particular to indicate whether it's
* kept locally (future versions/extensions may distribute supersets between nodes,
* e.g. every second bin on node A and the other half on node B, except likely more nodes!).
*/
for (int i = 0; i < nbins; i++) {
String n = "bin" + binName(i);
if (setup) {
bins[i] = new SuperBin(SuperSet.this, i, defaultLocal);
x.setValue(n, bins[i].toDataObject());
new File(binDataPath(i)).mkdirs();
} else {
DataObject o = x.getDataObject(n);
if (!(o instanceof DataObject.Map)) {
throw new Error("Expected map describing bin, got: " + o);
}
bins[i] = new SuperBin(SuperSet.this, i, (DataObject.Map)o);
}
}
x.finish();
}
});
}
/**
* Returns the bin number in a canonical text format (with leading 0s if necessary to match the longest number
* in this superset).
* @param binNumber
* @return
*/
public String binName(int binNumber) {
String result = Integer.toHexString(binNumber);
String longest = Integer.toHexString(bins.length-1);
while (result.length() < longest.length()) {
result = "0" + result;
}
return result.toUpperCase();
}
public String binDataPath(int binNumber) {
return pathBase + "/data" + binName(binNumber);
}
public String binIndexPath(int binNumber) {
return pathBase + "/index" + binName(binNumber) + ".db";
}
/**
* Returns the bin with the given number (assumed to be either the bin id, or a hash - which, modulo the number of bins, will be reduced to the bin id).
* @param binHash
* @return
*/
public SuperBin bin(int binHash) {
binHash &= 0x7FFFFFFF; // Ignore sign bit.
binHash %= bins.length;
return bins[binHash];
}
public SuperBin bin(DataString s) {
return bin(s.hashCode());
}
public SuperBin bin(String s) {
return bin(DataString.ofUTF8(s));
}
public SuperLock getLock() {
return lock;
}
/**
* Fetches a list of every document in every bin. This is not synchronised between bins (an earlier bin
* may have documents added while this function is fetching contents of a later bin in parallel) and it
* does not take into account deleted objects (these will still be returned if any version/record of them
* exists).
*
* @return A list of all document titles in the superset.
*/
public DataString[] listAll() {
DataString[][] l = new DataString[bins.length][];
int len = 0;
for (int i = 0; i < bins.length; i++) {
l[i] = bins[i].listAll();
len += l[i].length;
}
DataString[] result = new DataString[len];
int t = 0;
for (int i = 0; i < l.length; i++) {
for (int j = 0; j < l[i].length; j++) {
result[t] = l[i][j];
t++;
}
}
return result;
}
private SuperVersion listOneVersion(DataObject.Map map, DataObject.Text version) {
DataObject o = map.get(version);
if (o instanceof DataObject.Text) {
return new SuperVersion(version.value, false);
} else {
return new SuperVersion(version.value, true);
}
}
/**
* Lists basic version information about a document, starting with the latest version (the rest in random order).
* @param title
* @return
*/
public SuperVersion[] listVersions(DataString title) {
SuperBin bin = bin(title);
DataFile db = bin.openIndex();
DataObject stored = db.getDataObject(title);
if (stored == null) {
return new SuperVersion[0];
}
if (!(stored instanceof DataObject.Map)) {
throw new Error("Data in wrong format");
}
DataObject.Map m = (DataObject.Map) stored;
if (!m.has("latest")) {
throw new Error("Data in wrong format");
}
DataObject tmp = m.get("latest");
if (!(tmp instanceof DataObject.Text)) {
throw new Error("Data in wrong format");
}
Text[] keys = m.keys();
SuperVersion[] result = new SuperVersion[keys.length - 1];
result[0] = listOneVersion(m, (DataObject.Text) tmp);
int idx = 1;
for (Text t: keys) {
if (!(t.equals(DataObject.of("latest")) || t.equals(tmp))) {
result[idx] = listOneVersion(m, t);
idx++;
}
}
return result;
}
public SuperDoc request(DataString title, DataString version, DataObject credentials) {
SuperBin bin = bin(title);
DataFile db = bin.openIndex();
DataObject stored = db.getDataObject(title);
if (stored == null) {
if (version != null) {
throw new Error("Version doesn't exist");
}
return new SuperDoc(title, null, null);
}
if (!(stored instanceof DataObject.Map)) {
throw new Error("Data in wrong format");
}
DataObject.Map m = (DataObject.Map) stored;
if (version == null) {
if (!m.has("latest")) {
throw new Error("Data in wrong format");
}
DataObject tmp = m.get("latest");
if (!(tmp instanceof DataObject.Text)) {
throw new Error("Data in wrong format");
}
version = ((DataObject.Text) tmp).value;
}
DataObject entry = m.get(DataObject.of(version));
if (entry == null) {
throw new Error("Version doesn't exist");
}
/* Simplest format - data is embedded as a Text. */
if (entry instanceof DataObject.Text) {
DataString data = ((DataObject.Text) entry).value;
return new SuperDoc(title, version, data);
}
/* TODO: Individual storage for large documents?
* if (entry instanceof DataObject.Map) {
}*/
throw new Error("Data in wrong format");
}
public boolean submit(final SuperDoc document, DataObject credentials) {
final SuperBin bin = bin(document.title);
final DataString newver = DataString.ofUTF8(document.nextVersionString(credentials));
final DataVariable<Boolean> isLatest = new DataVariable<Boolean>(false);
bin.openIndex().doOp(new DataOp() {
@Override
public void perform(DataTransaction x) {
DataObject stored = x.getDataObject(document.title);
DataObject.Map m;
boolean waslatest = false;
if (stored == null) {
m = new DataObject.Map();
waslatest = true;
} else {
if (!(stored instanceof DataObject.Map)) {
throw new Error("Data in wrong format");
}
m = (DataObject.Map)stored;
/* If the latest stored document matches the (not-yet-updated) version in the document object, then
* the new version will be the latest version stored here.
*/
DataObject l = m.get("latest");
if (!(l instanceof DataObject.Text)) {
throw new Error("Data in wrong format");
}
DataString storedver = ((DataObject.Text) l).value;
if (storedver.equals(document.version)) {
waslatest = true;
}
}
if (m.has(DataObject.of(newver))) {
throw new Error("Version conflict");
}
m.set(DataObject.of(newver), document.contents == null ? null : DataObject.of(document.contents));
if (waslatest) {
m.set("latest", DataObject.of(newver));
isLatest.setValue(true);
}
x.setValue(document.title, m);
x.finish();
}
});
document.version = newver;
return isLatest.getValue();
}
}

View File

@ -0,0 +1,20 @@
package swap.superdata;
import swap.data.DataString;
public class SuperVersion {
public DataString version;
public boolean deleted;
public SuperVersion(DataString version, boolean deleted) {
this.version = version;
this.deleted = deleted;
}
@Override
public String toString() {
return "SuperVersion [version=" + version.getUTF8() + ", deleted=" + deleted + "]";
}
}

View File

@ -0,0 +1,19 @@
package swap.template;
public class Attribute {
private final String key;
private final String value;
public Attribute(String key, String value) {
super();
this.key = key;
this.value = value;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
}

View File

@ -0,0 +1,116 @@
package swap.template;
import java.io.PrintStream;
import java.util.ArrayList;
public class Element extends Node {
private final String tag;
private final ArrayList<Node> nodes = new ArrayList<Node>();
private final ArrayList<Attribute> headers = new ArrayList<Attribute>();
public Element(String tag) {
this.tag = tag;
}
public String getTag() {
return tag;
}
public void addAttribute(Attribute attribute) {
headers.add(attribute);
}
public void addAttribute(String key, String value) {
addAttribute(new Attribute(key, value));
}
public int countAttributes() {
return headers.size();
}
public Attribute getAttribute(int i) {
if (i < 0 || i >= headers.size()) {
return null;
} else {
return headers.get(i);
}
}
public Attribute getAttribute(String key) {
for (int i = headers.size() - 1; i >= 0; i--) {
if (headers.get(i).getKey().equals(key)) {
return headers.get(i);
}
}
return null;
}
public boolean hasAttribute(String key) {
return getAttribute(key) != null;
}
public void addNode(Node node) {
nodes.add(node);
}
public void addAttribute(String text) {
addNode(new Text(text));
}
public int countNodes() {
return nodes.size();
}
public Node getNode(int i) {
if (i < 0 || i >= nodes.size()) {
return null;
} else {
return nodes.get(i);
}
}
@Override
public void printRecursively(PrintStream target, String indent, String nextIndent) {
target.print(indent + "<" + getTag());
for (int i = 0; i < countAttributes(); i++) {
Attribute a = getAttribute(i);
if (i != 0) {
target.print(" ");
}
target.print(a.getKey() + "=\"" + a.getValue() + "\""); // TODO: Escape it properly
}
if (countNodes() < 1) {
target.println("/>");
return;
}
target.println(">");
for (int i = 0; i < countNodes(); i++) {
Node n = getNode(i);
n.printRecursively(target, indent + nextIndent, nextIndent);
}
target.println(indent + "</" + getTag() + ">");
}
public Element a(String key, String value) {
return a(new Attribute(key, value));
}
public Element a(Attribute attribute) {
addAttribute(attribute);
return this;
}
public Element n(String title) {
return n(new Text(title));
}
public Element n(Node node) {
addNode(node);
return this;
}
}

View File

@ -0,0 +1,39 @@
package swap.template;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import swap.httpd.Content;
public abstract class Node implements Content {
@Override
public String getType() {
return "text/html";
}
@Override
public byte[] getPayload() {
ByteArrayOutputStream o = new ByteArrayOutputStream();
PrintStream p;
try {
p = new PrintStream(o, true, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
printRecursively(p, "", " ");
p.flush();
return o.toByteArray();
}
public abstract void printRecursively(PrintStream target, String indent, String nextIndent);
@Override
public String toString() {
try {
return new String(getPayload(), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
}
}

View File

@ -0,0 +1,63 @@
package swap.template;
import java.io.UnsupportedEncodingException;
import swap.httpd.Content;
/**
* A simple high-level page template.
*
* @author Zak
*
*/
public class Page implements Content {
private final Element html;
private final Element head;
private final Element body;
public Page(String title) {
html = new Element("html");
head = new Element("head");
html.addNode(head);
body = new Element("body");
html.addNode(body);
head.addNode(new Element("title").n(title));
head.addNode(new Element("meta").a("charset", "utf-8"));
}
@Override
public String getType() {
return "text/html";
}
@Override
public byte[] getPayload() {
try {
return ("<!doctype html>\n" + html).getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
}
public Page h(Node node) {
head.addNode(node);
return this;
}
public Page b(Node node) {
body.addNode(node);
return this;
}
public Element getHTML() {
return html;
}
public Element getHead() {
return head;
}
public Element getBody() {
return body;
}
}

View File

@ -0,0 +1,10 @@
package swap.template;
public class StructuredPage extends Page {
public StructuredPage(String title) {
super(title);
// TODO Auto-generated constructor stub
}
}

View File

@ -0,0 +1,78 @@
package swap.template;
import java.io.PrintStream;
public class Text extends Node {
private final String raw;
public Text(String text) {
raw = text;
}
public String getRaw() {
return raw;
}
@Override
public void printRecursively(PrintStream target, String indent, String nextIndent) {
target.println(indent + rawhtml(raw));
}
static boolean isGarbage(char c) {
switch (c) {
case ':':
case ';':
case '.':
case '(':
case ')':
case '|':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return true;
}
return !Character.isAlphabetic(c);
}
public static String trim(String s) {
s = s.toLowerCase();
while (s.length() >= 1) {
if (isGarbage(s.charAt(0))) {
s = s.substring(1);
} else if (isGarbage(s.charAt(s.length() - 1))) {
s = s.substring(0, s.length() - 1);
} else {
return s;
}
}
return s;
}
public static boolean isEnglish(char x) {
return (x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z') || (x >= '0' && x <= '9');
}
public static String rawhtml(String text) {
int[] u = text.codePoints().toArray();
String r = "";
for (int x: u) {
if (x == ' ') {
r += " ";
} else if (x == '\n') {
r += "\n<p/>\n";
} else if (((int)(char)x) != x || isGarbage((char)x) || !isEnglish((char)x)) {
r += "&#" + x + ";";
} else {
r += new String(Character.toChars(x));
}
}
return r;
}
}

View File

@ -0,0 +1,74 @@
package swap.testing;
import java.io.File;
import swap.data.DataFile;
import swap.data.DataOp;
import swap.data.DataString;
import swap.data.DataTransaction;
public class DBTest {
public static void innerTest(DataFile f) {
DataString prev = f.getValue(DataString.ofUTF8("foo"));
String str = "";
DataString prevBar = f.getValue(DataString.ofUTF8("bar"));
String strBar = "";
if (prev != null) {
str = prev.getUTF8();
}
if (prevBar != null) {
strBar = prevBar.getUTF8();
}
System.out.println("foo='" + str + "'");
System.out.println("bar='" + strBar + "'");
//f.setValue(DataString.ofUTF8("foo"), DataString.ofUTF8(str + "bar"));
f.doOp(new DataOp() {
@Override
public void perform(DataTransaction x) {
DataString dataX = x.getValue(DataString.ofUTF8("foo"));
String strX = "";
if (dataX != null) {
strX = dataX.getUTF8();
}
x.setValue(DataString.ofUTF8("foo"), DataString.ofUTF8(strX + "bar"));
x.setValue(DataString.ofUTF8("bar"), DataString.ofUTF8(strX));
x.setValue(DataString.ofUTF8("baz"), DataString.ofUTF8(strX));
x.setValue(DataString.ofUTF8("bong"), DataString.ofUTF8(strX));
x.setValue(DataString.ofUTF8("bus"), DataString.ofUTF8(strX));
x.setValue(DataString.ofUTF8(strX), DataString.ofUTF8(strX));
x.finish();
}
});
prev = f.getValue(DataString.ofUTF8("foo"));
str = "";
if (prev != null) {
str = prev.getUTF8();
}
prevBar = f.getValue(DataString.ofUTF8("bar"));
strBar = "";
if (prevBar != null) {
strBar = prevBar.getUTF8();
}
System.out.println("foo='" + str + "'");
System.out.println("bar='" + strBar + "'");
}
public static void main(String[] args) {
String dtp = System.getProperty("user.home") + "/OneDrive/Desktop/TestDir";
final DataFile f = new DataFile(new File(dtp + "/DBTest.db"));
for (int i = 0; i < 20; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
innerTest(f);
}
});
t.start();
}
}
}

View File

@ -0,0 +1,39 @@
package swap.testing;
import java.io.File;
import swap.data.DataString;
import swap.superdata.SuperDoc;
import swap.superdata.SuperSet;
import swap.superdata.SuperVersion;
public class SuperTest {
public static void main(String[] args) {
String dtp = System.getProperty("user.home") + "/OneDrive/Desktop/TestDir";
String superdb = dtp + "/supdb";
SuperSet set = new SuperSet(new File(superdb));
SuperDoc a = set.request(DataString.ofUTF8("a"), null, null);
if (a.isNew()) {
a.contents = DataString.ofUTF8("START");
} else {
System.out.println("Found old data: '" + a.contents.getUTF8() + "'");
a.contents = a.contents.cat(DataString.ofUTF8("a"));
SuperDoc another = set.request(a.contents, null, null);
another.contents = DataString.ofUTF8("This is just added as a test");
set.submit(another, null);
}
set.submit(a, null);
DataString[] docs = set.listAll();
for (DataString d: docs) {
System.out.println("Has document '" + d.getUTF8() + "'");
for (SuperVersion v: set.listVersions(d)) {
System.out.println(" > " + v);
}
}
}
}

2279
src/swap/util/JSON.java Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
package swap.webwall;
import java.io.UnsupportedEncodingException;
import java.util.Base64;
import swap.httpd.Handler;
import swap.httpd.Header;
import swap.httpd.LoggedServerGroup;
import swap.httpd.Request;
import swap.httpd.Response;
import swap.httpd.SecureListenerThread;
import swap.httpd.ServerGroup;
import swap.template.Element;
import swap.template.StructuredPage;
public class Main {
public static void main(String[] args) {
ServerGroup server = new LoggedServerGroup(new Handler() {
@Override
public Response handle(Request r) {
System.err.println("Handling request for '" + r.getURL() + "'");
for (int i = 0; i < r.countHeaders(); i++) {
Header h = r.getHeader(i);
System.err.println("Header '" + h.getKey() + "' = '" + h.getValue() + "'");
}
if (r.hasHeader("Authorization")) {
String auth = r.getHeader("Authorization").getValue();
if (auth.startsWith("Basic ")) {
auth = auth.substring("Basic ".length());
byte[] credBytes = Base64.getDecoder().decode(auth);
String credString = null;
try {
credString = new String(credBytes, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Error("Encoding problem", e);
}
String[] creds = credString.split(":");
if (creds.length != 2) {
throw new Error("Credentials error");
}
System.err.println("Got '" + creds[0] + "' with pw '" + creds[1] + "'");
}
}
Response res = new Response(Response.UNAUTHORISED); //.OK);
res.addHeader("WWW-Authenticate", "Basic");
//res.addHeader("Content-Type", "text/html");
StructuredPage p = new StructuredPage("Testing");
p.getBody().n("poop poop").n(new Element("p").n("poop"));
//res.setContent(p);//"<html><head><title>Test</title></head><body>Blah blah <p/>Blah</body></html>");
return res;
}
});
server.startHTTP(8080);
SecureListenerThread slt = new SecureListenerThread(server, 4430, "C:\\Users\\Zak\\OneDrive\\Desktop\\TestDir\\testkeys", "testkeys");
slt.start();
}
}