package nbtool.data.log; import nbtool.data.SExpr; import nbtool.util.Debug; import nbtool.util.ToolSettings; import nbtool.util.Utility; public class SExprLog { //Top-level keys protected static final String LOG_FIRST_ATOM_S = "nblog"; protected static final String LOG_CONTENTS_S = "contents"; protected static final String LOG_CREATED_S = "created"; protected static final String LOG_VERSION_S = "version"; protected static final String LOG_CHECKSUM_S = "checksum"; protected static final String LOG_HOST_TYPE_S = "host_type"; protected static final String LOG_HOST_NAME_S = "host_name"; protected static final String LOG_FROM_ADDR_S = "from_address"; //content item keys protected static final String CONTENT_TYPE_S = "type"; protected static final String CONTENT_FROM_S = "from"; protected static final String CONTENT_WHEN_S = "when"; protected static final String CONTENT_IINDEX_S = "iindex"; protected static final String CONTENT_NBYTES_S = "nbytes"; protected static final String CONTENT_IMAGE_WIDTH_S = "width"; protected static final String CONTENT_IMAGE_HEIGHT_S = "height"; protected static final String CONTENT_IMAGE_ENCODING_S = "encoding"; //command key protected static final String COMMAND_FIRST_ATOM_S = "command"; //nbcross wildcard protected static final String NBCROSS_WILDCARD_TYPE = "__WILDCARD__"; protected SExprLog() {} protected SExprLog(String d, byte[] b) { this(); this.name = null; this.tree = SExpr.deserializeFrom(d); this.bytes = b; } protected SExprLog(SExpr t, byte[] d) { this(); this.name = null; this.tree = t; this.bytes = d; } protected static SExprLog logWithType(String type) { return SExprLog.logWithType(type, null); } protected static SExprLog logWithType(String type, byte[] b) { SExpr typeField = SExpr.newKeyValue(CONTENT_TYPE_S, type); SExpr fieldList = SExpr.newList(typeField); SExpr topLevel = SExpr.newList(SExpr.newAtom(LOG_FIRST_ATOM_S), SExpr.newKeyValue(LOG_CONTENTS_S, fieldList)); return new SExprLog(topLevel, b); } protected static SExprLog logWithTypePlus(String type, byte[] b, SExpr... fields) { SExpr typeField = SExpr.newKeyValue(CONTENT_TYPE_S, type); SExpr fieldList = SExpr.newList(typeField); fieldList.append(fields); SExpr topLevel = SExpr.newList(SExpr.newAtom(LOG_FIRST_ATOM_S), SExpr.newKeyValue(LOG_CONTENTS_S, fieldList)); return new SExprLog(topLevel, b); } protected static SExprLog simpleCommandLog(String cmndName, byte[] bytes) { SExpr commandTree = SExpr.newList(SExpr.newAtom(COMMAND_FIRST_ATOM_S), SExpr.newAtom(cmndName)); SExprLog cmnd = new SExprLog(commandTree, bytes); return cmnd; } /* * Unique number for every log found in this process. * */ private static final Object indexLock = new Object(); private static long class_index = 0; private static long getID() { long ret; synchronized(indexLock) { ret = class_index++; } return ret; } protected final long unique_id = getID(); //Core opaque log fields: protected byte[] bytes; private SExpr tree; //used in some rare legacy situations. ALMOST ALWAYS NULL. protected String _olddesc_; //user supplied name, often null. Used as file name. protected String name; protected static enum SOURCE { DERIVED, //created programmatically from other logs FILE, //loaded from filesystem NETWORK, //streamed from network during this process's lifetime GENERATED //totally synthetic } protected SOURCE source; protected byte[] data() { return bytes; } protected SExpr tree() { return tree; } protected String description() { return tree.serialize(); } protected String description(int nchars) { String ser = tree.serialize(); if (ser.length() > nchars) { return ser.substring(0, nchars - 3) + "..."; } else return ser; } protected void setTree(SExpr nt) { if (nt.isAtom()) { Debug.warn("log tree being set atom: %s", nt.serialize()); } this.tree = nt; } /* this is typically used as a programmatically generated file name * so the most important quality of the created String is to be a valid and * unique file name on all systems. * */ protected void setNameFromDesc() { this.name = String.format("type=%s_from=%s_v=%d_i%d_c%d", primaryType(), primaryFrom(), version(), this.unique_id, this.checksum()); this.name = this.name .substring(Math.max(0, this.name.length() - 240)) .replace('/', '_').replace(' ', '_').replace(':', '-').replace('.', '-') + ".nblog"; } public String toString() { if (name != null) return name; else return description(100); } /* ALL ATTRIBUTES MUST BE OBJECTS SO THAT NULL CAN BE RETURNED IF THEY'RE NOT FOUND */ /* * Attributes relating to the log whole */ protected String madeWhere() { SExpr where = tree().find(LOG_CREATED_S).get(1); if (where.exists() && where.isAtom()) return where.value(); else return null; } protected String madeWhen() { SExpr when = tree().find(LOG_CREATED_S).get(2); if (when.exists() && when.isAtom()) return when.value(); else return null; } protected Integer checksum() { SExpr cs = tree().find(LOG_CHECKSUM_S).get(1); if (cs.exists() && cs.isAtom()) return cs.valueAsInt(); else return null; } //what is this attribute going to return? //protected String protoRobotLocation() { //return getAttributes().get("proto-RobotLocation"); //} protected Integer version() { SExpr v = tree().find(LOG_VERSION_S).get(1); if (v.exists() && v.isAtom()) return v.valueAsInt(); else return null; } //Does not include the "contents" key. protected Integer contentCount() { SExpr v = tree().find(LOG_CONTENTS_S); if (v.exists()) return (v.count() - 1); return -1; } /* * Most logs have content count 1. This returns attributes of the first content. * (the "primary" content) * */ protected Integer primaryBytes() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find("bytes").get(1); return c.exists() && c.isAtom() ? c.valueAsInt() : null; } protected String primaryType() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find(CONTENT_TYPE_S).get(1); return c.exists() && c.isAtom() ? c.value() : null; } protected String primaryFrom() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find(CONTENT_FROM_S).get(1); return c.exists() && c.isAtom() ? c.value() : null; } protected Integer primaryImgIndex() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find(CONTENT_IINDEX_S).get(1); return c.exists() && c.isAtom() ? c.valueAsInt() : null; } protected Long primaryTime() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find("time").get(1); return c.exists() && c.isAtom() ? c.valueAsLong() : null; } protected Boolean primaryIsProtobuf() { String t = primaryType(); if (t == null) return false; return t.startsWith(ToolSettings.PROTOBUF_TYPE_PREFIX); } /* * Attributes relating to possible image content. */ protected String primaryEncoding() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find(CONTENT_IMAGE_ENCODING_S).get(1); return c.exists() && c.isAtom() ? c.value() : null; } protected Integer primaryWidth() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find(CONTENT_IMAGE_WIDTH_S).get(1); return c.exists() && c.isAtom() ? c.valueAsInt() : null; } protected Integer primaryHeight() { SExpr c = tree().find(LOG_CONTENTS_S).get(1).find(CONTENT_IMAGE_HEIGHT_S).get(1); return c.exists() && c.isAtom() ? c.valueAsInt() : null; } /* * Helpers for non-primary content items. * * 0 based – index 0 refers to the content item after the 'contents' key * */ protected Integer contentNumBytes(int index) { SExpr cont = tree().find(LOG_CONTENTS_S); if (!cont.exists() || index >= (cont.count() - 1) ) return null; SExpr item = cont.get(index + 1); if (item.isAtom()) return null; SExpr bytes = item.find(CONTENT_NBYTES_S).get(1); return bytes.exists() && bytes.isAtom() ? bytes.valueAsInt() : null; } protected Integer contentOffset(int index) { SExpr cont = tree().find(LOG_CONTENTS_S); if (!cont.exists() || index >= (cont.count() - 1) ) return null; int offset = 0; for (int i = 0; i < index; ++i) { SExpr bytes = cont.get(i + 1).find(CONTENT_NBYTES_S).get(1); if (!bytes.exists() || !bytes.isAtom()) return null; offset += bytes.valueAsInt(); } return offset; } protected byte[] bytesForContentItem(int index) { Integer offset = contentOffset(index); Integer total = contentNumBytes(index); if (offset == null || total == null) return null; return Utility.subArray(bytes, offset, total); } protected SExpr sexprForContentItem(int index) { SExpr cont = tree().find(LOG_CONTENTS_S); if (!cont.exists() || index >= (cont.count() - 1) ) return null; return cont.get(index + 1); } //TESTING public static void main(String[] args) { SExpr clist = SExpr.newList( SExpr.newAtom("contents"), SExpr.newList(SExpr.newKeyValue("bytes", "10")), SExpr.newList(SExpr.newKeyValue("bytes", "50")), SExpr.newList(SExpr.newKeyValue("bytes", "100")) ); SExpr top = SExpr.newList(clist); SExprLog log = new SExprLog(top, null); System.out.println("desc = " + log.description()); System.out.println("count = " + log.contentCount()); System.out.println("b0 = " + log.contentNumBytes(0)); System.out.println("b2 = " + log.contentNumBytes(2)); System.out.println("b3 = " + log.contentNumBytes(3)); System.out.println(""); System.out.println("o0 = " + log.contentOffset(0)); System.out.println("o2 = " + log.contentOffset(2)); } }