package com.framsticks.params; import java.util.LinkedList; import java.util.ListIterator; import javax.annotation.Nullable; import org.apache.commons.lang3.ClassUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import com.framsticks.communication.File; import com.framsticks.params.types.ListParam; import com.framsticks.parsers.MultiParamLoader; import com.framsticks.structure.messages.Result; import com.framsticks.util.FramsticksException; import com.framsticks.util.FramsticksUnsupportedOperationException; import com.framsticks.util.Misc; import com.framsticks.util.UnimplementedException; import com.framsticks.util.lang.Casting; import com.framsticks.util.lang.Containers; import com.framsticks.util.lang.Holder; import com.framsticks.util.lang.Pair; // import com.framsticks.util.lang.Containers; import static com.framsticks.params.SetStateFlags.*; import static com.framsticks.util.lang.Containers.filterInstanceof; public final class AccessOperations { private final static Logger log = LogManager.getLogger(AccessOperations.class); /** * */ private AccessOperations() { } /** * Simple String key, value class. */ public static class Entry { public final String key; public final String value; public Entry(String key, String value) { this.key = key; this.value = value; } @Override public String toString() { return key + " = " + value; } } private static Entry readEntry(Source source) { String line; String key = null; StringBuilder value = null; while ((line = source.readLine()) != null) { if (key == null) { int colonIndex = line.indexOf(':'); if (colonIndex == -1) { return null; } key = line.substring(0, colonIndex); String inlineValue = line.substring(colonIndex + 1); if (!inlineValue.startsWith("~")) { return new Entry(key, inlineValue); } value = new StringBuilder(); value.append(inlineValue.substring(1)); continue; } if (value.length() != 0) { value.append(System.getProperty("line.separator")); } if (line.endsWith("~") && !line.endsWith("\\~")) { value.append(line.substring(0, line.length() - 1)); return new Entry(key, value.toString().replaceAll("\\\\~", "~")); } value.append(line); } return null; } public static A assureSelected(A access) { if (access.getSelected() == null) { access.select(access.createAccessee()); } return access; } public static Access loadAll(@Nullable final Access rootAccess, Source source, final Registry registry) { final MultiParamLoader loader = new MultiParamLoader(); log.trace("loading all from {} into {}", source, rootAccess); loader.setNewSource(source); final LinkedList accessesStack = new LinkedList<>(); if (rootAccess != null) { assureSelected(rootAccess); accessesStack.add(rootAccess); } final Holder first = new Holder<>(true); final Holder needAdd = new Holder<>(); final Holder currentAccess = new Holder<>(); final Holder> parent = new Holder<>(); loader.setAccessProvider(new AccessProvider() { @Override public Access getAccess(String name) { if (first.get()) { first.set(false); if (rootAccess != null) { if (name.equals(rootAccess.getTypeId())) { needAdd.set(false); currentAccess.set(rootAccess); return rootAccess; } } else { Access access = registry.createAccess(name); needAdd.set(false); currentAccess.set(access); return access; } } ListIterator accessIterator = accessesStack.listIterator(accessesStack.size()); parent.set(null); // log.debug("accesses stack: {}", accessesStack); while (accessIterator.hasPrevious()) { Access a = accessIterator.previous(); assert a != null; for (CompositeParam p : Containers.filterInstanceof(a.getParams(), CompositeParam.class)) { if (p.getContainedTypeName().equals(name)) { if (parent.get() != null) { throw new FramsticksException().msg("ambiguity encountered during loading").arg("name", name); } if (p instanceof ListParam) { ListAccess listAccess = Casting.assertCast(ListAccess.class, registry.prepareAccess(p, true)); Object list = a.get(p, Object.class); if (list == null) { list = listAccess.createAccessee(); a.set(p, list); } listAccess.select(list); parent.set(new Pair(listAccess, listAccess.prepareParamFor(Integer.toString(listAccess.getParamCount())))); } else { parent.set(Pair.make(a, p)); } } } if (parent.get() == null) { log.trace("{} cannot be placed in {}", name, a); accessIterator.remove(); } } if (parent.get() == null) { throw new FramsticksException().msg("failed to find place for loaded object").arg("name", name); //.arg("in", accessesStack); } currentAccess.set(registry.prepareAccess(parent.get().second, true)); Object object = parent.get().first.get(parent.get().second, Object.class); if (object != null) { currentAccess.get().select(object); needAdd.set(false); } else { object = currentAccess.get().createAccessee(); currentAccess.get().select(object); needAdd.set(true); } return currentAccess.get(); } }); loader.addListener(MultiParamLoader.Status.AfterObject, new MultiParamLoader.StatusListener() { @Override public void onStatusChange() { if (needAdd.get()) { parent.get().first.set(parent.get().second, currentAccess.get().getSelected()); } if (currentAccess.get() != rootAccess) { accessesStack.add(currentAccess.get()); } currentAccess.set(null); } }); loader.go(); if (accessesStack.isEmpty()) { throw new FramsticksException().msg("failed to load from source").arg("source", source); } return accessesStack.get(0); } public static S saveAll(Access access, S sink, Registry registry) { if (access instanceof ObjectAccess) { savePrimitives(access, sink); } for (CompositeParam p : filterInstanceof(access.getParams(), CompositeParam.class)) { Object child = access.get(p, Object.class); if (child == null) { continue; } saveAll(registry.prepareAccess(p, true).select(child), sink, registry); } return sink; } public static void saveComposites(Access access, Sink sink, Registry registry) { for (CompositeParam p : filterInstanceof(access.getParams(), CompositeParam.class)) { Object child = access.get(p, Object.class); if (child == null) { continue; } savePrimitives(registry.prepareAccess(p, true).select(child), sink); } } public static void savePrimitives(Access access, Sink sink) { if (access instanceof ObjectAccess) { ObjectAccess objectAccess = (ObjectAccess) access; boolean headerNeeded = true; // sink.print(framsClass.getId()).print(":").breakLine(); for (PrimitiveParam p : filterInstanceof(access.getParams(), PrimitiveParam.class)) { Object value = objectAccess.get(p, Object.class); if ((value == null) || value.equals(p.getDef(Object.class))) { continue; } if (headerNeeded) { sink.print(access.getTypeId()).print(":").breakLine(); headerNeeded = false; } String stringValue = p.serialize(value); sink.print(p.getId()).print(":").print(stringValue); // p.save(sink, stringValue); sink.breakLine(); } if (!headerNeeded) { sink.breakLine(); } return; } throw new FramsticksException().msg("invalid type of access for primitive save").arg("access", access); } public static void save(Access access, Sink sink) { if (access instanceof ObjectAccess) { savePrimitives(access, sink); return; } if (access instanceof ListAccess) { ListAccess listAccess = (ListAccess) access; for (CompositeParam p : filterInstanceof(listAccess.getParams(), CompositeParam.class)) { Object child = listAccess.get(p, Object.class); //this is probably an assertion assert child != null; save(listAccess.getElementAccess().select(child), sink); } return; } throw new FramsticksException().msg("unknown access category").arg("access", access); } public static void loadComposites(Access access, Source source, final Registry registry) { if (access instanceof ObjectAccess) { final ObjectAccess objectAccess = (ObjectAccess) access; MultiParamLoader loader = new MultiParamLoader(); loader.setNewSource(source); loader.setAccessProvider(new AccessProvider() { @Override public Access getAccess(String name) { CompositeParam result = null; for (CompositeParam p : filterInstanceof(objectAccess.getParams(), CompositeParam.class)) { if (p.getContainedTypeName().equals(name)) { if (result != null) { throw new FramsticksException().msg("class name is ambiguous in access").arg("name", name).arg("first candidate", result).arg("second candidate", p); } result = p; } } if (result == null) { throw new FramsticksException().msg("class name is unknown").arg("name", name).arg("in", objectAccess); } return registry.prepareAccess(result, true).select(objectAccess.get(result, Object.class)); } }); loader.go(); return; } throw new UnimplementedException().msg("unknown access category").arg("access", access); } public static void load(Access access, Source source) { if (!(access instanceof ObjectAccess)) { throw new FramsticksException().msg("access is not an object access").arg("access", access); } Entry entry; while ((entry = readEntry(source)) != null) { Param param = access.getParam(entry.key); if (param == null) { throw new FramsticksException().msg("param not found in access").arg("name", entry.key).arg("access", access); } if (!(param instanceof ValueParam)) { throw new FramsticksException().msg("param is not a value param").arg("param", param).arg("access", access); } if ((param.getFlags() & ParamFlags.DONTLOAD) == 0) { ValueParam valueParam = (ValueParam) param; // Object object = valueParam.deserialize(entry.value, null, Object.class); int retFlags = access.set(valueParam, entry.value); if ((retFlags & (PSET_HITMIN | PSET_HITMAX)) != 0) { String which = ((retFlags & PSET_HITMIN) != 0) ? "small" : "big"; log.warn("value of key '{}' was too {}, adjusted", entry.key, which); } } } } public interface Adjuster { public Holder adjust(ValueParam param); public Class getParamType(); } public static class MinAdjuster implements Adjuster { /** * */ public MinAdjuster() { } @Override public Class getParamType() { return PrimitiveParam.class; } @Override public Holder adjust(ValueParam param) { Object value = ((PrimitiveParam) param).getMin(Object.class); if (value == null) { return null; } return Holder.make(value); } } public static class MaxAdjuster implements Adjuster { /** * */ public MaxAdjuster() { } @Override public Class getParamType() { return PrimitiveParam.class; } @Override public Holder adjust(ValueParam param) { Object value = ((PrimitiveParam) param).getMax(Object.class); if (value == null) { return null; } return Holder.make(value); } } public static class DefAdjuster implements Adjuster { protected final boolean numericOnly; /** * @param numericOnly */ public DefAdjuster(boolean numericOnly) { this.numericOnly = numericOnly; } public Class getParamType() { return ValueParam.class; } @Override public Holder adjust(ValueParam param) { if (numericOnly && !(param.isNumeric())) { return null; } return Holder.make(param.getDef(Object.class)); } } public static void adjustAll(Access access, Adjuster adjuster) { for (ValueParam param : Containers.filterInstanceof(access.getParams(), adjuster.getParamType())) { Holder value = adjuster.adjust(param); if (value != null) { access.set(param, value.get()); } } } public static Object wrapValueInResultIfPrimitive(Object object) { Class javaClass = object.getClass(); if (ClassUtils.isPrimitiveOrWrapper(javaClass)) { return new Result(object); } if (javaClass.equals(String.class)) { return new Result(object); } return object; } /** * * If both arguments are File, than do nothing; otherwise: * * If from argument is a File: * - if toJavaClass is Object.class, than try read using registry * - otherwise: try use loadComposites * * If to argument is a File: * - use Registry to saveAll * */ public static T convert(Class toJavaClass, F from, Registry registry) { log.trace("converting from {} to {}", from, toJavaClass); if (toJavaClass.equals(from.getClass())) { return toJavaClass.cast(from); } if (from instanceof File) { File file = (File) from; return Casting.throwCast(toJavaClass, loadAll((toJavaClass.equals(Object.class) ? null : registry.createAccess(toJavaClass)), file.getContent(), registry).getSelected()); } if (toJavaClass.equals(File.class)) { ListSink sink = new ListSink(); saveAll(registry.createAccess(from.getClass()).select(from), sink, registry); return Casting.throwCast(toJavaClass, new File("", new ListSource(sink.getOut()))); } throw new FramsticksUnsupportedOperationException().msg("conversion").arg("from", from.getClass()).arg("to", toJavaClass); } @SuppressWarnings("serial") public static class EqualityException extends FramsticksException { } public static void assureEquality(Access a, Access b, Registry registry) { try { if (a.getParamCount() != b.getParamCount()) { throw new EqualityException().msg("param count not equal").arg("left", a.getParamCount()).arg("right", b.getParamCount()); } for (ValueParam avp : Containers.filterInstanceof(a.getParams(), ValueParam.class)) { Param bp = b.getParam(avp.getId()); if (bp == null) { throw new EqualityException().msg("param from left not present in right").arg("param", avp); } Misc.checkEquals(avp.getClass(), bp.getClass(), "params type not equals", null); ValueParam bvp = (ValueParam) bp; Object oa = a.get(avp, Object.class); Object ob = b.get(avp, Object.class); if (avp instanceof CompositeParam) { assureEquality(registry.prepareAccess((CompositeParam) avp, false).select(oa), registry.prepareAccess((CompositeParam) bvp, false).select(ob), registry); continue; } Misc.checkEquals(oa, ob, "values not equal", null); } } catch (EqualityException e) { throw e.arg("left", a).arg("right", b); } } public static boolean areEqual(Access a, Access b, Registry registry) { try { assureEquality(a, b, registry); return true; } catch (EqualityException e) { } return false; } }