Merge GenericLogcatItem and MiscLogcatItem.

Also, support merging logcat items together.

Change-Id: I133b0a8b32228ffaae389ea75009f156d6181df1
diff --git a/src/com/android/loganalysis/heuristic/KernelResetHeuristic.java b/src/com/android/loganalysis/heuristic/KernelResetHeuristic.java
index 945ee9c..11e67b9 100644
--- a/src/com/android/loganalysis/heuristic/KernelResetHeuristic.java
+++ b/src/com/android/loganalysis/heuristic/KernelResetHeuristic.java
@@ -94,7 +94,7 @@
         if (mKernelLog != null) {
             for (MiscKernelLogItem item : mKernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET)) {
                 sb.append(String.format("Reason: %s, Time: %.6f\nPreamble:\n%s\n\n",
-                        item.getMessage(), item.getEventTime(), item.getPreamble()));
+                        item.getStack(), item.getEventTime(), item.getPreamble()));
             }
         }
         return sb.toString();
diff --git a/src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java b/src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java
index 9b70b7c..d4041c2 100644
--- a/src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java
+++ b/src/com/android/loganalysis/heuristic/RuntimeRestartHeuristic.java
@@ -131,7 +131,7 @@
         if (mLogcat != null) {
             for (MiscLogcatItem item : mLogcat.getMiscEvents(LogcatParser.RUNTIME_RESTART)) {
                 sb.append(String.format("Message: %s, Time: %s\n\nLast lines of logcat:\n%s\n\n" +
-                        "Process lines for pid %d:\n%s\n\n", item.getMessage(), item.getEventTime(),
+                        "Process lines for pid %d:\n%s\n\n", item.getStack(), item.getEventTime(),
                         item.getLastPreamble(), item.getPid(), item.getProcessPreamble()));
             }
         }
diff --git a/src/com/android/loganalysis/item/AnrItem.java b/src/com/android/loganalysis/item/AnrItem.java
index d028335..fe440bb 100644
--- a/src/com/android/loganalysis/item/AnrItem.java
+++ b/src/com/android/loganalysis/item/AnrItem.java
@@ -15,6 +15,8 @@
  */
 package com.android.loganalysis.item;
 
+import com.android.loganalysis.parser.LogcatParser;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
@@ -22,7 +24,7 @@
 /**
  * An {@link IItem} used to store ANR info.
  */
-public class AnrItem extends GenericLogcatItem {
+public class AnrItem extends MiscLogcatItem {
     /**
      * An enum used to select the CPU usage category.
      */
@@ -47,8 +49,6 @@
     /** Constant for JSON output */
     public static final String REASON = "REASON";
     /** Constant for JSON output */
-    public static final String STACK = "STACK";
-    /** Constant for JSON output */
     public static final String TRACE = "TRACE";
 
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
@@ -59,13 +59,14 @@
         LoadCategory.LOAD_1.toString(),
         LoadCategory.LOAD_5.toString(),
         LoadCategory.LOAD_15.toString(),
-        ACTIVITY, REASON, STACK, TRACE));
+        ACTIVITY, REASON, TRACE));
 
     /**
      * The constructor for {@link AnrItem}.
      */
     public AnrItem() {
         super(ATTRIBUTES);
+        setCategory(LogcatParser.ANR);
     }
 
     /**
@@ -125,20 +126,6 @@
     }
 
     /**
-     * Get the stack for the ANR.
-     */
-    public String getStack() {
-        return (String) getAttribute(STACK);
-    }
-
-    /**
-     * Set the stack for the ANR.
-     */
-    public void setStack(String stack) {
-        setAttribute(STACK, stack);
-    }
-
-    /**
      * Get the main trace for the ANR.
      */
     public String getTrace() {
diff --git a/src/com/android/loganalysis/item/GenericItem.java b/src/com/android/loganalysis/item/GenericItem.java
index a98ed28..0ebf870 100644
--- a/src/com/android/loganalysis/item/GenericItem.java
+++ b/src/com/android/loganalysis/item/GenericItem.java
@@ -55,7 +55,7 @@
             throw new ConflictingItemException("Conflicting class types");
         }
 
-        return new GenericItem(mAllowedAttributes, mergeAttributes(other));
+        return new GenericItem(mAllowedAttributes, mergeAttributes(other, mAllowedAttributes));
     }
 
     /**
@@ -68,7 +68,8 @@
      * @return A Map from Strings to Objects containing merged attributes.
      * @throws ConflictingItemException If the two items are not consistent.
      */
-    protected Map<String, Object> mergeAttributes(IItem other) throws ConflictingItemException {
+    protected Map<String, Object> mergeAttributes(IItem other, Set<String> attributes)
+            throws ConflictingItemException {
         if (this == other) {
             return mAttributes;
         }
@@ -78,7 +79,7 @@
 
         GenericItem item = (GenericItem) other;
         Map<String, Object> mergedAttributes = new HashMap<String, Object>();
-        for (String attribute : mAllowedAttributes) {
+        for (String attribute : attributes) {
             mergedAttributes.put(attribute,
                     mergeObjects(getAttribute(attribute), item.getAttribute(attribute)));
         }
diff --git a/src/com/android/loganalysis/item/GenericLogcatItem.java b/src/com/android/loganalysis/item/GenericLogcatItem.java
deleted file mode 100644
index 03ef704..0000000
--- a/src/com/android/loganalysis/item/GenericLogcatItem.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.loganalysis.item;
-
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * A generic item containing attributes for time, process, and thread and can be extended for
- * items such as {@link AnrItem} and {@link JavaCrashItem}.
- */
-public abstract class GenericLogcatItem extends GenericItem {
-
-    /** Constant for JSON output */
-    public static final String EVENT_TIME = "EVENT_TIME";
-    /** Constant for JSON output */
-    public static final String PID = "PID";
-    /** Constant for JSON output */
-    public static final String TID = "TID";
-    /** Constant for JSON output */
-    public static final String APP = "APP";
-    /** Constant for JSON output */
-    public static final String LAST_PREAMBLE = "LAST_PREAMBLE";
-    /** Constant for JSON output */
-    public static final String PROCESS_PREAMBLE = "PROCESS_PREAMBLE";
-
-    private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
-            EVENT_TIME, PID, TID, APP, LAST_PREAMBLE, PROCESS_PREAMBLE));
-
-    /**
-     * Constructor for {@link GenericLogcatItem}.
-     *
-     * @param attributes A list of allowed attributes.
-     */
-    protected GenericLogcatItem(Set<String> attributes) {
-        super(getAllAttributes(attributes));
-    }
-
-    /**
-     * Get the {@link Date} object when the event happened.
-     */
-    public Date getEventTime() {
-        return (Date) getAttribute(EVENT_TIME);
-    }
-
-    /**
-     * Set the {@link Date} object when the event happened.
-     */
-    public void setEventTime(Date time) {
-        setAttribute(EVENT_TIME, time);
-    }
-
-    /**
-     * Get the PID of the event.
-     */
-    public Integer getPid() {
-        return (Integer) getAttribute(PID);
-    }
-
-    /**
-     * Set the PID of the event.
-     */
-    public void setPid(Integer pid) {
-        setAttribute(PID, pid);
-    }
-
-    /**
-     * Get the TID of the event.
-     */
-    public Integer getTid() {
-        return (Integer) getAttribute(TID);
-    }
-
-    /**
-     * Set the TID of the event.
-     */
-    public void setTid(Integer tid) {
-        setAttribute(TID, tid);
-    }
-
-    /**
-     * Get the app or package name of the event.
-     */
-    public String getApp() {
-        return (String) getAttribute(APP);
-    }
-
-    /**
-     * Set the app or package name of the event.
-     */
-    public void setApp(String app) {
-        setAttribute(APP, app);
-    }
-
-    /**
-     * Get the last preamble for the event.
-     */
-    public String getLastPreamble() {
-        return (String) getAttribute(LAST_PREAMBLE);
-    }
-
-    /**
-     * Set the last preamble for the event.
-     */
-    public void setLastPreamble(String preamble) {
-        setAttribute(LAST_PREAMBLE, preamble);
-    }
-
-    /**
-     * Get the process preamble for the event.
-     */
-    public String getProcessPreamble() {
-        return (String) getAttribute(PROCESS_PREAMBLE);
-    }
-
-    /**
-     * Set the process preamble for the event.
-     */
-    public void setProcessPreamble(String preamble) {
-        setAttribute(PROCESS_PREAMBLE, preamble);
-    }
-
-    /**
-     * Combine an array of attributes with the internal list of attributes.
-     */
-    private static Set<String> getAllAttributes(Set<String> attributes) {
-        Set<String> allAttributes = new HashSet<String>(ATTRIBUTES);
-        allAttributes.addAll(attributes);
-        return allAttributes;
-    }
-}
diff --git a/src/com/android/loganalysis/item/JavaCrashItem.java b/src/com/android/loganalysis/item/JavaCrashItem.java
index bcf7e90..cd81ed6 100644
--- a/src/com/android/loganalysis/item/JavaCrashItem.java
+++ b/src/com/android/loganalysis/item/JavaCrashItem.java
@@ -15,6 +15,8 @@
  */
 package com.android.loganalysis.item;
 
+import com.android.loganalysis.parser.LogcatParser;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
@@ -22,23 +24,22 @@
 /**
  * An {@link IItem} used to store Java crash info.
  */
-public class JavaCrashItem extends GenericLogcatItem {
+public class JavaCrashItem extends MiscLogcatItem {
 
     /** Constant for JSON output */
     public static final String EXCEPTION = "EXCEPTION";
     /** Constant for JSON output */
     public static final String MESSAGE = "MESSAGE";
-    /** Constant for JSON output */
-    public static final String STACK = "STACK";
 
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
-            EXCEPTION, MESSAGE, STACK));
+            EXCEPTION, MESSAGE));
 
     /**
      * The constructor for {@link JavaCrashItem}.
      */
     public JavaCrashItem() {
         super(ATTRIBUTES);
+        setCategory(LogcatParser.JAVA_CRASH);
     }
 
     /**
@@ -68,18 +69,4 @@
     public void setMessage(String message) {
         setAttribute(MESSAGE, message);
     }
-
-    /**
-     * Get the stack for the ANR.
-     */
-    public String getStack() {
-        return (String) getAttribute(STACK);
-    }
-
-    /**
-     * Set the stack for the ANR.
-     */
-    public void setStack(String stack) {
-        setAttribute(STACK, stack);
-    }
 }
diff --git a/src/com/android/loganalysis/item/LogcatItem.java b/src/com/android/loganalysis/item/LogcatItem.java
index 1353ef8..04e753c 100644
--- a/src/com/android/loganalysis/item/LogcatItem.java
+++ b/src/com/android/loganalysis/item/LogcatItem.java
@@ -15,6 +15,10 @@
  */
 package com.android.loganalysis.item;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.util.Arrays;
 import java.util.Date;
 import java.util.HashSet;
@@ -37,9 +41,8 @@
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
             START_TIME, STOP_TIME, EVENTS));
 
-    private class ItemList extends LinkedList<IItem> {
-        private static final long serialVersionUID = 1088529764741812025L;
-    }
+    @SuppressWarnings("serial")
+    private class ItemList extends LinkedList<MiscLogcatItem> {}
 
     /**
      * The constructor for {@link LogcatItem}.
@@ -79,16 +82,16 @@
     }
 
     /**
-     * Get the list of all {@link IItem} events.
+     * Get the list of all {@link MiscLogcatItem} events.
      */
-    public List<IItem> getEvents() {
+    public List<MiscLogcatItem> getEvents() {
         return (ItemList) getAttribute(EVENTS);
     }
 
     /**
-     * Add an {@link IItem} event to the end of the list of events.
+     * Add an {@link MiscLogcatItem} event to the end of the list of events.
      */
-    public void addEvent(IItem event) {
+    public void addEvent(MiscLogcatItem event) {
         ((ItemList) getAttribute(EVENTS)).add(event);
     }
 
@@ -144,4 +147,77 @@
         }
         return items;
     }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public LogcatItem merge(IItem other) throws ConflictingItemException {
+        if (this == other) {
+            return this;
+        }
+        if (other == null || !(other instanceof LogcatItem)) {
+            throw new ConflictingItemException("Conflicting class types");
+        }
+
+        LogcatItem logcat = (LogcatItem) other;
+
+        Date start = logcat.getStartTime().before(getStartTime()) ?
+                logcat.getStartTime() : getStartTime();
+        Date stop = logcat.getStopTime().after(getStopTime()) ?
+                logcat.getStopTime() : getStopTime();
+        Date overlapStart = logcat.getStartTime().after(getStartTime()) ?
+                logcat.getStartTime() : getStartTime();
+        Date overlapStop = logcat.getStopTime().before(getStopTime()) ?
+                logcat.getStopTime() : getStopTime();
+
+        // Make sure that all events in the overlapping span are
+        ItemList mergedEvents = new ItemList();
+        for (MiscLogcatItem event : getEvents()) {
+            final Date eventTime = event.getEventTime();
+            if (eventTime.after(overlapStart) && eventTime.before(overlapStop) &&
+                    !logcat.getEvents().contains(event)) {
+                throw new ConflictingItemException("Event in first logcat not contained in " +
+                        "overlapping portion of other logcat.");
+            }
+            mergedEvents.add(event);
+        }
+
+        for (MiscLogcatItem event : logcat.getEvents()) {
+            final Date eventTime = event.getEventTime();
+            if (eventTime.after(overlapStart) && eventTime.before(overlapStop)) {
+                if (!getEvents().contains(event)) {
+                    throw new ConflictingItemException("Event in first logcat not contained in " +
+                            "overlapping portion of other logcat.");
+                }
+            } else {
+                mergedEvents.add(event);
+            }
+        }
+
+        LogcatItem mergedLogcat = new LogcatItem();
+        mergedLogcat.setStartTime(start);
+        mergedLogcat.setStopTime(stop);
+        mergedLogcat.setAttribute(EVENTS, mergedEvents);
+        return mergedLogcat;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public JSONObject toJson() {
+        JSONObject output = super.toJson();
+        JSONArray events = new JSONArray();
+        for (MiscLogcatItem event : getEvents()) {
+            events.put(event.toJson());
+        }
+
+        try {
+            output.put(EVENTS, events);
+        } catch (JSONException e) {
+            // Ignore
+        }
+        return output;
+    }
 }
diff --git a/src/com/android/loganalysis/item/MiscKernelLogItem.java b/src/com/android/loganalysis/item/MiscKernelLogItem.java
index f5d853b..6897567 100644
--- a/src/com/android/loganalysis/item/MiscKernelLogItem.java
+++ b/src/com/android/loganalysis/item/MiscKernelLogItem.java
@@ -32,10 +32,10 @@
     /** Constant for JSON output */
     public static final String CATEGORY = "CATEGORY";
     /** Constant for JSON output */
-    public static final String MESSAGE = "MESSAGE";
+    public static final String STACK = "STACK";
 
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
-            EVENT_TIME, PREAMBLE, CATEGORY, MESSAGE));
+            EVENT_TIME, PREAMBLE, CATEGORY, STACK));
 
     /**
      * Constructor for {@link MiscKernelLogItem}.
@@ -87,16 +87,16 @@
     }
 
     /**
-     * Get the message for the event.
+     * Get the stack for the event.
      */
-    public String getMessage() {
-        return (String) getAttribute(MESSAGE);
+    public String getStack() {
+        return (String) getAttribute(STACK);
     }
 
     /**
-     * Set the message for the event.
+     * Set the stack for the event.
      */
-    public void setMessage(String message) {
-        setAttribute(MESSAGE, message);
+    public void setStack(String stack) {
+        setAttribute(STACK, stack);
     }
 }
diff --git a/src/com/android/loganalysis/item/MiscLogcatItem.java b/src/com/android/loganalysis/item/MiscLogcatItem.java
index fdc692a..831cb0c 100644
--- a/src/com/android/loganalysis/item/MiscLogcatItem.java
+++ b/src/com/android/loganalysis/item/MiscLogcatItem.java
@@ -16,30 +16,144 @@
 package com.android.loganalysis.item;
 
 import java.util.Arrays;
+import java.util.Date;
 import java.util.HashSet;
 import java.util.Set;
 
 /**
  * An {@link IItem} used to store miscellaneous logcat info.
  */
-public class MiscLogcatItem extends GenericLogcatItem {
-
+public class MiscLogcatItem extends GenericItem {
+    /** Constant for JSON output */
+    public static final String EVENT_TIME = "EVENT_TIME";
+    /** Constant for JSON output */
+    public static final String PID = "PID";
+    /** Constant for JSON output */
+    public static final String TID = "TID";
+    /** Constant for JSON output */
+    public static final String APP = "APP";
+    /** Constant for JSON output */
+    public static final String LAST_PREAMBLE = "LAST_PREAMBLE";
+    /** Constant for JSON output */
+    public static final String PROCESS_PREAMBLE = "PROCESS_PREAMBLE";
     /** Constant for JSON output */
     public static final String CATEGORY = "CATEGORY";
     /** Constant for JSON output */
-    public static final String MESSAGE = "MESSAGE";
+    public static final String STACK = "STACK";
 
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
-            CATEGORY, MESSAGE));
+            EVENT_TIME, PID, TID, APP, LAST_PREAMBLE, PROCESS_PREAMBLE, CATEGORY, STACK));
 
     /**
-     * The constructor for {@link MiscLogcatItem}.
+     * Constructor for {@link MiscLogcatItem}.
      */
     public MiscLogcatItem() {
         super(ATTRIBUTES);
     }
 
     /**
+     * Constructor for {@link MiscLogcatItem}.
+     *
+     * @param attributes A list of allowed attributes.
+     */
+    protected MiscLogcatItem(Set<String> attributes) {
+        super(getAllAttributes(attributes));
+    }
+
+    /**
+     * Get the {@link Date} object when the event happened.
+     */
+    public Date getEventTime() {
+        return (Date) getAttribute(EVENT_TIME);
+    }
+
+    /**
+     * Set the {@link Date} object when the event happened.
+     */
+    public void setEventTime(Date time) {
+        setAttribute(EVENT_TIME, time);
+    }
+
+    /**
+     * Get the PID of the event.
+     */
+    public Integer getPid() {
+        return (Integer) getAttribute(PID);
+    }
+
+    /**
+     * Set the PID of the event.
+     */
+    public void setPid(Integer pid) {
+        setAttribute(PID, pid);
+    }
+
+    /**
+     * Get the TID of the event.
+     */
+    public Integer getTid() {
+        return (Integer) getAttribute(TID);
+    }
+
+    /**
+     * Set the TID of the event.
+     */
+    public void setTid(Integer tid) {
+        setAttribute(TID, tid);
+    }
+
+    /**
+     * Get the app or package name of the event.
+     */
+    public String getApp() {
+        return (String) getAttribute(APP);
+    }
+
+    /**
+     * Set the app or package name of the event.
+     */
+    public void setApp(String app) {
+        setAttribute(APP, app);
+    }
+
+    /**
+     * Get the last preamble for the event.
+     */
+    public String getLastPreamble() {
+        return (String) getAttribute(LAST_PREAMBLE);
+    }
+
+    /**
+     * Set the last preamble for the event.
+     */
+    public void setLastPreamble(String preamble) {
+        setAttribute(LAST_PREAMBLE, preamble);
+    }
+
+    /**
+     * Get the process preamble for the event.
+     */
+    public String getProcessPreamble() {
+        return (String) getAttribute(PROCESS_PREAMBLE);
+    }
+
+    /**
+     * Set the process preamble for the event.
+     */
+    public void setProcessPreamble(String preamble) {
+        setAttribute(PROCESS_PREAMBLE, preamble);
+    }
+
+    /**
+     * Combine an array of attributes with the internal list of attributes.
+     */
+    private static Set<String> getAllAttributes(Set<String> attributes) {
+        Set<String> allAttributes = new HashSet<String>(ATTRIBUTES);
+        allAttributes.addAll(attributes);
+        return allAttributes;
+    }
+
+    /**
      * Get the category of the event.
      */
     public String getCategory() {
@@ -54,16 +168,16 @@
     }
 
     /**
-     * Get the message for the event.
+     * Get the stack for the event.
      */
-    public String getMessage() {
-        return (String) getAttribute(MESSAGE);
+    public String getStack() {
+        return (String) getAttribute(STACK);
     }
 
     /**
-     * Set the message for the event.
+     * Set the stack for the event.
      */
-    public void setMessage(String message) {
-        setAttribute(MESSAGE, message);
+    public void setStack(String stack) {
+        setAttribute(STACK, stack);
     }
 }
diff --git a/src/com/android/loganalysis/item/MonkeyLogItem.java b/src/com/android/loganalysis/item/MonkeyLogItem.java
index 7bc44f2..454e587 100644
--- a/src/com/android/loganalysis/item/MonkeyLogItem.java
+++ b/src/com/android/loganalysis/item/MonkeyLogItem.java
@@ -334,15 +334,15 @@
      * Get the {@link AnrItem}, {@link JavaCrashItem}, or {@link NativeCrashItem} for the monkey run
      * or null if there was no crash.
      */
-    public GenericLogcatItem getCrash() {
-        return (GenericLogcatItem) getAttribute(CRASH);
+    public MiscLogcatItem getCrash() {
+        return (MiscLogcatItem) getAttribute(CRASH);
     }
 
     /**
      * Set the {@link AnrItem}, {@link JavaCrashItem}, or {@link NativeCrashItem} for the monkey
      * run.
      */
-    public void setCrash(GenericLogcatItem crash) {
+    public void setCrash(MiscLogcatItem crash) {
         setAttribute(CRASH, crash);
     }
 
diff --git a/src/com/android/loganalysis/item/NativeCrashItem.java b/src/com/android/loganalysis/item/NativeCrashItem.java
index e6679c2..762a1cc 100644
--- a/src/com/android/loganalysis/item/NativeCrashItem.java
+++ b/src/com/android/loganalysis/item/NativeCrashItem.java
@@ -15,6 +15,8 @@
  */
 package com.android.loganalysis.item;
 
+import com.android.loganalysis.parser.LogcatParser;
+
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Set;
@@ -22,21 +24,20 @@
 /**
  * An {@link IItem} used to store native crash info.
  */
-public class NativeCrashItem extends GenericLogcatItem {
+public class NativeCrashItem extends MiscLogcatItem {
 
     /** Constant for JSON output */
     public static final String FINGERPRINT = "FINGERPRINT";
-    /** Constant for JSON output */
-    public static final String STACK = "STACK";
 
     private static final Set<String> ATTRIBUTES = new HashSet<String>(Arrays.asList(
-            FINGERPRINT, STACK));
+            FINGERPRINT));
 
     /**
      * The constructor for {@link NativeCrashItem}.
      */
     public NativeCrashItem() {
         super(ATTRIBUTES);
+        setCategory(LogcatParser.NATIVE_CRASH);
     }
 
     /**
@@ -52,18 +53,4 @@
     public void setFingerprint(String fingerprint) {
         setAttribute(FINGERPRINT, fingerprint);
     }
-
-    /**
-     * Get the stack for the crash.
-     */
-    public String getStack() {
-        return (String) getAttribute(STACK);
-    }
-
-    /**
-     * Set the stack for the crash.
-     */
-    public void setStack(String stack) {
-        setAttribute(STACK, stack);
-    }
 }
diff --git a/src/com/android/loganalysis/parser/BugreportParser.java b/src/com/android/loganalysis/parser/BugreportParser.java
index f4a7383..a42e8e4 100644
--- a/src/com/android/loganalysis/parser/BugreportParser.java
+++ b/src/com/android/loganalysis/parser/BugreportParser.java
@@ -18,11 +18,11 @@
 import com.android.loganalysis.item.AnrItem;
 import com.android.loganalysis.item.BugreportItem;
 import com.android.loganalysis.item.DumpsysItem;
-import com.android.loganalysis.item.GenericLogcatItem;
 import com.android.loganalysis.item.IItem;
 import com.android.loganalysis.item.KernelLogItem;
 import com.android.loganalysis.item.LogcatItem;
 import com.android.loganalysis.item.MemInfoItem;
+import com.android.loganalysis.item.MiscLogcatItem;
 import com.android.loganalysis.item.ProcrankItem;
 import com.android.loganalysis.item.SystemPropsItem;
 import com.android.loganalysis.item.TopItem;
@@ -159,9 +159,9 @@
 
             if (mBugreport.getSystemLog() != null && mBugreport.getProcrank() != null) {
                 for (IItem item : mBugreport.getSystemLog().getEvents()) {
-                    if (item instanceof GenericLogcatItem &&
-                            ((GenericLogcatItem) item).getApp() == null) {
-                        GenericLogcatItem logcatItem = (GenericLogcatItem) item;
+                    if (item instanceof MiscLogcatItem &&
+                            ((MiscLogcatItem) item).getApp() == null) {
+                        MiscLogcatItem logcatItem = (MiscLogcatItem) item;
                         logcatItem.setApp(mBugreport.getProcrank().getProcessName(
                                 logcatItem.getPid()));
                     }
diff --git a/src/com/android/loganalysis/parser/KernelLogParser.java b/src/com/android/loganalysis/parser/KernelLogParser.java
index 635563f..5c7afe0 100644
--- a/src/com/android/loganalysis/parser/KernelLogParser.java
+++ b/src/com/android/loganalysis/parser/KernelLogParser.java
@@ -114,7 +114,7 @@
             MiscKernelLogItem kernelLogItem = new MiscKernelLogItem();
             kernelLogItem.setEventTime(mStopTime);
             kernelLogItem.setPreamble(mPreambleUtil.getLastTail());
-            kernelLogItem.setMessage(message);
+            kernelLogItem.setStack(message);
             kernelLogItem.setCategory(category);
             mKernelLog.addEvent(kernelLogItem);
         }
diff --git a/src/com/android/loganalysis/parser/LogcatParser.java b/src/com/android/loganalysis/parser/LogcatParser.java
index 88f029a..229c6f6 100644
--- a/src/com/android/loganalysis/parser/LogcatParser.java
+++ b/src/com/android/loganalysis/parser/LogcatParser.java
@@ -15,7 +15,6 @@
  */
 package com.android.loganalysis.parser;
 
-import com.android.loganalysis.item.GenericLogcatItem;
 import com.android.loganalysis.item.LogcatItem;
 import com.android.loganalysis.item.MiscLogcatItem;
 import com.android.loganalysis.util.ArrayUtil;
@@ -45,6 +44,9 @@
  * </p>
  */
 public class LogcatParser implements IParser {
+    public static final String ANR = "ANR";
+    public static final String JAVA_CRASH = "JAVA_CRASH";
+    public static final String NATIVE_CRASH = "NATIVE_CRASH";
     public static final String HIGH_CPU_USAGE = "HIGH_CPU_USAGE";
     public static final String HIGH_MEMORY_USAGE = "HIGH_MEMORY_USAGE";
     public static final String RUNTIME_RESTART = "RUNTIME_RESTART";
@@ -249,7 +251,7 @@
      */
     private void commit() {
         for (LogcatData data : mDataList) {
-            GenericLogcatItem item = null;
+            MiscLogcatItem item = null;
             if ("E".equals(data.mLevel) && "ActivityManager".equals(data.mTag)) {
                 // CLog.v("Parsing ANR: %s", data.mLines);
                 item = new AnrParser().parse(data.mLines);
@@ -265,7 +267,7 @@
                 if (category != null) {
                     MiscLogcatItem logcatItem = new MiscLogcatItem();
                     logcatItem.setCategory(category);
-                    logcatItem.setMessage(msg);
+                    logcatItem.setStack(msg);
                     item = logcatItem;
                 }
             }
diff --git a/src/com/android/loganalysis/parser/MonkeyLogParser.java b/src/com/android/loganalysis/parser/MonkeyLogParser.java
index e55e94a..3cb4c45 100644
--- a/src/com/android/loganalysis/parser/MonkeyLogParser.java
+++ b/src/com/android/loganalysis/parser/MonkeyLogParser.java
@@ -16,7 +16,7 @@
 package com.android.loganalysis.parser;
 
 import com.android.loganalysis.item.AnrItem;
-import com.android.loganalysis.item.GenericLogcatItem;
+import com.android.loganalysis.item.MiscLogcatItem;
 import com.android.loganalysis.item.MonkeyLogItem;
 import com.android.loganalysis.item.MonkeyLogItem.DroppedCategory;
 import com.android.loganalysis.item.TracesItem;
@@ -145,7 +145,7 @@
                 mBlock.add(line);
                 return;
             } else {
-                GenericLogcatItem crash = null;
+                MiscLogcatItem crash = null;
                 if (mMatchingJavaCrash) {
                     crash = new JavaCrashParser().parse(mBlock);
                 } else if (mMatchingNativeCrash) {
@@ -267,7 +267,7 @@
     /**
      * Add a crash to the monkey log item and reset the parser state for crashes.
      */
-    private void addCrashAndReset(GenericLogcatItem crash) {
+    private void addCrashAndReset(MiscLogcatItem crash) {
         if (crash != null) {
             crash.setPid(mPid);
             crash.setApp(mApp);
diff --git a/tests/src/com/android/loganalysis/item/GenericItemTest.java b/tests/src/com/android/loganalysis/item/GenericItemTest.java
index 7532fd3..94e3236 100644
--- a/tests/src/com/android/loganalysis/item/GenericItemTest.java
+++ b/tests/src/com/android/loganalysis/item/GenericItemTest.java
@@ -71,49 +71,49 @@
     }
 
     /**
-     * Test for {@link GenericItem#mergeAttributes(IItem)}.
+     * Test for {@link GenericItem#mergeAttributes(IItem, Set)}.
      */
     public void testMergeAttributes() throws ConflictingItemException {
         Map<String, Object> attributes;
 
-        attributes = mEmptyItem1.mergeAttributes(mEmptyItem1);
+        attributes = mEmptyItem1.mergeAttributes(mEmptyItem1, ATTRIBUTES);
         assertNull(attributes.get("string"));
         assertNull(attributes.get("integer"));
 
-        attributes = mEmptyItem1.mergeAttributes(mEmptyItem2);
+        attributes = mEmptyItem1.mergeAttributes(mEmptyItem2, ATTRIBUTES);
         assertNull(attributes.get("string"));
         assertNull(attributes.get("integer"));
 
-        attributes = mEmptyItem2.mergeAttributes(mEmptyItem1);
+        attributes = mEmptyItem2.mergeAttributes(mEmptyItem1, ATTRIBUTES);
         assertNull(attributes.get("string"));
         assertNull(attributes.get("integer"));
 
-        attributes = mEmptyItem1.mergeAttributes(mStringItem);
+        attributes = mEmptyItem1.mergeAttributes(mStringItem, ATTRIBUTES);
         assertEquals(mStringAttribute, attributes.get("string"));
         assertNull(attributes.get("integer"));
 
-        attributes = mStringItem.mergeAttributes(mEmptyItem1);
+        attributes = mStringItem.mergeAttributes(mEmptyItem1, ATTRIBUTES);
         assertEquals(mStringAttribute, attributes.get("string"));
         assertNull(attributes.get("integer"));
 
-        attributes = mIntegerItem.mergeAttributes(mStringItem);
+        attributes = mIntegerItem.mergeAttributes(mStringItem, ATTRIBUTES);
         assertEquals(mStringAttribute, attributes.get("string"));
         assertEquals(mIntegerAttribute, attributes.get("integer"));
 
-        attributes = mEmptyItem1.mergeAttributes(mFullItem1);
+        attributes = mEmptyItem1.mergeAttributes(mFullItem1, ATTRIBUTES);
         assertEquals(mStringAttribute, attributes.get("string"));
         assertEquals(mIntegerAttribute, attributes.get("integer"));
 
-        attributes = mFullItem1.mergeAttributes(mEmptyItem1);
+        attributes = mFullItem1.mergeAttributes(mEmptyItem1, ATTRIBUTES);
         assertEquals(mStringAttribute, attributes.get("string"));
         assertEquals(mIntegerAttribute, attributes.get("integer"));
 
-        attributes = mFullItem1.mergeAttributes(mFullItem2);
+        attributes = mFullItem1.mergeAttributes(mFullItem2, ATTRIBUTES);
         assertEquals(mStringAttribute, attributes.get("string"));
         assertEquals(mIntegerAttribute, attributes.get("integer"));
 
         try {
-            mFullItem1.mergeAttributes(mInconsistentItem);
+            mFullItem1.mergeAttributes(mInconsistentItem, ATTRIBUTES);
             fail("Expecting a ConflictingItemException");
         } catch (ConflictingItemException e) {
             // Expected
@@ -228,9 +228,11 @@
      */
     public void testToJson() throws JSONException {
         GenericItem item = new GenericItem(new HashSet<String>(Arrays.asList(
-                "string", "date", "object", "integer", "long", "float", "double", "null")));
+                "string", "date", "object", "integer", "long", "float", "double", "item", "null")));
         Date date = new Date();
         Object object = new Object();
+        NativeCrashItem subItem = new NativeCrashItem();
+
         item.setAttribute("string", "foo");
         item.setAttribute("date", date);
         item.setAttribute("object", object);
@@ -238,6 +240,7 @@
         item.setAttribute("long", 1L);
         item.setAttribute("float", 2.5f);
         item.setAttribute("double", 3.5);
+        item.setAttribute("item", subItem);
         item.setAttribute("null", null);
 
         // Convert to JSON string and back again
@@ -257,6 +260,8 @@
         assertEquals(2.5, output.get("float"));
         assertTrue(output.has("double"));
         assertEquals(3.5, output.get("double"));
+        assertTrue(output.has("item"));
+        assertTrue(output.get("item") instanceof JSONObject);
         assertFalse(output.has("null"));
     }
 }
diff --git a/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java b/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java
index 0785269..32236b7 100644
--- a/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java
+++ b/tests/src/com/android/loganalysis/parser/KernelLogParserTest.java
@@ -46,7 +46,7 @@
         MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
         assertEquals(1.0, item.getEventTime(), 0.0000005);
         assertEquals("[    0.000000] Start", item.getPreamble());
-        assertEquals("Kernel panic", item.getMessage());
+        assertEquals("Kernel panic", item.getStack());
     }
 
     /**
@@ -68,7 +68,7 @@
         MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
         assertEquals(1.0, item.getEventTime(), 0.0000005);
         assertEquals("<1>[    0.000000] Start", item.getPreamble());
-        assertEquals("Kernel panic", item.getMessage());
+        assertEquals("Kernel panic", item.getStack());
     }
 
     /**
@@ -90,7 +90,7 @@
         MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
         assertEquals(2.0, item.getEventTime(), 0.0000005);
         assertEquals("[    0.000000] Start\n[    2.000000] End", item.getPreamble());
-        assertEquals("Last boot reason: hw_reset", item.getMessage());
+        assertEquals("Last boot reason: hw_reset", item.getStack());
     }
 
     /**
@@ -110,6 +110,6 @@
         MiscKernelLogItem item = kernelLog.getMiscEvents(KernelLogParser.KERNEL_RESET).get(0);
         assertNull(item.getEventTime());
         assertEquals("", item.getPreamble());
-        assertEquals("Last boot reason: hw_reset", item.getMessage());
+        assertEquals("Last boot reason: hw_reset", item.getStack());
     }
 }