/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IMemento;
import org.eclipse.ui.INavigationHistory;
import org.eclipse.ui.INavigationLocation;
import org.eclipse.ui.INavigationLocationProvider;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.internal.StartupThreading.StartupRunnable;

/**
 * Implementation of the back and forward actions.
 */
public class NavigationHistory implements INavigationHistory {

	private static final boolean DEBUG= false;

	private static final int CAPACITY = 50;

    private NavigationHistoryAction backwardAction;

    private NavigationHistoryAction forwardAction;

    private int ignoreEntries;

    private ArrayList history = new ArrayList(CAPACITY);

    Map perTabHistoryMap = new HashMap();

    private ArrayList editors = new ArrayList(CAPACITY);

    private IWorkbenchPage page;

    private int activeEntry = 0;

	/**
	 * Creates a new NavigationHistory to keep the NavigationLocation entries of the specified page.
	 *
	 * @param page the workbench page
	 */
    public NavigationHistory(final IWorkbenchPage page) {
        this.page = page;
        page.addPartListener(new IPartListener2() {
            @Override
			public void partActivated(IWorkbenchPartReference partRef) {
            }

            @Override
			public void partBroughtToTop(IWorkbenchPartReference partRef) {
            }

            @Override
			public void partDeactivated(IWorkbenchPartReference partRef) {
            }

            @Override
			public void partOpened(IWorkbenchPartReference partRef) {
            }

            @Override
			public void partHidden(IWorkbenchPartReference partRef) {
            }

            @Override
			public void partVisible(IWorkbenchPartReference partRef) {
            }

            @Override
			public void partClosed(IWorkbenchPartReference partRef) {
            	if (isPerTabHistoryEnabled() && partRef instanceof EditorReference) {
            		if (!((EditorReference)partRef).isDisposed()) {
						Object editorTabCookie = ((EditorReference) partRef).getModel().getWidget();
	            		disposeHistoryForTab(editorTabCookie);
	            		updateActions();
            		}
            	}
				updateNavigationHistory(partRef, true);
            }

			@Override
			public void partInputChanged(IWorkbenchPartReference partRef) {
				updateNavigationHistory(partRef, false);
			}

			private void updateNavigationHistory(IWorkbenchPartReference partRef, boolean partClosed) {
                if (partRef != null && partRef.getPart(false) instanceof IEditorPart) {
                    IEditorPart editor = (IEditorPart) partRef.getPart(false);
                    IEditorInput input = editor.getEditorInput();
                    String id = editor.getSite().getId();
                    Iterator e = editors.iterator();
                    NavigationHistoryEditorInfo info = null;
                    NavigationHistoryEditorInfo currentInfo = null;
                    NavigationHistoryEntry current = getEntry(activeEntry);
                    if (current != null) {
						currentInfo = current.editorInfo;
					}
                    while (e.hasNext()) {
                        info = (NavigationHistoryEditorInfo) e.next();
                        if (id.equals(info.editorID)
                                && input.equals(info.editorInput)) {
                            if (partClosed && info != currentInfo) {
								info.handlePartClosed();
							}
                            break;
                        }
						info = null;
                    }
                    if (info == null) {
						return;
					}
                    boolean isEntryDisposed = false;
                    e = history.iterator();
                    int i = 0;
                    while (e.hasNext()) {
                        NavigationHistoryEntry entry = (NavigationHistoryEntry) e
                                .next();
                        if (entry.editorInfo == info) {
							if (!entry.handlePartClosed()) {
                                // update the active entry since we are removing an item
                                if (i < activeEntry) {
                                    activeEntry--;
                                } else if (i == activeEntry) {
                                    if (i != 0) {
										activeEntry--;
									}
                                } else {
                                    // activeEntry is before item we deleted
                                    i++;
                                }
                                isEntryDisposed = true;
                                e.remove();
                                disposeEntry(entry);
                            } else {
                                i++;
                            }
						}
                    }

                    /*
                     * Promote the entry of the last closed editor to be the active
                     * one, see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=154431
                     */
                    if (!isEntryDisposed && page.getActiveEditor() == null && activeEntry < history.size())
                    	activeEntry++;

                    updateActions();
                }
            }
        });
    }

    private Display getDisplay() {
        return page.getWorkbenchWindow().getShell().getDisplay();
    }

    private boolean isPerTabHistoryEnabled() {
		return false;
    }

    /*
     * Adds an editor to the editor history without getting its location.
     */
    public void markEditor(final IEditorPart part) {
        if (ignoreEntries > 0 || part == null) {
			return;
		}
        /* Ignore all entries until the async exec runs. Workaround to avoid
         * extra entry when using Open Declaration (F3) that opens another editor. */
        ignoreEntries++;
        getDisplay().asyncExec(() -> {
		    if (--ignoreEntries == 0) {
		        if (part.getEditorSite() instanceof EditorSite) {
					EditorSite site = (EditorSite) part.getEditorSite();
					Control c = (Control) site.getModel().getWidget();
		            if (c == null || c.isDisposed()) {
						return;
					}
		            NavigationHistoryEntry e = getEntry(activeEntry);
		            if (e != null
		                    && part.getEditorInput() != e.editorInfo.editorInput) {
						updateEntry(e);
					}
		            addEntry(part);
		        }
		    }
		});
    }

    @Override
	public void markLocation(IEditorPart part) {
        addEntry(part);
    }

    /*
     * Return the backward history entries.  Return in restore order (i.e., the
     * first entry is the entry that would become active if the "Backward" action
     * was executed).
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    NavigationHistoryEntry[] getBackwardEntries() {
    	if (isPerTabHistoryEnabled()) {
    		return getEntriesForTab(false);
    	}
        int length = activeEntry;
        NavigationHistoryEntry[] entries = new NavigationHistoryEntry[length];
        for (int i = 0; i < activeEntry; i++) {
            entries[activeEntry - 1 - i] = getEntry(i);
        }
        return entries;
    }

    /*
     * Return the forward history entries.  Return in restore order (i.e., the first
     * entry is the entry that would become active if the "Forward" action was
     * executed).
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    NavigationHistoryEntry[] getForwardEntries() {
    	if (isPerTabHistoryEnabled()) {
    		return getEntriesForTab(true);
    	}
        int length = history.size() - activeEntry - 1;
        length = Math.max(0, length);
        NavigationHistoryEntry[] entries = new NavigationHistoryEntry[length];
        for (int i = activeEntry + 1; i < history.size(); i++) {
            entries[i - activeEntry - 1] = getEntry(i);
        }
        return entries;
    }

    @Override
	public INavigationLocation[] getLocations() {
        INavigationLocation result[] = new INavigationLocation[history.size()];
        for (int i = 0; i < result.length; i++) {
            NavigationHistoryEntry e = (NavigationHistoryEntry) history.get(i);
            result[i] = e.location;
        }
        return result;
    }

    @Override
	public INavigationLocation getCurrentLocation() {
        NavigationHistoryEntry entry = getEntry(activeEntry);
        return entry == null ? null : entry.location;
    }

    /**
     * Disposes this NavigationHistory and all entries.
     */
    public void dispose() {
    	disposeHistoryForTabs();
        Iterator e = history.iterator();
        while (e.hasNext()) {
            NavigationHistoryEntry entry = (NavigationHistoryEntry) e.next();
            disposeEntry(entry);
        }
    }

    /**
     * Keeps a reference to the forward action to update its state
     * whenever needed.
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    void setForwardAction(NavigationHistoryAction action) {
        forwardAction = action;
        updateActions();
    }

    /**
     * Keeps a reference to the backward action to update its state
     * whenever needed.
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    void setBackwardAction(NavigationHistoryAction action) {
        backwardAction = action;
        updateActions();
    }

    /*
     * Returns the history entry indexed by <code>index</code>
     */
    private NavigationHistoryEntry getEntry(int index) {
        if (0 <= index && index < history.size()) {
			return (NavigationHistoryEntry) history.get(index);
		}
        return null;
    }

    /*
     * Adds the specified entry to the history.
     */
    private void add(NavigationHistoryEntry entry) {
        removeForwardEntries();
        if (history.size() == CAPACITY) {
            NavigationHistoryEntry e = (NavigationHistoryEntry) history
                    .remove(0);
            disposeEntry(e);
        }
        history.add(entry);
        activeEntry = history.size() - 1;
    }

    /*
     * Remove all entries after the active entry.
     */
    private void removeForwardEntries() {
        int length = history.size();
        for (int i = activeEntry + 1; i < length; i++) {
            NavigationHistoryEntry e = (NavigationHistoryEntry) history
                    .remove(activeEntry + 1);
            disposeEntry(e);
        }
    }

    /*
     * Adds a location to the history.
     */
    private void addEntry(IEditorPart part) {
        if (ignoreEntries > 0 || part == null) {
			return;
		}

        if (isPerTabHistoryEnabled()) {
        	markLocationForTab(part);
        }
        INavigationLocation location = null;
        if (part instanceof INavigationLocationProvider) {
			location = ((INavigationLocationProvider) part)
                    .createNavigationLocation();
		}

        NavigationHistoryEntry current = getEntry(activeEntry);
        if (current != null && current.editorInfo.memento != null) {
            current.editorInfo.restoreEditor();
            checkDuplicates(current.editorInfo);
        }
        NavigationHistoryEntry e = createEntry(page, part, location);
        if (current == null) {
            add(e);
        } else {
            if (e.mergeInto(current)) {
                disposeEntry(e);
                removeForwardEntries();
            } else {
                add(e);
            }
        }
        printEntries("added entry"); //$NON-NLS-1$
        updateActions();
    }

    /*
     * Prints all the entries in the console. For debug only.
     */
    private void printEntries(String label) {
        if (DEBUG) {
            System.out.println("+++++ " + label + "+++++ "); //$NON-NLS-1$ //$NON-NLS-2$
            int size = history.size();
            for (int i = 0; i < size; i++) {
                String append = activeEntry == i ? ">>" : ""; //$NON-NLS-1$ //$NON-NLS-2$
                System.out.println(append
                        + "Index: " + i + " " + history.get(i)); //$NON-NLS-1$ //$NON-NLS-2$
            }
        }
    }

    /*
     * Returns true if the forward action can be performed otherwise returns false.
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    /* package */boolean canForward() {
    	if (isPerTabHistoryEnabled()) {
    		return hasEntriesForTab(true);
    	}
        return (0 <= activeEntry + 1) && (activeEntry + 1 < history.size());
    }

    /*
     * Returns true if the backward action can be performed otherwise returns false.
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    /* package */boolean canBackward() {
    	if (isPerTabHistoryEnabled()) {
    		return hasEntriesForTab(false);
    	}
        return (0 <= activeEntry - 1) && (activeEntry - 1 < history.size());
    }

    /*
     * Update the actions enable/disable and tooltip state.
     */
    private void updateActions() {
        if (backwardAction != null) {
			backwardAction.update();
		}
        if (forwardAction != null) {
			forwardAction.update();
		}
    }

    /*
     * Restore the specified entry
     */
    private void gotoEntry(NavigationHistoryEntry entry) {
        if (entry == null) {
			return;
		}
        try {
            ignoreEntries++;
            if (entry.editorInfo.memento != null) {
                entry.editorInfo.restoreEditor();
                checkDuplicates(entry.editorInfo);
            }
            entry.restoreLocation();
            updateActions();
            printEntries("goto entry"); //$NON-NLS-1$
        } finally {
            ignoreEntries--;
        }
    }

    /*
     * update the active entry
     */
    private void updateEntry(NavigationHistoryEntry activeEntry) {
        if (activeEntry == null || activeEntry.location == null) {
			return;
		}
        activeEntry.location.update();
        printEntries("updateEntry"); //$NON-NLS-1$
    }

    /*
     * Perform the forward action by getting the next location and restoring
     * its context.
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    void forward() {
    	if (isPerTabHistoryEnabled()) {
    		forwardForTab();
    		return;
    	}
        if (canForward()) {
			shiftEntry(true);
		}
    }

    /*
     * Perform the backward action by getting the previous location and restoring
     * its context.
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    void backward() {
    	if (isPerTabHistoryEnabled()) {
    		backwardForTab();
    		return;
    	}
        if (canBackward()) {
			shiftEntry(false);
		}
    }

    /*
     * Shift the history back or forward
     */
    private void shiftEntry(boolean forward) {
        updateEntry(getEntry(activeEntry));
        if (forward) {
			activeEntry++;
		} else {
			activeEntry--;
		}
        NavigationHistoryEntry entry = getEntry(activeEntry);
        if (entry != null) {
			gotoEntry(entry);
		}
    }

    /*
     * Shift the history to the given entry.
     * <p>
     * (Called by NavigationHistoryAction)
     * </p>
     */
    void shiftCurrentEntry(NavigationHistoryEntry entry, boolean forward) {
    	if (isPerTabHistoryEnabled()) {
    		gotoEntryForTab(entry, forward);
    		return;
    	}
        updateEntry(getEntry(activeEntry));
        activeEntry = history.indexOf(entry);
        gotoEntry(entry);
    }

    /**
     * Save the state of this history into the memento.
     */
    void saveState(IMemento memento) {
        NavigationHistoryEntry cEntry = getEntry(activeEntry);
        if (cEntry == null || !cEntry.editorInfo.isPersistable()) {
			return;
		}

        ArrayList editors = (ArrayList) this.editors.clone();
        for (Iterator iter = editors.iterator(); iter.hasNext();) {
            NavigationHistoryEditorInfo info = (NavigationHistoryEditorInfo) iter
                    .next();
            if (!info.isPersistable()) {
				iter.remove();
			}
        }
        IMemento editorsMem = memento
                .createChild(IWorkbenchConstants.TAG_EDITORS);
        for (Iterator iter = editors.iterator(); iter.hasNext();) {
            NavigationHistoryEditorInfo info = (NavigationHistoryEditorInfo) iter
                    .next();
            info.saveState(editorsMem
                    .createChild(IWorkbenchConstants.TAG_EDITOR));
        }

        ArrayList list = new ArrayList(history.size());
        int size = history.size();
        for (int i = 0; i < size; i++) {
            NavigationHistoryEntry entry = (NavigationHistoryEntry) history
                    .get(i);
            if (entry.editorInfo.isPersistable()) {
				list.add(entry);
			}
        }
        size = list.size();
        for (int i = 0; i < size; i++) {
            NavigationHistoryEntry entry = (NavigationHistoryEntry) list.get(i);
            IMemento childMem = memento
                    .createChild(IWorkbenchConstants.TAG_ITEM);
            if (entry == cEntry) {
				childMem.putString(IWorkbenchConstants.TAG_ACTIVE, "true"); //$NON-NLS-1$
			}
            entry.saveState(childMem, list);
            childMem.putInteger(IWorkbenchConstants.TAG_INDEX, editors
                    .indexOf(entry.editorInfo));
        }
    }

    /**
     * Restore the state of this history from the memento.
     */
    void restoreState(IMemento memento) {
        IMemento editorsMem = memento.getChild(IWorkbenchConstants.TAG_EDITORS);
        IMemento items[] = memento.getChildren(IWorkbenchConstants.TAG_ITEM);
        if (items.length == 0 || editorsMem == null) {
            if (page.getActiveEditor() != null) {
				markLocation(page.getActiveEditor());
			}
            return;
        }

        IMemento children[] = editorsMem
                .getChildren(IWorkbenchConstants.TAG_EDITOR);
        NavigationHistoryEditorInfo editorsInfo[] = new NavigationHistoryEditorInfo[children.length];
        for (int i = 0; i < editorsInfo.length; i++) {
            editorsInfo[i] = new NavigationHistoryEditorInfo(children[i]);
            editors.add(editorsInfo[i]);
        }

        for (int i = 0; i < items.length; i++) {
            IMemento item = items[i];
            int index = item.getInteger(IWorkbenchConstants.TAG_INDEX)
                    .intValue();
            NavigationHistoryEditorInfo info = editorsInfo[index];
            info.refCount++;
            NavigationHistoryEntry entry = new NavigationHistoryEntry(info,
                    page, null, null);
            history.add(entry);
            entry.restoreState(item);
            if (item.getString(IWorkbenchConstants.TAG_ACTIVE) != null) {
				activeEntry = i;
			}
        }

        final NavigationHistoryEntry entry = getEntry(activeEntry);
        if (entry != null && entry.editorInfo.editorInput != null) {
            if (page.getActiveEditor() == page
                    .findEditor(entry.editorInfo.editorInput)) {
            	StartupThreading.runWithoutExceptions(new StartupRunnable() {

					@Override
					public void runWithException() throws Throwable {
						gotoEntry(entry);
					}});
			}
        }
    }

    public NavigationHistoryEntry createEntry(IWorkbenchPage page,
            IEditorPart part, INavigationLocation location) {
        String editorID = part.getSite().getId();
        IEditorInput editorInput = part.getEditorInput();
        NavigationHistoryEditorInfo info = null;
        for (Iterator iter = editors.iterator(); iter.hasNext();) {
            info = (NavigationHistoryEditorInfo) iter.next();
            if (editorID.equals(info.editorID)
                    && editorInput.equals(info.editorInput)) {
                info.refCount++;
                break;
            }
			info= null;
        }
        if (info == null) {
            info = new NavigationHistoryEditorInfo(part);
            info.refCount++;
            editors.add(info);
        }
        return new NavigationHistoryEntry(info, page, part, location);
    }

    public void disposeEntry(NavigationHistoryEntry entry) {
        if (entry.editorInfo == null) {
			return;
		}
        entry.editorInfo.refCount--;
        if (entry.editorInfo.refCount == 0) {
			editors.remove(entry.editorInfo);
		}
        entry.dispose();
    }

    void checkDuplicates(NavigationHistoryEditorInfo info) {
        NavigationHistoryEditorInfo dup = null;
        if (info.editorInput == null) {
			return;
		}
        for (Iterator iter = editors.iterator(); iter.hasNext();) {
            dup = (NavigationHistoryEditorInfo) iter.next();
            if (info != dup && info.editorID.equals(dup.editorID)
                    && info.editorInput.equals(dup.editorInput)) {
				break;
			}
			dup= null;
        }
        if (dup == null) {
			return;
		}
        for (Iterator iter = history.iterator(); iter.hasNext();) {
            NavigationHistoryEntry entry = (NavigationHistoryEntry) iter.next();
            if (entry.editorInfo == dup) {
                entry.editorInfo = info;
                info.refCount++;
            }
        }
        editors.remove(dup);
    }

    /*********************************************************/
    /*** new per-tab history code                          ***/
    /*********************************************************/


    private static class PerTabHistory {
    	LinkedList backwardEntries = new LinkedList();
    	NavigationHistoryEntry currentEntry = null;
    	LinkedList forwardEntries = new LinkedList();
    }

    private void setNewCurrentEntryForTab(PerTabHistory perTabHistory, NavigationHistoryEntry entry) {
    	if (perTabHistory.currentEntry != null) {
    		perTabHistory.backwardEntries.addFirst(perTabHistory.currentEntry);
    	}
    	perTabHistory.currentEntry = entry;
    	removeEntriesForTab(perTabHistory.forwardEntries);
    }

    private Object getCookieForTab(IEditorPart part) {
    	if (part != null) {
	        IWorkbenchPartSite site = part.getSite();
	        if (site instanceof PartSite) {
	        	PartSite partSite = (PartSite) site;
	        	WorkbenchPartReference ref = (WorkbenchPartReference) partSite.getPartReference();
	        	if (!ref.isDisposed()) {
					return partSite.getModel().getWidget();
	        	}
	        }
    	}
    	return null;
    }

    private void markLocationForTab(IEditorPart part) {
    	if (part instanceof ErrorEditorPart) {
    		updateActions();
    		return;
    	}
		Object tabCookie = getCookieForTab(part);
		if (tabCookie != null) {
			INavigationLocation location = null;
			if (part instanceof INavigationLocationProvider) {
				location = ((INavigationLocationProvider) part)
						.createNavigationLocation();
			}
			PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap
					.get(tabCookie);
			if (perTabHistory == null) {
				perTabHistory = new PerTabHistory();
				perTabHistoryMap.put(tabCookie, perTabHistory);
			}
			NavigationHistoryEntry current = perTabHistory.currentEntry;
			if (current != null && current.editorInfo.memento != null) {
				current.editorInfo.restoreEditor();
				checkDuplicates(current.editorInfo);
			}
			NavigationHistoryEntry entry = createEntry(page, part, location);
			if (current != null && entry.mergeInto(current)) {
				disposeEntry(entry);
				removeEntriesForTab(perTabHistory.forwardEntries);
			} else {
				setNewCurrentEntryForTab(perTabHistory, entry);
			}
		}
		updateActions();
	}

    public void updateCookieForTab(Object oldCookie, Object newCookie) {
    	if (newCookie.equals(oldCookie)) {
    		return;
    	}
    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.remove(oldCookie);
    	if (perTabHistory != null) {
    		perTabHistoryMap.put(newCookie, perTabHistory);
    	}
    }

    private void gotoEntryForTab(NavigationHistoryEntry target, boolean forward) {
    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
    	if (editorTabCookie!=null) {
	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
	    	if (perTabHistory != null) {
	    		LinkedList source = forward ? perTabHistory.forwardEntries : perTabHistory.backwardEntries;
	    		LinkedList destination = forward ? perTabHistory.backwardEntries : perTabHistory.forwardEntries;
				if (perTabHistory.currentEntry != null) {
					if (perTabHistory.currentEntry.location != null) {
						perTabHistory.currentEntry.location.update();
					}
					destination.addFirst(perTabHistory.currentEntry);
				}
				NavigationHistoryEntry newCurrent = null;
	    		while (!source.isEmpty() && newCurrent==null) {
		    		NavigationHistoryEntry entry = (NavigationHistoryEntry) source
							.removeFirst();
		    		if (entry.equals(target)) {
		    			newCurrent = entry;
		    		} else {
		    			destination.addFirst(entry);
		    		}
	    		}
	    		Assert.isTrue(newCurrent != null);
	    		perTabHistory.currentEntry = newCurrent;
	            try {
	                ignoreEntries++;
	                if (newCurrent.editorInfo.memento != null) {
	                	newCurrent.editorInfo.restoreEditor();
	                	checkDuplicates(newCurrent.editorInfo);
	                }
	                newCurrent.restoreLocation();
	                updateActions();
	            } finally {
	            	ignoreEntries--;
	            }
	    	}
    	}
    }

	private void forwardForTab() {
    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
    	if (editorTabCookie!=null) {
	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
	    	if (perTabHistory != null && !perTabHistory.forwardEntries.isEmpty()) {
	    		NavigationHistoryEntry newCurrent = (NavigationHistoryEntry) perTabHistory.forwardEntries
						.removeFirst();
	    		if (perTabHistory.currentEntry != null) {
	    			final INavigationLocation location = perTabHistory.currentEntry.location;
	    			if (location!=null) {
	    				location.update();
	    			}
	    			perTabHistory.backwardEntries.addFirst(perTabHistory.currentEntry);
	    		}
	    		perTabHistory.currentEntry = newCurrent;
	            try {
	                ignoreEntries++;
	                if (newCurrent.editorInfo.memento != null) {
	                	newCurrent.editorInfo.restoreEditor();
	                	checkDuplicates(newCurrent.editorInfo);
	                }
	                newCurrent.restoreLocation();
	                updateActions();
	            } finally {
	            	ignoreEntries--;
	            }
	    	}
    	}
    }

    private void backwardForTab() {
    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
    	if (editorTabCookie!=null) {
	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
	    	if (perTabHistory != null && !perTabHistory.backwardEntries.isEmpty()) {
	    		NavigationHistoryEntry newCurrent = (NavigationHistoryEntry) perTabHistory.backwardEntries
	    				.removeFirst();
	    		if (perTabHistory.currentEntry != null) {
	    			perTabHistory.currentEntry.location.update();
	    			perTabHistory.forwardEntries.addFirst(perTabHistory.currentEntry);
	    		}
	    		perTabHistory.currentEntry = newCurrent;
	            try {
	                ignoreEntries++;
	                if (newCurrent.editorInfo.memento != null) {
	                	newCurrent.editorInfo.restoreEditor();
	                	checkDuplicates(newCurrent.editorInfo);
	                }
	                newCurrent.restoreLocation();
	                updateActions();
	            } finally {
	            	ignoreEntries--;
	            }
	    	}
    	}
    }

    private boolean hasEntriesForTab(boolean forward) {
    	Object editorTabCookie = getCookieForTab(page.getActiveEditor());
    	if (editorTabCookie!=null) {
	    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.get(editorTabCookie);
	    	if (perTabHistory != null) {
		    	LinkedList entries = forward ? perTabHistory.forwardEntries : perTabHistory.backwardEntries;
		    	return !entries.isEmpty();
	    	}
    	}
    	return false;
    }

	/**
	 * Returns entries in restore order.
	 *
	 * @param forward <code>true</code> for forward and <code>false</code> for backward history
	 * @return the navigation history entries
	 */
    private NavigationHistoryEntry[] getEntriesForTab(boolean forward) {
		Object editorTabCookie = getCookieForTab(page.getActiveEditor());
		if (editorTabCookie != null) {
			PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap
					.get(editorTabCookie);
			if (perTabHistory != null) {
				LinkedList entries = forward ? perTabHistory.forwardEntries
						: perTabHistory.backwardEntries;
				return (NavigationHistoryEntry[]) entries
				.toArray(new NavigationHistoryEntry[entries.size()]);
			}
		}
		return new NavigationHistoryEntry[0];
	}

    private void disposeHistoryForTabs() {
    	Object[] keys = perTabHistoryMap.keySet().toArray();
    	for (Object key : keys) {
			disposeHistoryForTab(key);
		}
    }

    void disposeHistoryForTab(Object editorTabCookie) {
    	PerTabHistory perTabHistory = (PerTabHistory) perTabHistoryMap.remove(editorTabCookie);
    	if (perTabHistory != null) {
    		if (perTabHistory.currentEntry != null) {
    			disposeEntry(perTabHistory.currentEntry);
    			perTabHistory.currentEntry = null;
    		}
    		removeEntriesForTab(perTabHistory.backwardEntries);
    		removeEntriesForTab(perTabHistory.forwardEntries);
    	}
    }

	private void removeEntriesForTab(LinkedList entries) {
		for (Iterator it = entries.iterator(); it.hasNext();) {
			NavigationHistoryEntry entry = (NavigationHistoryEntry) it.next();
			disposeEntry(entry);
			it.remove();
		}
	}

	public boolean updateActive(IEditorPart editor) {
        NavigationHistoryEntry e = getEntry(activeEntry);
        if (e == null)
        	return false;
        // 1) check if editor ID matches
        IWorkbenchPartSite site = editor.getSite();
        if (site == null) // might happen if site has not being initialized yet
        	return false;
        String editorID = site.getId();
        if (editorID == null) // should not happen for an editor
        	return false;
        if (!editorID.equals(e.editorInfo.editorID))
            return false;
        // 2) check that input matches
		IEditorInput input = editor.getEditorInput();
		if (input == null)
			return false;
		if (!input.equals(e.editorInfo.editorInput))
			return false;
		updateEntry(e);
		return true;
    }
}
