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:
   *     IBM Corporation - initial API and implementation
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.ui.javaeditor.selectionactions;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.List;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.jface.action.Action;
  import org.eclipse.jface.text.ITextSelection;
  import org.eclipse.ui.IEditorInput;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.ui.texteditor.IUpdate;
  import org.eclipse.wst.jsdt.core.IInitializer;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IMember;
  import org.eclipse.wst.jsdt.core.ISourceRange;
  import org.eclipse.wst.jsdt.core.ISourceReference;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.ToolFactory;
  import org.eclipse.wst.jsdt.core.compiler.IScanner;
  import org.eclipse.wst.jsdt.core.compiler.ITerminalSymbols;
  import org.eclipse.wst.jsdt.core.compiler.InvalidInputException;
  import org.eclipse.wst.jsdt.internal.corext.SourceRange;
  import org.eclipse.wst.jsdt.internal.ui.IJavaHelpContextIds;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.IClassFileEditorInput;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.JavaEditor;
  
  public class GoToNextPreviousMemberAction extends Action implements IUpdate {
  
  	public static final String NEXT_MEMBER= "GoToNextMember"; //$NON-NLS-1$
  	public static final String PREVIOUS_MEMBER= "GoToPreviousMember"; //$NON-NLS-1$
  	private JavaEditor fEditor;
  	private boolean fIsGotoNext;
  
  	public static GoToNextPreviousMemberAction newGoToNextMemberAction(JavaEditor editor) {
  		String text= SelectionActionMessages.GotoNextMember_label;
  		return new GoToNextPreviousMemberAction(editor, text, true);
  	}
  
  	public static GoToNextPreviousMemberAction newGoToPreviousMemberAction(JavaEditor editor) {
  		String text= SelectionActionMessages.GotoPreviousMember_label;
  		return new GoToNextPreviousMemberAction(editor, text, false);
  	}
  
  	private GoToNextPreviousMemberAction(JavaEditor editor, String text, boolean isGotoNext) {
  		super(text);
  		Assert.isNotNull(editor);
  		fEditor= editor;
  		fIsGotoNext= isGotoNext;
  		update();
  		if (isGotoNext)
  			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.GOTO_NEXT_MEMBER_ACTION);
  		else
  			PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.GOTO_PREVIOUS_MEMBER_ACTION);
  	}
  
  	/*
  	 * This constructor is for testing purpose only.
  	 */
  	public GoToNextPreviousMemberAction(boolean isSelectNext) {
  		super(""); //$NON-NLS-1$
  		fIsGotoNext= isSelectNext;
  	}
  
  	public void update() {
  		boolean enabled= false;
  		ISourceReference ref= getSourceReference();
  		if (ref != null) {
  			ISourceRange range;
  			try {
  				range= ref.getSourceRange();
  				enabled= range != null && range.getLength() > 0;
  			} catch (JavaScriptModelException e) {
  				// enabled= false;
  			}
  		}
  		setEnabled(enabled);
  	}
  
  	/* (non-JavaDoc)
  	 * Method declared in IAction.
  	 */
  	public final  void run() {
  		ITextSelection selection= getTextSelection();
  		ISourceRange newRange= getNewSelectionRange(createSourceRange(selection), null);
  		// Check if new selection differs from current selection
  		if (selection.getOffset() == newRange.getOffset() && selection.getLength() == newRange.getLength())
  			return;
  		fEditor.selectAndReveal(newRange.getOffset(), newRange.getLength());
  	}
  
  	private IType[] getTypes() throws JavaScriptModelException {
  		IEditorInput input= fEditor.getEditorInput();
  		if (input instanceof IClassFileEditorInput) {
  			return new IType[] { ((IClassFileEditorInput)input).getClassFile().getType() };
  		} else {
  			return JavaScriptPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(input).getAllTypes();
  		}
  	}
  
  	private ISourceReference getSourceReference() {
  		IEditorInput input= fEditor.getEditorInput();
  		if (input instanceof IClassFileEditorInput) {
  			return ((IClassFileEditorInput)input).getClassFile();
  		} else {
  			return JavaScriptPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(input);
  		}
  	}
  
  	private ITextSelection getTextSelection() {
  		return (ITextSelection)fEditor.getSelectionProvider().getSelection();
  	}
  
  	public ISourceRange getNewSelectionRange(ISourceRange oldSourceRange, IType[] types) {
  		try{
  			if (types == null)
  				types= getTypes();
  			Integer[] offsetArray= createOffsetArray(types);
  			if (offsetArray.length == 0)
  				return oldSourceRange;
  			Arrays.sort(offsetArray);
  			Integer oldOffset= Integer.valueOf(oldSourceRange.getOffset());
  			int index= Arrays.binarySearch(offsetArray, oldOffset);
  
  			if (fIsGotoNext)
  				return createNewSourceRange(getNextOffset(index, offsetArray, oldOffset));
  			else
  				return createNewSourceRange(getPreviousOffset(index, offsetArray, oldOffset));
  
  	 	}	catch (JavaScriptModelException e){
  	 		JavaScriptPlugin.log(e); //dialog would be too heavy here
  	 		return oldSourceRange;
  	 	}
  	}
  
  	private static Integer getPreviousOffset(int index, Integer[] offsetArray, Integer oldOffset) {
  		if (index == -1)
  			return oldOffset;
  		if (index == 0)
  			return offsetArray[0];
  		if (index > 0)
  			return offsetArray[index - 1];
  		Assert.isTrue(index < -1);
  		int absIndex= Math.abs(index);
  		return offsetArray[absIndex - 2];
  	}
  
  	private static Integer getNextOffset(int index, Integer[] offsetArray, Integer oldOffset) {
  		if (index == -1)
  			return offsetArray[0];
  
  		if (index == 0){
  			if (offsetArray.length != 1)
  				return offsetArray[1];
  			else
  				return offsetArray[0];
  		}
  		if (index > 0){
  			if (index == offsetArray.length - 1)
  				return oldOffset;
  			return offsetArray[index + 1];
  		}
  		Assert.isTrue(index < -1);
  		int absIndex= Math.abs(index);
  		if (absIndex > offsetArray.length)
  			return oldOffset;
  		else
  			return offsetArray[absIndex - 1];
  	}
  
  	private static ISourceRange createNewSourceRange(Integer offset){
  		return new SourceRange(offset.intValue(), 0);
  	}
  
  	private static Integer[] createOffsetArray(IType[] types) throws JavaScriptModelException {
  		List result= new ArrayList();
  		for (int i= 0; i < types.length; i++) {
  			IType iType= types[i];
  			addOffset(result, iType.getNameRange().getOffset());
  			addOffset(result, iType.getSourceRange().getOffset() + iType.getSourceRange().getLength());
  			addMemberOffsetList(result, iType.getFunctions());
  			addMemberOffsetList(result, iType.getFields());
  			addMemberOffsetList(result, iType.getInitializers());
  		}
  		return (Integer[]) result.toArray(new Integer[result.size()]);
  	}
  
  	private static void addMemberOffsetList(List result, IMember[] members) throws JavaScriptModelException {
  		for (int i= 0; i < members.length; i++) {
  			addOffset(result, getOffset(members[i]));
  		}
  	}
  
  	private static int getOffset(IMember iMember) throws JavaScriptModelException {
  		//special case
  		if (iMember.getElementType() == IJavaScriptElement.INITIALIZER)
  			return firstOpeningBraceOffset((IInitializer)iMember);
  
  		if (iMember.getNameRange() != null && iMember.getNameRange().getOffset() >= 0)
  			return iMember.getNameRange().getOffset();
  		return iMember.getSourceRange().getOffset();
  	}
  
  	private static int firstOpeningBraceOffset(IInitializer iInitializer) throws JavaScriptModelException {
  		try {
  			IScanner scanner= ToolFactory.createScanner(false, false, false, false);
  			scanner.setSource(iInitializer.getSource().toCharArray());
  			int token= scanner.getNextToken();
  			while (token != ITerminalSymbols.TokenNameEOF && token != ITerminalSymbols.TokenNameLBRACE)
  				token= scanner.getNextToken();
  			if (token == ITerminalSymbols.TokenNameLBRACE)
  				return iInitializer.getSourceRange().getOffset() + scanner.getCurrentTokenStartPosition() + scanner.getRawTokenSource().length;
  			return iInitializer.getSourceRange().getOffset();
  		} catch (InvalidInputException e) {
  			return iInitializer.getSourceRange().getOffset();
  		}
  	}
  
  	//-- private helper methods
  
  	private static ISourceRange createSourceRange(ITextSelection ts) {
  		return new SourceRange(ts.getOffset(), ts.getLength());
  	}
  
  	private static void addOffset(List result, int offset) {
  		if (offset >= 0)
  			result.add(Integer.valueOf(offset));
  	}
  }
  