<commit_before>//
//  JSONConvertible.swift
//  PerfectLib
//
//  Created by <NAME> on 2016-01-21.
//  Copyright © 2016 Treefrog. All rights reserved.
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Affero General Public License as
//	published by the Free Software Foundation, either version 3 of the
//	License, or (at your option) any later version, as supplemented by the
//	Perfect Additional Terms.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Affero General Public License, as supplemented by the
//	Perfect Additional Terms, for more details.
//
//	You should have received a copy of the GNU Affero General Public License
//	and the Perfect Additional Terms that immediately follow the terms and
//	conditions of the GNU Affero General Public License along with this
//	program. If not, see <http://www.perfect.org/AGPL_3_0_With_Perfect_Additional_Terms.txt>.
//

#if os(Linux)
	import SwiftGlibc
#else
	import Darwin
#endif

/// This non-instantiable object serves as an access point to a registry for JSONConvertibleObjects
/// A JSONConvertibleObject is a custom class or struct which can be converted to and from JSON.
public class JSONDecoding {
	private init() {}
	
	public static let objectIdentifierKey = "_jsonobjid"
	public typealias JSONConvertibleObjectCreator = () -> JSONConvertibleObject
	
	static private var jsonDecodableRegistry = [String:JSONConvertibleObjectCreator]()
	
	static public func registerJSONDecodable(name: String, creator: JSONConvertibleObjectCreator) {
		JSONDecoding.jsonDecodableRegistry[name] = creator
	}
	
	static public func createJSONConvertibleObject(values:[String:Any]) -> JSONConvertibleObject? {
		guard let objkey = values[JSONDecoding.objectIdentifierKey] as? String else {
			return nil
		}
		return JSONDecoding.createJSONConvertibleObject(objkey, values: values)
	}
	
	static public func createJSONConvertibleObject(name: String, values:[String:Any]) -> JSONConvertibleObject? {
		guard let creator = JSONDecoding.jsonDecodableRegistry[name] else {
			return nil
		}
		let jsonObj = creator()
		jsonObj.setJSONValues(values)
		return jsonObj
	}
}

public protocol JSONConvertible {
	
	/// Returns the JSON encoded String for any JSONConvertible type.
	func jsonEncodedString() throws -> String
}

public protocol JSONConvertibleObject: JSONConvertible {
	func setJSONValues(values:[String:Any])
	func getJSONValues() -> [String:Any]
}

public extension JSONConvertibleObject {
	func getJSONValue<T: JSONConvertible>(named: String, from:[String:Any], defaultValue: T) -> T {
		let f = from[named]
		if let v = f as? T {
			return v
		}
		return defaultValue
	}
	func jsonEncodedString() throws -> String {
		return try self.getJSONValues().jsonEncodedString()
	}
}

public enum JSONConversionError: ErrorType {
	case NotConvertible(Any)
	case InvalidKey(Any)
	case SyntaxError
}

private let jsonBackSlash = UnicodeScalar(UInt32(92))
private let jsonBackSpace = UnicodeScalar(UInt32(8))
private let jsonFormFeed = UnicodeScalar(UInt32(12))
private let jsonLF = UnicodeScalar(UInt32(10))
private let jsonCR = UnicodeScalar(UInt32(13))
private let jsonTab = UnicodeScalar(UInt32(9))
private let jsonQuoteDouble = UnicodeScalar(UInt32(34))

private let jsonOpenObject = UnicodeScalar(UInt32(123))
private let jsonOpenArray = UnicodeScalar(UInt32(91))
private let jsonCloseObject = UnicodeScalar(UInt32(125))
private let jsonCloseArray = UnicodeScalar(UInt32(93))
private let jsonWhiteSpace = UnicodeScalar(UInt32(32))
private let jsonColon = UnicodeScalar(UInt32(58))
private let jsonComma = UnicodeScalar(UInt32(44))

// this is a stand-in for a JSON null
// may be produced when decoding
public struct JSONConvertibleNull: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		return "null"
	}
}

extension String: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		var s = "\""
		for uchar in self.unicodeScalars {
			switch(uchar) {
			case jsonBackSlash:
				s.appendContentsOf("\\\\")
			case jsonQuoteDouble:
				s.appendContentsOf("\\\"")
			case jsonBackSpace:
				s.appendContentsOf("\\b")
			case jsonFormFeed:
				s.appendContentsOf("\\f")
			case jsonLF:
				s.appendContentsOf("\\n")
			case jsonCR:
				s.appendContentsOf("\\r")
			case jsonTab:
				s.appendContentsOf("\\t")
			default:
				s.append(uchar)
			}
		}
		s.appendContentsOf("\"")
		return s
	}
}

extension Int: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		return String(self)
	}
}

extension Double: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		return String(self)
	}
}

extension Optional: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		if self == nil {
			return "null"
		} else if let v = self! as? JSONConvertible {
			return try v.jsonEncodedString()
		}
		throw JSONConversionError.NotConvertible(self)
	}
}

extension Bool: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		if true == self {
			return "true"
		}
		return "false"
	}
}

// !FIX! Downcasting to protocol dows not work on Linux
// Not sure if this is intentional, or a bug.
private func jsonEncodedStringWorkAround(o: Any) throws -> String {
	switch o {
	case let jsonAble as JSONConvertible:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as String:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as Int:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as Double:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as Bool:
		return try jsonAble.jsonEncodedString()
	default:
		throw JSONConversionError.NotConvertible(o)
	}
}

extension Array: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		var s = "["
		var first = true
		for v in self {
			if !first {
				s.appendContentsOf(",")
			} else {
				first = false
			}
			s.appendContentsOf(try jsonEncodedStringWorkAround(v))
		}
		s.appendContentsOf("]")
		return s<commit_msg>Added cases for Array and Dictionary to convertible work-around.<commit_after>//
//  JSONConvertible.swift
//  PerfectLib
//
//  Created by <NAME> on 2016-01-21.
//  Copyright © 2016 Treefrog. All rights reserved.
//
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU Affero General Public License as
//	published by the Free Software Foundation, either version 3 of the
//	License, or (at your option) any later version, as supplemented by the
//	Perfect Additional Terms.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU Affero General Public License, as supplemented by the
//	Perfect Additional Terms, for more details.
//
//	You should have received a copy of the GNU Affero General Public License
//	and the Perfect Additional Terms that immediately follow the terms and
//	conditions of the GNU Affero General Public License along with this
//	program. If not, see <http://www.perfect.org/AGPL_3_0_With_Perfect_Additional_Terms.txt>.
//

#if os(Linux)
	import SwiftGlibc
#else
	import Darwin
#endif

/// This non-instantiable object serves as an access point to a registry for JSONConvertibleObjects
/// A JSONConvertibleObject is a custom class or struct which can be converted to and from JSON.
public class JSONDecoding {
	private init() {}
	
	public static let objectIdentifierKey = "_jsonobjid"
	public typealias JSONConvertibleObjectCreator = () -> JSONConvertibleObject
	
	static private var jsonDecodableRegistry = [String:JSONConvertibleObjectCreator]()
	
	static public func registerJSONDecodable(name: String, creator: JSONConvertibleObjectCreator) {
		JSONDecoding.jsonDecodableRegistry[name] = creator
	}
	
	static public func createJSONConvertibleObject(values:[String:Any]) -> JSONConvertibleObject? {
		guard let objkey = values[JSONDecoding.objectIdentifierKey] as? String else {
			return nil
		}
		return JSONDecoding.createJSONConvertibleObject(objkey, values: values)
	}
	
	static public func createJSONConvertibleObject(name: String, values:[String:Any]) -> JSONConvertibleObject? {
		guard let creator = JSONDecoding.jsonDecodableRegistry[name] else {
			return nil
		}
		let jsonObj = creator()
		jsonObj.setJSONValues(values)
		return jsonObj
	}
}

public protocol JSONConvertible {
	
	/// Returns the JSON encoded String for any JSONConvertible type.
	func jsonEncodedString() throws -> String
}

public protocol JSONConvertibleObject: JSONConvertible {
	func setJSONValues(values:[String:Any])
	func getJSONValues() -> [String:Any]
}

public extension JSONConvertibleObject {
	func getJSONValue<T: JSONConvertible>(named: String, from:[String:Any], defaultValue: T) -> T {
		let f = from[named]
		if let v = f as? T {
			return v
		}
		return defaultValue
	}
	func jsonEncodedString() throws -> String {
		return try self.getJSONValues().jsonEncodedString()
	}
}

public enum JSONConversionError: ErrorType {
	case NotConvertible(Any)
	case InvalidKey(Any)
	case SyntaxError
}

private let jsonBackSlash = UnicodeScalar(UInt32(92))
private let jsonBackSpace = UnicodeScalar(UInt32(8))
private let jsonFormFeed = UnicodeScalar(UInt32(12))
private let jsonLF = UnicodeScalar(UInt32(10))
private let jsonCR = UnicodeScalar(UInt32(13))
private let jsonTab = UnicodeScalar(UInt32(9))
private let jsonQuoteDouble = UnicodeScalar(UInt32(34))

private let jsonOpenObject = UnicodeScalar(UInt32(123))
private let jsonOpenArray = UnicodeScalar(UInt32(91))
private let jsonCloseObject = UnicodeScalar(UInt32(125))
private let jsonCloseArray = UnicodeScalar(UInt32(93))
private let jsonWhiteSpace = UnicodeScalar(UInt32(32))
private let jsonColon = UnicodeScalar(UInt32(58))
private let jsonComma = UnicodeScalar(UInt32(44))

// this is a stand-in for a JSON null
// may be produced when decoding
public struct JSONConvertibleNull: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		return "null"
	}
}

extension String: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		var s = "\""
		for uchar in self.unicodeScalars {
			switch(uchar) {
			case jsonBackSlash:
				s.appendContentsOf("\\\\")
			case jsonQuoteDouble:
				s.appendContentsOf("\\\"")
			case jsonBackSpace:
				s.appendContentsOf("\\b")
			case jsonFormFeed:
				s.appendContentsOf("\\f")
			case jsonLF:
				s.appendContentsOf("\\n")
			case jsonCR:
				s.appendContentsOf("\\r")
			case jsonTab:
				s.appendContentsOf("\\t")
			default:
				s.append(uchar)
			}
		}
		s.appendContentsOf("\"")
		return s
	}
}

extension Int: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		return String(self)
	}
}

extension Double: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		return String(self)
	}
}

extension Optional: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		if self == nil {
			return "null"
		} else if let v = self! as? JSONConvertible {
			return try v.jsonEncodedString()
		}
		throw JSONConversionError.NotConvertible(self)
	}
}

extension Bool: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		if true == self {
			return "true"
		}
		return "false"
	}
}

// !FIX! Downcasting to protocol does not work on Linux
// Not sure if this is intentional, or a bug.
private func jsonEncodedStringWorkAround(o: Any) throws -> String {
	switch o {
	case let jsonAble as JSONConvertible:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as String:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as Int:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as Double:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as Bool:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as [Any]:
		return try jsonAble.jsonEncodedString()
	case let jsonAble as [String:Any]:
		return try jsonAble.jsonEncodedString()
	default:
		throw JSONConversionError.NotConvertible(o)
	}
}

extension Array: JSONConvertible {
	public func jsonEncodedString() throws -> String {
		var s = "["
		var first = true
		for v in self {
			if !first {
				s.appendContentsOf(",")
			} else {
				first = false
			}
			s.appendContentsOf(try jsonEncodedStringWorkAround(v))
		}
		s.appendContentsOf("]")
		return s
{'fraction_non_alphanumeric': 0.09201720841300191, 'fraction_numerical': 0.01131293817718292, 'mean_word_length': 4.3485300383468255, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5263894', 'n_tokens_mistral': 4113, 'n_tokens_neox': 3700, 'n_words': 1217}