/**
* Copyright 2012 Tobias Gierke <tobias.gierke@code-sourcery.de>
*
* 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 de.codesourcery.jasm16.ide;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import de.codesourcery.jasm16.compiler.io.IResource;
/**
* Keeps track of a specific number of visited source code locations.
*
* @author tobias.gierke@code-sourcery.de
*/
public class NavigationHistory
{
private static final Logger LOG = Logger.getLogger(NavigationHistory.class);
private static final boolean DEBUG = false;
public static final int DEFAULT_NAVIGATION_HISTORY_SIZE = 50;
private final int maxSize;
// @GuardedBy( stack )
private final List<Location> stack = new ArrayList<>();
// @GuardedBy( stack )
private int currentPtr = 0;
public interface INavigationHistoryListener
{
public void navigationHistoryChanged();
}
// @GuardedBy( listeners );
private final List<INavigationHistoryListener> listeners = new ArrayList<>();
public final static class Location
{
private final IAssemblyProject project;
private final IResource resource;
private final int offset;
public Location(IAssemblyProject project,IResource resource, int offset)
{
if ( project == null ) {
throw new IllegalArgumentException("project must not be NULL.");
}
if ( resource == null ) {
throw new IllegalArgumentException("resource must not be NULL.");
}
if ( offset < 0 ) {
throw new IllegalArgumentException("Invalid offset "+offset);
}
this.project = project;
this.resource = resource;
this.offset = offset;
}
@Override
public String toString()
{
return "Location[ project="+project+", offset="+offset+" , resource="+resource+"]";
}
public IResource getResource()
{
return resource;
}
public IAssemblyProject getProject()
{
return project;
}
public int getOffset()
{
return offset;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + offset;
result = prime * result + ((resource == null) ? 0 : resource.getIdentifier().hashCode());
return result;
}
@Override
public boolean equals(Object o)
{
if ( o == this ) {
return true;
}
if ( o instanceof Location) {
return this.offset == ((Location) o).offset &&
this.resource.getIdentifier().equals( ((Location) o).getResource().getIdentifier() );
}
return false;
}
}
public NavigationHistory()
{
this( DEFAULT_NAVIGATION_HISTORY_SIZE );
}
public void addListener(INavigationHistoryListener listener)
{
synchronized (listeners) {
listeners.add( listener );
}
}
public void removeListener(INavigationHistoryListener listener)
{
synchronized (listeners) {
listeners.remove( listener );
}
}
private void notifyListeners() {
if ( DEBUG ) {
System.out.println("\n----------- Navigation history -----------\n"+toString() );
}
final List<INavigationHistoryListener> copy;
synchronized (listeners) {
copy = new ArrayList<>( listeners );
}
for ( INavigationHistoryListener l : copy ) {
try {
l.navigationHistoryChanged();
} catch(Exception e) {
LOG.error("notifyListeners(): Listener "+l+" failed",e);
}
}
}
public NavigationHistory(int maxSize)
{
if ( maxSize < 0 ) {
throw new IllegalArgumentException("Invalid maxSize "+maxSize);
}
this.maxSize = maxSize;
}
public boolean isEmpty() {
synchronized (stack) {
return stack.isEmpty();
}
}
/**
*
* @param location
* @return <code>true</code> if the location was added, <code>false</code> if it's already
* present at the latest navigation history position
*/
public boolean add(Location location)
{
if ( location == null ) {
throw new IllegalArgumentException("location must not be NULL.");
}
synchronized (stack)
{
if ( ! stack.isEmpty() && stack.get( stack.size() -1 ).equals( location ) ) {
return false;
}
if ( stack.size() == maxSize ) {
stack.remove(0);
}
stack.add( location );
currentPtr = stack.size()-1;
}
notifyListeners();
return true;
}
public boolean clear()
{
boolean containedEntries;
synchronized (stack)
{
containedEntries = ! stack.isEmpty();
stack.clear();
currentPtr=0;
}
if ( containedEntries ) {
notifyListeners();
}
return containedEntries;
}
public Location goBack()
{
boolean notifyListeners = true;
try
{
synchronized (stack)
{
if ( ! canGoBack() ) {
notifyListeners = false;
return null;
}
currentPtr--;
return stack.get( currentPtr );
}
}
finally
{
if ( notifyListeners ) {
notifyListeners();
}
}
}
public boolean canGoBack() {
synchronized (stack) {
return currentPtr > 0;
}
}
public Location goForward()
{
boolean notifyListeners = true;
try
{
synchronized (stack)
{
if ( ! canGoForward() ) {
notifyListeners = false;
return null;
}
currentPtr++;
return stack.get( currentPtr );
}
}
finally
{
if ( notifyListeners ) {
notifyListeners();
}
}
}
public boolean canGoForward() {
synchronized (stack) {
return currentPtr < (stack.size()-1);
}
}
@Override
public String toString()
{
final List<Location> copy;
synchronized (stack)
{
copy = new ArrayList<>(stack);
}
String result = "";
int i = 0;
for (Iterator<Location> it = copy.iterator(); it.hasNext();i++) {
Location loc = it.next();
result += "["+( i == currentPtr ? "*" : " " )+"] "+loc;
if ( it.hasNext() ) {
result += "\n";
}
}
result+= ("\n canGoBack: "+canGoBack()+" / canGoForward: "+canGoForward());
return result;
}
}