content: /*******************************************************************************
   * Copyright (c) 2005, 2007 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.ui.text.java;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.ITextViewer;
  
  /**
   * Describes the context of an invocation of content assist in a text viewer. The context knows the
   * document, the invocation offset and can lazily compute the identifier prefix preceding the
   * invocation offset. It may know the viewer.
   * <p>
   * Subclasses may add information to their environment. For example, source code editors may provide
   * specific context information such as an AST.
   * </p>
   * <p>
   * Clients may instantiate and subclass.
   * </p>
   * 
   *
   * 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 class ContentAssistInvocationContext {
  	
  	/* state */
  	private final ITextViewer fViewer;
  	private final IDocument fDocument;
  	private final int fOffset;
  	
  	/* cached additional info */
  	private CharSequence fPrefix;
  	
  	/**
  	 * Equivalent to
  	 * {@linkplain #ContentAssistInvocationContext(ITextViewer, int) ContentAssistInvocationContext(viewer, viewer.getSelectedRange().x)}.
  	 * 
  	 * @param viewer the text viewer that content assist is invoked in
  	 */
  	public ContentAssistInvocationContext(ITextViewer viewer) {
  		this(viewer, viewer.getSelectedRange().x);
  	}
  
  	/**
  	 * Creates a new context for the given viewer and offset.
  	 * 
  	 * @param viewer the text viewer that content assist is invoked in
  	 * @param offset the offset into the viewer's document where content assist is invoked at
  	 */
  	public ContentAssistInvocationContext(ITextViewer viewer, int offset) {
  		Assert.isNotNull(viewer);
  		fViewer= viewer;
  		fDocument= null;
  		fOffset= offset;
  	}
  	
  	/**
  	 * Creates a new context with no viewer or invocation offset set.
  	 */
  	protected ContentAssistInvocationContext() {
  		fDocument= null;
  		fViewer= null;
  		fOffset= -1;
  	}
  	
  	/**
  	 * Creates a new context for the given document and offset.
  	 * 
  	 * @param document the document that content assist is invoked in
  	 * @param offset the offset into the document where content assist is invoked at
  	 */
  	public ContentAssistInvocationContext(IDocument document, int offset) {
  		Assert.isNotNull(document);
  		Assert.isTrue(offset >= 0);
  		fViewer= null;
  		fDocument= document;
  		fOffset= offset;
  	}
  	
  	/**
  	 * Returns the invocation offset.
  	 * 
  	 * @return the invocation offset
  	 */
  	public final int getInvocationOffset() {
  		return fOffset;
  	}
  	
  	/**
  	 * Returns the viewer, <code>null</code> if not available.
  	 * 
  	 * @return the viewer, possibly <code>null</code>
  	 */
  	public final ITextViewer getViewer() {
  		return fViewer;
  	}
  	
  	/**
  	 * Returns the document that content assist is invoked on, or <code>null</code> if not known.
  	 * 
  	 * @return the document or <code>null</code>
  	 */
  	public IDocument getDocument() {
  		if (fDocument == null) {
  			if (fViewer == null)
  				return null;
  			return fViewer.getDocument();
  		}
  		return fDocument;
  	}
  	
  	/**
  	 * Computes the identifier (as specified by {@link Character#isJavaIdentifierPart(char)}) that
  	 * immediately precedes the invocation offset.
  	 * 
  	 * @return the prefix preceding the content assist invocation offset, <code>null</code> if
  	 *         there is no document
  	 * @throws BadLocationException if accessing the document fails
  	 */
  	public CharSequence computeIdentifierPrefix() throws BadLocationException {
  		if (fPrefix == null) {
  			IDocument document= getDocument();
  			if (document == null)
  				return null;
  			int end= getInvocationOffset();
  			int start= end;
  			while (--start >= 0) {
  				if (!Character.isJavaIdentifierPart(document.getChar(start)))
  					break;
  			}
  			start++;
  			fPrefix= document.get(start, end - start);
  		}
  		
  		return fPrefix;
  	}
  	
  	/**
  	 * Invocation contexts are equal if they describe the same context and are of the same type.
  	 * This implementation checks for <code>null</code> values and class equality. Subclasses
  	 * should extend this method by adding checks for their context relevant fields (but not
  	 * necessarily cached values).
  	 * <p>
  	 * Example:
  	 * 
  	 * <pre>
  	 * class MyContext extends ContentAssistInvocationContext {
  	 * 	private final Object fState;
  	 * 	private Object fCachedInfo;
  	 * 
  	 * 	...
  	 * 
  	 * 	public boolean equals(Object obj) {
  	 * 		if (!super.equals(obj))
  	 * 			return false;
  	 * 		MyContext other= (MyContext) obj;
  	 * 		return fState.equals(other.fState);
  	 * 	}
  	 * }
  	 * </pre>
  	 * 
  	 * </p>
  	 * <p>
  	 * Subclasses should also extend {@link Object#hashCode()}.
  	 * </p>
  	 * 
  	 * @param obj {@inheritDoc}
  	 * @return {@inheritDoc}
  	 */
  	public boolean equals(Object obj) {
  		if (obj == null)
  			return false;
  		if (!getClass().equals(obj.getClass()))
  			return false;
  		ContentAssistInvocationContext other= (ContentAssistInvocationContext) obj;
  		return (fViewer == null && other.fViewer == null || fViewer != null && fViewer.equals(other.fViewer)) && fOffset == other.fOffset && (fDocument == null && other.fDocument == null || fDocument != null && fDocument.equals(other.fDocument));
  	}
  	
  	/*
  	 * @see java.lang.Object#hashCode()
  	 */
  	public int hashCode() {
  		return 23459213 << 5 | (fViewer == null ? 0 : fViewer.hashCode() << 3) | fOffset;
  	}
  }
  