package com.framsticks.params; import java.util.ArrayList; // import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.Nonnull; // import org.apache.logging.log4j.Logger; // import org.apache.logging.log4j.LogManager; import com.framsticks.util.FramsticksException; import com.framsticks.util.lang.Numbers; import static com.framsticks.util.lang.Containers.filterInstanceof; /** * @author Piotr Sniegowski */ public abstract class ParamsUtil { // private final static Logger log = LogManager.getLogger(ParamsUtil.class.getName()); public static String readSourceToString(Source source) { StringBuilder result = new StringBuilder(); String line; while ((line = source.readLine()) != null) { result.append(line).append(" "); } source.close(); return result.toString(); } public static List stripAccess(List accesses, Class type) { List result = new ArrayList(); for (Access a : accesses) { Object object = a.getSelected(); if (!type.isInstance(object)) { throw new FramsticksException().msg("extracted object is of invalid type").arg("object", object).arg("desired", type).arg("actual", object.getClass()).arg("access", a); } result.add(type.cast(object)); } return result; } public static int takeAllNonNullValues(Access to, Access from) { int copied = 0; for (ValueParam f : filterInstanceof(from.getParams(), ValueParam.class)) { Object v = from.get(f, Object.class); if (v == null) { continue; } // if (to.get(f, Object.class) != null) { // continue; // } to.set(f, v); ++copied; } return copied; } public static int copyExistingParamsTypeSafe(Access to, Access from) { int copied = 0; for (ValueParam f : filterInstanceof(from.getParams(), ValueParam.class)) { Param t = from.getParam(f.getId()); if (!(t instanceof ValueParam)) { continue; } if (to.getClass() != f.getClass()) { continue; } to.set((ValueParam) t, from.get(f, Object.class)); ++copied; } return copied; } public static T selectObjectForAccess(Access access, Object object, Class type) { if (object == null) { return null; } if (!type.isInstance(object)) { throw new FramsticksException().msg("trying to select object of wrong type").arg("object", object).arg("type", object.getClass()).arg("in", access); } return type.cast(object); } public static int getNumberOfCompositeParamChild(Access access, Object child) { int count = access.getCompositeParamCount(); for (int i = 0; i < count; ++i) { if (access.get(i, Object.class) == child) { return i; } } return -1; } public static Object[] arguments(Object... args) { return args; } public static Param getParam(ParamCollection collection, int i) { return collection.getParam(i); } public static Param getParam(ParamCollection collection, String id) { return collection.getParam(id); } public static @Nonnull T castedParam(ParamCollection collection, @Nonnull final Param param, @Nonnull final Class type, Object name) { if (param == null) { // return null; throw new FramsticksException().msg("param is missing").arg("name", name).arg("in", collection); } if (!type.isInstance(param)) { // return null; throw new FramsticksException().msg("wrong type of param").arg("actual", param.getClass()).arg("requested", type).arg("in", collection); } return type.cast(param); } /** * Gets the param entry. * * @param i * the offset of parameter * @return the param entry */ public static @Nonnull T getParam(ParamCollection collection, final int i, @Nonnull final Class type) { return castedParam(collection, collection.getParam(i), type, i); } /** * Gets the param entry. * * @param id * the getId of parameter * @return the param entry */ public static @Nonnull T getParam(ParamCollection collection, @Nonnull final String id, @Nonnull final Class type) { return castedParam(collection, collection.getParam(id), type, id); } public static final String SERIALIZED = "@Serialized:"; public static class SerializationContext { protected final StringBuilder builder = new StringBuilder(); protected final ArrayList objects = new ArrayList<>(); protected void appendString(String value) { builder.append('"').append(value).append('"'); } protected boolean serializeInternal(Object value) { if (value == null) { builder.append("null"); return true; } /** indexOf is not used here, because it uses equals() internally, which results in StackOverflowError */ Class type = value.getClass(); if (type.equals(String.class)) { String stringValue = (String) value; appendString(stringValue); return stringValue.startsWith(SERIALIZED); } if (type.equals(Boolean.class)) { builder.append(((Boolean) value) ? "1" : "0"); return false; } if (type.equals(Double.class) || type.equals(Integer.class)) { builder.append(value.toString()); return false; } for (int i = 0; i < objects.size(); ++i) { if (objects.get(i) == value) { builder.append("^").append(i); return true; } } if (List.class.isAssignableFrom(type)) { objects.add(value); List list = (List) value; boolean placeComma = false; builder.append("["); for (Object element : list) { if (placeComma) { builder.append(","); } else { placeComma = true; } serializeInternal(element); } builder.append("]"); return true; } if (Map.class.isAssignableFrom(type)) { objects.add(value); Map map = (Map) value; boolean placeComma = false; builder.append("{"); for (Map.Entry entry : map.entrySet()) { if (placeComma) { builder.append(","); } else { placeComma = true; } if (!(entry.getKey() instanceof String)) { throw new FramsticksException().msg("non string keys are not allowed in serialization").arg("key type", entry.getKey().getClass()); } appendString((String) entry.getKey()); builder.append(":"); serializeInternal(entry.getValue()); } builder.append("}"); return true; } if (type.equals(OpaqueObject.class)) { builder.append(value.toString()); return true; } throw new FramsticksException().msg("invalid type for serialization").arg("type", type); } public String serialize(Object value) { if (value instanceof String) { String stringValue = (String) value; if (!stringValue.startsWith(SERIALIZED)) { return stringValue; } } boolean complex = serializeInternal(value); return (complex ? SERIALIZED : "") + builder.toString(); } } public static String serialize(T value) { return new SerializationContext().serialize(value); } public static class DeserializationContext { public static final Pattern OPAQUE_OBJECT_PATTERN = Pattern.compile("^(\\w+)<0x([a-fA-F0-9]+)>$"); @SuppressWarnings("serial") public static class Exception extends FramsticksException { } protected final ArrayList objects = new ArrayList<>(); protected int cursor = 0; protected final String input; /** * @param input */ public DeserializationContext(String input) { this.input = input; cursor = 0; } protected boolean isFinished() { return cursor == input.length(); } protected boolean is(char value) { if (isFinished()) { throw fail().msg("input ended"); } return input.charAt(cursor) == value; } protected boolean isOneOf(String values) { if (isFinished()) { throw fail().msg("input ended"); } return values.indexOf(input.charAt(cursor)) != -1; } protected char at() { return input.charAt(cursor); } protected char at(int pos) { return input.charAt(pos); } protected FramsticksException fail() { return new Exception().arg("at", cursor).arg("input", input); } protected void force(char value) { if (!is(value)) { throw fail().msg("invalid character").arg("expected", value).arg("found", at()); } next(); } protected boolean isAndNext(char value) { if (!is(value)) { return false; } next(); return true; } protected void next() { ++cursor; // log.info("at: {}|{}", input.substring(0, cursor), input.substring(cursor)); } protected void goToNext(char value) { while (cursor < input.length()) { if (is(value)) { return; } next(); } throw fail().msg("passed end").arg("searching", value); } protected String forceStringRemaining() { int start = cursor; while (true) { goToNext('"'); if (at(cursor - 1) != '\\') { next(); return input.substring(start, cursor - 1); } next(); // it is finishing that loop, because of throwing in goToNext() } // throw fail(); } public Object deserialize() { if (isAndNext('[')) { List list = new ArrayList<>(); objects.add(list); while (!isAndNext(']')) { if (!list.isEmpty()) { force(','); } Object child = deserialize(); list.add(child); } return list; } if (isAndNext('{')) { Map map = new TreeMap<>(); objects.add(map); while (!isAndNext('}')) { if (!map.isEmpty()) { force(','); } force('"'); String key = forceStringRemaining(); force(':'); Object value = deserialize(); map.put(key, value); } return map; } if (isAndNext('"')) { return forceStringRemaining(); } int start = cursor; while (!isFinished() && !isOneOf("]},")) { next(); } if (start == cursor) { throw fail().msg("empty value"); } String value = input.substring(start, cursor); if (value.equals("null")) { //TODO: add this to list? return null; } Matcher matcher = OPAQUE_OBJECT_PATTERN.matcher(value); if (matcher.matches()) { return new OpaqueObject(matcher.group(1), Long.parseLong(matcher.group(2), 16)); } Object number = DeserializationContext.tryParseNumber(value); if (number != null) { return number; } if (value.charAt(0) == '^') { Integer reference = Numbers.parse(value.substring(1), Integer.class); if (reference == null) { throw fail().msg("invalid reference").arg("reference", reference); } return objects.get(reference); } //TODO: parse ^ //TODO: parse opaque object throw fail().msg("unknown entity"); } public static Object tryParseNumber(String value) { Integer i = Numbers.parse(value, Integer.class); if (i != null) { return i; } Double d = Numbers.parse(value, Double.class); if (d != null) { return d; } return null; } } public static Object deserialize(String value) { Object number = DeserializationContext.tryParseNumber(value); if (number != null) { return number; } if (!value.startsWith(SERIALIZED)) { return value; } return new DeserializationContext(value.substring(SERIALIZED.length())).deserialize(); } public static T deserialize(String value, Class type) { Object object = deserialize(value); return type.cast(object); } }