15 changed files with 1257 additions and 81 deletions
-
90.gitignore
-
14configs.json
-
6src/edu/clarkson/cosi/wishlist/WebSys/IWebSys.java
-
152src/edu/clarkson/cosi/wishlist/WebSys/WebSys.java
-
36src/edu/clarkson/cosi/wishlist/core/Action.java
-
99src/edu/clarkson/cosi/wishlist/core/AddressMatcher.java
-
62src/edu/clarkson/cosi/wishlist/core/Main.java
-
99src/edu/clarkson/cosi/wishlist/core/Util.java
-
139src/edu/clarkson/cosi/wishlist/database/Database.java
-
12src/edu/clarkson/cosi/wishlist/database/IDBAction.java
-
12src/edu/clarkson/cosi/wishlist/database/IDatabase.java
-
18src/net/pop1040/JSON/JSON.java
-
175src/net/pop1040/JSON/JSONArray.java
-
153src/net/pop1040/JSON/JSONObject.java
-
271src/net/pop1040/JSON/JSONValue.java
@ -1,82 +1,10 @@ |
|||
# ---> Eclipse |
|||
|
|||
.metadata |
|||
bin/ |
|||
tmp/ |
|||
*.tmp |
|||
*.bak |
|||
*.swp |
|||
*~.nib |
|||
local.properties |
|||
.settings/ |
|||
.loadpath |
|||
.recommenders |
|||
|
|||
# Eclipse Core |
|||
.project |
|||
|
|||
# External tool builders |
|||
.externalToolBuilders/ |
|||
|
|||
# Locally stored "Eclipse launch configurations" |
|||
*.launch |
|||
|
|||
# PyDev specific (Python IDE for Eclipse) |
|||
*.pydevproject |
|||
|
|||
# CDT-specific (C/C++ Development Tooling) |
|||
.cproject |
|||
|
|||
# JDT-specific (Eclipse Java Development Tools) |
|||
.classpath |
|||
|
|||
# Java annotation processor (APT) |
|||
.factorypath |
|||
|
|||
# PDT-specific (PHP Development Tools) |
|||
.buildpath |
|||
|
|||
# sbteclipse plugin |
|||
.target |
|||
|
|||
# Tern plugin |
|||
.tern-project |
|||
|
|||
# TeXlipse plugin |
|||
.texlipse |
|||
|
|||
# STS (Spring Tool Suite) |
|||
.springBeans |
|||
|
|||
# Code Recommenders |
|||
.recommenders/ |
|||
|
|||
# Scala IDE specific (Scala & Java development for Eclipse) |
|||
.cache-main |
|||
.scala_dependencies |
|||
.worksheet |
|||
|
|||
# ---> Java |
|||
# Compiled class file |
|||
*.class |
|||
|
|||
# Log file |
|||
*.log |
|||
|
|||
# BlueJ files |
|||
*.ctxt |
|||
|
|||
# Mobile Tools for Java (J2ME) |
|||
.mtj.tmp/ |
|||
|
|||
# Package Files # |
|||
*.jar |
|||
*.war |
|||
*.ear |
|||
*.zip |
|||
*.tar.gz |
|||
*.rar |
|||
|
|||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml |
|||
hs_err_pid* |
|||
|
|||
/.settings/ |
|||
/.classpath |
|||
/.project |
|||
/bin/ |
|||
/database/ |
|||
/Wishlist.iml |
|||
/.idea/ |
|||
/out/ |
|||
.DS_Store |
@ -0,0 +1,14 @@ |
|||
{ |
|||
"database":{ |
|||
"databaseDirectory":"database/" |
|||
}, |
|||
"webSystem":{ |
|||
"port": 8027 |
|||
}, |
|||
"allowedIPRanges":[ |
|||
{ |
|||
"IP":"localhost", |
|||
"Mask":"/22" |
|||
} |
|||
] |
|||
} |
@ -0,0 +1,6 @@ |
|||
package edu.clarkson.cosi.wishlist.WebSys; |
|||
|
|||
public interface IWebSys { |
|||
|
|||
void start(); |
|||
} |
@ -0,0 +1,152 @@ |
|||
package edu.clarkson.cosi.wishlist.WebSys; |
|||
|
|||
import com.sun.net.httpserver.HttpExchange; |
|||
import com.sun.net.httpserver.HttpServer; |
|||
|
|||
import edu.clarkson.cosi.wishlist.core.Action; |
|||
import edu.clarkson.cosi.wishlist.core.Main; |
|||
import net.pop1040.JSON.JSON; |
|||
import net.pop1040.JSON.JSONObject; |
|||
import net.pop1040.JSON.JSONValue; |
|||
|
|||
import java.io.*; |
|||
import java.net.InetSocketAddress; |
|||
import java.util.concurrent.Executors; |
|||
|
|||
public class WebSys implements IWebSys { |
|||
|
|||
final int port; |
|||
final int DEFAULT_PORT = 8027; |
|||
|
|||
public WebSys(JSONObject configs) { |
|||
if (configs.isLong("port")) { |
|||
port = (int) configs.getLong("port"); |
|||
|
|||
} else if (configs.exists("port")) { |
|||
System.err.println("The field \"port\" exists in the webSystem portion of the config file, but it is not an integer (whole number), defaulting to " + DEFAULT_PORT); |
|||
|
|||
if(configs.isString("port"))try{ |
|||
Integer.parseInt(configs.getString("port")); |
|||
System.err.println("(It looks like the field \"port\" was set to a string (text in quotes) that contains an integer, did you accidentally add quotes?)"); |
|||
}catch(NumberFormatException e) {} |
|||
|
|||
port = DEFAULT_PORT; |
|||
} else { |
|||
System.out.println("The integer (whole number) \"port\" was not specified in the webSystem portion of the config file, defaulting to " + DEFAULT_PORT); |
|||
port = DEFAULT_PORT; |
|||
} |
|||
} |
|||
|
|||
@Override |
|||
public void start() { |
|||
|
|||
try { |
|||
|
|||
HttpServer serverSocket = HttpServer.create(new InetSocketAddress(port), 0); |
|||
serverSocket.createContext("/", (HttpExchange httpExchange) -> { |
|||
|
|||
// Begin HTTP Exchange |
|||
InputStream is = httpExchange.getRequestBody(); |
|||
StringBuilder sb = new StringBuilder(); |
|||
|
|||
// Read input |
|||
new BufferedReader(new InputStreamReader(is)); |
|||
int c; |
|||
while ((c = is.read()) != -1) { |
|||
|
|||
sb.append((char) c); |
|||
} |
|||
|
|||
JSONObject output = null; |
|||
|
|||
System.out.println("Input:[" + sb.toString() + "]"); |
|||
|
|||
// Parse input |
|||
JSONValue jsonpacket = JSON.parse(sb.toString()); |
|||
if (jsonpacket == null) { |
|||
|
|||
output = new JSONObject().setBoolean("error", true); |
|||
output.setString("error", "Unable to parse JSON packet.").setString("errorType", "Root error"); |
|||
} else { |
|||
|
|||
// Check if JSON packet is an object |
|||
if (!(jsonpacket instanceof JSONObject)) { |
|||
|
|||
output = new JSONObject().setBoolean("error", true); |
|||
output.setString("error", "JSON packet was not an object.").setString("errorType", "Root error"); |
|||
} else { |
|||
|
|||
JSONObject input = (JSONObject) jsonpacket; |
|||
|
|||
// Determine which action was performed |
|||
String actionName = input.isString("action") ? input.getString("action") : null; |
|||
System.out.println("Action: " + actionName + ", json: " + input.toString()); |
|||
|
|||
Action action = Main.actionLookup(actionName); |
|||
|
|||
if (action == null) output = new JSONObject().setString("error", "unknown action, are you probing for commands?").setString("errorType", "Root error"); |
|||
else { |
|||
try { |
|||
action.setRequesterAddress(httpExchange.getRemoteAddress()); |
|||
action.setJson(input); |
|||
output = Main.processAction(action); |
|||
} catch (Exception e) { |
|||
output = new JSONObject().setString("error", "Internal server error, database errored").setString("errorType", "Internal error"); |
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
|
|||
// if (actionClass == null) { |
|||
// |
|||
// output = new JSONObject().setBoolean("error", true); |
|||
// output.setString("text", "Action does not exist."); |
|||
// } else { |
|||
// Constructor<?> cons; |
|||
// try { |
|||
// |
|||
// //Create and perform generic action |
|||
// cons = actionClass.getConstructor(JSONObject.class); |
|||
// Action action = (Action) cons.newInstance(input); |
|||
// output = Main.processAction(action); |
|||
// } catch (NoSuchMethodException e) { |
|||
// |
|||
// e.printStackTrace(); |
|||
// } catch (IllegalAccessException e) { |
|||
// |
|||
// e.printStackTrace(); |
|||
// } catch (InstantiationException e) { |
|||
// |
|||
// e.printStackTrace(); |
|||
// } catch (InvocationTargetException e) { |
|||
// |
|||
// e.printStackTrace(); |
|||
// } finally { |
|||
// if (output == null) { |
|||
// |
|||
// output = new JSONObject().setBoolean("error", true); |
|||
// output.setString("text", "Internal server error."); |
|||
// } |
|||
// } |
|||
// } |
|||
} |
|||
} |
|||
|
|||
// Send data |
|||
String response = output.toString(); |
|||
httpExchange.getResponseHeaders().add("Content-Type", "application/json"); |
|||
httpExchange.sendResponseHeaders(200, output.toString().length()+1); |
|||
OutputStreamWriter sw = new OutputStreamWriter(httpExchange.getResponseBody()); |
|||
sw.write(response); |
|||
sw.write('\n'); |
|||
sw.flush(); |
|||
sw.close(); |
|||
}); |
|||
serverSocket.setExecutor(Executors.newCachedThreadPool()); // because isn't supporting more than one user at |
|||
// a time nice? |
|||
serverSocket.start(); |
|||
} catch (IOException e) { |
|||
|
|||
e.printStackTrace(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,36 @@ |
|||
package edu.clarkson.cosi.wishlist.core; |
|||
|
|||
import java.net.InetSocketAddress; |
|||
|
|||
import net.pop1040.JSON.JSONObject; |
|||
|
|||
public class Action { |
|||
|
|||
protected String actionName; |
|||
protected InetSocketAddress remoteAddress; |
|||
protected JSONObject json; |
|||
|
|||
public Action(String actionName) { |
|||
this.actionName = actionName; |
|||
} |
|||
|
|||
public String getActionName() { |
|||
return actionName; |
|||
} |
|||
|
|||
public void setRequesterAddress(InetSocketAddress remoteAddress) { |
|||
this.remoteAddress = remoteAddress; |
|||
} |
|||
|
|||
public InetSocketAddress getRemoteAddress() { |
|||
return remoteAddress; |
|||
} |
|||
|
|||
public void setJson(JSONObject json) { |
|||
this.json = json; |
|||
} |
|||
|
|||
public JSONObject getJson() { |
|||
return json; |
|||
} |
|||
} |
@ -0,0 +1,99 @@ |
|||
package edu.clarkson.cosi.wishlist.core; |
|||
|
|||
import java.net.DatagramSocket; |
|||
import java.net.Inet4Address; |
|||
import java.net.InetAddress; |
|||
import java.net.SocketException; |
|||
import java.net.UnknownHostException; |
|||
import java.nio.ByteBuffer; |
|||
import java.util.ArrayList; |
|||
|
|||
public class AddressMatcher { |
|||
|
|||
private static class Range{ |
|||
|
|||
byte[] ip; |
|||
byte[] mask; |
|||
|
|||
public Range(byte[] ip, byte[] mask) { |
|||
this.ip = ip; |
|||
this.mask = mask; |
|||
System.out.println((0xFF & ip [0]) + "." + (0xFF & ip [1]) + "." + (0xFF & ip [2]) + "." + (0xFF & ip [3])); |
|||
System.out.println((0xFF & mask[0]) + "." + (0xFF & mask[1]) + "." + (0xFF & mask[2]) + "." + (0xFF & mask[3])); |
|||
} |
|||
|
|||
boolean matches(Inet4Address address) { |
|||
for(int i=0; i<4; i++)if((address.getAddress()[i] & mask[i]) != (ip[i] & mask[i]))return false; |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
ArrayList<Range> ranges = new ArrayList<Range>(); |
|||
|
|||
public boolean inRange(Inet4Address address) { |
|||
for(Range r : ranges) if(r.matches(address))return true; |
|||
return false; |
|||
} |
|||
|
|||
public void addRange(String ip, String mask) { |
|||
byte[] ipVal = null; |
|||
byte[] maskVal = null; |
|||
if("localhost".equalsIgnoreCase(ip)) { |
|||
try(final DatagramSocket socket = new DatagramSocket()){ //get preferred out bound IP address, 1.1.1.1 does not need to be reachable |
|||
socket.connect(InetAddress.getByName("1.1.1.1"), 10002); |
|||
ipVal = socket.getLocalAddress().getAddress(); |
|||
} catch (UnknownHostException e) { |
|||
e.printStackTrace(); |
|||
} catch (SocketException e1) { |
|||
e1.printStackTrace(); |
|||
} |
|||
if(ipVal == null) { //If the IP is IPv6 or fails for another reason, fall back to normal local host |
|||
try { |
|||
ipVal = Inet4Address.getLocalHost().getAddress(); |
|||
} catch (UnknownHostException e) { |
|||
e.printStackTrace(); |
|||
System.err.println("Unable to determine localhost, something is very wrong"); |
|||
System.exit(2); |
|||
} |
|||
} |
|||
}else { //if its not localhost, just parse it normally |
|||
try { |
|||
ipVal = Inet4Address.getByName(ip).getAddress(); |
|||
} catch (UnknownHostException e) { |
|||
e.printStackTrace(); |
|||
System.err.println("Unable to parse IP address \"" + ip + "\""); |
|||
System.exit(2); |
|||
} |
|||
} |
|||
|
|||
if(mask != null && mask.startsWith("/")) { |
|||
try { |
|||
int amount = Integer.parseInt(mask.substring(1)); |
|||
if(amount < 0) { |
|||
System.err.println("Mask cannot be less than 0, that doesn't make any sense, 0 selects all IPs, how could you have more than that"); |
|||
System.exit(1); |
|||
} |
|||
if(amount > 32) { |
|||
System.err.println("Mask cannot be more than 32, that doesn't make any sense, you can't get any more specific than only the specified IP"); |
|||
System.exit(1); |
|||
} |
|||
maskVal = ByteBuffer.allocate(4).putInt(0xFFFFFFFF << (32 - amount)).array(); |
|||
}catch(NumberFormatException e) { |
|||
e.printStackTrace(); |
|||
System.err.println("Unable to parse Mask address \"" + ip + "\", could not parse " + mask.substring(1) + " as an integer (whole number)"); |
|||
System.exit(2); |
|||
} |
|||
}else { |
|||
try { |
|||
maskVal = Inet4Address.getByName(mask).getAddress(); |
|||
} catch (UnknownHostException e) { |
|||
e.printStackTrace(); |
|||
System.err.println("Unable to parse Mask address \"" + ip + "\""); |
|||
System.exit(2); |
|||
} |
|||
} |
|||
|
|||
ranges.add(new Range(ipVal, maskVal)); |
|||
|
|||
} |
|||
} |
@ -0,0 +1,62 @@ |
|||
package edu.clarkson.cosi.wishlist.core; |
|||
|
|||
import java.net.Inet4Address; |
|||
import java.net.Inet6Address; |
|||
import java.net.InetAddress; |
|||
|
|||
import edu.clarkson.cosi.wishlist.WebSys.IWebSys; |
|||
import edu.clarkson.cosi.wishlist.WebSys.WebSys; |
|||
import edu.clarkson.cosi.wishlist.database.Database; |
|||
import edu.clarkson.cosi.wishlist.database.IDatabase; |
|||
import net.pop1040.JSON.JSONObject; |
|||
|
|||
public class Main { |
|||
|
|||
|
|||
static IWebSys webSys; |
|||
static IDatabase database; |
|||
static AddressMatcher addressMatcher = null; |
|||
|
|||
public static void main(String[] args) { |
|||
|
|||
JSONObject config = Util.loadConfig(); |
|||
if(!config.isJSONObject("database")) { |
|||
System.err.println("\"database\" JSONObject section not found in config file, please fix it or delete it for another to be autogenerated"); |
|||
System.exit(1); |
|||
} |
|||
if(!config.isJSONObject("webSystem")) { |
|||
System.err.println("\"webSystem\" JSONObject section not found in config file, please fix it or delete it for another to be autogenerated"); |
|||
System.exit(1); |
|||
} |
|||
|
|||
if(!config.isJSONArray("allowedIPRanges")) { |
|||
System.err.println("\"allowedIPRanges\" JSONArray section not found in config file, defaulting to allowing all IPs"); |
|||
}else { |
|||
addressMatcher = Util.loadIPRanges(config.getArray("allowedIPRanges")); |
|||
} |
|||
|
|||
database = new Database(config.getObject("database")); |
|||
webSys = new WebSys(config.getObject("webSystem")); |
|||
|
|||
database.start(); |
|||
webSys.start(); |
|||
System.out.println("Started"); |
|||
|
|||
} |
|||
|
|||
public static Action actionLookup(String actionName) { |
|||
if(database.isAction(actionName))return new Action(actionName); |
|||
return null; |
|||
} |
|||
|
|||
public static IDatabase getDatabase() { |
|||
return database; |
|||
} |
|||
|
|||
public static JSONObject processAction(Action action) { |
|||
InetAddress address = action.remoteAddress.getAddress(); |
|||
if(address instanceof Inet6Address)return database.processAction(action); // Override security bla becuase IPv6 |
|||
if(addressMatcher != null)if(!(address instanceof Inet4Address) || addressMatcher.inRange((Inet4Address) address))return new JSONObject().setString("error", "Unauthorized, your IP address (" + address + ") is not whitelisted").setString("errorType", "Authentication error"); |
|||
return database.processAction(action); |
|||
} |
|||
} |
@ -0,0 +1,99 @@ |
|||
package edu.clarkson.cosi.wishlist.core; |
|||
|
|||
import java.io.BufferedReader; |
|||
import java.io.FileInputStream; |
|||
import java.io.FileNotFoundException; |
|||
import java.io.FileWriter; |
|||
import java.io.IOException; |
|||
import java.io.InputStream; |
|||
import java.io.InputStreamReader; |
|||
import java.io.Writer; |
|||
|
|||
import net.pop1040.JSON.JSON; |
|||
import net.pop1040.JSON.JSONArray; |
|||
import net.pop1040.JSON.JSONObject; |
|||
import net.pop1040.JSON.JSONValue; |
|||
|
|||
public class Util { |
|||
protected static JSONObject loadConfig() { |
|||
InputStream is = null; |
|||
String file = ""; |
|||
|
|||
try { |
|||
|
|||
//Read config file |
|||
is = new FileInputStream("configs.json"); |
|||
StringBuilder sb = new StringBuilder(); |
|||
|
|||
new BufferedReader(new InputStreamReader(is)); |
|||
int c; |
|||
while ((c = is.read()) != -1) { |
|||
|
|||
sb.append((char) c); |
|||
} |
|||
is.close(); |
|||
file = sb.toString(); |
|||
} catch (FileNotFoundException e) { |
|||
|
|||
//Create default config |
|||
file = "{\r\n" + |
|||
"\t\"database\":{\r\n" + |
|||
"\t\t\"databaseDirectory\":\"database/\"\r\n" + |
|||
"\t},\r\n" + |
|||
"\t\"webSystem\":{\r\n" + |
|||
"\t\t\"port\": 8027\r\n" + |
|||
"\t},\r\n" + |
|||
"\t\"allowedIPRanges\":[\r\n" + |
|||
"\t\t{\r\n" + |
|||
"\t\t\t\"IP\":\"localhost\",\r\n" + |
|||
"\t\t\t\"Mask\":\"/22\"\r\n" + |
|||
"\t\t}\r\n" + |
|||
"\t]\r\n" + |
|||
"}"; |
|||
try { |
|||
|
|||
//Write default config to new configs file |
|||
Writer fw = new FileWriter("configs.json"); |
|||
fw.write(file); |
|||
fw.flush(); |
|||
fw.close(); |
|||
} catch (FileNotFoundException e1) { |
|||
|
|||
e1.printStackTrace(); |
|||
} catch (IOException e1) { |
|||
|
|||
e1.printStackTrace(); |
|||
} |
|||
} catch (IOException e) { |
|||
|
|||
e.printStackTrace(); |
|||
} |
|||
JSONValue json = JSON.parse(file); |
|||
if (!(json instanceof JSONObject)) { |
|||
|
|||
System.err.println("Unable to parse configs, please delete it for another to be autogenerated"); |
|||
System.exit(1); |
|||
} |
|||
return (JSONObject) json; |
|||
} |
|||
|
|||
public static AddressMatcher loadIPRanges(JSONArray array) { |
|||
AddressMatcher matcher = new AddressMatcher(); |
|||
for(int i=0; i<array.size(); i++) { |
|||
if(array.isJSONObject(i)) { |
|||
JSONObject obj = array.getJSONObject(i); |
|||
if(obj.isString("IP")) { |
|||
if(obj.isString("Mask")) { |
|||
String ip = obj.getString("IP"); |
|||
String mask = obj.getString("Mask"); |
|||
|
|||
matcher.addRange(ip, mask); |
|||
|
|||
}else System.err.println("Element " + i + " does not contain the String \"Mask\", which can contain either a forward slash followed by how many bits must match the IP, or a subnet mask style IP address (like 255.255.255.0)"); |
|||
}else System.err.println("Element " + i + " does not contain the String \"IP\", which can contain either an IPv4 address or the text localhost"); |
|||
|
|||
}else System.err.println("Element " + i + " in allowedIPRanges array is not a JSON Object, ignoring"); |
|||
} |
|||
return matcher; |
|||
} |
|||
} |
@ -0,0 +1,139 @@ |
|||
package edu.clarkson.cosi.wishlist.database; |
|||
|
|||
import java.io.File; |
|||
import java.util.ArrayList; |
|||
import java.util.HashMap; |
|||
import java.util.Map; |
|||
import java.util.stream.Collectors; |
|||
import java.util.stream.Stream; |
|||
|
|||
import org.neo4j.graphdb.GraphDatabaseService; |
|||
import org.neo4j.graphdb.QueryExecutionException; |
|||
import org.neo4j.graphdb.Result; |
|||
import org.neo4j.graphdb.factory.GraphDatabaseFactory; |
|||
|
|||
import edu.clarkson.cosi.wishlist.core.Action; |
|||
import net.pop1040.JSON.JSONArray; |
|||
import net.pop1040.JSON.JSONObject; |
|||
|
|||
public class Database implements IDatabase{ |
|||
|
|||
protected String databaseDirectory; |
|||
HashMap<String, IDBAction> actionHandlers = new HashMap<>(); |
|||
GraphDatabaseService db; |
|||
|
|||
public Database(JSONObject config) { |
|||
if(config.isString("databaseDirectory")) { |
|||
databaseDirectory = config.getString("databaseDirectory"); |
|||
}else { |
|||
databaseDirectory = "database/"; |
|||
System.out.println("The string \"databaseDirectory\" was not specified in the database portion of the config file, defaulting to \"database/\""); |
|||
} |
|||
registerActionHandlers(); |
|||
} |
|||
|
|||
@Override |
|||
public void start() { |
|||
//we use the embedded database builder so we can set the folder for the database before initialization. |
|||
System.out.println("Starting internal database"); |
|||
final GraphDatabaseService db = new GraphDatabaseFactory().newEmbeddedDatabaseBuilder(new File(databaseDirectory)).newGraphDatabase(); |
|||
Runtime.getRuntime().addShutdownHook(new Thread(()->{ |
|||
db.shutdown(); |
|||
System.out.println("database shut down"); |
|||
})); //make sure database is shut down on close |
|||
|
|||
this.db = db; |
|||
System.out.println("Database started"); |
|||
} |
|||
|
|||
@Override |
|||
public JSONObject processAction(Action action) { |
|||
IDBAction actionHandle = actionHandlers.get(action.getActionName()); |
|||
if(actionHandle == null)return new JSONObject().setString("error", "Action unknown to database").setString("errorType", "Internal error"); |
|||
return actionHandle.process(action, db); |
|||
} |
|||
|
|||
@Override |
|||
public boolean isAction(String actionName) { |
|||
return actionHandlers.containsKey(actionName); |
|||
} |
|||
|
|||
private void registerActionHandlers() { |
|||
actionHandlers.put("searchComments", (Action action, GraphDatabaseService database)->{ |
|||
String name = ""; //name search |
|||
String query = ""; //search query, the text to search for |
|||
if(action.getJson().isString("name"))name = action.getJson().getString("name"); |
|||
if(action.getJson().isString("text"))query = action.getJson().getString("text"); |
|||
JSONArray arry = new JSONArray(); |
|||
ArrayList<Map<String, Object>> res = queryAll("Match (n:Comment)-[:By]->(p:User) Where toLower(n.text) CONTAINS $text and toLower(p.name) CONTAINS $name Return n.text as text, p.name as name", new Object[][] {{"name", name.toLowerCase()}, {"text", query.toLowerCase()}}, db); |
|||
for(Map<String, Object> iter : res) { |
|||
Object tex = iter.get("text"); |
|||
Object nm = iter.get("name"); |
|||
if(tex instanceof String && nm instanceof String) { |
|||
arry.addJSONObject(new JSONObject().setString("text", (String) tex).setString("name", (String) nm)); |
|||
} |
|||
} |
|||
return new JSONObject().setBoolean("success", true).setJSONArray("comments", arry); |
|||
}); |
|||
actionHandlers.put("addComment", (Action action, GraphDatabaseService database)->{ |
|||
if(!action.getJson().isString("name") || !action.getJson().isString("text"))return new JSONObject().setString("error", "both name and text must be specified for submitting a comment").setString("errorType", "Formatting error"); |
|||
String name = action.getJson().getString("name"); //name |
|||
String text = action.getJson().getString("text"); //comment body |
|||
query("Merge (p:User{name: $name}) With p Create (n:Comment{text: $text})-[:By]->(p)", new Object[][] {{"name", name}, {"text", text}}, db); |
|||
return new JSONObject().setBoolean("success", true); |
|||
}); |
|||
} |
|||
/** |
|||
* Makes a Cypher query and returns only one row |
|||
* @param query |
|||
* @param db |
|||
* @return |
|||
*/ |
|||
protected static Map<String, Object> query(String query, Object[][] params, GraphDatabaseService db) { |
|||
|
|||
try (Result result = db.execute(query, Stream.of(params).collect(Collectors.toMap(data -> (String) data[0], data -> data[1])))) { |
|||
|
|||
if(result.hasNext()) { |
|||
|
|||
return result.next(); |
|||
} |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
protected static Map<String, Object> querySafe(String query, Object[][] params, GraphDatabaseService db){ |
|||
Map<String, Object> res = query(query, params, db); |
|||
return res==null?new HashMap<>():res; |
|||
} |
|||
|
|||
|
|||
/** |
|||
* Makes a Cypher query and returns all rows |
|||
* @param query |
|||
* @param db |
|||
* @return |
|||
*/ |
|||
protected static ArrayList<Map<String, Object>> queryAll(String query, Object[][] params, GraphDatabaseService db) { |
|||
|
|||
try (Result result = db.execute(query, Stream.of(params).collect(Collectors.toMap(data -> (String) data[0], data -> data[1])))) { |
|||
|
|||
ArrayList<Map<String, Object>> output = new ArrayList<>(); |
|||
|
|||
while(result.hasNext())output.add(result.next()); |
|||
|
|||
return output; |
|||
}catch(QueryExecutionException e) { |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
|
|||
@SuppressWarnings("unchecked") //because it actually is, but dynamically because generic methods don't support the normal instance of method |
|||
protected static <T> ArrayList<T> queryAll(String query, Object[][] params, GraphDatabaseService db, String collum, Class<T> clazz) { |
|||
ArrayList<Map<String, Object>> q = queryAll(query, params, db); |
|||
ArrayList<T> output = new ArrayList<>(); |
|||
for(Map<String, Object> iter : q)if(iter.get(collum).getClass() == clazz)output.add((T) iter.get(collum)); |
|||
return output; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,12 @@ |
|||
package edu.clarkson.cosi.wishlist.database; |
|||
|
|||
import org.neo4j.graphdb.GraphDatabaseService; |
|||
|
|||
import edu.clarkson.cosi.wishlist.core.Action; |
|||
import net.pop1040.JSON.JSONObject; |
|||
|
|||
|
|||
public interface IDBAction { |
|||
JSONObject process(Action action, GraphDatabaseService database); |
|||
|
|||
} |
@ -0,0 +1,12 @@ |
|||
package edu.clarkson.cosi.wishlist.database; |
|||
|
|||
import edu.clarkson.cosi.wishlist.core.Action; |
|||
import net.pop1040.JSON.JSONObject; |
|||
|
|||
public interface IDatabase { |
|||
|
|||
public void start(); |
|||
JSONObject processAction(Action action); |
|||
public boolean isAction(String actionName); |
|||
|
|||
} |
@ -0,0 +1,18 @@ |
|||
package net.pop1040.JSON; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.PrimitiveIterator.OfInt; |
|||
|
|||
public class JSON { |
|||
|
|||
public static JSONValue parse(String s) { |
|||
OfInt iter = s.codePoints().iterator(); |
|||
try { |
|||
return JSONValue.parseValue(iter).val; |
|||
} catch (IOException e) { |
|||
e.printStackTrace(); |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
} |
@ -0,0 +1,175 @@ |
|||
package net.pop1040.JSON; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.ArrayList; |
|||
import java.util.PrimitiveIterator.OfInt; |
|||
|
|||
import net.pop1040.JSON.JSONValue.JSONNum.JSONFloat; |
|||
import net.pop1040.JSON.JSONValue.JSONNum.JSONInt; |
|||
|
|||
public class JSONArray extends JSONValue { |
|||
|
|||
ArrayList<JSONValue> values; |
|||
|
|||
public JSONArray(ArrayList<JSONValue> val) { |
|||
this.values = val; |
|||
} |
|||
public JSONArray() { |
|||
values = new ArrayList<>(); |
|||
} |
|||
|
|||
public static JSONArray parse(OfInt s) throws IOException { |
|||
ArrayList<JSONValue> values = new ArrayList<JSONValue>(); |
|||
JSONValue.ParseRet r; //parse val and add to array, if the next char is a ] then the array has ended. |
|||
while((r = JSONValue.parseValue(s)).lastChar != ']')if(r.lastChar == ',')values.add(r.val);else throw new IOException("Invalid JSON array"); |
|||
if(!r.isEnd)values.add(r.val); //If the array is empty, ParseRet will set the isEnd flag, so we know that the value is not null |
|||
return new JSONArray(values); |
|||
} |
|||
|
|||
public JSONValue getValue(int i) { |
|||
return values.get(i); |
|||
} |
|||
public String getString(int i) { |
|||
return ((JSONString)values.get(i)).val; |
|||
} |
|||
public long getLong(int i) { |
|||
return ((JSONInt)values.get(i)).val; |
|||
} |
|||
public double getDouble(int i) { |
|||
return ((JSONFloat)values.get(i)).val; |
|||
} |
|||
public boolean getBoolean(int i) { |
|||
return ((JSONBool)values.get(i)).val; |
|||
} |
|||
public JSONArray getJSONArray(int i) { |
|||
return ((JSONArray)values.get(i)); |
|||
} |
|||
public JSONObject getJSONObject(int i) { |
|||
return ((JSONObject)values.get(i)); |
|||
} |
|||
/** |
|||
* Get the size of this array |
|||
* @return |
|||
*/ |
|||
public int size() { |
|||
return values.size(); |
|||
} |
|||
private void ensureCapacity(int i) { |
|||
while(this.size()<i)values.add(new JSONNull()); |
|||
} |
|||
|
|||
public JSONArray setValue(JSONValue v, int i) { |
|||
ensureCapacity(i+1); |
|||
values.set(i, v); |
|||
return this; |
|||
} |
|||
public JSONArray setLong(long l, int i) { |
|||
setValue(new JSONInt(l), i); |
|||
return this; |
|||
} |
|||
public JSONArray setDouble(double d, int i) { |
|||
setValue(new JSONFloat(d), i); |
|||
return this; |
|||
} |
|||
public JSONArray setString(String s, int i) { |
|||
setValue(new JSONString(s), i); |
|||
return this; |
|||
} |
|||
public JSONArray setNull(int i) { |
|||
setValue(new JSONNull(), i); |
|||
return this; |
|||
} |
|||
public JSONArray setBoolean(boolean b, int i) { |
|||
setValue(new JSONBool(b), i); |
|||
return this; |
|||
} |
|||
public JSONArray setJSONObject(JSONObject obj, int i) { |
|||
setValue(obj, i); |
|||
return this; |
|||
} |
|||
public JSONArray setJSONArray(JSONArray arr, int i) { |
|||
setValue(arr, i); |
|||
return this; |
|||
} |
|||
|
|||
public JSONArray addValue(JSONValue v) { |
|||
values.add(v); |
|||
return this; |
|||
} |
|||
public JSONArray addLong(long l) { |
|||
addValue(new JSONInt(l)); |
|||
return this; |
|||
} |
|||
public JSONArray addDouble(double d) { |
|||
addValue(new JSONFloat(d)); |
|||
return this; |
|||
} |
|||
public JSONArray addString(String s) { |
|||
addValue(new JSONString(s)); |
|||
return this; |
|||
} |
|||
public JSONArray addNull() { |
|||
addValue(new JSONNull()); |
|||
return this; |
|||
} |
|||
public JSONArray addBoolean(boolean b) { |
|||
addValue(new JSONBool(b)); |
|||
return this; |
|||
} |
|||
public JSONArray addJSONObject(JSONObject obj) { |
|||
addValue(obj); |
|||
return this; |
|||
} |
|||
public JSONArray addJSONArray(JSONArray arr) { |
|||
addValue(arr); |
|||
return this; |
|||
} |
|||
|
|||
public boolean exists(int i) { |
|||
return i>=0 && i<values.size(); |
|||
} |
|||
public boolean isString(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONString; |
|||
return false; |
|||
} |
|||
|
|||
public boolean isNum(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONNum; |
|||
return false; |
|||
} |
|||
public boolean isLong(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONInt; |
|||
return false; |
|||
} |
|||
public boolean isDouble(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONFloat; |
|||
return false; |
|||
} |
|||
public boolean isJSONObject(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONObject; |
|||
return false; |
|||
} |
|||
public boolean isJSONArray(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONArray; |
|||
return false; |
|||
} |
|||
public boolean isNull(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONNull || getValue(i) == null; |
|||
return false; |
|||
} |
|||
public boolean isBoolean(int i) { |
|||
if(exists(i))return getValue(i) instanceof JSONBool; |
|||
return false; |
|||
} |
|||
@Override |
|||
public String toString() { |
|||
StringBuilder buff = new StringBuilder(); |
|||
buff.append('['); |
|||
for(int i=0; i<values.size(); i++) { |
|||
if(i!=0)buff.append(','); |
|||
buff.append(values.get(i).toString()); |
|||
} |
|||
buff.append(']'); |
|||
return buff.toString(); |
|||
} |
|||
} |
@ -0,0 +1,153 @@ |
|||
package net.pop1040.JSON; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.HashMap; |
|||
import java.util.Iterator; |
|||
import java.util.Map.Entry; |
|||
import java.util.PrimitiveIterator.OfInt; |
|||
|
|||
import net.pop1040.JSON.JSONValue.JSONNum.JSONFloat; |
|||
import net.pop1040.JSON.JSONValue.JSONNum.JSONInt; |
|||
|
|||
public class JSONObject extends JSONValue{ |
|||
|
|||
HashMap<String, JSONValue> values; |
|||
|
|||
protected JSONObject(HashMap<String, JSONValue> vals) { |
|||
this.values = vals; |
|||
} |
|||
public JSONObject() { |
|||
values = new HashMap<>(); |
|||
} |
|||
|
|||
public static JSONObject parse(OfInt s) throws IOException { |
|||
HashMap<String, JSONValue> vals = new HashMap<String, JSONValue>(); |
|||
boolean firstRun = true; |
|||
while(true) { |
|||
int c = JSONValue.nextNonWhitespace(s); |
|||
if(c == '}') { |
|||
if(firstRun)break; |
|||
else throw new IOException("Invalid JSON Object, } was directly after ,"); |
|||
} |
|||
if(c != '"')throw new IOException("Invalid JSON Object, key is not valid string"); |
|||
|
|||
String name = JSONValue.parseString(s); |
|||
|
|||
c = JSONValue.nextNonWhitespace(s); |
|||
if(c != ':')throw new IOException("Invalid JSON Object, : was not found after key"); |
|||
|
|||
JSONValue.ParseRet val = JSONValue.parseValue(s); |
|||
|
|||
if(val.isEnd)throw new IOException("Invalid JSON Object, encountered " + val.lastChar + " is not a JSON value"); |
|||
vals.put(name, val.val); |
|||
if(val.lastChar == '}')break; |
|||
if(val.lastChar != ',')throw new IOException("Invalid JSON Object, encountered " + val.lastChar + " after a value, expected either , or }"); |
|||
|
|||
firstRun = false; |
|||
} |
|||
return new JSONObject(vals); |
|||
} |
|||
|
|||
public JSONValue getValue(String key) { |
|||
return values.get(key); |
|||
} |
|||
public String getString(String key) { |
|||
return ((JSONString)values.get(key)).val; |
|||
} |
|||
public long getLong(String key) { |
|||
return ((JSONInt)values.get(key)).val; |
|||
} |
|||
public double getDouble(String key) { |
|||
return ((JSONFloat)values.get(key)).val; |
|||
} |
|||
public boolean getBoolean(String key) { |
|||
return ((JSONBool)values.get(key)).val; |
|||
} |
|||
public JSONArray getArray(String key) { |
|||
return ((JSONArray)values.get(key)); |
|||
} |
|||
public JSONObject getObject(String key) { |
|||
return ((JSONObject)values.get(key)); |
|||
} |
|||
|
|||
public boolean exists(String key) { |
|||
return values.containsKey(key); |
|||
} |
|||
public boolean isString(String key) { |
|||
return values.get(key) instanceof JSONString; |
|||
} |
|||
public boolean isLong(String key) { |
|||
return values.get(key) instanceof JSONInt; |
|||
} |
|||
public boolean isDouble(String key) { |
|||
return values.get(key) instanceof JSONFloat; |
|||
} |
|||
public boolean isBoolean(String key) { |
|||
return values.get(key) instanceof JSONBool; |
|||
} |
|||
public boolean isNull(String key) { |
|||
return values.get(key) instanceof JSONNull || (values.containsKey(key) && values.get(key) == null); |
|||
} |
|||
public boolean isJSONObject(String key) { |
|||
return values.get(key) instanceof JSONObject; |
|||
} |
|||
public boolean isJSONArray(String key) { |
|||
return values.get(key) instanceof JSONArray; |
|||
} |
|||
|
|||
public JSONObject setNull(String key) { |
|||
values.put(key, new JSONNull()); |
|||
return this; |
|||
} |
|||
public JSONObject setString(String key, String val) { |
|||
values.put(key, new JSONString(val)); |
|||
return this; |
|||
} |
|||
public JSONObject setLong(String key, long val) { |
|||
values.put(key, new JSONInt(val)); |
|||
return this; |
|||
} |
|||
public JSONObject setDouble(String key, double val) { |
|||
values.put(key, new JSONFloat(val)); |
|||
return this; |
|||
} |
|||
public JSONObject setBoolean(String key, boolean val) { |
|||
values.put(key, new JSONBool(val)); |
|||
return this; |
|||
} |
|||
public JSONObject setJSONObject(String key, JSONObject val) { |
|||
values.put(key, val); |
|||
return this; |
|||
} |
|||
public JSONObject setJSONArray(String key, JSONArray val) { |
|||
values.put(key, val); |
|||
return this; |
|||
} |
|||
@Override |
|||
public String toString() { |
|||
StringBuilder buff = new StringBuilder(); |
|||
buff.append('{'); |
|||
Iterator<Entry<String, JSONValue>> iter = values.entrySet().iterator(); |
|||
if(iter.hasNext()) { |
|||
Entry<String, JSONValue> first = iter.next(); |
|||
buff.append(new JSONString(first.getKey()).toString()); |
|||
buff.append(':'); |
|||
buff.append(first.getValue().toString()); |
|||
} |
|||
while(iter.hasNext()){ |
|||
Entry<String, JSONValue> next = iter.next(); |
|||
buff.append(','); |
|||
buff.append(new JSONString(next.getKey()).toString()); |
|||
buff.append(':'); |
|||
buff.append(next.getValue().toString()); |
|||
} |
|||
buff.append('}'); |
|||
return buff.toString(); |
|||
} |
|||
|
|||
public String[] getKeys() { |
|||
return values.keySet().toArray(new String[] {}); |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,271 @@ |
|||
package net.pop1040.JSON; |
|||
|
|||
import java.io.IOException; |
|||
import java.util.PrimitiveIterator.OfInt; |
|||
|
|||
public class JSONValue { |
|||
|
|||
public static class JSONString extends JSONValue{ |
|||
String val; |
|||
public JSONString(String val) { |
|||
this.val = val; |
|||
} |
|||
@Override |
|||
public String toString() { |
|||
StringBuilder buff = new StringBuilder(); |
|||
buff.append('\"'); |
|||
for(int i=0; i<val.length(); i++) { |
|||
char c = val.charAt(i); |
|||
switch(c) { |
|||
case '\"': buff.append("\\\""); break; |
|||
case '\\': buff.append("\\\\"); break; |
|||
case '/': buff.append("\\/" ); break; |
|||
case '\b': buff.append("\\b"); break; |
|||
case '\r': buff.append("\\r"); break; |
|||
case '\n': buff.append("\\n"); break; |
|||
case '\f': buff.append("\\f"); break; |
|||
case '\t': buff.append("\\t"); break; |
|||
default: buff.append(c); break; |
|||
} |
|||
} |
|||
buff.append('\"'); |
|||
return buff.toString(); |
|||
} |
|||
} |
|||
public static class JSONNum extends JSONValue{ |
|||
|
|||
protected static class JSONInt extends JSONNum{ |
|||
long val; |
|||
public JSONInt(long l) { |
|||
this.val = l; |
|||
} |
|||
@Override |
|||
public String toString() { |
|||
return Long.toString(val); |
|||
} |
|||
} |
|||
protected static class JSONFloat extends JSONNum{ |
|||
double val; |
|||
public JSONFloat(double val) { |
|||
this.val = val; |
|||
} |
|||
@Override |
|||
public String toString() { |
|||
return Double.toString(val); |
|||
} |
|||
} |
|||
|
|||
} |
|||
public static class JSONBool extends JSONValue{ |
|||
boolean val; |
|||
public JSONBool(boolean val) { |
|||
this.val = val; |
|||
} |
|||
@Override |
|||
public String toString() { |
|||
return val ? "true" : "false"; |
|||
} |
|||
} |
|||
public static class JSONNull extends JSONValue{ |
|||
@Override |
|||
public String toString() { |
|||
return "null"; |
|||
} |
|||
} |
|||
|
|||
protected static String parseString(OfInt s) { |
|||
StringBuilder buf = new StringBuilder(); //place to store created string |
|||
while(s.hasNext()) { //so we exit if we run out of input, we should return before that |
|||
int c = s.nextInt(); |
|||
switch(c) { |
|||
case '"': //we've hit the end of the string, return |
|||
return buf.toString(); |
|||
case '\\': //need to deal with escaped characters |
|||
if(!s.hasNext())return null; |
|||
switch(s.nextInt()) { |
|||
case '"' :buf.append('"' );break; //most escaped characters are simple |
|||
case '\\':buf.append('\\');break; |
|||
case '/' :buf.append('/' );break; |
|||
case 'b' :buf.append('\b');break; |
|||
case 'f' :buf.append('\f');break; |
|||
case 'n' :buf.append('\n');break; |
|||
case 'r' :buf.append('\r');break; |
|||
case 't' :buf.append('\t');break; |
|||
case 'u' : // \\u is annoying though, its followed by four hex chars |
|||
char[] hexChars = new char[4]; //buffer for four (it is always four) hex chars |
|||
for(int i=0; i<4; i++)if(s.hasNext()){ //read them in |
|||
int hexChar = s.nextInt(); |
|||
if(!((hexChar >= '0' && hexChar <= '9') || |
|||
(hexChar >= 'a' && hexChar <= 'f') || |
|||
(hexChar >= 'A' && hexChar <= 'F')))return null; //make sure they are in fact valid hex |
|||
hexChars[i] = (char)hexChar; //put in buffer |
|||
}else return null; //unexpected end of input, we bail |
|||
buf.append((char) Integer.parseInt(new String(hexChars), 16)); //parse buffer of hex chars to integer, cast to character and append |
|||
//If like me you wonder how full unicode is encoded with \\u if \\u only supports four hex characters which is only 16 bit, anything |
|||
//past the Basic Multilingual Plane as its called (anything beyond 16bit) is stored in surrogate pairs just like how java handles things, |
|||
//so since string builder and strings just use characters, and thus also use surrogate pairs, adding a character like this means that if |
|||
//the JSON file contained valid surrogate pairs, so will the string, everything is handled for us |
|||
break; |
|||
default: //this is an invalid escape, bad string. |
|||
return null; |
|||
} |
|||
break; |
|||
default: //add normal characters, reason for the code point stuff is because JSON can be in UTF8, and we don't want emoji breaking our system |
|||
buf.appendCodePoint(c); //java uses 16bit characters (be glad we aren't dealing with 8bit characters for this), so emoji are stored in UTF-16 surrogate pairs normally |
|||
} //So that we don't have to deal with those manually, I'm just handling everything as codepoints, ints (32bit) that store a full unicode character. |
|||
|
|||
} |
|||
return null; |
|||
} |
|||
|
|||
protected static ParseRet parseValue(OfInt s) throws IOException { |
|||
int c = nextNonWhitespace(s); |
|||
switch(c) { |
|||
case '{': return new ParseRet(JSONObject.parse(s), nextNonWhitespace(s)); |
|||
case '[': return new ParseRet(JSONArray.parse(s), nextNonWhitespace(s)); |
|||
case '}': return new ParseRet((int)'}'); //Not actually a value, but rather the object ends early |
|||
case ']': return new ParseRet((int)']'); |
|||
case 'n':{ |
|||
if(!s.hasNext()) throw new IOException("JSON null was not completed"); |
|||
if(s.nextInt() != 'u')throw new IOException("JSON null was not completed"); |
|||
if(!s.hasNext()) throw new IOException("JSON null was not completed"); |
|||
if(s.nextInt() != 'l')throw new IOException("JSON null was not completed"); |
|||
if(!s.hasNext()) throw new IOException("JSON null was not completed"); |
|||
if(s.nextInt() != 'l')throw new IOException("JSON null was not completed"); |
|||
return new ParseRet(new JSONNull(), nextNonWhitespace(s)); |
|||
} |
|||
case 'f':{ |
|||
if(!s.hasNext()) throw new IOException("JSON boolean false was not completed"); |
|||
if(s.nextInt() != 'a')throw new IOException("JSON boolean false was not completed"); |
|||
if(!s.hasNext()) throw new IOException("JSON boolean false was not completed"); |
|||
if(s.nextInt() != 'l')throw new IOException("JSON boolean false was not completed"); |
|||
if(!s.hasNext()) throw new IOException("JSON boolean false was not completed"); |
|||
if(s.nextInt() != 's')throw new IOException("JSON boolean false was not completed"); |
|||
if(!s.hasNext()) throw new IOException("JSON boolean false was not completed"); |
|||
if(s.nextInt() != 'e')throw new IOException("JSON boolean false was not completed"); |
|||
return new ParseRet(new JSONBool(false), nextNonWhitespace(s)); |
|||
} |
|||
case 't':{ |
|||
if(!s.hasNext()) throw new IOException("JSON boolean true was not completed"); |
|||
if(s.nextInt() != 'r')throw new IOException("JSON boolean true was not completed"); |
|||
if(!s.hasNext()) throw new IOException("JSON boolean true was not completed"); |
|||
if(s.nextInt() != 'u')throw new IOException("JSON boolean true was not completed"); |
|||
if(!s.hasNext()) throw new IOException("JSON boolean true was not completed"); |
|||
if(s.nextInt() != 'e')throw new IOException("JSON boolean true was not completed"); |
|||
return new ParseRet(new JSONBool(true), nextNonWhitespace(s)); |
|||
} |
|||
case '\"': return new ParseRet(new JSONString(parseString(s)), nextNonWhitespace(s)); |
|||
default: //We'll now try to parse a number |
|||
boolean negative = false; |
|||
boolean powerNegative = false; |
|||
|
|||
if(c == '-') { //first check if its negative, if so we set a flag and get another character for the next section |
|||
negative = true; |
|||
if(!s.hasNext())throw new IOException("parsing number, ended unexpectedly after reading just a -"); |
|||
c = s.nextInt(); |
|||
} |
|||
|
|||
byte state = 0; |
|||
StringBuilder power = null, decimal = null, digit; //buffer for the digits before the decimal point, after but before an E, and after the E |
|||
digit = new StringBuilder(); //there should always be at least one digit before the decimal |
|||
|
|||
while(true) { |
|||
if(c >= '0' && c <= '9')switch(state) { |
|||
case 0: digit .appendCodePoint(c);break; |
|||
case 1: decimal.appendCodePoint(c);break; |
|||
case 2: power .appendCodePoint(c);break; |
|||
}else switch(state) { |
|||
case 0: |
|||
if(c == '.') { |
|||
if(digit.length() == 0)throw new IOException("JSON number must contain atleast one digit before a decmial"); |
|||
state = 1; |
|||
decimal = new StringBuilder(); |
|||
}else if(c == 'e' || c == 'E') { |
|||
if(digit.length() == 0)throw new IOException("JSON number must contain atleast one digit before scientific power"); |
|||
state = 2; |
|||
power = new StringBuilder(); |
|||
}else { |
|||
if(digit.length() == 0) { |
|||
if(negative)throw new IOException("JSON number must contain atleast one digit, a singular hyphen is not a number"); |
|||
else throw new IOException("Malformed JSON, not a valid value where a value was expected"); |
|||
} |
|||
return new ParseRet(new JSONNum.JSONInt(negative?-Long.parseLong(digit.toString()):Long.parseLong(digit.toString())), nextNonWhitespace(c, s)); |
|||
} |
|||
break; |
|||
case 1: |
|||
if(c == 'e' || c == 'E') { |
|||
if(decimal.length() == 0)throw new IOException("JSON number must contain atleast one digit between a decimal and scientific power"); |
|||
state = 2; |
|||
power = new StringBuilder(); |
|||
}else { |
|||
if(decimal.length() == 0)throw new IOException("JSON number has to have atleast one digit after a decimal"); |
|||
return new ParseRet(new JSONNum.JSONFloat(negative?-Double.parseDouble(digit.toString() + '.' + decimal.toString()):Double.parseDouble(digit.toString() + '.' + decimal.toString())), nextNonWhitespace(c, s)); |
|||
} |
|||
break; |
|||
case 2: |
|||
if(c == '-') { |
|||
if(power.length() != 0)throw new IOException("unexpectedly encountered - in the middle of parsing scientific notation"); |
|||
powerNegative = true; |
|||
if(!s.hasNext())throw new IOException("parsing number, ended unexpectedly after reading a negative sign after a scientific notation marker (e or E)"); |
|||
c = s.nextInt(); |
|||
}else if(c == '+') { |
|||
if(power.length() != 0)throw new IOException("unexpectedly encountered + in the middle of parsing scientific notation"); |
|||
if(!s.hasNext())throw new IOException("parsing number, ended unexpectedly after reading a positive sign after a scientific notation marker (e or E)"); |
|||
c = s.nextInt(); |
|||
}else { |
|||
if(power.length() == 0)throw new IOException("JSON number has to have atleast one digit after an e or E denoting a scientific power"); |
|||
if(decimal == null || decimal.length() == 0) { |
|||
return new ParseRet(new JSONNum.JSONFloat(negative?-Double.parseDouble(digit.toString() + (powerNegative?"E-":"E") + power.toString()):Double.parseDouble(digit.toString() + (powerNegative?"E-":"E") + power.toString())), nextNonWhitespace(c, s)); |
|||
} |
|||
return new ParseRet(new JSONNum.JSONFloat(negative?-Double.parseDouble(digit.toString() + '.' + decimal.toString() + (powerNegative?"E-":"E") + power.toString()):Double.parseDouble(digit.toString() + '.' + decimal.toString() + (powerNegative?"E-":"E") + power.toString())), nextNonWhitespace(c, s)); |
|||
} |
|||
break; |
|||
} |
|||
if(!s.hasNext())throw new IOException("parsing number, ended unexpectedly during parsing a number"); |
|||
c = s.nextInt(); |
|||
} |
|||
} |
|||
// return new ParseRet(null, 0); |
|||
} |
|||
|
|||
|
|||
|
|||
public static class ParseRet{ |
|||
boolean isEnd; |
|||
public JSONValue val; |
|||
public int lastChar; |
|||
public ParseRet(JSONValue val, int lastChar) { |
|||
this.val = val; |
|||
this.lastChar = lastChar; |
|||
this.isEnd = false; |
|||
} |
|||
public ParseRet(int lastChar) { |
|||
this.isEnd = true; |
|||
this.lastChar = lastChar; |
|||
} |
|||
@Override |
|||
public String toString() { |
|||
return "<" + isEnd + "," + val + "," + new String(Character.toChars(lastChar)) + "(" + lastChar + ")>"; |
|||
} |
|||
} |
|||
/** |
|||
* |
|||
* Gets the next character (codepoint specifically, because unicode support) that isn't whitespace. |
|||
* If the end of the input is encountered, 0 is returned |
|||
* @param s input |
|||
* @return next non-whitespace codepoint, 0 if none left |
|||
*/ |
|||
protected static int nextNonWhitespace(OfInt s){ |
|||
while(true) { |
|||
if(!s.hasNext())return 0; |
|||
int n = s.nextInt(); |
|||
if(n != '\t' && n != '\r' && n != '\n' && n != ' ')return n; |
|||
} |
|||
} |
|||
protected static int nextNonWhitespace(int n, OfInt s){ |
|||
if(n != '\t' && n != '\r' && n != '\n' && n != ' ')return n; |
|||
return nextNonWhitespace(s); |
|||
} |
|||
|
|||
|
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue