/********************************************************************************************* * Copyright (c) 2014-2015 Software Behaviour Analysis Lab, Concordia University, Montreal, Canada * * All rights reserved. This program and the accompanying materials * are made available under the terms of Eclipse Public License v1.0 License which * accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Syed Shariyar Murtaza -- Initial design and implementation **********************************************************************************************/ package org.eclipse.tracecompass.internal.totalads.algorithms.sequencematching; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.eclipse.tracecompass.totalads.algorithms.IAlgorithmOutStream; import org.eclipse.tracecompass.totalads.dbms.IDBCursor; import org.eclipse.tracecompass.totalads.dbms.IDBRecord; import org.eclipse.tracecompass.totalads.dbms.IDataAccessObject; import org.eclipse.tracecompass.totalads.exceptions.TotalADSDBMSException; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; /** * This class implements the tree transformation methods for the sequence matching * algorithm. It converts a trace of events into a tree of events and serializes * to database when needed. * * @author Syed Shariyar Murtaza * * */ class SequenceMatchingTree { /** * Constructor */ public SequenceMatchingTree() { } /** * Searches and adds a sequence in the tree of events forming sequences * * @param newSequence * the sequence to search and add * @param existinTreesofSeqs * Existing trees of sequences * @param out * Output stream to display processing messages */ public void searchAndAddSequence(Integer[] newSequence, HashMap<Integer, Event[]> existinTreesofSeqs, IAlgorithmOutStream out) { Integer seqSize = newSequence.length; // Find the first event in the tree of existing sequence Event[] eventSequence = existinTreesofSeqs.get(newSequence[0]); // load from database // Event[] eventSequence= loadTreeFromDatabase(newSequence[0], database, // dataAccessObject) ; if (eventSequence == null) { // if there is no such starting event then // add sequence to such an event eventSequence = new Event[seqSize + 1]; for (int j = 0; j < seqSize; j++) { eventSequence[j] = new Event(); eventSequence[j].setEvent(newSequence[j]); // out.addOutputEvent(newSequence[j].toString()+ " "); } eventSequence[seqSize] = new Event(); eventSequence[seqSize].setEvent(1); // out.addNewLine(); } else { Boolean isFound = searchAndAppendSequence(eventSequence, newSequence);// search // in // tree // from // this // node/sequence if (isFound == false) {// if not match is found then add on 0 branch // because only top event matches eventSequence[0].setBranches(new ArrayList<Event[]>()); Event[] newBranchSeq = new Event[seqSize + 1]; for (int j = 0; j < seqSize - 1; j++) { newBranchSeq[j] = new Event(); newBranchSeq[j].setEvent(newSequence[j]); // out.addOutputEvent(newSequence[j].toString()+ " "); } newBranchSeq[seqSize] = new Event(); newBranchSeq[seqSize].setEvent(1); eventSequence[0].getBranches().add(newBranchSeq); // out.addNewLine(); } } // putting the sequence (tree actually) to the starting event (node) as // a name existinTreesofSeqs.put(newSequence[0], eventSequence); // add to database // saveTreeInDatabase( database, dataAccessObject, eventSequence, // HmmModelCollection.COLLECTION_NAME.toString()); } /** * Searches and appends a sequence(set of events) to an already existing * tree of events. If a sequence already exists, it updates the counter Use * the following tree example to understand the code * * 108-106-5-55-45-90 | -3-9-6 | |-3-10 | |-3-6 | 106 | 5 i.e., the * sequences are 108-106-5-55-45-90,108-106-5-3-9-6, 108-106-5-3-3-10, * 108-106-5-3-3-6, 108-106-5-3-3-106, and 108-106-5-3-3-5 * * @param existingTree * an already existing tree of events * @param newSeq * a sequence of events that requires to be appended * @return */ private Boolean searchAndAppendSequence(Event[] existingTree, Integer[] newSeq) { Integer seqSize = newSeq.length; Integer j; for (j = 0; j < seqSize; j++) { if (!existingTree[j].getEvent().equals(newSeq[j])) { break; } } Integer matchIdx = j - 1; if (matchIdx >= seqSize - 1) { Integer counter = existingTree[seqSize].getEvent() + 1; existingTree[seqSize].setEvent(counter); return true; } else if (matchIdx < 0) { return false; // return false if mismatch on the first idx } else { Event[] newEventSeq = new Event[seqSize - matchIdx];// +1 for the // count Integer[] newTmpSeq = new Integer[seqSize - matchIdx - 1]; Boolean isFound = false; Integer i; for (i = 0; i < newEventSeq.length - 1; i++) { newEventSeq[i] = new Event(); newEventSeq[i].setEvent(newSeq[matchIdx + i + 1]);// that is // copy from // the next // index of // matched // index newTmpSeq[i] = newSeq[matchIdx + i + 1]; } newEventSeq[i] = new Event(); newEventSeq[i].setEvent(1);// add 1 as a counter at the leaf, for // the first sequence on a branch ArrayList<Event[]> branches = existingTree[matchIdx].getBranches(); // When there are no more branches then we shall automatically // add a new branch by skipping the if block if (branches != null) { // if the branches exist then we need to recursively go through // the remaining branches to find // a possible location to append the new sequence for (int bCount = 0; bCount < branches.size(); bCount++) { Event[] branchEventSeq = branches.get(bCount); // / ****** recursive call isFound = searchAndAppendSequence(branchEventSeq, newTmpSeq); // / ****** recursive call if (isFound == true) {// {// there is no need to iterate // more branches, we have found a // match // branches.set(bCount, branchEventSeq); // branches.add(returnSeq); break; } } } else { branches = new ArrayList<>(); } // We have just found out where to append a branch in the tree // add a new branch to the event and return the eventSeq. if (isFound == false) { branches.add(newEventSeq); } existingTree[matchIdx].setBranches(branches); return true; } // End of function searchAndAddSequence } /** * This function saves the model in the database by converting HashMap to * JSON and serializing to a NoSQL database * * @param outStream * An object to display output * @param database * Database name * @param dataAccessObject * IDataAccessObject object * @param sysCallSequences * A map containing one tree of events for every name * @throws TotalADSDBMSException * DBMS related exception */ public void saveinDatabase(IAlgorithmOutStream outStream, String database, IDataAccessObject dataAccessObject, HashMap<Integer, Event[]> sysCallSequences) throws TotalADSDBMSException { outStream.addOutputEvent(Messages.SlidingWindowTree_SaveinDB); outStream.addNewLine(); for (Map.Entry<Integer, Event[]> nodes : sysCallSequences.entrySet()) { Event[] events = nodes.getValue(); com.google.gson.Gson gson = new com.google.gson.Gson(); JsonElement jsonArray = gson.toJsonTree(events); JsonObject jsonObject = new JsonObject(); jsonObject.addProperty(TraceCollection.KEY.toString(), nodes.getKey()); jsonObject.add(TraceCollection.TREE.toString(), jsonArray); JsonObject jsonKey = new JsonObject(); jsonKey.addProperty(TraceCollection.KEY.toString(), nodes.getKey()); dataAccessObject.insertOrUpdateUsingJSON(database, jsonKey, jsonObject, TraceCollection.COLLECTION_NAME.toString()); } } /** * Saves a tree in the database * * @param database * Database name * @param dataAccessObject * Data access object * @param tree * Tree * @param collectionName * Collection name * @throws TotalADSDBMSException * DBMS Exception */ public void saveTreeInDatabase(String database, IDataAccessObject dataAccessObject, Event[] tree, String collectionName) throws TotalADSDBMSException { Integer key = tree[0].getEvent(); // Top node is the name com.google.gson.Gson gson = new com.google.gson.Gson(); JsonElement jsonArray = gson.toJsonTree(tree); JsonObject jsonObject = new JsonObject(); jsonObject.addProperty(TraceCollection.KEY.toString(), key); jsonObject.add(TraceCollection.TREE.toString(), jsonArray); JsonObject jsonKey = new JsonObject(); jsonKey.addProperty(TraceCollection.TREE.toString(), key); dataAccessObject.insertOrUpdateUsingJSON(database, jsonKey, jsonObject, collectionName); } /** * Loads a tree based on the root node from the database * * @param rootNode * Root event * @param database * Database name * @param dataAccessObject * Data access object * @return A tree * @throws TotalADSDBMSException * DBMS related exception */ public Event[] loadTreeFromDatabase(String rootNode, String database, IDataAccessObject dataAccessObject) throws TotalADSDBMSException { Event[] event = null; try (IDBCursor cursor = dataAccessObject.select(TraceCollection.KEY.toString(), "", //$NON-NLS-1$ rootNode, database, TraceCollection.COLLECTION_NAME.toString())) { if (cursor.hasNext()) { IDBRecord record = cursor.next(); Gson gson = new Gson(); event = gson.fromJson(record.get(TraceCollection.TREE.toString()).toString(), Event[].class); } } return event; } /** * Prints all the sequences in a tree; use for testing * * @param outStream * An object to display information * @param sysCallSequences * A map containing one tree of events for every name * @param nameToId * NameToIDMapper */ public void printSequence(IAlgorithmOutStream outStream, HashMap<Integer, Event[]> sysCallSequences, NameToIDMapper nameToId) { for (Map.Entry<Integer, Event[]> nodes : sysCallSequences.entrySet()) { printRecursive(nodes.getValue(), "", outStream, nameToId); //$NON-NLS-1$ } } /** * /** This function goes through the tree of events and print the sequences * in a human readable form. * * @param nodes * Root event * @param prefix * Prefix of the event sequence * @param OutStream * An object to display output * @param nameToId * Name to id mapper */ private void printRecursive(Event[] nodes, String prefix, IAlgorithmOutStream outStream, NameToIDMapper nameToId) { // Boolean isPrefixPrinted=false; String thePrefix = prefix; for (int nodeCount = 0; nodeCount < nodes.length; nodeCount++) { ArrayList<Event[]> branches = nodes[nodeCount].getBranches(); if (nodeCount == nodes.length - 1) { thePrefix = thePrefix + Messages.SlidingWindowTree_Count + nodes[nodeCount].getEvent();// the // last // element // is // the // count // of // the // sequence } else { // Just append the events thePrefix = thePrefix + "\"" + nameToId.getKey(nodes[nodeCount].getEvent().intValue()) + "\" "; //$NON-NLS-1$ //$NON-NLS-2$ } if (branches != null) { // if there are branches on an event then // keep them for (int i = 0; i < branches.size(); i++) { printRecursive(branches.get(i), thePrefix, outStream, nameToId); } } else { // Print only when we reach a leaf of a branch if (nodeCount == nodes.length - 1) { outStream.addOutputEvent(thePrefix); } // console.printText(nodes[nodeCount].event+"-"); } } outStream.addNewLine(); } /** * Searches a matching sequence in the tree * * @param eventSeq * Tree * @param newSeq * New sequence * @return True if a sequence matches, else false */ public Boolean searchMatchingSequenceInTree(Event[] eventSeq, String[] newSeq) { Integer seqSize = newSeq.length; Integer j; for (j = 0; j < seqSize; j++) { if (!eventSeq[j].getEvent().equals(newSeq[j])) { break; } } Integer matchIdx = j - 1; if (matchIdx >= seqSize - 1) { return true; } else if (matchIdx < 0) { return false; // return null if mismatch on the first idx } else { String[] newTmpSeq = new String[seqSize - matchIdx - 1]; Boolean isFound = false; Integer i; for (i = 0; i < newTmpSeq.length - 1; i++) { // That is copy from the next index of matched index newTmpSeq[i] = newSeq[matchIdx + i + 1]; } ArrayList<Event[]> branches = eventSeq[matchIdx].getBranches(); // When there are no more branches then we shall automatically // add a new branch by skipping the if block if (branches != null) { // if the branches exist then we need to recursively go through // the remaining branches to find // a possible location to append the new sequence for (int bCount = 0; bCount < branches.size(); bCount++) { Event[] branchEventSeq = branches.get(bCount); // / ****** recursive call isFound = searchMatchingSequenceInTree(branchEventSeq, newTmpSeq); // / ****** recursive call if (isFound == true) { break; } } } if (isFound) { return true; } return false; } // End of function searchMatchingSequence } /** * Searches a new sequence in the tree and returns the Hamming distance. If * Hamming distance is zero then a sequence matched; otherwise, Hamming * distance is equal to the number of mismatches * * @param nodes * Tree of events * @param newSeq * Sequence to search * @return Hamming distance */ public Integer getHammingAndSearch(Event[] nodes, Integer[] newSeq) { Integer hammDis = 0, minHammDis = 100000000;// Initializing minimum // Hamming distance with a // very large number for (int nodeCount = 0; nodeCount < newSeq.length; nodeCount++) { ArrayList<Event[]> branches = nodes[nodeCount].getBranches(); if (!nodes[nodeCount].getEvent().equals(newSeq[nodeCount])) { // System.out.print (", x "+nodes[nodeCount].getEvent()); hammDis++; } // else // System.out.print (", =="+nodes[nodeCount].getEvent()); if (branches != null) { // if there are branches on an event then // keep Integer[] newTmpSeq = new Integer[newSeq.length - nodeCount - 1]; for (int i = 0; i < newTmpSeq.length; i++) { newTmpSeq[i] = newSeq[nodeCount + i + 1];// that is copy // from the next // index of matched // index } for (int i = 0; i < branches.size(); i++) { // System.out.println(); Integer branchHamming = getHammingAndSearch(branches.get(i), newTmpSeq); branchHamming = branchHamming + hammDis;// add the // mismatches that // have been found // before this // branch if (branchHamming == 0) { // there is no need to get further // branches minHammDis = 0; // we have found a match, as hamming is // 0 break; } else if (branchHamming < minHammDis) { minHammDis = branchHamming; } } } } if (hammDis < minHammDis) { minHammDis = hammDis; } return minHammDis; } // End of class }