rabbit.tracking.internal.trackers.JavaTrackerTest.java Source code

Java tutorial

Introduction

Here is the source code for rabbit.tracking.internal.trackers.JavaTrackerTest.java

Source

/*
 * Copyright 2010 The Rabbit Eclipse Plug-in 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 rabbit.tracking.internal.trackers;

import rabbit.data.store.model.JavaEvent;
import rabbit.tracking.internal.IdleDetector;
import rabbit.tracking.internal.TrackingPlugin;
import rabbit.tracking.internal.trackers.AbstractTracker;
import rabbit.tracking.internal.trackers.JavaTracker;

import static org.eclipse.jdt.internal.ui.actions.SelectionConverter.getElementAtOffset;
import static org.eclipse.jdt.internal.ui.actions.SelectionConverter.getInput;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.ui.actions.OpenJavaPerspectiveAction;
import org.eclipse.jdt.ui.wizards.JavaCapabilityConfigurationPage;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.eclipse.ui.views.IViewDescriptor;
import org.joda.time.Interval;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

import static java.lang.String.format;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.util.Observable;
import java.util.Random;

/**
 * @see JavaTracker
 */
@SuppressWarnings("restriction")
public class JavaTrackerTest extends AbstractTrackerTest<JavaEvent> {

    private static IJavaProject project;
    private static IPackageFragment pkg;
    private static ICompilationUnit unit;

    @AfterClass
    public static void afterClass() {
        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
    }

    @BeforeClass
    public static void beforeClass() throws Exception {
        new OpenJavaPerspectiveAction().run();

        // Creates the project:
        IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
        IProject proj = root.getProject(System.currentTimeMillis() + "");
        JavaCapabilityConfigurationPage.createProject(proj, (URI) null, null);
        project = JavaCore.create(proj);
        JavaCapabilityConfigurationPage page = new JavaCapabilityConfigurationPage();
        page.init(project, null, null, false);
        page.configureJavaProject(null);

        // Creates the package:
        IPackageFragmentRoot src = project.getAllPackageFragmentRoots()[0];
        pkg = src.createPackageFragment("pkg", true, null);

        // Creates the class:
        String className = "Program";
        StringBuilder builder = new StringBuilder();
        builder.append(format("package %s;%n", pkg.getElementName()));
        builder.append(format("public class %s {%n", className));
        builder.append(format("}%n"));
        String content = builder.toString();
        unit = pkg.createCompilationUnit(className + ".java", content, true, null);
        unit.open(null);

        PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
    }

    /**
     * Tests when the editor is no longer the active part:
     */
    @Test
    public void testEditorDeactivated() throws Exception {
        final JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        int offset = getDocument(editor).get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true); // Start
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        long preEnd = System.currentTimeMillis();
        // Sets another view to be active to cause the editor to lose focus:
        editor.getSite().getPage().showView(getRandomView().getId());
        long postEnd = System.currentTimeMillis();

        // One data should be in the collection (the selected package declaration):
        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();

        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(getElementAtOffset(editor), event.getElement());
    }

    /**
     * Tests that events are recorded properly with the different states of the
     * editor.
     */
    @Test
    public void testEditorDeactivatedThenActivated() throws Exception {
        JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        int offset = getDocument(editor).get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Now run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        long preEnd = System.currentTimeMillis();
        editor.getSite().getPage().showView(getRandomView().getId());
        long postEnd = System.currentTimeMillis();

        // One data should be in the collection (the selected package declaration):
        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();

        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(getElementAtOffset(editor), event.getElement());

        // Now we activate the editor again to see if the tracker will start to
        // track events again:
        tracker.flushData();

        preStart = System.currentTimeMillis();
        editor.getSite().getPage().activate(editor);
        postStart = System.currentTimeMillis();

        Thread.sleep(20);

        preEnd = System.currentTimeMillis();
        editor.getSite().getPage().showView(getRandomView().getId());
        postEnd = System.currentTimeMillis();

        // One data should be in the collection (the selected package declaration):
        assertEquals(1, tracker.getData().size());
        event = tracker.getData().iterator().next();
        start = event.getInterval().getStartMillis();
        end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(getElementAtOffset(editor), event.getElement());
    }

    /**
     * Test when the user changes from working on a Java element to another.
     */
    @Test
    public void testElementChanged() throws Exception {
        JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        IDocument document = getDocument(editor);
        int offset = document.get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Keeps the reference of the package declaration for testing latter:
        final IJavaElement element = getElementAtOffset(editor);

        // Run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        // Change the element the user is working on:
        offset = document.get().indexOf(unit.getTypes()[0].getElementName());
        int line = document.getLineOfOffset(offset);
        StyledText text = editor.getViewer().getTextWidget();
        text.setCaretOffset(document.getLineOffset(line));
        text.insert(" ");

        long preEnd = System.currentTimeMillis();
        text.notifyListeners(SWT.KeyDown, new Event());
        long postEnd = System.currentTimeMillis();

        // One data should be in the collection (the selected package declaration):
        assertEquals(1, tracker.getData().size());

        JavaEvent event = tracker.getData().iterator().next();
        assertEquals(element, event.getElement());
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
    }

    /**
     * When the tracker is set to enable, but if there is no active workbench
     * window, no data will be collected.
     */
    @Test
    public void testEnable_noActiveWorkbenchWindow() throws Exception {
        JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        int offset = getDocument(editor).get().indexOf(unit.getElementName());
        int len = unit.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Open a new shell to cause the workbench window to lose focus:
        Shell shell = null;
        try {
            shell = new Shell(Display.getCurrent());
            shell.open();
            shell.forceActive();

            tracker.setEnabled(true);
            Thread.sleep(30);
            tracker.setEnabled(false);
            assertEquals(0, tracker.getData().size());

        } finally {
            if (shell != null) {
                shell.dispose();
            }
        }
    }

    @Test
    public void testEnableThenDisable() throws Exception {
        JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        String className = unit.getTypes()[0].getElementName();
        int offset = getDocument(editor).get().indexOf(className);
        int len = className.length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(30);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // One data should be in the collection (the selected package declaration):
        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(getElementAtOffset(editor), event.getElement());
    }

    /**
     * Test an event on an anonymous. This event should be filtered on save, so
     * that instead of showing a user spent x amount of time on the anonymous
     * class, we show that a user spent x amount of time on the anonymous's parent
     * type element (a method or a type that is not anonymous).
     */
    @Test
    public void testFilter_anonymousClass() throws Exception {
        /*
         * Here we test that: a method containing an anonymous Runnable which also
         * contains another anonymous Runnable, and the most inner Runnable is
         * selected (to emulate that the user is working on that), then when filter
         * on save the data should indicate that the user has spent x amount of time
         * working on the method, not any of the Runnable's.
         */

        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);

        StringBuilder builder = new StringBuilder();
        builder.append("void aMethod() {");
        builder.append("  new Runnable() { ");
        builder.append("    public void run(){");
        builder.append("      new Runnable() {");
        builder.append("        public void run() {}");
        builder.append("      };");
        builder.append("    } ");
        builder.append("  };");
        builder.append("}");

        String content = document.get();
        int offset = content.indexOf("{") + 1;
        int len = 0;
        document.replace(offset, len, builder.toString());

        content = document.get();
        offset = content.indexOf("Runnable", content.indexOf("Runnable") + 1);
        len = "Runnable".length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(30);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());

        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);

        IJavaElement element = getElementAtOffset(editor);
        // This two are to check we've set the selection right in this test:
        assertEquals(IJavaElement.TYPE, element.getElementType());
        assertTrue(((IType) element).isAnonymous());
        // getParent().getParent().getParent() will give us the method:
        assertEquals(IJavaElement.METHOD, event.getElement().getElementType());
        assertEquals("aMethod", event.getElement().getElementName());
    }

    /**
     * This test is for the same purpose as
     * {@link #testFilter_deletedElement_typeMembers()}, but with a little
     * difference, that is, if the whole Java file is deleted, all the data about
     * the Java elements in the file will be stored under the Java file, even
     * though it's deleted.
     * 
     * @see #testFilter_deletedElement_typeMembers()
     */
    @Test
    public void testFilter_deletedElement_mainType() throws Exception {
        // Create a new class:
        String newClassName = unit.getTypes()[0].getElementName() + "abc";
        StringBuilder builder = new StringBuilder();
        builder.append(format("package %s;%n", pkg.getElementName()));
        builder.append(format("public class %s {", newClassName));
        builder.append(format("}%n"));
        ICompilationUnit myUnit = pkg.createCompilationUnit(newClassName + ".java", builder.toString(), true, null);

        JavaEditor editor = closeAndOpenEditor(myUnit);

        // Set the editor to select the package declaration:
        int offset = getDocument(editor).get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));
        // Make sure we got the selection right:
        assertEquals(IJavaElement.PACKAGE_DECLARATION, getElementAtOffset(editor).getElementType());

        // Run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(35);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Delete the file:
        myUnit.getResource().delete(true, null);

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        // One data should be in the collection (the parent of the previously
        // selected package declaration):
        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);

        // Test the data is placed under the root element:
        assertEquals(myUnit, event.getElement());
    }

    /**
     * Tests that if a Java element, a field for example, is deleted from the Java
     * source file, then when we save the tracker's data, the data about the
     * deleted field should be store as the parent's data.
     * <p>
     * For example: If we have a field called "fieldA" under the class "ClassA"
     * and the tracker has recorded that the user has spent 20 seconds working on
     * the field, but the field is then deleted from the class. So when the
     * tracker saves the data, instead of saving
     * "The user has spent 20 seconds working on fieldA" we store
     * "The user has spent 20 seconds working on classA".
     * </p>
     * <p>
     * Another important purpose of this feature is that: Then a user starts to
     * type a new java element, like a method, he/she knows what the name he/she
     * is going to type for the method, but we have no way of knowing that, so
     * lots of events may be recorded before he/she finishes typing the name. For
     * example, if the user want to type "hello" as the method name, there will be
     * events recorded about the java element "hel", or "hell", or "hello", we
     * only need one of them ("hello") but we also want to keep the time about the
     * invalid ones, so before we save the data, we check for non-existent java
     * elements, and instead of saving the data under those elements, we save the
     * data under the first existing parent of the elements, if all parents are
     * missing (e.g. deletes the file), we save it under the file parent, like
     * "File.java", even though the file has been deleted.
     * </p>
     * 
     * @see #testFilter_deletedElement_mainType()
     */
    @Test
    public void testFilter_deletedElement_typeMembers() throws Exception {
        final JavaEditor editor = closeAndOpenEditor();
        final IDocument document = getDocument(editor);

        // Place a field in the body of the class, note that we don't want to add
        // errors to the class:

        String field = "private int aVeryUniqueFieldName = 0;";
        int offset = document.get().lastIndexOf('}') - 1;
        int len = 0;
        document.replace(offset, len, field);

        // Set the editor to select the field:
        offset = document.get().indexOf(field);
        len = field.length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(25);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Keeps a reference to the field statement first, for testing latter:
        final IJavaElement element = getElementAtOffset(editor);

        // Now delete the field statement from the source file, note that there
        // is no need to save the document (and we should not save the document,
        // other tests may depend on it):
        offset = document.get().indexOf(field);
        len = field.length();
        document.replace(offset, len, "");

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        // Gets the data, the data is remained in the tracker as long as we don't
        // enable it again (according to the contract of the tracker):
        //
        // One data should be in the collection
        // (the parent of the selected package declaration):
        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);

        // Now check that the element is the parent of the package declaration
        // instead of the deleted package declaration itself:
        assertEquals(element.getParent(), event.getElement());
    }

    /**
     * Test an event on an import statement. This event should be filtered on
     * save, so that instead of showing a user spent x amount of time on the
     * import statement , we show that a user spent x amount of time on the type
     * root (ITypeRoot) element, (a.k.a the Java file).
     */
    @Test
    public void testFilter_existingElement_importStatement() throws Exception {
        final JavaEditor editor = closeAndOpenEditor();
        final IDocument document = getDocument(editor);
        String importStatement = "import java.util.*;";
        int offset = document.get().indexOf(";") + 1; // Position after package
        // declaration
        int len = 0;
        document.replace(offset, len, importStatement);

        offset = document.get().indexOf(importStatement);
        len = importStatement.length();
        ITextSelection selection = new TextSelection(offset, len);
        editor.getSelectionProvider().setSelection(selection);

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());

        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(getInput(editor), event.getElement());
    }

    /**
     * Test an event on a static initialiser. This event should not be filtered on
     * save.
     */
    @Test
    public void testFilter_existingElement_initializer() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);
        String staticName = "static";
        String methodText = staticName + " {}";
        int offset = document.get().indexOf("{") + 1;
        int len = 0;
        document.replace(offset, len, methodText);

        offset = document.get().indexOf(staticName);
        len = staticName.length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        IJavaElement element = getElementAtOffset(editor);
        // Check we got the selection right
        assertEquals(IJavaElement.INITIALIZER, element.getElementType());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(element, event.getElement());
    }

    /**
     * Test an event on a method, that is a member of an anonymous class. This
     * event should be filtered so that we so the user has spent x amount of time
     * on the method's first non-anonymous parent.
     */
    @Test
    public void testFilter_existingElement_methodParentIsAnonymous() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);

        StringBuilder anonymous = new StringBuilder();
        anonymous.append("void aMethod() {");
        anonymous.append("  new Runnable() { ");
        anonymous.append("    public void run(){");
        anonymous.append("      new Runnable() {");
        anonymous.append("        public void run() {}");
        anonymous.append("      };");
        anonymous.append("    } ");
        anonymous.append("  };");
        anonymous.append("}");

        int offset = document.get().indexOf("{") + 1;
        int len = 0;
        document.replace(offset, len, anonymous.toString());

        String content = document.get();
        offset = content.indexOf("run", content.indexOf("run") + 1);
        len = "run".length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        IJavaElement element = getElementAtOffset(editor);
        // Make sure we got the selection right:
        assertEquals("run", element.getElementName());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals("aMethod", event.getElement().getElementName());
    }

    /**
     * Test an event on a method, that is a member of a non-anonymous class. This
     * event should not be filtered on save.
     */
    @Test
    public void testFilter_existingElement_methodParentNotAnonymous() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);
        String methodName = "aMethodName";
        String methodText = format("void %s() {}", methodName);
        int offset = document.get().indexOf("{") + 1;
        int length = 0;
        document.replace(offset, length, methodText);

        offset = document.get().indexOf(methodName);
        length = methodName.length();
        ITextSelection selection = new TextSelection(offset, length);
        editor.getSelectionProvider().setSelection(selection);

        IJavaElement element = getElementAtOffset(editor);
        // Make sure we got the selection right:
        assertEquals(IJavaElement.METHOD, element.getElementType());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(30);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());

        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(element, event.getElement());
    }

    /**
     * Test an event on an package declaration. This event should be filtered on
     * save, so that instead of showing a user spent x amount of time on the
     * package declaration , we show that a user spent x amount of time on the
     * main type element.
     */
    @Test
    public void testFilter_existingElement_packageDeclaration() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);
        int offset = document.get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        IJavaElement element = getElementAtOffset(editor);
        // Make sure we got the selection right:
        assertEquals(IJavaElement.PACKAGE_DECLARATION, element.getElementType());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(30);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());

        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(element.getParent(), event.getElement());
    }

    /**
     * Test an event on an anonymous type. This event should be filtered so that
     * we show the user has spent x amount of time on the type's first
     * non-anonymous parent.
     */
    @Test
    public void testFilter_existingElement_typeAnonymous() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);
        StringBuilder anonymous = new StringBuilder();
        anonymous.append("void aMethod() {");
        anonymous.append("  new Runnable() { ");
        anonymous.append("    public void run(){");
        anonymous.append("    } ");
        anonymous.append("  };");
        anonymous.append("}");

        int offset = document.get().indexOf("{") + 1;
        int len = 0;
        document.replace(offset, len, anonymous.toString());

        offset = document.get().indexOf("Runnable");
        len = "Runnable".length();
        ITextSelection selection = new TextSelection(offset, len);
        editor.getSelectionProvider().setSelection(selection);

        IJavaElement element = getElementAtOffset(editor);
        // Check that we got the selection right:
        assertEquals(IJavaElement.TYPE, element.getElementType());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(35);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals("aMethod", event.getElement().getElementName());
        assertEquals(IJavaElement.METHOD, event.getElement().getElementType());
    }

    /**
     * Test an event on an inner class. This event should be not filtered on save.
     */
    @Test
    public void testFilter_existingElement_typeInner() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);
        String innerClassName = "anInnerClassName";
        String innerClassText = format("%nstatic class %s {}", innerClassName);
        int offset = document.get().indexOf("{") + 1;
        int len = 0;
        document.replace(offset, len, innerClassText);

        offset = document.get().indexOf(innerClassName);
        len = innerClassName.length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        IJavaElement element = getElementAtOffset(editor);
        assertEquals(IJavaElement.TYPE, element.getElementType());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());
        final JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(innerClassName, element.getElementName());
        assertEquals(element, event.getElement());
    }

    /**
     * Test an event on a normal Java type (not anonymous, not inner class). This
     * event should not be filtered on save.
     */
    @Test
    public void testFilter_existingElement_typeNormal() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        String className = unit.getTypes()[0].getElementName();
        int offset = getDocument(editor).get().indexOf(className);
        int len = className.length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        IJavaElement element = getElementAtOffset(editor);
        assertEquals(IJavaElement.TYPE, element.getElementType());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(30);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());
        final JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(element, event.getElement());
    }

    /**
     * Test an event on a field. This event should be filtered on save, so that
     * instead of showing a user spent x amount of time on the field, we show that
     * a user spent x amount of time on the field's parent type.
     */
    @Test
    public void testFilter_exsitingElement_field() throws Exception {
        JavaEditor editor = closeAndOpenEditor();
        IDocument document = getDocument(editor);
        String fieldName = "aFieldName";
        String methodText = format("private int %s = 1;", fieldName);
        int offset = document.get().indexOf("{") + 1;
        int len = 0;
        document.replace(offset, len, methodText);

        offset = document.get().indexOf(fieldName);
        len = fieldName.length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        IJavaElement element = getElementAtOffset(editor);
        assertEquals(IJavaElement.FIELD, element.getElementType());

        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        long preEnd = System.currentTimeMillis();
        tracker.setEnabled(false);
        long postEnd = System.currentTimeMillis();

        // Ask the tracker to save the data, the data should be appropriately
        // filtered
        tracker.saveData();

        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);

        // The filtered event should be on the field's parent, not on the field
        // itself:
        assertEquals(element.getParent(), event.getElement());
    }

    /**
     * Tests when the user becomes inactive.
     */
    @Test
    public void testUserInactive() throws Exception {
        JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        int offset = getDocument(editor).get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(30);

        long preEnd = System.currentTimeMillis();
        callIdleDetectorToNotify();
        long postEnd = System.currentTimeMillis();

        // One data should be in the collection (the selected package declaration):
        assertEquals(1, tracker.getData().size());
        JavaEvent event = tracker.getData().iterator().next();
        long start = event.getInterval().getStartMillis();
        long end = event.getInterval().getEndMillis();
        checkTime(preStart, start, postStart, preEnd, end, postEnd);
        assertEquals(getElementAtOffset(editor), event.getElement());
    }

    /**
     * Tests when the window lose focus.
     */
    @Test
    public void testWindowDeactivated() throws Exception {
        JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        int offset = getDocument(editor).get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        // Run the tracker to capture the event:
        long preStart = System.currentTimeMillis();
        tracker.setEnabled(true);
        long postStart = System.currentTimeMillis();

        Thread.sleep(20);

        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        try {
            // Minimize the shell to cause it to lose focus:
            long preEnd = System.currentTimeMillis();
            shell.setMinimized(true);
            long postEnd = System.currentTimeMillis();

            // One data should be in the collection (the selected package
            // declaration):
            assertEquals(1, tracker.getData().size());
            JavaEvent event = tracker.getData().iterator().next();
            long start = event.getInterval().getStartMillis();
            long end = event.getInterval().getEndMillis();
            checkTime(preStart, start, postStart, preEnd, end, postEnd);
            assertEquals(getElementAtOffset(editor), event.getElement());

        } finally {
            shell.setMinimized(false);
        }
    }

    /**
     * Tests that events are recorded properly with the different states of the
     * window.
     */
    @Test
    public void testWindowDeactivatedThenActivated() throws Exception {
        JavaEditor editor = closeAndOpenEditor();

        // Set the editor to select the package declaration:
        int offset = getDocument(editor).get().indexOf(pkg.getElementName());
        int len = pkg.getElementName().length();
        editor.getSelectionProvider().setSelection(new TextSelection(offset, len));

        Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
        try {
            // Now run the tracker to capture the event:
            long preStart = System.currentTimeMillis();
            tracker.setEnabled(true);
            long postStart = System.currentTimeMillis();

            Thread.sleep(20);

            // Minimize the shell to cause it to lose focus:
            long preEnd = System.currentTimeMillis();
            shell.setMinimized(true);
            long postEnd = System.currentTimeMillis();

            // One data should be in the collection (the selected package
            // declaration):
            assertEquals(1, tracker.getData().size());
            JavaEvent event = tracker.getData().iterator().next();
            long start = event.getInterval().getStartMillis();
            long end = event.getInterval().getEndMillis();
            checkTime(preStart, start, postStart, preEnd, end, postEnd);
            assertEquals(getElementAtOffset(editor), event.getElement());

            // Restore the shell to see if tracker will start tracking again:
            tracker.flushData();

            preStart = System.currentTimeMillis();
            shell.setMinimized(false);
            postStart = System.currentTimeMillis();

            Thread.sleep(25);

            // Minimise the shell to cause it to lose focus:
            preEnd = System.currentTimeMillis();
            shell.setMinimized(true);
            postEnd = System.currentTimeMillis();

            // One data should be in the collection (the selected package
            // declaration):
            assertEquals(1, tracker.getData().size());
            event = tracker.getData().iterator().next();
            start = event.getInterval().getStartMillis();
            end = event.getInterval().getEndMillis();
            checkTime(preStart, start, postStart, preEnd, end, postEnd);
            assertEquals(getElementAtOffset(editor), event.getElement());

        } finally {
            shell.setMinimized(false);
        }
    }

    /**
     * Hacks the global idle detector to cause it to notify it's observers.
     */
    protected void callIdleDetectorToNotify() throws Exception {
        Field isActive = IdleDetector.class.getDeclaredField("isActive");
        isActive.setAccessible(true);

        Method setChanged = Observable.class.getDeclaredMethod("setChanged");
        setChanged.setAccessible(true);

        Method notifyObservers = Observable.class.getDeclaredMethod("notifyObservers");
        notifyObservers.setAccessible(true);

        IdleDetector detector = TrackingPlugin.getDefault().getIdleDetector();
        detector.setRunning(true);
        isActive.set(detector, false);
        setChanged.invoke(detector);
        notifyObservers.invoke(detector);
        detector.setRunning(false);
    }

    /**
     * Closes all the editor in the workbench page, contents of editors are not
     * saved. Then opens the Java editor on {@link #unit}.
     * 
     * @return The editor.
     */
    protected JavaEditor closeAndOpenEditor() throws PartInitException {
        return closeAndOpenEditor(unit);
    }

    /**
     * Closes all the editor in the workbench page, contents of editors are not
     * saved. Then opens the Java editor on the file.
     * 
     * @param unit The Java file to open.
     * @return The editor.
     */
    protected JavaEditor closeAndOpenEditor(ICompilationUnit unit) throws PartInitException {
        IWorkbench workbench = PlatformUI.getWorkbench();
        IWorkbenchPage page = workbench.getActiveWorkbenchWindow().getActivePage();
        page.closeAllEditors(false);

        IFile file = (IFile) unit.getResource();
        IEditorDescriptor editor = workbench.getEditorRegistry().getDefaultEditor(file.getName());
        IEditorInput input = new FileEditorInput(file);
        return (JavaEditor) page.openEditor(input, editor.getId());
    }

    @Override
    protected JavaEvent createEvent() {
        return new JavaEvent(new Interval(0, 1), JavaCore.create("=Enfo/src<enfo{EnfoPlugin.java"));
    }

    @Override
    protected AbstractTracker<JavaEvent> createTracker() {
        return new JavaTracker();
    }

    /**
     * Gets the document from the editor.
     * 
     * @param editor The editor.
     * @return The document.
     */
    protected IDocument getDocument(JavaEditor editor) {
        IDocument doc = editor.getDocumentProvider().getDocument(editor.getEditorInput());
        if (doc == null) {
            fail("Document is null");
        }
        return doc;
    }

    private IViewDescriptor getRandomView() {
        IViewDescriptor[] v = PlatformUI.getWorkbench().getViewRegistry().getViews();
        return v[new Random().nextInt(v.length)];
    }

}