package com.dexels.immutable.impl; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.dexels.immutable.api.ImmutableMessage; import com.dexels.immutable.api.ImmutableMessageParser; import com.dexels.immutable.api.ImmutableTypeParser; import com.dexels.immutable.api.customtypes.CoordinateType; import com.dexels.immutable.factory.ImmutableFactory; public class ImmutableMessageImpl implements ImmutableMessage { private final static Logger logger = LoggerFactory.getLogger(ImmutableMessageImpl.class); private final Map values; private final Map types; private final Map subMessageMap; private final Map> subMessagesMap; public ImmutableMessageImpl(Map values, Map types,Map submessage, Map> submessages) { this.values = Collections.unmodifiableMap(values); this.types = Collections.unmodifiableMap(types); this.subMessageMap = Collections.unmodifiableMap(submessage); this.subMessagesMap = Collections.unmodifiableMap(submessages); } public ImmutableMessageImpl(ImmutableMessage message1, ImmutableMessage message2, String key) { Map values = new HashMap<>(); for (String c : message1.columnNames()) { Optional value = message1.value(c); if(value.isPresent()) { values.put(c, value); } } for (String c : message2.columnNames()) { Optional newValue = message2.value(c); if(values.containsKey(c)) { Object original = values.get(c); if(!newValue.isPresent()) { continue; } if(!original.equals(newValue.get())) { logger.debug("Conflict in values. Value {} is present in both messages, but different: {} vs {}",c,original,newValue); } } // System.err.println("Adding key:"+c+"::"+newValue); values.put(c, newValue); } this.values = Collections.unmodifiableMap(values); Map types1 = message1.types(); Map types2 = message2.types(); this.types = combineTypes(types1,types2); if(message1.subMessageNames().isEmpty()) { if(message2.subMessageMap().isEmpty()) { this.subMessageMap = Collections.emptyMap(); } else { this.subMessageMap = message2.subMessageMap(); } } else { if(message2.subMessageMap().isEmpty()) { this.subMessageMap = message1.subMessageMap(); } else { // combine. somehow. HashMap m = new HashMap<>(message1.subMessageMap()); m.putAll(message2.subMessageMap()); this.subMessageMap = Collections.unmodifiableMap(m); } } if(message1.subMessageListMap().isEmpty()) { if(message2.subMessageListMap().isEmpty()) { this.subMessagesMap = Collections.emptyMap(); } else { this.subMessagesMap = message2.subMessageListMap(); } } else { if(message2.subMessageListMap().isEmpty()) { this.subMessagesMap = message1.subMessageListMap(); } else { HashMap> m = new HashMap<>(message1.subMessageListMap()); m.putAll(message2.subMessageListMap()); this.subMessagesMap = Collections.unmodifiableMap(m); } } } @Override public Set subMessageNames() { if(subMessageMap==null) { return Collections.emptySet(); } return Collections.unmodifiableSet(subMessageMap.keySet()); } @Override public Set subMessageListNames() { if(subMessagesMap==null) { return Collections.emptySet(); } return Collections.unmodifiableSet(subMessagesMap.keySet()); } @Override public byte[] toBytes(ImmutableMessageParser c) { return c.serialize(this); } public ImmutableMessageImpl(Throwable t) { logger.error("Creating failure replication message",t); t.printStackTrace(System.err); t.printStackTrace(System.out); this.values = Collections.emptyMap(); this.types = Collections.emptyMap(); this.subMessageMap = Collections.emptyMap(); this.subMessagesMap = Collections.emptyMap(); } @SuppressWarnings("unchecked") public ImmutableMessageImpl(Map initial) { this.values = Collections.unmodifiableMap((Map) initial.get("Columns")); this.types = Collections.unmodifiableMap(resolveTypesFromValues(this.values)); this.subMessageMap = Collections.emptyMap(); this.subMessagesMap = Collections.emptyMap(); } private Map resolveTypesFromValues(Map values) { Map t = new HashMap<>(); for (Entry e : values.entrySet()) { Object val = e.getValue(); if(val==null) { // t.put(e.getKey(), "unknown"); } else if(val instanceof Long) { t.put(e.getKey(), "long"); } else if(val instanceof Double) { t.put(e.getKey(), "double"); } else if(val instanceof Integer) { t.put(e.getKey(), "integer"); } else if(val instanceof Float) { t.put(e.getKey(), "float"); } else if(val instanceof Date) { t.put(e.getKey(), "date"); } else if(val instanceof Boolean) { t.put(e.getKey(), "boolean"); } else if(val instanceof String) { t.put(e.getKey(), "string"); } else if (val instanceof CoordinateType) { t.put(e.getKey(), "coordinate"); } else { logger.warn("Unknown type::: {}",val.getClass()); t.put(e.getKey(), "string"); } } return t; } private Map combineTypes(Map typesa, Map typesb) { HashMap combine = new HashMap<>(typesa); for (Entry e : typesb.entrySet()) { // TODO sanity check types? combine.put(e.getKey(), e.getValue()); } return Collections.unmodifiableMap(combine); } @Override public Map types() { return this.types; } @Override public Map> toDataMap() { Map> columns = new HashMap<>(); this.values.entrySet().stream().forEach(element->{ Map m = new HashMap<>(); m.put("Type", types.get(element.getKey())); m.put("Value", values.get(element.getKey())); columns.put(element.getKey(), m); }); return columns; } @Override public Map valueMap(boolean ignoreNull, Set ignore) { return valueMap(ignoreNull,ignore,Collections.emptyList()); } private static Function checkIgnoreList(Set ignoreList) { return item->!ignoreList.contains(item); } @Override public Map valueMap(boolean ignoreNull, Set ignore, List currentPath) { Map result = new HashMap<>(); for (Entry e : values.entrySet()) { if(ignore.contains(e.getKey())) { continue; } if(e.getValue()==null && ignoreNull) { continue; } if(checkIgnoreList(ignore).apply(e.getKey())) { result.put(e.getKey(), e.getValue()); } } if(this.subMessageMap!=null) { for (Entry e : subMessageMap.entrySet()) { List withPath = new LinkedList<>(currentPath); withPath.add(e.getKey()); result.put(e.getKey(), e.getValue().valueMap(ignoreNull, ignore,withPath)); } } if(this.subMessagesMap!=null) { for (Entry> e : subMessagesMap.entrySet()) { List withPath = new LinkedList<>(currentPath); withPath.add(e.getKey()); List> elts = e.getValue().stream().map(msg->msg.valueMap(ignoreNull, ignore,withPath)).collect(Collectors.toList()); result.put(e.getKey(), elts); } } return Collections.unmodifiableMap(result); } @Override public Set columnNames() { return this.values.keySet(); } @Override public Object columnValue(String name) { int path = name.indexOf('/'); if(path==-1) { return values.get(name); } String submp = name.substring(0,path); final Optional value = subMessage(submp).orElse(ImmutableFactory.empty()).value(name.substring(path+1,name.length())); return value.orElse(null); } @Override public String columnType(String name) { return types.get(name); } @Override public String toString() { return "Values: "+values+" types: "+types; } @Override public Optional> subMessages(String field) { if(this.subMessagesMap==null) { return Optional.empty(); } List messageList = subMessagesMap.get(field); if(messageList==null || messageList.isEmpty()) { return Optional.empty(); } return Optional.of(messageList); } @Override public Optional subMessage(String field) { if(this.subMessageMap==null) { return Optional.empty(); } ImmutableMessage message = subMessageMap.get(field); return Optional.ofNullable(message); } @Override public ImmutableMessage withSubMessages(String field, List message) { Map> res = new HashMap<>(this.subMessagesMap); res.put(field, message); return new ImmutableMessageImpl(this.values,this.types,this.subMessageMap,Collections.unmodifiableMap(res)); } @Override public ImmutableMessage withSubMessage(String field, ImmutableMessage message) { Map res = new HashMap<>(this.subMessageMap); res.put(field, message); return new ImmutableMessageImpl(this.values,this.types,Collections.unmodifiableMap(res),this.subMessagesMap); } @Override public ImmutableMessage without(String columnName) { Map localValues = new HashMap<>(this.values); Map localTypes = new HashMap<>(this.types); localValues.remove(columnName); localTypes.remove(columnName); return new ImmutableMessageImpl(localValues, localTypes,this.subMessageMap,this.subMessagesMap); } @Override public ImmutableMessage without(List columns) { Map localValues = new HashMap<>(this.values); Map localTypes = new HashMap<>(this.types); for (String columnName : columns) { localValues.remove(columnName); localTypes.remove(columnName); } return new ImmutableMessageImpl(localValues, localTypes,this.subMessageMap,this.subMessagesMap); } @Override public ImmutableMessage rename(String columnName, String newName) { if (columnNames().contains(columnName)) { return this.without(columnName).with(newName, columnValue(columnName), types.get(columnName)); } return this; } @Override public ImmutableMessage with(String key, Object value, String type) { int firstSlash = key.indexOf('/'); if(firstSlash!=-1) { String[] parts = key.split("/"); Optional subm = subMessage(parts[0]); if(!subm.isPresent()) { logger.warn("Path: {} not found",key); ImmutableMessage newSub = ImmutableFactory.empty().with(key.substring(firstSlash+1), value, type); return withSubMessage(parts[0], newSub); } return withSubMessage(parts[0], subm.get()).with(key.substring(firstSlash+1), value, type); } Map localValues = new HashMap<>(this.values); Map localTypes = new HashMap<>(this.types); // TODO check if the key does not change? if("immutable".equals(type)) { ImmutableMessage im = (ImmutableMessage) value; return this.withSubMessage(key, im); } else { localValues.put(key, value); localTypes.put(key, type); return new ImmutableMessageImpl(localValues, localTypes,this.subMessageMap,this.subMessagesMap); } } @Override public String toFlatString(ImmutableMessageParser parser) { if(parser==null) { logger.info("Can not flatten parser, no parser present"); return ""; } else { return parser.describe(this); } } @Override public ImmutableMessage withoutSubMessages(String field) { Map> res = new HashMap<>(this.subMessagesMap); res.remove(field); return new ImmutableMessageImpl(this.values,this.types,this.subMessageMap,Collections.unmodifiableMap(res)); } @Override public ImmutableMessage withoutSubMessage(String field) { Map res = new HashMap<>(this.subMessageMap); res.remove(field); return new ImmutableMessageImpl(this.values,this.types,Collections.unmodifiableMap(res),this.subMessagesMap); } @Override public Map subMessageMap() { return this.subMessageMap; } @Override public Map> subMessageListMap() { return this.subMessagesMap; } @Override public ImmutableMessage merge(ImmutableMessage other, Optional> only) { ImmutableMessage msg = this; Map mergedSubMessageMap = new HashMap<>(this.subMessageMap); mergedSubMessageMap.putAll(other.subMessageMap()); Map> mergedSubMessagesMap = new HashMap<>(this.subMessagesMap); mergedSubMessagesMap.putAll(other.subMessageListMap()); try { if(only.isPresent()) { for (String key : only.get()) { ImmutableMessage lookupMsg = other; while (key.contains(".") && lookupMsg != null) { String submsgName = key.substring(0, key.indexOf('.')); key = key.substring(submsgName.length()+1); Optional subMessage = lookupMsg.subMessage(submsgName); if (subMessage.isPresent()) { if (lookupMsg == other) mergedSubMessageMap.remove(submsgName); lookupMsg = subMessage.get(); } else { lookupMsg = null; } } Object found = null; if (lookupMsg != null) { found = lookupMsg.value(key); if (found != null) { msg = msg.with(key, found, lookupMsg.columnType(key)); } else { Optional subMessage = lookupMsg.subMessage(key); if (subMessage.isPresent()) { mergedSubMessageMap.put(key, subMessage.get()); } Optional> subMessages = lookupMsg.subMessages(key); if (subMessages.isPresent()) { mergedSubMessagesMap.put(key, subMessages.get()); } } } } } else { for (String key : other.columnNames()) { Object found =other.value(key); if(found!=null) { msg = msg.with(key, found, other.columnType(key)); } } } } catch (Throwable t) { logger.error("Err", t); } return msg.withAllSubMessageLists(mergedSubMessagesMap).withAllSubMessage(mergedSubMessageMap); } @Override public ImmutableMessage withOnlySubMessages(List subMessages) { Map newSubMessages = new HashMap<>(this.subMessageMap); Map> newSubMessageList = new HashMap<>(this.subMessagesMap); for (String elt : subMessageMap.keySet()) { if(!subMessages.contains(elt)) { newSubMessages.remove(elt); } } for (String elt : subMessagesMap.keySet()) { if(!subMessages.contains(elt)) { newSubMessageList.remove(elt); } } return new ImmutableMessageImpl(this.values,this.types,Collections.unmodifiableMap(newSubMessages),Collections.unmodifiableMap(newSubMessageList)); } @Override public ImmutableMessage withOnlyColumns(List columns) { Map newValues = new HashMap<>(this.values); Map newTypes = new HashMap<>(this.types); for (String elt : values.keySet()) { if(!columns.contains(elt)) { newValues.remove(elt); newTypes.remove(elt); } } Map newsubmessage = new HashMap<>(Collections.emptyMap()); Map> newSubmessages = new HashMap<>(Collections.emptyMap()); // Submessage columns for (String key : columns) { if (!key.contains(".")) { continue; } ImmutableMessage lookupMsg = this; while (key.contains(".") && lookupMsg != null) { String submsgName = key.substring(0, key.indexOf(".")); key = key.substring(submsgName.length()+1); Optional subMessage = lookupMsg.subMessage(submsgName); if (subMessage.isPresent()) { lookupMsg = subMessage.get(); } else { lookupMsg = null; } } Object found = null; if (lookupMsg != null) { found = lookupMsg.value(key); if (found != null) { newValues.put(key, found); newTypes.put(key, lookupMsg.columnType(key)); } else { Optional subMessage = lookupMsg.subMessage(key); if (subMessage.isPresent()) { newsubmessage.put(key, subMessage.get()); } Optional> subMessages = lookupMsg.subMessages(key); if (subMessages.isPresent()) { newSubmessages.put(key, subMessages.get()); } } } } return new ImmutableMessageImpl(newValues, newTypes, newsubmessage, newSubmessages); } public ImmutableMessage withAllSubMessageLists(Map> subMessageListMap) { return new ImmutableMessageImpl(values, types, this.subMessageMap, subMessageListMap); } public ImmutableMessage withAllSubMessage(Map subMessageMap) { return new ImmutableMessageImpl(values, types, subMessageMap, subMessagesMap); } @Override public ImmutableMessage withAddedSubMessage(String field, ImmutableMessage message) { List subMessageList = new ArrayList<>(subMessages(field).orElse(new ArrayList<>())); subMessageList.add(message); return withSubMessages(field, subMessageList); } @Override public ImmutableMessage withoutSubMessageInList(String field,Predicate selector) { List subMessageList = subMessages(field).orElse(Collections.emptyList()) .stream() .filter(m->!selector.test(m)) .collect(Collectors.toList()); return withSubMessages(field, subMessageList); } @Override public Map flatValueMap(boolean ignoreNull, Set ignore, String prefix) { Map localValues; if ("".equals(prefix)) { localValues = new HashMap<>(this.values); } else { localValues = new HashMap<>(); for (Entry e : this.values.entrySet()) { localValues.put(prefix+"/"+e.getKey(), e.getValue()); } } for (Entry e : this.subMessageMap.entrySet()) { String newPrefix = !"".equals(prefix) ? prefix +"_"+e.getKey() : e.getKey(); localValues.putAll(e.getValue().flatValueMap(ignoreNull, ignore, newPrefix)); } if(!subMessagesMap.isEmpty()) { for (Entry> e : this.subMessagesMap.entrySet()) { int i = 0; for (ImmutableMessage msg : e.getValue()) { String pr = e.getKey()+"@"+i; String newPrefix = !"".equals(prefix) ? prefix +"/"+pr : pr; msg.flatValueMap(ignoreNull,ignore, newPrefix).entrySet().forEach(ee->localValues.put(ee.getKey(), ee.getValue())); i++; } } } return Collections.unmodifiableMap(localValues); } @Override public Map flatValueMap(String prefix, Trifunction processType) { Map localValues = getFlatValueMap(prefix, processType); for (Entry e : this.subMessageMap.entrySet()) { String newPrefix = !"".equals(prefix) ? prefix +"_"+e.getKey() : e.getKey(); localValues.putAll(((ImmutableMessageImpl)e.getValue()).flatValueMap(newPrefix,processType)); } return Collections.unmodifiableMap(localValues); } private Map getFlatValueMap(String prefix, Trifunction processType) { Map localValues; if ("".equals(prefix)) { localValues = new HashMap<>(); for (Entry e : this.values.entrySet()) { String type = this.types.get(e.getKey()); final Object processed = processType.apply(e.getKey(),type, e.getValue()); if(processed!=null) { localValues.put(e.getKey(), processed); } } } else { localValues = new HashMap<>(); for (Entry e : this.values.entrySet()) { String type = this.types.get(e.getKey()); final Object processed = processType.apply(e.getKey(),type, e.getValue()); if(processed!=null) { localValues.put(prefix+"_"+e.getKey(), processed ); } } } return localValues; } public boolean equalsToMessage(ImmutableMessage c) { Map other = c.flatValueMap(false,Collections.emptySet(), ""); final Map myMap = this.flatValueMap(false, Collections.emptySet(), ""); return myMap.equals(other); } @Override public Map values() { return this.values; } @Override public Map toTypedDataMap() { Map columns = new HashMap<>(); this.values.entrySet().stream().forEach(element->{ ValueType t = ImmutableTypeParser.parseType(this.columnType(element.getKey())); columns.put(element.getKey(), new TypedData(t, element.getValue())); }); return columns; } }