blob: a32de2fd5c6cd72887fbc917532eeff6053b6c77 [file] [log] [blame]
/*
* Copyright (C) 2007 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.ddmuilib.logcat;
import com.android.ddmlib.Log;
import com.android.ddmlib.Log.LogLevel;
import com.android.ddmuilib.annotation.UiThread;
import com.android.ddmuilib.logcat.LogPanel.LogMessage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableItem;
import java.util.ArrayList;
import java.util.regex.PatternSyntaxException;
/** logcat output filter class */
public class LogFilter {
public final static int MODE_PID = 0x01;
public final static int MODE_TAG = 0x02;
public final static int MODE_LEVEL = 0x04;
private String mName;
/**
* Filtering mode. Value can be a mix of MODE_PID, MODE_TAG, MODE_LEVEL
*/
private int mMode = 0;
/**
* pid used for filtering. Only valid if mMode is MODE_PID.
*/
private int mPid;
/** Single level log level as defined in Log.mLevelChar. Only valid
* if mMode is MODE_LEVEL */
private int mLogLevel;
/**
* log tag filtering. Only valid if mMode is MODE_TAG
*/
private String mTag;
private Table mTable;
private TabItem mTabItem;
private boolean mIsCurrentTabItem = false;
private int mUnreadCount = 0;
/** Temp keyword filtering */
private String[] mTempKeywordFilters;
/** temp pid filtering */
private int mTempPid = -1;
/** temp tag filtering */
private String mTempTag;
/** temp log level filtering */
private int mTempLogLevel = -1;
private LogColors mColors;
private boolean mTempFilteringStatus = false;
private final ArrayList<LogMessage> mMessages = new ArrayList<LogMessage>();
private final ArrayList<LogMessage> mNewMessages = new ArrayList<LogMessage>();
private boolean mSupportsDelete = true;
private boolean mSupportsEdit = true;
private int mRemovedMessageCount = 0;
/**
* Creates a filter with a particular mode.
* @param name The name to be displayed in the UI
*/
public LogFilter(String name) {
mName = name;
}
public LogFilter() {
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(mName);
sb.append(':');
sb.append(mMode);
if ((mMode & MODE_PID) == MODE_PID) {
sb.append(':');
sb.append(mPid);
}
if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
sb.append(':');
sb.append(mLogLevel);
}
if ((mMode & MODE_TAG) == MODE_TAG) {
sb.append(':');
sb.append(mTag);
}
return sb.toString();
}
public boolean loadFromString(String string) {
String[] segments = string.split(":"); // $NON-NLS-1$
int index = 0;
// get the name
mName = segments[index++];
// get the mode
mMode = Integer.parseInt(segments[index++]);
if ((mMode & MODE_PID) == MODE_PID) {
mPid = Integer.parseInt(segments[index++]);
}
if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
mLogLevel = Integer.parseInt(segments[index++]);
}
if ((mMode & MODE_TAG) == MODE_TAG) {
mTag = segments[index++];
}
return true;
}
/** Sets the name of the filter. */
void setName(String name) {
mName = name;
}
/**
* Returns the UI display name.
*/
public String getName() {
return mName;
}
/**
* Set the Table ui widget associated with this filter.
* @param tabItem The item in the TabFolder
* @param table The Table object
*/
public void setWidgets(TabItem tabItem, Table table) {
mTable = table;
mTabItem = tabItem;
}
/**
* Returns true if the filter is ready for ui.
*/
public boolean uiReady() {
return (mTable != null && mTabItem != null);
}
/**
* Returns the UI table object.
* @return
*/
public Table getTable() {
return mTable;
}
public void dispose() {
mTable.dispose();
mTabItem.dispose();
mTable = null;
mTabItem = null;
}
/**
* Resets the filtering mode to be 0 (i.e. no filter).
*/
public void resetFilteringMode() {
mMode = 0;
}
/**
* Returns the current filtering mode.
* @return A bitmask. Possible values are MODE_PID, MODE_TAG, MODE_LEVEL
*/
public int getFilteringMode() {
return mMode;
}
/**
* Adds PID to the current filtering mode.
* @param pid
*/
public void setPidMode(int pid) {
if (pid != -1) {
mMode |= MODE_PID;
} else {
mMode &= ~MODE_PID;
}
mPid = pid;
}
/** Returns the pid filter if valid, otherwise -1 */
public int getPidFilter() {
if ((mMode & MODE_PID) == MODE_PID)
return mPid;
return -1;
}
public void setTagMode(String tag) {
if (tag != null && tag.length() > 0) {
mMode |= MODE_TAG;
} else {
mMode &= ~MODE_TAG;
}
mTag = tag;
}
public String getTagFilter() {
if ((mMode & MODE_TAG) == MODE_TAG)
return mTag;
return null;
}
public void setLogLevel(int level) {
if (level == -1) {
mMode &= ~MODE_LEVEL;
} else {
mMode |= MODE_LEVEL;
mLogLevel = level;
}
}
public int getLogLevel() {
if ((mMode & MODE_LEVEL) == MODE_LEVEL) {
return mLogLevel;
}
return -1;
}
public boolean supportsDelete() {
return mSupportsDelete ;
}
public boolean supportsEdit() {
return mSupportsEdit;
}
/**
* Sets the selected state of the filter.
* @param selected selection state.
*/
public void setSelectedState(boolean selected) {
if (selected) {
if (mTabItem != null) {
mTabItem.setText(mName);
}
mUnreadCount = 0;
}
mIsCurrentTabItem = selected;
}
/**
* Adds a new message and optionally removes an old message.
* <p/>The new message is filtered through {@link #accept(LogMessage)}.
* Calls to {@link #flush()} from a UI thread will display it (and other
* pending messages) to the associated {@link Table}.
* @param logMessage the MessageData object to filter
* @return true if the message was accepted.
*/
public boolean addMessage(LogMessage newMessage, LogMessage oldMessage) {
synchronized (mMessages) {
if (oldMessage != null) {
int index = mMessages.indexOf(oldMessage);
if (index != -1) {
// TODO check that index will always be -1 or 0, as only the oldest message is ever removed.
mMessages.remove(index);
mRemovedMessageCount++;
}
// now we look for it in mNewMessages. This can happen if the new message is added
// and then removed because too many messages are added between calls to #flush()
index = mNewMessages.indexOf(oldMessage);
if (index != -1) {
// TODO check that index will always be -1 or 0, as only the oldest message is ever removed.
mNewMessages.remove(index);
}
}
boolean filter = accept(newMessage);
if (filter) {
// at this point the message is accepted, we add it to the list
mMessages.add(newMessage);
mNewMessages.add(newMessage);
}
return filter;
}
}
/**
* Removes all the items in the filter and its {@link Table}.
*/
public void clear() {
mRemovedMessageCount = 0;
mNewMessages.clear();
mMessages.clear();
mTable.removeAll();
}
/**
* Filters a message.
* @param logMessage the Message
* @return true if the message is accepted by the filter.
*/
boolean accept(LogMessage logMessage) {
// do the regular filtering now
if ((mMode & MODE_PID) == MODE_PID && mPid != logMessage.data.pid) {
return false;
}
if ((mMode & MODE_TAG) == MODE_TAG && (
logMessage.data.tag == null ||
logMessage.data.tag.equals(mTag) == false)) {
return false;
}
int msgLogLevel = logMessage.data.logLevel.getPriority();
// test the temp log filtering first, as it replaces the old one
if (mTempLogLevel != -1) {
if (mTempLogLevel > msgLogLevel) {
return false;
}
} else if ((mMode & MODE_LEVEL) == MODE_LEVEL &&
mLogLevel > msgLogLevel) {
return false;
}
// do the temp filtering now.
if (mTempKeywordFilters != null) {
String msg = logMessage.msg;
for (String kw : mTempKeywordFilters) {
try {
if (msg.contains(kw) == false && msg.matches(kw) == false) {
return false;
}
} catch (PatternSyntaxException e) {
// if the string is not a valid regular expression,
// this exception is thrown.
return false;
}
}
}
if (mTempPid != -1 && mTempPid != logMessage.data.pid) {
return false;
}
if (mTempTag != null && mTempTag.length() > 0) {
if (mTempTag.equals(logMessage.data.tag) == false) {
return false;
}
}
return true;
}
/**
* Takes all the accepted messages and display them.
* This must be called from a UI thread.
*/
@UiThread
public void flush() {
// if scroll bar is at the bottom, we will scroll
ScrollBar bar = mTable.getVerticalBar();
boolean scroll = bar.getMaximum() == bar.getSelection() + bar.getThumb();
// if we are not going to scroll, get the current first item being shown.
int topIndex = mTable.getTopIndex();
// disable drawing
mTable.setRedraw(false);
int totalCount = mNewMessages.size();
try {
// remove the items of the old messages.
for (int i = 0 ; i < mRemovedMessageCount && mTable.getItemCount() > 0 ; i++) {
mTable.remove(0);
}
if (mUnreadCount > mTable.getItemCount()) {
mUnreadCount = mTable.getItemCount();
}
// add the new items
for (int i = 0 ; i < totalCount ; i++) {
LogMessage msg = mNewMessages.get(i);
addTableItem(msg);
}
} catch (SWTException e) {
// log the error and keep going. Content of the logcat table maybe unexpected
// but at least ddms won't crash.
Log.e("LogFilter", e);
}
// redraw
mTable.setRedraw(true);
// scroll if needed, by showing the last item
if (scroll) {
totalCount = mTable.getItemCount();
if (totalCount > 0) {
mTable.showItem(mTable.getItem(totalCount-1));
}
} else if (mRemovedMessageCount > 0) {
// we need to make sure the topIndex is still visible.
// Because really old items are removed from the list, this could make it disappear
// if we don't change the scroll value at all.
topIndex -= mRemovedMessageCount;
if (topIndex < 0) {
// looks like it disappeared. Lets just show the first item
mTable.showItem(mTable.getItem(0));
} else {
mTable.showItem(mTable.getItem(topIndex));
}
}
// if this filter is not the current one, we update the tab text
// with the amount of unread message
if (mIsCurrentTabItem == false) {
mUnreadCount += mNewMessages.size();
totalCount = mTable.getItemCount();
if (mUnreadCount > 0) {
mTabItem.setText(mName + " (" // $NON-NLS-1$
+ (mUnreadCount > totalCount ? totalCount : mUnreadCount)
+ ")"); // $NON-NLS-1$
} else {
mTabItem.setText(mName); // $NON-NLS-1$
}
}
mNewMessages.clear();
}
void setColors(LogColors colors) {
mColors = colors;
}
int getUnreadCount() {
return mUnreadCount;
}
void setUnreadCount(int unreadCount) {
mUnreadCount = unreadCount;
}
void setSupportsDelete(boolean support) {
mSupportsDelete = support;
}
void setSupportsEdit(boolean support) {
mSupportsEdit = support;
}
void setTempKeywordFiltering(String[] segments) {
mTempKeywordFilters = segments;
mTempFilteringStatus = true;
}
void setTempPidFiltering(int pid) {
mTempPid = pid;
mTempFilteringStatus = true;
}
void setTempTagFiltering(String tag) {
mTempTag = tag;
mTempFilteringStatus = true;
}
void resetTempFiltering() {
if (mTempPid != -1 || mTempTag != null || mTempKeywordFilters != null) {
mTempFilteringStatus = true;
}
mTempPid = -1;
mTempTag = null;
mTempKeywordFilters = null;
}
void resetTempFilteringStatus() {
mTempFilteringStatus = false;
}
boolean getTempFilterStatus() {
return mTempFilteringStatus;
}
/**
* Add a TableItem for the index-th item of the buffer
* @param filter The index of the table in which to insert the item.
*/
private void addTableItem(LogMessage msg) {
TableItem item = new TableItem(mTable, SWT.NONE);
item.setText(0, msg.data.time);
item.setText(1, new String(new char[] { msg.data.logLevel.getPriorityLetter() }));
item.setText(2, msg.data.pidString);
item.setText(3, msg.data.tag);
item.setText(4, msg.msg);
// add the buffer index as data
item.setData(msg);
if (msg.data.logLevel == LogLevel.INFO) {
item.setForeground(mColors.infoColor);
} else if (msg.data.logLevel == LogLevel.DEBUG) {
item.setForeground(mColors.debugColor);
} else if (msg.data.logLevel == LogLevel.ERROR) {
item.setForeground(mColors.errorColor);
} else if (msg.data.logLevel == LogLevel.WARN) {
item.setForeground(mColors.warningColor);
} else {
item.setForeground(mColors.verboseColor);
}
}
}