package com.laytonsmith.core.functions;
import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.PureUtilities.XMLDocument;
import com.laytonsmith.annotations.api;
import com.laytonsmith.annotations.core;
import com.laytonsmith.annotations.noboilerplate;
import com.laytonsmith.core.CHVersion;
import com.laytonsmith.core.Static;
import com.laytonsmith.core.constructs.CArray;
import com.laytonsmith.core.constructs.CNull;
import com.laytonsmith.core.constructs.CString;
import com.laytonsmith.core.constructs.Construct;
import com.laytonsmith.core.constructs.Target;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.exceptions.CRE.CRECastException;
import com.laytonsmith.core.exceptions.CRE.CREFormatException;
import com.laytonsmith.core.exceptions.CRE.CREThrowable;
import com.laytonsmith.core.exceptions.ConfigRuntimeException;
import com.laytonsmith.core.exceptions.MarshalException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import javax.xml.xpath.XPathExpressionException;
import org.xml.sax.SAXException;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.parser.ParserException;
import org.yaml.snakeyaml.scanner.ScannerException;
/**
*
*/
@core
public class DataTransformations {
public static String docs() {
return "This class provides functions that are able to transform data from native objects to"
+ " their serialized forms, i.e. json, ini, etc.";
}
@api
public static class json_encode extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray ca = Static.getArray(args[0], t);
try {
return new CString(Construct.json_encode(ca, t), t);
} catch (MarshalException ex) {
throw new CRECastException(ex.getMessage(), t);
}
}
@Override
public String getName() {
return "json_encode";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "string {array} Converts an array into a JSON encoded string. Both normal and associative arrays are supported."
+ " Within the array, only primitives and arrays can be encoded.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
}
@api
public static class json_decode extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CREFormatException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
String s = args[0].val();
try {
return Construct.json_decode(s, t);
} catch (MarshalException ex) {
throw new CREFormatException("The input JSON string is improperly formatted. Check your formatting and try again.", t, ex);
}
}
@Override
public String getName() {
return "json_decode";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "array {string} Takes a JSON encoded string, and returns an array, either normal or associative,"
+ " depending on the contents of the JSON string. If the JSON string is improperly formatted,"
+ " a FormatException is thrown.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
}
@api
public static class yml_encode extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
CArray ca = Static.getArray(args[0], t);
boolean prettyPrint = false;
if(args.length == 2){
prettyPrint = Static.getBoolean(args[1]);
}
DumperOptions options = new DumperOptions();
if (prettyPrint) {
options.setPrettyFlow(true);
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
}
Yaml yaml = new Yaml(options);
try{
return new CString(yaml.dump(Construct.GetPOJO(ca)), t);
} catch(ClassCastException ex){
throw new CRECastException(ex.getMessage(), t);
}
}
@Override
public String getName() {
return "yml_encode";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2};
}
@Override
public String docs() {
return "string {array, [prettyPrint]} Converts an array into a YML encoded string. Only associative arrays are supported."
+ " prettyPrint defaults to false. Within the array, only primitives and arrays can be encoded.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
}
@api
public static class yml_decode extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CREFormatException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
String data = args[0].val();
Yaml yaml = new Yaml();
Object ret = null;
Exception cause = null;
try {
ret = yaml.load(data);
} catch(ScannerException | ParserException ex){
cause = ex;
}
if(!(ret instanceof Map) && !(ret instanceof Collection)){
throw new CREFormatException("Improperly formatted YML", t, cause);
}
return Construct.GetConstruct(ret);
}
@Override
public String getName() {
return "yml_decode";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "array {string} Takes a YML encoded string, and returns an associative array,"
+ " depending on the contents of the YML string. If the YML string is improperly formatted,"
+ " a FormatException is thrown.";
}
@Override
public CHVersion since() {
return CHVersion.V3_3_1;
}
}
@api
public static class ini_encode extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CREFormatException.class, CRECastException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
Properties props = new Properties();
CArray arr = Static.getArray(args[0], t);
String comment = null;
if(args.length == 2){
comment = args[1].val();
}
if(!arr.inAssociativeMode()){
throw new CRECastException("Expecting an associative array", t);
}
for(String key : arr.stringKeySet()){
Construct c = arr.get(key, t);
String val;
if(c instanceof CNull){
val = "";
} else if(c instanceof CArray){
throw new CRECastException("Arrays cannot be encoded with ini_encode.", t);
} else {
val = c.val();
}
props.setProperty(key, val);
}
StringWriter writer = new StringWriter();
try {
props.store(writer, comment);
} catch (IOException ex) {
// Won't happen
}
return new CString(writer.toString(), t);
}
@Override
public String getName() {
return "ini_encode";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1, 2};
}
@Override
public String docs() {
return "string {array, [comment]} Encodes an array into an INI format output. An associative array is expected, and"
+ " a format exception is thrown if it is a normal array. The comment is optional, but if provided will"
+ " be added to the header of the returned string. Inner arrays cannot be stored, and will"
+ " throw a CastException if attempted. Nulls are encoded as an empty string,"
+ " so when reading the value back in, the difference between '' and null is lost. All values are"
+ " stored as strings, so if 1 is stored, it will be returned as a string '1'. This is a limitation"
+ " of the ini format, as it is expected that the code that reads the ini knows what the type of the"
+ " data is anticipated, not the data itself.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
}
@api
public static class ini_decode extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CREFormatException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
Properties props = new Properties();
Reader reader = new StringReader(args[0].val());
try {
props.load(reader);
} catch (IOException ex) {
throw new CREFormatException(ex.getMessage(), t);
}
CArray arr = CArray.GetAssociativeArray(t);
for(String key : props.stringPropertyNames()){
arr.set(key, props.getProperty(key));
}
return arr;
}
@Override
public String getName() {
return "ini_decode";
}
@Override
public Integer[] numArgs() {
return new Integer[]{1};
}
@Override
public String docs() {
return "array {string} Returns an array, given an INI format input. INI files are loosely defined"
+ " as a set of key->value pairs, which lends itself to an associative array format. Key value"
+ " pairs are denoted usually by a <code>key=value</code> format. The specific rules for"
+ " decoding an INI file can be found [http://docs.oracle.com/javase/6/docs/api/java/util/Properties.html#load%28java.io.Reader%29 here]."
+ " An associative array is returned. All values are"
+ " stored as strings, so if 1 was stored, it will be returned as a string '1'. This is a limitation"
+ " of the ini format, as it is expected that the code that reads the ini knows what the type of the"
+ " data is anticipated, not the data itself. You can easily cast data that is expected to be numeric"
+ " via the {{function|integer}} and {{function|float}} functions when reading in the data if exact types"
+ " are truly needed. INI doesn't easily support non-string values, if that is needed, consider using"
+ " {{function|json_encode}}/{{function|json_decode}} instead.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
}
@api
@noboilerplate
public static class xml_read extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
return new Class[]{CREFormatException.class};
}
@Override
public boolean isRestricted() {
return false;
}
@Override
public Boolean runAsync() {
return null;
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
XMLDocument doc;
try {
doc = new XMLDocument(args[0].val());
} catch (SAXException ex) {
throw new CREFormatException("Malformed XML.", t, ex);
}
try {
return Static.resolveConstruct(doc.getNode(args[1].val()), t);
} catch (XPathExpressionException ex) {
throw new CREFormatException(ex.getMessage(), t, ex);
}
}
@Override
public String getName() {
return "xml_read";
}
@Override
public Integer[] numArgs() {
return new Integer[]{2};
}
@Override
public String docs() {
return "mixed {xml, xpath} Reads a field from some xml using an XPath address. The XPath address is assumed"
+ " to be absolute, even if it doesn't start with a '/'.";
}
@Override
public Version since() {
return CHVersion.V3_3_1;
}
}
//@api
public static class xml_write extends AbstractFunction {
@Override
public Class<? extends CREThrowable>[] thrown() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean isRestricted() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Boolean runAsync() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Construct exec(Target t, Environment environment, Construct... args) throws ConfigRuntimeException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String getName() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Integer[] numArgs() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public String docs() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Version since() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
}