org.bitbucket.mlopatkin.android.liblogcat.file.DumpstateFileDataSource.java Source code

Java tutorial

Introduction

Here is the source code for org.bitbucket.mlopatkin.android.liblogcat.file.DumpstateFileDataSource.java

Source

/*
 * Copyright 2011 Mikhail Lopatkin
 *
 * 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 org.bitbucket.mlopatkin.android.liblogcat.file;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.bitbucket.mlopatkin.android.liblogcat.DataSource;
import org.bitbucket.mlopatkin.android.liblogcat.KernelLogParser;
import org.bitbucket.mlopatkin.android.liblogcat.KernelLogRecord;
import org.bitbucket.mlopatkin.android.liblogcat.LogRecord;
import org.bitbucket.mlopatkin.android.liblogcat.LogRecord.Buffer;
import org.bitbucket.mlopatkin.android.liblogcat.LogRecordParser;
import org.bitbucket.mlopatkin.android.liblogcat.ProcessListParser;
import org.bitbucket.mlopatkin.android.liblogcat.RecordListener;
import org.bitbucket.mlopatkin.android.liblogcat.file.ParsingStrategies.Strategy;
import org.bitbucket.mlopatkin.android.logviewer.Configuration;

public class DumpstateFileDataSource implements DataSource {
    private static final Logger logger = Logger.getLogger(DumpstateFileDataSource.class);
    private static final int READ_AHEAD_LIMIT = 65536;

    private List<SectionHandler> handlers = new ArrayList<SectionHandler>();
    private List<LogRecord> records = new ArrayList<LogRecord>();
    private EnumSet<Buffer> buffers = EnumSet.noneOf(Buffer.class);
    private RecordListener<LogRecord> logcatListener;
    private RecordListener<KernelLogRecord> kernelListener;

    private File file;

    public DumpstateFileDataSource(File file, BufferedReader in) throws IOException, ParseException {
        this.file = file;
        initSectionHandlers();
        parseFile(in);
    }

    private void parseFile(BufferedReader in) throws IOException, ParseException {
        String line = in.readLine();
        while (line != null) {
            String sectionName = getSectionName(line);
            if (sectionName != null) {
                parseSection(in, sectionName);
            }
            line = in.readLine();
        }
    }

    private void parseSection(BufferedReader in, String sectionName) throws IOException, ParseException {
        SectionHandler handler = getSectionHandler(sectionName);
        if (handler == null) {
            return;
        }
        handler.startSection(sectionName);
        in.mark(READ_AHEAD_LIMIT);
        String line = in.readLine();
        while (line != null) {
            if (getSectionName(line) != null) {
                // found start of a new section
                in.reset();
                break;
            }
            boolean shouldBreak = !handler.handleLine(line);
            if (shouldBreak) {
                // handler reported that his section is over
                break;
            }
            in.mark(READ_AHEAD_LIMIT);
            line = in.readLine();
        }
        handler.endSection();
    }

    private SectionHandler getSectionHandler(String sectionName) {
        for (SectionHandler handler : handlers) {
            if (handler.isSupportedSection(sectionName)) {
                logger.debug("Supported section: " + sectionName);
                return handler;
            }
        }
        logger.debug("Unsupported section: " + sectionName);
        return null;
    }

    private void initSectionHandlers() {
        handlers.add(new LogcatSectionHandler());
        handlers.add(new ProcessesSectionHandler());
        handlers.add(new KernelLogHandler());
    }

    @Override
    public void close() {

    }

    @Override
    public EnumSet<Buffer> getAvailableBuffers() {
        return buffers;
    }

    @Override
    public Map<Integer, String> getPidToProcessConverter() {
        return converter;
    }

    @Override
    public boolean reset() {
        setLogRecordListener(logcatListener);
        setKernelLogListener(kernelListener);
        return true;
    }

    @Override
    public void setLogRecordListener(RecordListener<LogRecord> listener) {
        this.logcatListener = listener;
        Collections.sort(records);
        listener.setRecords(records);
    }

    /**
     * Handles one section of the dumpstate file
     */
    private interface SectionHandler {
        /**
         * Checks if the implementation supports some section.
         * 
         * @param sectionName
         *            section name as appears in the file without wrapping
         *            dashes
         * @return {@code true} if the implementation can handle this section
         */
        boolean isSupportedSection(String sectionName);

        /**
         * Handles one line from the file.
         * 
         * @param line
         *            one line from the file (not null but can be empty)
         * @return {@code true} if the line wasn't last line in section and the
         *         handler is expecting more
         */
        boolean handleLine(String line) throws ParseException;

        /**
         * Called when the section ends due to end of the file or because other
         * section starts.
         */
        void endSection();

        /**
         * Starts section processing.
         */
        void startSection(String sectionName);
    }

    private static final Pattern SECTION_NAME_PATTERN = Pattern.compile("^------ (.*) ------$");

    private static String getSectionName(String line) {
        Matcher m = SECTION_NAME_PATTERN.matcher(line);
        if (m.matches()) {
            return m.group(1);
        } else {
            return null;
        }
    }

    private static final Pattern SECTION_END_PATTERN = Pattern.compile("^\\[.*: .* elapsed\\]$");

    private void addLogRecord(LogRecord record) {
        records.add(record);
    }

    private void addBuffer(Buffer buffer) {
        buffers.add(buffer);
    }

    private class LogcatSectionHandler implements SectionHandler {

        private Buffer buffer;
        private Strategy parsingStrategy;

        @Override
        public void endSection() {
            assert buffer != null;
            addBuffer(buffer);
        }

        @Override
        public boolean handleLine(String line) throws ParseException {
            if (isEnd(line)) {
                return false;
            }
            if (StringUtils.isBlank(line) || LogRecordParser.isLogBeginningLine(line)) {
                return true;
            }
            if (parsingStrategy == null) {
                parsingStrategy = chooseParsingStrategy(line);
            }
            assert buffer != null;
            LogRecord record = parsingStrategy.parse(buffer, line);
            if (record == null) {
                logger.debug("Null record: " + line);
            } else {
                addLogRecord(record);
            }
            return true;
        }

        private Strategy chooseParsingStrategy(String line) throws ParseException {
            for (Strategy strategy : ParsingStrategies.supportedStrategies) {
                if (strategy.parse(null, line) != null) {
                    return strategy;
                }
            }
            throw new ParseException("Cannot figure out log format from " + line, 0);
        }

        @Override
        public boolean isSupportedSection(String sectionName) {
            for (Buffer buffer : Buffer.values()) {
                String header = Configuration.dump.bufferHeader(buffer);
                if (header != null && sectionName.startsWith(header + " LOG")) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public void startSection(String sectionName) {
            parsingStrategy = null;
            buffer = getBufferFromName(sectionName);
        }

        private Buffer getBufferFromName(String sectionName) {
            for (Buffer buffer : Buffer.values()) {
                String header = Configuration.dump.bufferHeader(buffer);
                if (header != null && sectionName.startsWith(header)) {
                    return buffer;
                }
            }
            return Buffer.UNKNOWN;
        }

        private boolean isEnd(String line) {
            return SECTION_END_PATTERN.matcher(line).matches();
        }
    }

    private static final String PROCESSES_SECTION = "PROCESSES (ps -P)";
    private Map<Integer, String> converter = new HashMap<Integer, String>();

    private class ProcessesSectionHandler implements SectionHandler {

        @Override
        public void endSection() {
        }

        @Override
        public boolean handleLine(String line) throws ParseException {
            if (isEnd(line)) {
                return false;
            }

            if (StringUtils.isBlank(line) || ProcessListParser.isProcessListHeader(line)) {
                return true;
            }
            Matcher m = ProcessListParser.parseProcessListLine(line);
            converter.put(ProcessListParser.getPid(m), ProcessListParser.getProcessName(m));
            return true;
        }

        @Override
        public boolean isSupportedSection(String sectionName) {
            return PROCESSES_SECTION.equalsIgnoreCase(sectionName);
        }

        @Override
        public void startSection(String sectionName) {
        }

        private boolean isEnd(String line) {
            return SECTION_END_PATTERN.matcher(line).matches();
        }

    }

    private static final String KERNEL_SECTION = "KERNEL LOG (dmesg)";

    private final List<KernelLogRecord> kernelRecords = new ArrayList<KernelLogRecord>();

    private class KernelLogHandler implements SectionHandler {

        @Override
        public boolean isSupportedSection(String sectionName) {
            return KERNEL_SECTION.equals(sectionName);
        }

        @Override
        public boolean handleLine(String line) throws ParseException {
            if (isEnd(line)) {
                return false;
            }
            KernelLogRecord record = KernelLogParser.parseRecord(line);
            if (record != null) {
                kernelRecords.add(record);
            }
            return true;
        }

        @Override
        public void endSection() {
        }

        @Override
        public void startSection(String sectionName) {
        }

        private boolean isEnd(String line) {
            return SECTION_END_PATTERN.matcher(line).matches();
        }
    }

    @Override
    public String toString() {
        return file.getName();
    }

    @Override
    public boolean setKernelLogListener(RecordListener<KernelLogRecord> listener) {
        kernelListener = listener;
        kernelListener.setRecords(kernelRecords);
        return true;
    }
}