org.eclipse.titan.log.viewer.utils.LogFileCacheHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.titan.log.viewer.utils.LogFileCacheHandler.java

Source

/******************************************************************************
 * Copyright (c) 2000-2016 Ericsson Telecom AB
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 ******************************************************************************/
package org.eclipse.titan.log.viewer.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InvalidClassException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.List;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.titan.common.logging.ErrorReporter;
import org.eclipse.titan.common.utils.FileUtils;
import org.eclipse.titan.common.utils.IOUtils;
import org.eclipse.titan.log.viewer.exceptions.TechnicalException;
import org.eclipse.titan.log.viewer.exceptions.TitanLogExceptionHandler;
import org.eclipse.titan.log.viewer.exceptions.UserException;
import org.eclipse.titan.log.viewer.extractors.TestCaseExtractor;
import org.eclipse.titan.log.viewer.models.LogFileMetaData;
import org.eclipse.titan.log.viewer.models.LogRecordIndex;
import org.eclipse.titan.log.viewer.parsers.data.TestCase;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IViewReference;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.navigator.CommonNavigator;

/**
 * This class is responsible for creating and updating the log file cache
 *
 */
public final class LogFileCacheHandler {

    /** long = 8 bytes, int = 4 bytes */
    private static final int LOG_RECORD_INDEX_SIZE = 8 + 2 * 4;
    /** 128 KB */
    private static final int BUFFER_SIZE = 128 * 1024;

    private LogFileCacheHandler() {
        // Do nothing
    }

    /**
     * Checks if the log file cache is up to date
     * 
     * @param logFile the log file to cache
     * @return true if the log file has changed, false if not
     */
    public static boolean hasLogFileChanged(final IFile logFile) {
        File file = new File(logFile.getLocationURI());

        // Check if property file exists...
        File propertyFile = getPropertyFileForLogFile(logFile);
        if (!propertyFile.exists()) {
            return true;
        }

        // Check if property file (log file meta data is valid)
        if (isPropertyFileInvalid(logFile, propertyFile)) {
            return true;
        }

        // Check if index file exists...
        File indexFile = getIndexFileForLogFile(logFile);
        if (!indexFile.exists()) {
            return true;
        }

        // Check if log record index file exists
        File logRecordIndexFile = getLogRecordIndexFileForLogFile(logFile);
        if (!logRecordIndexFile.exists()) {
            return true;
        }

        // Check if update is needed (log file file has changed)
        return updateNeeded(file, propertyFile);
    }

    private static boolean isPropertyFileInvalid(IFile logFile, File propertyFile) {
        try {
            LogFileMetaData logFileMetaData = logFileMetaDataReader(propertyFile);
            String projectName = logFile.getProject().getName();
            String projectRelativePath = File.separator + projectName + File.separator
                    + logFile.getProjectRelativePath().toOSString();
            URI logFilePath = logFile.getLocationURI();
            if (!logFileMetaData.getProjectRelativePath().contentEquals(projectRelativePath)
                    || !logFileMetaData.getFilePath().equals(logFilePath)) {
                return true;
            }
        } catch (final IOException e) {
            return true;
        } catch (final ClassNotFoundException e) {
            return true;
        }
        return false;
    }

    /**
     * Deletes the log file cache for the given log file, is it exist
     * 
     * @param logFile the log file
     */
    private static void clearLogFilePropertyFile(final IFile logFile) {
        File propertyFile = getPropertyFileForLogFile(logFile);
        if (propertyFile.exists()) {
            propertyFile.delete();
        }
    }

    /**
     * Deletes the index file for the given log file, is it exist
     * 
     * @param logFile the log file
     */
    private static void clearLogFileIndexFile(final IFile logFile) {
        File indexFile = getIndexFileForLogFile(logFile);
        FileUtils.deleteQuietly(indexFile);
    }

    /**
     * Deletes the log record index file for the given log file, is it exist
     * 
     * @param logFile the log file
     */
    private static void clearLogFileLogRecordIndexFile(final IFile logFile) {
        File indexFile = getLogRecordIndexFileForLogFile(logFile);
        FileUtils.deleteQuietly(indexFile);
    }

    /**
     * Deletes the index file for the given log file, is it exist and all files in folder
     * 
     * @param logFile the log file
     */
    public static void clearLogFolderCache(final IFolder logFolder) {
        File indexFile = getCacheFolderFor(logFolder);

        if (indexFile.exists() && indexFile.isDirectory()) {
            File[] filesInFolder = indexFile.listFiles();
            for (File aFilesInFolder : filesInFolder) {
                aFilesInFolder.delete();
            }
            FileUtils.deleteQuietly(indexFile);
        }
    }

    /**
     * Creates an index file for with the given test case vector
     * 
     * @param indexFile the index file to write
     * @param testCaseVector the test case vector
     * @throws IOException if index file can not be written
     */
    private static void createIndexFile(final File indexFile, final List<TestCase> testCaseVector)
            throws IOException {
        ObjectOutputStream objectFile = null;
        try {
            // Delete file if it already exist, path already created when creating property file
            if (indexFile.exists()) {
                try {
                    indexFile.delete();
                } catch (SecurityException e) {
                    ErrorReporter.logExceptionStackTrace(e);
                    throw new IOException(Messages.getString("LogFileCacheHandler.0")); //$NON-NLS-1$
                }
            }
            // Create new file
            indexFile.createNewFile();
            // Write test case vector to file
            objectFile = new ObjectOutputStream(new FileOutputStream(indexFile));
            objectFile.writeObject(testCaseVector);

        } finally {
            IOUtils.closeQuietly(objectFile);
        }
    }

    /**
     * @param logFile the log file
     * @return a file which represents the cache file for the given log file
     *          this file may or may not exist
     */
    public static File getPropertyFileForLogFile(final IFile logFile) {
        return new File(getPropertyIFileForLogFile(logFile).getLocationURI());
    }

    /**
     * @param logFile the log file
     * @return a file which represents the cache file for the given log file
     *         this file may or may not exist
     */
    public static IFile getPropertyIFileForLogFile(final IFile logFile) {
        final IFolder cacheFolder = getCacheFolderFor(logFile);
        final String propertyFileName = logFile.getName() + Constants.PROPERTY_EXTENSION;

        return cacheFolder.getFile(propertyFileName);
    }

    /**
     * Gets the cache file for a given IFile
     * 
     * @param logFile the log file to get the index file for
     * @return File the index file, which may or may not exist
     */
    public static File getIndexFileForLogFile(final IFile logFile) {
        return new File(getIndexIFileForLogFile(logFile).getLocationURI());
    }

    /**
     * Gets the cache file for a given IFile
     * 
     * @param logFile the log file to get the index file for
     * @return File the index file, which may or may not exist
     */
    public static IFile getIndexIFileForLogFile(final IFile logFile) {
        final IFolder cacheFolder = getCacheFolderFor(logFile);
        final String indexFileName = logFile.getName() + Constants.INDEX_EXTENSION;

        return cacheFolder.getFile(indexFileName);
    }

    /**
     * Gets the log record cache file for a given IFile
     * 
     * @param logFile the log file
     * @return File the log record index file, which may or may not exist
     */
    public static File getLogRecordIndexFileForLogFile(final IFile logFile) {
        return new File(getLogRecordIndexIFileForLogFile(logFile).getLocationURI());
    }

    /**
     * Gets the log record cache file for a given IFile
     * 
     * @param logFile the log file
     * @return File the log record index file, which may or may not exist
     */
    public static IFile getLogRecordIndexIFileForLogFile(final IFile logFile) {
        final IFolder cacheFolder = getCacheFolderFor(logFile);
        final String logFileName = logFile.getName() + Constants.RECORD_INDEX_EXTENSION;

        return cacheFolder.getFile(logFileName);
    }

    /**
     * @param logFile the log file
     * @return a file which represents the index file for the given log file
     *         this file may or may not exist
     */
    private static File getCacheFolderFor(final IFolder logFolder) {
        final IPath projectRelativePath = Path.fromOSString(
                Constants.CACHE_DIRECTORY + File.separator + logFolder.getProjectRelativePath().toOSString());
        final IProject project = logFolder.getProject();

        return new File(project.getFolder(projectRelativePath).getLocationURI());
    }

    /**
     * 
     * @param logFile The logFile
     * @return The cache folder for the given file
     *         this folder may or may not exists
     */
    private static IFolder getCacheFolderFor(final IFile logFile) {
        final IProject project = logFile.getProject();
        final IPath projectRelativePath = Path.fromOSString(Constants.CACHE_DIRECTORY + File.separator
                + logFile.getProjectRelativePath().removeLastSegments(1).toOSString());

        return project.getFolder(projectRelativePath);
    }

    /**
     * @param logFile the log file
     * @param propertyFile the property of the log file
     * @return true if log file cache needs to be updated, otherwise false 
     */
    private static boolean updateNeeded(final File logFile, final File propertyFile) {
        // Check if cache file is up to date
        LogFileMetaData storedData;
        try {
            storedData = logFileMetaDataReader(propertyFile);
        } catch (IOException e) {
            return true;
        } catch (ClassNotFoundException e) {
            return true;
        }

        return !Constants.CURRENT_VERSION.equals(storedData.getVersion())
                || storedData.getSize() != logFile.length()
                || storedData.getLastModified() != logFile.lastModified();

    }

    /**
     * @param logFile the log file
     * @param propertyFile the property file to create
     * @throws IOException if properties can not be created
     */
    private static void createPropertyFile(final File propertyFile, final LogFileMetaData logFileMetaData)
            throws IOException {
        // If property file exist, delete it
        if (propertyFile.exists()) {
            try {
                propertyFile.delete();
            } catch (SecurityException e) {
                ErrorReporter.logExceptionStackTrace(e);
                throw new IOException(Messages.getString("LogFileCacheHandler.1")); //$NON-NLS-1$
            }
        } else {
            // Create dir(s)
            propertyFile.getParentFile().mkdirs();
        }
        // Create file
        propertyFile.createNewFile();
        logFileMetaDataSerializer(logFileMetaData, propertyFile);
    }

    /**
     * @param logFileMetaData file meta data
     * @param propertyFile he property file to create
     * @throws IOException if log file meta data can not be set
     */
    private static void logFileMetaDataSerializer(final LogFileMetaData logFileMetaData, final File propertyFile)
            throws IOException {
        FileOutputStream stream = null;
        ObjectOutputStream objectFile = null;
        try {
            stream = new FileOutputStream(propertyFile);

            objectFile = new ObjectOutputStream(stream);
            objectFile.writeObject(logFileMetaData);

        } finally {
            IOUtils.closeQuietly(stream, objectFile);
        }
    }

    /**
     * @param propertyFile property file to read from
     * @return Log file meta data
     * @throws IOException if log file meta data can not be found
     * @throws ClassNotFoundException if log file meta data can not be found
     */
    public static LogFileMetaData logFileMetaDataReader(final File propertyFile)
            throws IOException, ClassNotFoundException {
        FileInputStream stream = null;
        LogFileMetaData logFileMetaData = null;
        ObjectInputStream objectFile = null;
        try {
            stream = new FileInputStream(propertyFile);

            objectFile = new ObjectInputStream(stream);
            logFileMetaData = (LogFileMetaData) objectFile.readObject();
        } catch (final InvalidClassException e) {
            FileUtils.deleteQuietly(propertyFile);
        } finally {
            IOUtils.closeQuietly(stream, objectFile);
        }
        return logFileMetaData;
    }

    /**
     * Reads log records from an index file
     * 
     * @param indexFile the index file
     * @param startRecordIndex the log record index to start from 
     * @param numberOfRecords the number of log records indexes to be read
     * @return an array with the read log record indexes
     * @throws FileNotFoundException if the index file is not found
     * @throws IOException if i/o errors occurs 
     */
    public static LogRecordIndex[] readLogRecordIndexFile(final File indexFile, final int startRecordIndex,
            final int numberOfRecords) throws IOException {
        int bytesToRead = numberOfRecords * LOG_RECORD_INDEX_SIZE;
        ByteBuffer buffer = ByteBuffer.allocateDirect(bytesToRead);
        FileInputStream fileInputStream = new FileInputStream(indexFile);
        FileChannel fileChannel = fileInputStream.getChannel();
        try {
            fileChannel.position((long) startRecordIndex * LOG_RECORD_INDEX_SIZE);
            int bytesRead = fileChannel.read(buffer);
            buffer.rewind();
            LogRecordIndex[] logRecordIndexes = null;
            if (bytesRead == bytesToRead) {
                logRecordIndexes = new LogRecordIndex[numberOfRecords];
                for (int counter = 0; counter < numberOfRecords; counter++) {
                    logRecordIndexes[counter] = new LogRecordIndex(buffer.getLong(), buffer.getInt(),
                            buffer.getInt());
                }
            }
            return logRecordIndexes;
        } finally {
            IOUtils.closeQuietly(fileChannel, fileInputStream);
        }
    }

    /**
     * Writes the log record index file
     * 
     * @param indexFile the index file to write to
     * @param logRecordIndexes an array with the log record indexes to be written
     * @throws FileNotFoundException if the index file is not found
     * @throws IOException if i/o errors occurs
     */
    private static void writeLogRecordIndexFile(final File indexFile, final List<LogRecordIndex> logRecordIndexes)
            throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
        FileOutputStream fileOutputStream = new FileOutputStream(indexFile);
        FileChannel file = fileOutputStream.getChannel();
        file.truncate(0);
        try {
            for (LogRecordIndex currLogRecordIndex : logRecordIndexes) {
                buffer.putLong(currLogRecordIndex.getFileOffset());
                buffer.putInt(currLogRecordIndex.getRecordLength());
                buffer.putInt(currLogRecordIndex.getRecordNumber());
                if (!buffer.hasRemaining()) {
                    buffer.rewind();
                    file.write(buffer);
                    buffer.clear();
                }
                //logRecordIndexes.remove(i); -> causes error...
            }

            // Make sure that the remaining data is flushed to disc
            if (buffer.position() != 0) {
                buffer.flip();
                file.write(buffer);
            }

        } finally {
            IOUtils.closeQuietly(file, fileOutputStream);
        }
    }

    /**
     * Gets the number of records in an index file
     * @param indexFile the index file
     * @return the number of records in the file
     */
    public static int getNumberOfLogRecordIndexes(final File indexFile) {
        return (int) (indexFile.length() / LOG_RECORD_INDEX_SIZE);
    }

    /**
     * Initializes the cache for the given log file.
     */
    public static void fillCache(final IFile logFile, final LogFileMetaData logFileMetaData,
            final List<TestCase> testCaseVector, final List<LogRecordIndex> logRecordIndexVector)
            throws IOException {
        LogFileCacheHandler.createPropertyFile(LogFileCacheHandler.getPropertyFileForLogFile(logFile),
                logFileMetaData);
        LogFileCacheHandler.createIndexFile(LogFileCacheHandler.getIndexFileForLogFile(logFile), testCaseVector);
        LogFileCacheHandler.writeLogRecordIndexFile(LogFileCacheHandler.getLogRecordIndexFileForLogFile(logFile),
                logRecordIndexVector);
    }

    /**
     * Clears the cache of the log file.
     * The following files will be deleted (if exist)
     * <li>property file</li>
     * <li>test case index file</li>
     * <li>log record index file</li>
     * 
     * @param logFile The log file
     */
    public static void clearCache(final IFile logFile) {
        LogFileCacheHandler.clearLogFileIndexFile(logFile);
        LogFileCacheHandler.clearLogFileLogRecordIndexFile(logFile);
        LogFileCacheHandler.clearLogFilePropertyFile(logFile);
    }

    /**
     * Should be called when a log file has changed. Closes the associated views. Shows a <code>MessageBox</code> where the user can decide if he/she wants to process
     * the log file or just close the views. If the user choose yes, the processing will start in a new <code>WorkspaceJob</code>.
     * @param logFile
     */
    public static void handleLogFileChange(final IFile logFile) {
        final IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
        final IViewReference[] viewReferences = activePage.getViewReferences();
        ActionUtils.closeAssociatedViews(activePage, viewReferences, logFile);
        clearCache(logFile);

        Display.getDefault().syncExec(new Runnable() {
            @Override
            public void run() {
                IViewPart view = activePage.findView("org.eclipse.ui.navigator.ProjectExplorer");
                if (view instanceof CommonNavigator) {
                    CommonNavigator navigator = (CommonNavigator) view;
                    navigator.getCommonViewer().collapseToLevel(logFile, AbstractTreeViewer.ALL_LEVELS);
                }
            }
        });

        MessageBox mb = new MessageBox(activePage.getActivePart().getSite().getShell(),
                SWT.ICON_ERROR | SWT.OK | SWT.CANCEL);
        mb.setText("The log file has been modified.");
        mb.setMessage("The log file has been modified. Click on OK to extract the test cases"
                + " or CANCEL to close the associated views.");
        if (mb.open() == SWT.OK) {
            WorkspaceJob job = new WorkspaceJob("Testcase extraction from log file: " + logFile.getName()) {
                @Override
                public IStatus runInWorkspace(final IProgressMonitor monitor) throws CoreException {
                    boolean completed = LogFileCacheHandler.processLogFile(logFile, monitor, false);
                    return completed ? Status.OK_STATUS : Status.CANCEL_STATUS;
                }
            };
            job.setPriority(Job.LONG);
            job.setUser(true);
            job.setRule(logFile.getProject());
            job.schedule();
        }
    }

    /**
     * Processes the given LogFile if it has changed. The property file, index file, and log record index file will be created.
     * Does nothing if the log file has not changed, or test case extraction is already running on the file.
     * @param logFile The log file
     * @param pMonitor Progress monitor.
     * @param quietMode If false, the error messages will be displayed to the user.
     * @return true if the processing was successful, false otherwise
     */
    public static boolean processLogFile(final IFile logFile, final IProgressMonitor pMonitor,
            final boolean quietMode) {
        IProgressMonitor monitor = pMonitor == null ? new NullProgressMonitor() : pMonitor;

        if (!logFile.exists()) {
            if (!quietMode) {
                TitanLogExceptionHandler
                        .handleException(new UserException("The log file does not exist: " + logFile.getName())); //$NON-NLS-1$
            }
            return false;
        }

        try {
            Object temp = logFile.getSessionProperty(Constants.EXTRACTION_RUNNING);
            if (temp != null && (Boolean) temp) {
                if (!quietMode) {
                    TitanLogExceptionHandler.handleException(new TechnicalException(
                            "Test case extraction is running on the given logfile: " + logFile.getName())); //$NON-NLS-1$
                }
                return false;
            }
        } catch (CoreException e) {
            ErrorReporter.logExceptionStackTrace(e);
            TitanLogExceptionHandler.handleException(new UserException(e.getMessage()));
            return false;
        }

        if (!LogFileCacheHandler.hasLogFileChanged(logFile)) {
            return true;
        }

        try {
            logFile.setSessionProperty(Constants.EXTRACTION_RUNNING, true);
        } catch (CoreException e) {
            ErrorReporter.logExceptionStackTrace(e);
            return false;
        }

        final LogFileHandler logFileHandler = new LogFileHandler(logFile);

        try {
            LogFileMetaData logFileMetaData = logFileHandler.autoDetect();

            final TestCaseExtractor testCaseExtractor = new TestCaseExtractor();
            testCaseExtractor.extractTestCasesFromLogFile(logFileMetaData, monitor);

            if (monitor.isCanceled()) {
                Display.getDefault().asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        final IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                                .getActivePage();
                        IViewPart view = activePage.findView("org.eclipse.ui.navigator.ProjectExplorer");

                        if (view instanceof CommonNavigator) {
                            CommonNavigator navigator = (CommonNavigator) view;
                            navigator.getCommonViewer().update(logFile, null);
                            navigator.getCommonViewer().collapseToLevel(logFile, AbstractTreeViewer.ALL_LEVELS);
                        }
                    }
                });
                return false;
            }

            fillCache(logFile, logFileMetaData, testCaseExtractor.getTestCases(),
                    testCaseExtractor.getLogRecordIndexes());

            Display.getDefault().asyncExec(new Runnable() {
                @Override
                public void run() {
                    final IWorkbenchPage activePage = PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                            .getActivePage();
                    IViewPart view = activePage.findView("org.eclipse.ui.navigator.ProjectExplorer");

                    if (view instanceof CommonNavigator) {
                        CommonNavigator navigator = (CommonNavigator) view;
                        navigator.getCommonViewer().refresh(logFile, true);
                        navigator.getCommonViewer().expandToLevel(logFile, AbstractTreeViewer.ALL_LEVELS);
                    }
                }
            });

            if (testCaseExtractor.failedDuringExtraction()) {
                Display.getDefault().asyncExec(new Runnable() {
                    @Override
                    public void run() {
                        MessageDialog.openInformation(null,
                                Messages.getString("OpenTextTableProjectsViewMenuAction.8"),
                                Messages.getString("OpenTextTableProjectsViewMenuAction.9"));
                    }
                });
                return false;
            }

        } catch (IOException e) {
            if (!quietMode) {
                ErrorReporter.logExceptionStackTrace(e);
                TitanLogExceptionHandler.handleException(new TechnicalException(
                        Messages.getString("OpenTextTableProjectsViewMenuAction.2") + e.getMessage())); //$NON-NLS-1$
            }
            return false;
        } catch (TechnicalException e) { // invalid file format
            if (!quietMode) {
                MessageDialog.openError(Display.getDefault().getActiveShell(), "Invalid log file", e.getMessage());
            }
            return false;
        } finally {
            try {
                logFile.setSessionProperty(Constants.EXTRACTION_RUNNING, false);
            } catch (CoreException e) {
                ErrorReporter.logExceptionStackTrace(e);
            }
        }

        return true;
    }

    /**
     * Returns a scheduling rule for the given log file. This rule contains the log file, the property file and the two index files.
     * 
     * @param logFile
     * @return The created MultiRule
     */
    public static ISchedulingRule getSchedulingRule(final IFile logFile) {
        final ISchedulingRule[] rules = new ISchedulingRule[] { logFile, getPropertyIFileForLogFile(logFile),
                getIndexIFileForLogFile(logFile), getLogRecordIndexIFileForLogFile(logFile) };

        return new MultiRule(rules);
    }
}