Creating a Niagara HashMap<String, String>!
One great thing about creating a Niagara type is that Niagara can persist the data to the bog on reboots. Unfortunately there are no Niagara map types or lists or other data structures in the API that are not intended for public use or specific purposes.
There is a HashMap<String, BFormat> class/type (BNameMap), but that didn’t fit my needs, so I looked for a way to create my own structure that would persist the data and had found the BNameMap. Looking through the API and trying to fully understand how things work etc. I also found someone on the Niagara Community that said the object needs to extend the BSimple class, and needs to implement all required methods, namely encode and decode. If you have access to workbench and can access the help menu, look at BNameMap. That is what this object is modeled after. These methods, I believe are where the magic happens with persisting the data to the bog.
disclaimer: most of the code is from the BNameMap class
public final class BLogicInputMap extends BSimple {
public static BLogicInputMap make() {
HashMap<String, String> map = new HashMap<>();
return (BLogicInputMap)new BLogicInputMap(map);//.intern();
}
public static BLogicInputMap make(HashMap<String,String> map) {
HashMap<String, String> newMap = new HashMap<>(map);
return (BLogicInputMap)new BLogicInputMap(newMap);//.intern();
}
public static BLogicInputMap remove(BLogicInputMap orig, String name)
{
HashMap<String, String> map = new HashMap<>(orig.map);
map.remove(name);
return (BLogicInputMap)new BLogicInputMap(map);//.intern();
}
public void removeMapItem(String name){
this.map.remove(name);
}
public void put(String name,String value){
this.map.put(name,value);
}
private BLogicInputMap(HashMap<String, String> map)
{
this.map = map;
}
@Override
public boolean isNull()
{
return map.isEmpty();
}
public int hashCode()
{
try
{
if (hashCode == -1)
hashCode = encodeToString().hashCode();
return hashCode;
}
catch(Exception e)
{
return System.identityHashCode(this);
}
}
public boolean equals(Object obj)
{
if (obj instanceof BLogicInputMap)
{
BLogicInputMap x = (BLogicInputMap)obj;
if (map.size() != x.map.size()) return false;
Iterator<String> it = map.keySet().iterator();
while(it.hasNext())
{
String key = it.next();
String val = map.get(key);
if (!val.equals(x.map.get(key))) return false;
}
return true;
}
return false;
}
public static BLogicInputMap make(BLogicInputMap orig, String name, String value)
{
return make(orig, name, value);
}
public static BLogicInputMap make(BLogicInputMap orig, BLogicInputMap other)
{
HashMap<String, String> map = new HashMap<>(orig.map);
String[] otherKeys = other.list();
for (int i = 0; i < otherKeys.length; ++i)
map.put(otherKeys[i], other.get(otherKeys[i]));
return (BLogicInputMap)new BLogicInputMap(map);//.intern();
}
public String[] list()
{
return map.keySet().toArray(new String[map.size()]);
}
public String get(String name)
{
return map.get(name);
}
public void encode(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(encodeToString());
}
public BObject decode(DataInput dataInput) throws IOException {
return decodeFromString(dataInput.readUTF());
}
public String encodeToString()
{
if (string == null)
{
StringBuilder s = new StringBuilder();
s.append('{');
Iterator<String> it = map.keySet().iterator();
while(it.hasNext())
{
String key = it.next();
String val = map.get(key);
escape(s, key);
s.append('=');
escape(s, val);
s.append(';');
}
s.append('}');
string = s.toString();
}
return string;
}
public BObject decodeFromString(String s) throws IOException {
try {
// stripping this first let us add stuff in the future
//System.out.println("Decoding: ");
int start = s.indexOf('{');
int end = s.lastIndexOf('}');
if (start+1 == end) return null;
String body = s.substring(start+1, end);
// walk thru finding name=value pairs and unescaping
ArrayList<String> acc = new ArrayList<>();
int len = body.length();
StringBuilder buf = new StringBuilder();
int lastDelim = ';';
for(int i=0; i<len; ++i)
{
char c = body.charAt(i);
if (c == '\\') buf.append(body.charAt(++i));
else if (c != '=' && c != ';') buf.append(c);
else
{
if (c == lastDelim) throw new Exception();
lastDelim = c;
acc.add(buf.toString());
buf.setLength(0);
}
}
HashMap<String, String> map = new HashMap<>();
for(int i=0; i<acc.size(); i+=2)
{
String key = acc.get(i);
String val = acc.get(i+1);
map.put(key, val);
}
try {
this.map = new HashMap<>(map);
}catch (Exception e) {
e.printStackTrace();
}
// done
BLogicInputMap x = (BLogicInputMap) new BLogicInputMap(map);//.intern();
x.string = s;
return x;
}
catch(Exception e) {
System.out.println(e);
System.out.println(string);
throw new IOException("Invalid BNameMap: " + s.toString());
}
}
public static BLogicInputMap make(BLogicInputMap bmap,String s) throws IOException {
HashMap<String, String> newMap = new HashMap<>();
BLogicInputMap bLogicInputMap = (BLogicInputMap) bmap.decodeFromString(s);
return bLogicInputMap;
}
private void escape(StringBuilder buf, String s) {
int len = s.length();
for(int i=0; i<len; ++i)
{
char c = s.charAt(i);
switch(c)
{
case '\\': buf.append("\\\\"); break;
case '{': buf.append("\\{"); break;
case '}': buf.append("\\}"); break;
case '=': buf.append("\\="); break;
case ';': buf.append("\\;"); break;
default: buf.append(c); break;
}
}
}
public static final BLogicInputMap DEFAULT = BLogicInputMap.make(new HashMap<String,String>());
@Override
public Type getType() { return TYPE; }
public static final Type TYPE = Sys.loadType(BLogicInputMap.class);
private String string;
HashMap<String,String> map;
private int hashCode = -1;
}
Then you can use the new data structure as a property in other classes.
@NiagaraProperty(name = "InputNamesMap",type = "BLogicInputMap",defaultValue =
"BLogicInputMap.make(new HashMap<String, String>())",flags =
Flags.HIDDEN|Flags.READONLY)
In the main class that has the hasmap object as a property will need to initialize the data. I have found that creating a local map object, build it with data you wish to keep and then initialize the new data structure with this map.
if (getInputNamesMap().isNull()){
log.fine("InputNamesMap is null");
for (int i=0;i<getNumberOfInputs();++i){
String inputName = inputPrefix+(i+1);
String negateName = negPrefix+(i+1);
this.map.put(inputName,negateName);
log.fine("Adding to local Map : " + map.get(inputName));
log.fine("compare map to input for map - input:" + inputName + " map entry: " + map.get(inputName));
}
setInputNamesMap(BLogicInputMap.make((HashMap<String,String>)map.clone()));
log.fine("Creating input map datastructure from local map:"+getInputNamesMap().encodeToString());
}
Then throughout the code, use the new hashmap!
BBoolean tst = BBoolean.make(true);
for (BStatusBoolean sb : sbArr) {
if (sb.getStatus().isValid()) {
boolean b = sb.getBoolean();
String negName = getInputNamesMap().get(sb.getName());
BBoolean neg = (BBoolean) this.get(negName);
if (neg.getBoolean()) {
b = !b;
}
if (!b) {
return false;
}
}
}