content: /*******************************************************************************
   * Copyright (c) 2000, 2011 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;
   
  import java.io.IOException;
  import java.io.Reader;
  import java.io.StringReader;
  import java.util.ArrayList;
  import java.util.List;
  
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IConfigurationElement;
  import org.eclipse.core.runtime.IExtension;
  import org.eclipse.core.runtime.IExtensionPoint;
  import org.eclipse.core.runtime.IExtensionRegistry;
  import org.eclipse.core.runtime.Platform;
  import org.eclipse.wst.jsdt.core.IBuffer;
  import org.eclipse.wst.jsdt.core.IFunction;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.ILocalVariable;
  import org.eclipse.wst.jsdt.core.IMember;
  import org.eclipse.wst.jsdt.core.IOpenable;
  import org.eclipse.wst.jsdt.core.ISourceRange;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.ITypeHierarchy;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.util.SequenceReader;
  import org.eclipse.wst.jsdt.internal.core.MetadataFile;
  import org.eclipse.wst.jsdt.internal.corext.javadoc.JavaDocCommentReader;
  import org.eclipse.wst.jsdt.internal.corext.util.MethodOverrideTester;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.Logger;
  import org.eclipse.wst.jsdt.internal.ui.text.javadoc.JavaDoc2HTMLTextReader;
  import org.eclipse.wst.jsdt.internal.ui.text.javadoc.OAADocReader;
  
  /**
   * Helper needed to get the content of a Javadoc comment.
   * 
   * <p>
   * This class is not intended to be subclassed or instantiated by clients.
   * </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 JSdocContentAccess {
  	
  	public static final String EXTENSION_POINT= "documentationProvider"; //$NON-NLS-1$
  
  	protected static final String TAG_DOCUMENTATIONPROVIDER = "documentationProvider"; //$NON-NLS-1$
  	protected static final String ATTR_DOCUMENTATIONPROVIDER_CLASS = "class"; //$NON-NLS-1$
  
  	private static IDocumentationReader[] docReaders;
  	
  	private JSdocContentAccess() {
  		// do not instantiate
  	}
  	
  	/**
  	 * <p>Gets the content reader for either an {@link IMember} or {@link ILocalVariable}.</p>
  	 * 
  	 * @param element {@link IJavaScriptElement} to get the content reader for
  	 * @param allowInherited For methods with no (Javadoc) comment, the comment of the overridden class
  	 * is returned if <code>allowInherited</code> is <code>true</code>.
  	 * 
  	 * @return a reader for the Javadoc comment content or <code>null</code> if the element
  	 * does not contain a Javadoc comment or if no source is available
  	 * 
  	 * @throws JavaScriptModelException is thrown when the elements javadoc can not be accessed
  	 * 
  	 * @see #getContentReader(ILocalVariable, boolean)
  	 * @see #getContentReader(IMember, boolean)
  	 */
  	public static Reader getContentReader(IJavaScriptElement element, boolean allowInherited) throws JavaScriptModelException {
  		Reader reader = null;
  		
  		if(element instanceof IMember) {
  			reader = getContentReader((IMember)element, allowInherited);
  		} else if(element instanceof ILocalVariable) {
  			reader = getContentReader((ILocalVariable)element, allowInherited);
  		}
  		
  		return reader;
  	}
  	
  	/**
  	 * Gets a reader for an IMember's Javadoc comment content from the source attachment.
  	 * The content does contain only the text from the comment without the Javadoc leading star characters.
  	 * Returns <code>null</code> if the member does not contain a Javadoc comment or if no source is available.
  	 * @param member The member to get the Javadoc of.
  	 * @param allowInherited For methods with no (Javadoc) comment, the comment of the overridden class
  	 * is returned if <code>allowInherited</code> is <code>true</code>.
  	 * @return Returns a reader for the Javadoc comment content or <code>null</code> if the member
  	 * does not contain a Javadoc comment or if no source is available
  	 * @throws JavaScriptModelException is thrown when the elements javadoc can not be accessed
  	 */
  	public static Reader getContentReader(IMember member, boolean allowInherited) throws JavaScriptModelException {
  		List readers = new ArrayList(2);
  		IDocumentationReader[] docReaders = getDocReaders(member);
  		for (int i = 0; i < docReaders.length; i++) {
  			Reader contentReader = docReaders[i].getContentReader(member, allowInherited);
  			if(contentReader != null) {
  				readers.add(contentReader);
  			}
  		}
  		
  		IOpenable openable = member.getOpenable();
   		if (openable instanceof MetadataFile)
   		{
   			return new OAADocReader((MetadataFile)openable, member);
   		}
  		
  		IBuffer buf= openable.getBuffer();
  		if (buf != null) {
  			try {
  				// source or attachment found
  				ISourceRange jsDocRange = member.getJSdocRange();
  				if (jsDocRange == null && member.getElementType() == IJavaScriptElement.TYPE) {
  					IFunction constructor = ((IType) member).getFunction(member.getElementName(), null);
  					if (constructor.exists()) {
  						jsDocRange = constructor.getJSdocRange();
  					}
  				}
  				if (jsDocRange != null) {
  					JavaDocCommentReader reader = new JavaDocCommentReader(buf, jsDocRange.getOffset(), jsDocRange.getOffset() + jsDocRange.getLength() - 1);
  					if (!containsOnlyInheritDoc(reader, jsDocRange.getLength())) {
  						reader.reset();
  						readers.add(reader);
  					}
  					else if (allowInherited && (member.getElementType() == IJavaScriptElement.METHOD)) {
  						Reader hierarchyDocReader = findDocInHierarchy((IFunction) member);
  						if (hierarchyDocReader != null)
  							readers.add(hierarchyDocReader);
  					}
  				}
  			}
  			catch (JavaScriptModelException e) {
  				Logger.logException(Logger.ERROR_DEBUG, e);
  			}
  		}
  		
  		if (!readers.isEmpty()) {
  			if (readers.size() == 1)
  				return (Reader) readers.get(0);
  			return new SequenceReader((Reader[]) readers.toArray(new Reader[readers.size()]));
  		}
  		return null;
  	}
  	
  	/**
  	 * Gets a reader for an ILocalDeclaration's doc comment content from the
  	 * source attachment. Returns <code>null</code> if the declaration does
  	 * not have a doc comment or if no source is available.
  	 * 
  	 * @param declaration
  	 *            The declaration to get the doc of.
  	 * @param allowInherited
  	 *            For methods with no doc comment, the comment of the
  	 *            overridden class is returned if <code>allowInherited</code>
  	 *            is <code>true</code> and this is an argument.
  	 * @return Returns a reader for the doc comment content or
  	 *         <code>null</code> if the declaration does not contain a doc
  	 *         comment or if no source is available
  	 * @throws JavaScriptModelException
  	 *             is thrown when the declaration's doc can not be accessed
  	 */
  	public static Reader getContentReader(ILocalVariable declaration, boolean allowInherited) throws JavaScriptModelException {
  		List readers = new ArrayList(2);
  		IDocumentationReader[] docReaders = getDocReaders(declaration);
  		for (int i = 0; i < docReaders.length; i++) {
  			Reader contentReader = docReaders[i].getContentReader(declaration, allowInherited);
  			if (contentReader != null) {
  				readers.add(contentReader);
  			}
  		}
  
  		if (!readers.isEmpty()) {
  			if (readers.size() == 1)
  				return (Reader) readers.get(0);
  			return new SequenceReader((Reader[]) readers.toArray(new Reader[readers.size()]));
  		}
  		IOpenable openable = declaration.getOpenable();
  		if (!(openable instanceof MetadataFile)) {
  			IBuffer buf = openable.getBuffer();
  			JavaDocCommentReader r = new JavaDocCommentReader(buf, declaration.getNameRange().getOffset() - 1);
  			if(r.getOffset() != declaration.getNameRange().getOffset() - 1) {
  				return r;
  			}
  		}
   		return null;
  	}
  
  	/**
  	 * Checks whether the given reader only returns
  	 * the inheritDoc tag.
  	 * 
  	 * @param reader the reader
  	 * @param length the length of the underlying content
  	 * @return <code>true</code> if the reader only returns the inheritDoc tag
  	 * 
  	 */
  	private static boolean containsOnlyInheritDoc(Reader reader, int length) {
  		char[] content= new char[length];
  		try {
  			reader.read(content, 0, length);
  		} catch (IOException e) {
  			return false;
  		}
  		return new String(content).trim().equals("{@inheritDoc}"); //$NON-NLS-1$
  		
  	}
  	
  	/**
  	 * <p>Gets the HTML content reader for either an {@link IMember} or {@link ILocalVariable}.</p>
  	 * 
  	 * @param element {@link IJavaScriptElement} to get the Javadoc of
  	 * @param allowInherited for methods with no (Javadoc) comment, the comment of the overridden
  	 * class is returned if <code>allowInherited</code> is <code>true</code>
  	 * @param useAttachedJavadoc if <code>true</code> Javadoc will be extracted from attached Javadoc
  	 * if there's no source
  	 * 
  	 * @return a reader for the Javadoc comment content in HTML or <code>null</code> if the element
  	 * does not contain a Javadoc comment or if no source is available
  	 * 
  	 * @throws JavaScriptModelException is thrown when the elements Javadoc can not be accessed
  	 * 
  	 * @see #getHTMLContentReader(ILocalVariable, boolean, boolean)
  	 * @see #getHTMLContentReader(IMember, boolean, boolean)
  	 */
  	public static Reader getHTMLContentReader(IJavaScriptElement element, boolean allowInherited, boolean useAttachedJavadoc) throws JavaScriptModelException {
  		Reader reader = null;
  		
  		if(element instanceof IMember) {
  			reader = getHTMLContentReader((IMember)element, allowInherited, useAttachedJavadoc);
  		} else if(element instanceof ILocalVariable) {
  			reader = getHTMLContentReader((ILocalVariable)element, allowInherited, useAttachedJavadoc);
  		}
  		
  		return reader;
  	}
  
  	/**
  	 * Gets a reader for an IMember's Javadoc comment content from the source attachment.
  	 * and renders the tags in HTML. 
  	 * Returns <code>null</code> if the member does not contain a Javadoc comment or if no source is available.
  	 * 
  	 * @param member				the member to get the Javadoc of.
  	 * @param allowInherited		for methods with no (Javadoc) comment, the comment of the overridden
  	 * 									class is returned if <code>allowInherited</code> is <code>true</code>
  	 * @param useAttachedJavadoc	if <code>true</code> Javadoc will be extracted from attached Javadoc
  	 * 									if there's no source
  	 * @return a reader for the Javadoc comment content in HTML or <code>null</code> if the member
  	 * 			does not contain a Javadoc comment or if no source is available
  	 * @throws JavaScriptModelException is thrown when the elements Javadoc can not be accessed
  	 * 
  	 */
  	public static Reader getHTMLContentReader(IMember member, boolean allowInherited, boolean useAttachedJavadoc) throws JavaScriptModelException {
  		Reader contentReader= getContentReader(member, allowInherited);
  		if (contentReader != null)
  		{
  			IDocumentationReader[] docReaders = getDocReaders(member);
  			if (docReaders.length > 0) {
  				List htmlReaders = new ArrayList(docReaders.length);
  				for (int i = 0; i < docReaders.length; i++) {
  					Reader htmlReader = docReaders[i].getDocumentation2HTMLReader(contentReader);
  					if (htmlReader != null) {
  						htmlReaders.add(htmlReader);
  					}
  				}
  				/* return any and all HTML readers in sequence */
  				if (!htmlReaders.isEmpty()) {
  //					htmlReaders.add(/*0, */new JavaDoc2HTMLTextReader(contentReader));
  					return new SequenceReader((Reader[]) htmlReaders.toArray(new Reader[htmlReaders.size()]));
  				}
  			}
  			return new JavaDoc2HTMLTextReader(contentReader);
  		}
  		
  		if (useAttachedJavadoc && member.getOpenable().getBuffer() == null) { // only if no source available
  			String s= member.getAttachedJavadoc(null);
  			if (s != null)
  				return new StringReader(s);
  		}
  		return null;
  	}
  	
  	/**
  	 * Gets a reader for an ILocalDeclaration documentation comment content.
  	 * and renders the tags in HTML.
  	 * Returns <code>null</code> if the declaration does not contain a doc comment or if no source is available.
  	 * 
  	 * @param variable			the variable declaration to get the doc of.
  	 * @param allowInherited		for methods with no (JSDoc) comment, the comment of the overridden
  	 * 									class is returned if <code>allowInherited</code> is <code>true</code>
  	 * @param useAttachedDoc	if <code>true</code> JSDoc will be extracted from attached JSDoc
  	 * 									if there's no source
  	 * @return a reader for the JSDoc comment content in HTML or <code>null</code> if the member
  	 * 			does not contain a JSDoc comment or if no source is available
  	 * @throws JavaScriptModelException is thrown when the elements JSDoc can not be accessed
  	 * 
  	 */
  	public static Reader getHTMLContentReader(ILocalVariable variable, boolean allowInherited, boolean useAttachedDoc) throws JavaScriptModelException {
  		Reader contentReader= getContentReader(variable, allowInherited);
  		if (contentReader != null) {
  			IDocumentationReader[] docReaders = getDocReaders(variable);
  			if (docReaders.length > 0) {
  				List htmlReaders = new ArrayList(docReaders.length);
  				for (int i = 0; i < docReaders.length; i++) {
  					Reader documentation2htmlReader = docReaders[i].getDocumentation2HTMLReader(contentReader);
  					if (documentation2htmlReader != null) {
  						htmlReaders.add(documentation2htmlReader);
  					}
  				}
  				if (!htmlReaders.isEmpty()) {
  					htmlReaders.add(/*0, */new JavaDoc2HTMLTextReader(contentReader));
  					return new SequenceReader((Reader[]) htmlReaders.toArray(new Reader[htmlReaders.size()]));
  				}
  			}
  			return new JavaDoc2HTMLTextReader(contentReader);
  		}
  		
  		return null;
  	}
  
  	private static Reader findDocInHierarchy(IFunction method) throws JavaScriptModelException {
  		IType type= method.getDeclaringType();
  		if (type==null)
  			return null;
  		ITypeHierarchy hierarchy= type.newSupertypeHierarchy(null);
  		
  		MethodOverrideTester tester= new MethodOverrideTester(type, hierarchy);
  		
  		IType[] superTypes= hierarchy.getAllSuperclasses(type);
  		for (int i= 0; i < superTypes.length; i++) {
  			IType curr= superTypes[i];
  			IFunction overridden= tester.findOverriddenMethodInType(curr, method);
  			if (overridden != null) {
  				Reader reader= getContentReader(overridden, false);
  				if (reader != null) {
  					return reader;
  				}
  			}
  		}
  		return null;
  	}		
  
  	private static IDocumentationReader[] getDocReaders(IMember member)
  	{
  		if (docReaders==null)
  			loadExtensions();
  		List readers = new ArrayList(docReaders.length);
  		for (int i = 0; i < docReaders.length; i++) {
  			if (docReaders[i].appliesTo(member)) {
  				readers.add(docReaders[i]);
  			}
  		}
  		return (IDocumentationReader[]) readers.toArray(new IDocumentationReader[readers.size()]);
  	}
  	
  	private static IDocumentationReader[] getDocReaders(ILocalVariable declaration)
  	{
  		if (docReaders==null)
  			loadExtensions();
  		List readers = new ArrayList(docReaders.length);
  		for (int i = 0; i < docReaders.length; i++) {
  			if (docReaders[i].appliesTo(declaration)) {
  				readers.add(docReaders[i]);
  			}
  		}
  		return (IDocumentationReader[]) readers.toArray(new IDocumentationReader[readers.size()]);
  	}
  	
  	
  	private static void loadExtensions() {
  		IExtensionRegistry registry = Platform.getExtensionRegistry();
  		ArrayList extList = new ArrayList();
  		if (registry != null) {
  			IExtensionPoint point = registry.getExtensionPoint(
  					JavaScriptPlugin.getPluginId(), EXTENSION_POINT);
  
  			if (point != null) {
  				IExtension[] extensions = point.getExtensions();
  				for (int i = 0; i < extensions.length; i++) {
  					IConfigurationElement[] elements = extensions[i]
  							.getConfigurationElements();
  					for (int j = 0; j < elements.length; j++) {
  						try {
  							IDocumentationReader docProvider = null;
  							if (elements[j].getName().equals(TAG_DOCUMENTATIONPROVIDER)) {
  								  docProvider = (IDocumentationReader) elements[j]
  										.createExecutableExtension(ATTR_DOCUMENTATIONPROVIDER_CLASS);
  							}
  
  							extList.add(docProvider);
  						} catch (CoreException e) {
  							e.printStackTrace();
  						}
  					}
  				}
  			}
  		}
  
  		 docReaders = (IDocumentationReader[]) extList
  				.toArray(new IDocumentationReader[extList.size()]);
  	}
  
  
  	
  }
  