content: /*******************************************************************************
   * Copyright (c) 2000, 2008 IBM Corporation and others.
   * 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
   *
   * Contributors:
   *   Jesper Kamstrup Linnet (eclipse@kamstrup-linnet.dk) - initial API and implementation
   *          (report 36180: Callers/Callees view)
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.corext.callhierarchy;
  
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.OperationCanceledException;
  import org.eclipse.core.runtime.PlatformObject;
  import org.eclipse.ui.model.IWorkbenchAdapter;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IMember;
  import org.eclipse.wst.jsdt.internal.ui.callhierarchy.MethodWrapperWorkbenchAdapter;
  
  /**
  *
  * Provisional API: This class/interface is part of an interim API that is still under development and expected to
  * change significantly before reaching stability. It is being made available at this early stage to solicit feedback
  * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
  * (repeatedly) as the API evolves.
  */
  public abstract class MethodWrapper extends PlatformObject {
      private Map fElements = null;
  
      /*
       * A cache of previously found methods. This cache should be searched
       * before adding a "new" method object reference to the list of elements.
       * This way previously found methods won't be searched again.
       */
      private Map fMethodCache;
      private MethodCall fMethodCall;
      private MethodWrapper fParent;
      private int fLevel;
  
      /**
       * Constructor CallerElement.
       */
      public MethodWrapper(MethodWrapper parent, MethodCall methodCall) {
          Assert.isNotNull(methodCall);
  
          if (parent == null) {
              setMethodCache(new HashMap());
              fLevel = 1;
          } else {
              setMethodCache(parent.getMethodCache());
              fLevel = parent.getLevel() + 1;
          }
  
          this.fMethodCall = methodCall;
          this.fParent = parent;
      }
  
      public Object getAdapter(Class adapter) {
  		if (adapter == IJavaScriptElement.class) {
  	        return getMember();
  	    } else if (adapter == IWorkbenchAdapter.class){
  	    	return new MethodWrapperWorkbenchAdapter(this);
  	    } else {
  	    	return null;
  	    }
  	}
  
      /**
       * @return the child caller elements of this element
       */
      public MethodWrapper[] getCalls(IProgressMonitor progressMonitor) {
          if (fElements == null) {
              doFindChildren(progressMonitor);
          }
  
          MethodWrapper[] result = new MethodWrapper[fElements.size()];
          int i = 0;
  
          for (Iterator iter = fElements.keySet().iterator(); iter.hasNext();) {
              MethodCall methodCall = getMethodCallFromMap(fElements, iter.next());
              result[i++] = createMethodWrapper(methodCall);
          }
  
          return result;
      }
  
      public int getLevel() {
          return fLevel;
      }
  
      public IMember getMember() {
          return getMethodCall().getMember();
      }
  
      public MethodCall getMethodCall() {
          return fMethodCall;
      }
  
      public String getName() {
          if (getMethodCall() != null) {
              return getMethodCall().getMember().getElementName();
          } else {
              return ""; //$NON-NLS-1$
          }
      }
  
      public MethodWrapper getParent() {
          return fParent;
      }
  
      public boolean equals(Object oth) {
          if (this == oth) {
              return true;
          }
  
          if (oth == null) {
              return false;
          }
          
          if (oth instanceof MethodWrapperWorkbenchAdapter) {
              //Note: A MethodWrapper is equal to a referring MethodWrapperWorkbenchAdapter and vice versa (bug 101677).
          	oth= ((MethodWrapperWorkbenchAdapter) oth).getMethodWrapper();
          }
          
          if (oth.getClass() != getClass()) {
              return false;
          }
  
          MethodWrapper other = (MethodWrapper) oth;
  
          if (this.fParent == null) {
              if (other.fParent != null) {
                  return false;
              }
          } else {
              if (!this.fParent.equals(other.fParent)) {
                  return false;
              }
          }
  
          if (this.getMethodCall() == null) {
              if (other.getMethodCall() != null) {
                  return false;
              }
          } else {
              if (!this.getMethodCall().equals(other.getMethodCall())) {
                  return false;
              }
          }
  
          return true;
      }
  
      public int hashCode() {
          final int PRIME = 1000003;
          int result = 0;
  
          if (fParent != null) {
              result = (PRIME * result) + fParent.hashCode();
          }
  
          if (getMethodCall() != null) {
              result = (PRIME * result) + getMethodCall().getMember().hashCode();
          }
  
          return result;
      }
  
      private void setMethodCache(Map methodCache) {
          fMethodCache = methodCache;
      }
  
      protected abstract String getTaskName();
  
      private void addCallToCache(MethodCall methodCall) {
          Map cachedCalls = lookupMethod(this.getMethodCall());
          cachedCalls.put(methodCall.getKey(), methodCall);
      }
  
      protected abstract MethodWrapper createMethodWrapper(MethodCall methodCall);
  
      private void doFindChildren(IProgressMonitor progressMonitor) {
          Map existingResults = lookupMethod(getMethodCall());
  
          if (existingResults != null) {
              fElements = new HashMap();
              fElements.putAll(existingResults);
          } else {
              initCalls();
  
              if (progressMonitor != null) {
                  progressMonitor.beginTask(getTaskName(), 100);
              }
  
              try {
                  performSearch(progressMonitor);
              } finally {
                  if (progressMonitor != null) {
                      progressMonitor.done();
                  }
              }
  
              //                ModalContext.run(getRunnableWithProgress(), true, getProgressMonitor(),
              //                    Display.getCurrent());
          }
      }
  
      /**
       * Determines if the method represents a recursion call (i.e. whether the
       * method call is already in the cache.)
       *
       * @return True if the call is part of a recursion
       */
      public boolean isRecursive() {
          MethodWrapper current = getParent();
  
          while (current != null) {
              if (getMember().getHandleIdentifier().equals(current.getMember()
                                                                          .getHandleIdentifier())) {
                  return true;
              }
  
              current = current.getParent();
          }
  
          return false;
      }
  
      /**
       * This method finds the children of the current IFunction (either callers or
       * callees, depending on the concrete subclass.
       * @return The result of the search for children
       */
      protected abstract Map findChildren(IProgressMonitor progressMonitor);
  
      private Map getMethodCache() {
          return fMethodCache;
      }
  
      private void initCalls() {
          this.fElements = new HashMap();
  
          initCacheForMethod();
      }
  
      /**
       * Looks up a previously created search result in the "global" cache.
       * @return the List of previously found search results
       */
      private Map lookupMethod(MethodCall methodCall) {
          return (Map) getMethodCache().get(methodCall.getKey());
      }
  
      private void performSearch(IProgressMonitor progressMonitor) {
          fElements = findChildren(progressMonitor);
  
          for (Iterator iter = fElements.keySet().iterator(); iter.hasNext();) {
              checkCanceled(progressMonitor);
  
              MethodCall methodCall = getMethodCallFromMap(fElements, iter.next());
              addCallToCache(methodCall);
          }
      }
  
      private MethodCall getMethodCallFromMap(Map elements, Object key) {
          return (MethodCall) elements.get(key);
      }
  
      private void initCacheForMethod() {
          Map cachedCalls = new HashMap();
          getMethodCache().put(this.getMethodCall().getKey(), cachedCalls);
      }
  
      /**
       * Checks with the progress monitor to see whether the creation of the type hierarchy
       * should be canceled. Should be regularly called
       * so that the user can cancel.
       *
       * @exception OperationCanceledException if cancelling the operation has been requested
       * @see IProgressMonitor#isCanceled
       */
      protected void checkCanceled(IProgressMonitor progressMonitor) {
          if (progressMonitor != null && progressMonitor.isCanceled()) {
              throw new OperationCanceledException();
          }
      }
      
      /**
       * Allows a visitor to traverse the call hierarchy. The visiting is stopped when
       * a recursive node is reached.
       *  
       * @param visitor
       */
      public void accept(CallHierarchyVisitor visitor, IProgressMonitor progressMonitor) {
          if (getParent() != null && getParent().isRecursive()) {
              return;
          }
          checkCanceled(progressMonitor);
          
          visitor.preVisit(this);
          if (visitor.visit(this)) {
              MethodWrapper[] methodWrappers= getCalls(progressMonitor);
              for (int i= 0; i < methodWrappers.length; i++) {
                  methodWrappers[i].accept(visitor, progressMonitor);
              }
          }
          visitor.postVisit(this);
  
          if (progressMonitor != null) {        
              progressMonitor.worked(1);
          }
      }
  }
  