/*******************************************************************************
 * 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.jdt.internal.core;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.PerformanceStats;
import org.eclipse.jdt.core.*;
import org.eclipse.jdt.internal.codeassist.CompletionEngine;
import org.eclipse.jdt.internal.codeassist.SelectionEngine;
import org.eclipse.jdt.internal.core.util.Util;


/**
 * Abstract class for implementations of java elements which are IOpenable.
 *
 * @see IJavaElement
 * @see IOpenable
 */
public abstract class Openable extends JavaElement implements IOpenable, IBufferChangedListener {

protected Openable(JavaElement parent) {
	super(parent);
}
/**
 * The buffer associated with this element has changed. Registers
 * this element as being out of synch with its buffer's contents.
 * If the buffer has been closed, this element is set as NOT out of
 * synch with the contents.
 *
 * @see IBufferChangedListener
 */
public void bufferChanged(BufferChangedEvent event) {
	if (event.getBuffer().isClosed()) {
		JavaModelManager.getJavaModelManager().getElementsOutOfSynchWithBuffers().remove(this);
		getBufferManager().removeBuffer(event.getBuffer());
	} else {
		JavaModelManager.getJavaModelManager().getElementsOutOfSynchWithBuffers().add(this);
	}
}
/**
 * Builds this element's structure and properties in the given
 * info object, based on this element's current contents (reuse buffer
 * contents if this element has an open buffer, or resource contents
 * if this element does not have an open buffer). Children
 * are placed in the given newElements table (note, this element
 * has already been placed in the newElements table). Returns true
 * if successful, or false if an error is encountered while determining
 * the structure of this element.
 */
protected abstract boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws JavaModelException;
/*
 * Returns whether this element can be removed from the Java model cache to make space.
 */
public boolean canBeRemovedFromCache() {
	try {
		return !hasUnsavedChanges();
	} catch (JavaModelException e) {
		return false;
	}
}
/*
 * Returns whether the buffer of this element can be removed from the Java model cache to make space.
 */
public boolean canBufferBeRemovedFromCache(IBuffer buffer) {
	return !buffer.hasUnsavedChanges();
}
/**
 * Close the buffer associated with this element, if any.
 */
protected void closeBuffer() {
	if (!hasBuffer()) return; // nothing to do
	IBuffer buffer = getBufferManager().getBuffer(this);
	if (buffer != null) {
		buffer.close();
		buffer.removeBufferChangedListener(this);
	}
}
/**
 * This element is being closed.  Do any necessary cleanup.
 */
protected void closing(Object info) {
	closeBuffer();
}
protected void codeComplete(
		org.eclipse.jdt.internal.compiler.env.ICompilationUnit cu,
		org.eclipse.jdt.internal.compiler.env.ICompilationUnit unitToSkip,
		int position, CompletionRequestor requestor,
		WorkingCopyOwner owner,
		ITypeRoot typeRoot,
		IProgressMonitor monitor) throws JavaModelException {
	if (requestor == null) {
		throw new IllegalArgumentException("Completion requestor cannot be null"); //$NON-NLS-1$
	}
	PerformanceStats performanceStats = CompletionEngine.PERF
		? PerformanceStats.getStats(JavaModelManager.COMPLETION_PERF, this)
		: null;
	if(performanceStats != null) {
		performanceStats.startRun(new String(cu.getFileName()) + " at " + position); //$NON-NLS-1$
	}
	IBuffer buffer = getBuffer();
	if (buffer == null) {
		return;
	}
	if (position < -1 || position > buffer.getLength()) {
		throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS));
	}
	JavaProject project = (JavaProject) getJavaProject();
	SearchableEnvironment environment = project.newSearchableNameEnvironment(owner);

	// set unit to skip
	environment.unitToSkip = unitToSkip;

	// code complete
	CompletionEngine engine = new CompletionEngine(environment, requestor, project.getOptions(true), project, owner, monitor);
	engine.complete(cu, position, 0, typeRoot);
	if(performanceStats != null) {
		performanceStats.endRun();
	}
	if (NameLookup.VERBOSE) {
		System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInSourcePackage: " + environment.nameLookup.timeSpentInSeekTypesInSourcePackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
		System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInBinaryPackage: " + environment.nameLookup.timeSpentInSeekTypesInBinaryPackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
	}
}
protected IJavaElement[] codeSelect(org.eclipse.jdt.internal.compiler.env.ICompilationUnit cu, int offset, int length, WorkingCopyOwner owner) throws JavaModelException {
	PerformanceStats performanceStats = SelectionEngine.PERF
		? PerformanceStats.getStats(JavaModelManager.SELECTION_PERF, this)
		: null;
	if(performanceStats != null) {
		performanceStats.startRun(new String(cu.getFileName()) + " at [" + offset + "," + length + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}

	JavaProject project = (JavaProject)getJavaProject();
	SearchableEnvironment environment = project.newSearchableNameEnvironment(owner);

	SelectionRequestor requestor= new SelectionRequestor(environment.nameLookup, this);
	IBuffer buffer = getBuffer();
	if (buffer == null) {
		return requestor.getElements();
	}
	int end= buffer.getLength();
	if (offset < 0 || length < 0 || offset + length > end ) {
		throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INDEX_OUT_OF_BOUNDS));
	}

	// fix for 1FVXGDK
	SelectionEngine engine = new SelectionEngine(environment, requestor, project.getOptions(true), owner);
	engine.select(cu, offset, offset + length - 1);

	if(performanceStats != null) {
		performanceStats.endRun();
	}
	if (NameLookup.VERBOSE) {
		System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInSourcePackage: " + environment.nameLookup.timeSpentInSeekTypesInSourcePackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
		System.out.println(Thread.currentThread() + " TIME SPENT in NameLoopkup#seekTypesInBinaryPackage: " + environment.nameLookup.timeSpentInSeekTypesInBinaryPackage + "ms");  //$NON-NLS-1$ //$NON-NLS-2$
	}
	return requestor.getElements();
}
/*
 * Returns a new element info for this element.
 */
protected Object createElementInfo() {
	return new OpenableElementInfo();
}
/**
 * @see IJavaElement
 */
public boolean exists() {
	if (JavaModelManager.getJavaModelManager().getInfo(this) != null)
		return true;
	switch (getElementType()) {
		case IJavaElement.PACKAGE_FRAGMENT:
			PackageFragmentRoot root = getPackageFragmentRoot();
			if (root.isArchive()) {
				// pkg in a jar -> need to open root to know if this pkg exists
				JarPackageFragmentRootInfo rootInfo;
				try {
					rootInfo = (JarPackageFragmentRootInfo) root.getElementInfo();
				} catch (JavaModelException e) {
					return false;
				}
				return rootInfo.rawPackageInfo.containsKey(((PackageFragment) this).names);
			}
			break;
		case IJavaElement.CLASS_FILE:
			if (getPackageFragmentRoot().isArchive()) {
				// class file in a jar -> need to open this class file to know if it exists
				return super.exists();
			}
			break;
	}
	return validateExistence(resource()).isOK();
}
public String findRecommendedLineSeparator() throws JavaModelException {
	IBuffer buffer = getBuffer();
	String source = buffer == null ? null : buffer.getContents();
	return Util.getLineSeparator(source, getJavaProject());
}
protected void generateInfos(Object info, HashMap newElements, IProgressMonitor monitor) throws JavaModelException {

	if (JavaModelCache.VERBOSE){
		String element;
		switch (getElementType()) {
			case JAVA_PROJECT:
				element = "project"; //$NON-NLS-1$
				break;
			case PACKAGE_FRAGMENT_ROOT:
				element = "root"; //$NON-NLS-1$
				break;
			case PACKAGE_FRAGMENT:
				element = "package"; //$NON-NLS-1$
				break;
			case CLASS_FILE:
				element = "class file"; //$NON-NLS-1$
				break;
			case COMPILATION_UNIT:
				element = "compilation unit"; //$NON-NLS-1$
				break;
			default:
				element = "element"; //$NON-NLS-1$
		}
		System.out.println(Thread.currentThread() +" OPENING " + element + " " + this.toStringWithAncestors()); //$NON-NLS-1$//$NON-NLS-2$
	}

	// open its ancestors if needed
	openAncestors(newElements, monitor);

	// validate existence
	IResource underlResource = resource();
	IStatus status = validateExistence(underlResource);
	if (!status.isOK())
		throw newJavaModelException(status);

	if (monitor != null && monitor.isCanceled())
		throw new OperationCanceledException();

	 // puts the info before building the structure so that questions to the handle behave as if the element existed
	 // (case of compilation units becoming working copies)
	newElements.put(this, info);

	// build the structure of the openable (this will open the buffer if needed)
	try {
		OpenableElementInfo openableElementInfo = (OpenableElementInfo)info;
		boolean isStructureKnown = buildStructure(openableElementInfo, monitor, newElements, underlResource);
		openableElementInfo.setIsStructureKnown(isStructureKnown);
	} catch (JavaModelException e) {
		newElements.remove(this);
		throw e;
	}

	// remove out of sync buffer for this element
	JavaModelManager.getJavaModelManager().getElementsOutOfSynchWithBuffers().remove(this);

	if (JavaModelCache.VERBOSE) {
		System.out.println(JavaModelManager.getJavaModelManager().cacheToString("-> ")); //$NON-NLS-1$
	}
}
/**
 * Note: a buffer with no unsaved changes can be closed by the Java Model
 * since it has a finite number of buffers allowed open at one time. If this
 * is the first time a request is being made for the buffer, an attempt is
 * made to create and fill this element's buffer. If the buffer has been
 * closed since it was first opened, the buffer is re-created.
 *
 * @see IOpenable
 */
public IBuffer getBuffer() throws JavaModelException {
	if (hasBuffer()) {
		// ensure element is open
		Object info = getElementInfo();
		IBuffer buffer = getBufferManager().getBuffer(this);
		if (buffer == null) {
			// try to (re)open a buffer
			buffer = openBuffer(null, info);
		}
		if (buffer instanceof NullBuffer) {
			return null;
		}
		return buffer;
	} else {
		return null;
	}
}
/**
 * Answers the buffer factory to use for creating new buffers
 * @deprecated
 */
public IBufferFactory getBufferFactory(){
	return getBufferManager().getDefaultBufferFactory();
}

/**
 * Returns the buffer manager for this element.
 */
protected BufferManager getBufferManager() {
	return BufferManager.getDefaultBufferManager();
}
/**
 * Return my underlying resource. Elements that may not have a
 * corresponding resource must override this method.
 *
 * @see IJavaElement
 */
public IResource getCorrespondingResource() throws JavaModelException {
	return getUnderlyingResource();
}
/*
 * @see IJavaElement
 */
public IOpenable getOpenable() {
	return this;
}



/**
 * @see IJavaElement
 */
public IResource getUnderlyingResource() throws JavaModelException {
	IResource parentResource = this.parent.getUnderlyingResource();
	if (parentResource == null) {
		return null;
	}
	int type = parentResource.getType();
	if (type == IResource.FOLDER || type == IResource.PROJECT) {
		IContainer folder = (IContainer) parentResource;
		IResource resource = folder.findMember(getElementName());
		if (resource == null) {
			throw newNotPresentException();
		} else {
			return resource;
		}
	} else {
		return parentResource;
	}
}

/**
 * Returns true if this element may have an associated source buffer,
 * otherwise false. Subclasses must override as required.
 */
protected boolean hasBuffer() {
	return false;
}
/**
 * @see IOpenable
 */
public boolean hasUnsavedChanges() throws JavaModelException{

	if (isReadOnly() || !isOpen()) {
		return false;
	}
	IBuffer buf = getBuffer();
	if (buf != null && buf.hasUnsavedChanges()) {
		return true;
	}
	// for package fragments, package fragment roots, and projects must check open buffers
	// to see if they have an child with unsaved changes
	int elementType = getElementType();
	if (elementType == PACKAGE_FRAGMENT ||
		elementType == PACKAGE_FRAGMENT_ROOT ||
		elementType == JAVA_PROJECT ||
		elementType == JAVA_MODEL) { // fix for 1FWNMHH
		Enumeration openBuffers= getBufferManager().getOpenBuffers();
		while (openBuffers.hasMoreElements()) {
			IBuffer buffer= (IBuffer)openBuffers.nextElement();
			if (buffer.hasUnsavedChanges()) {
				IJavaElement owner= (IJavaElement)buffer.getOwner();
				if (isAncestorOf(owner)) {
					return true;
				}
			}
		}
	}

	return false;
}
/**
 * Subclasses must override as required.
 *
 * @see IOpenable
 */
public boolean isConsistent() {
	return true;
}
/**
 *
 * @see IOpenable
 */
public boolean isOpen() {
	return JavaModelManager.getJavaModelManager().getInfo(this) != null;
}
/**
 * Returns true if this represents a source element.
 * Openable source elements have an associated buffer created
 * when they are opened.
 */
protected boolean isSourceElement() {
	return false;
}
/**
 * @see IJavaElement
 */
public boolean isStructureKnown() throws JavaModelException {
	return ((OpenableElementInfo)getElementInfo()).isStructureKnown();
}
/**
 * @see IOpenable
 */
public void makeConsistent(IProgressMonitor monitor) throws JavaModelException {
	// only compilation units can be inconsistent
	// other openables cannot be inconsistent so default is to do nothing
}
/**
 * @see IOpenable
 */
public void open(IProgressMonitor pm) throws JavaModelException {
	getElementInfo(pm);
}

/**
 * Opens a buffer on the contents of this element, and returns
 * the buffer, or returns <code>null</code> if opening fails.
 * By default, do nothing - subclasses that have buffers
 * must override as required.
 */
protected IBuffer openBuffer(IProgressMonitor pm, Object info) throws JavaModelException {
	return null;
}

public IResource getResource() {
	PackageFragmentRoot root = getPackageFragmentRoot();
	if (root != null) {
		if (root.isExternal())
			return null;
		if (root.isArchive())
			return root.resource(root);
	}
	return resource(root);
}

public IResource resource() {
	PackageFragmentRoot root = getPackageFragmentRoot();
	if (root != null && root.isArchive())
		return root.resource(root);
	return resource(root);
}

protected abstract IResource resource(PackageFragmentRoot root);

/**
 * Returns whether the corresponding resource or associated file exists
 */
protected boolean resourceExists(IResource underlyingResource) {
	return underlyingResource.isAccessible();
}

/**
 * @see IOpenable
 */
public void save(IProgressMonitor pm, boolean force) throws JavaModelException {
	if (isReadOnly()) {
		throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.READ_ONLY, this));
	}
	IBuffer buf = getBuffer();
	if (buf != null) { // some Openables (like a JavaProject) don't have a buffer
		buf.save(pm, force);
		makeConsistent(pm); // update the element info of this element
	}
}

/**
 * Find enclosing package fragment root if any
 */
public PackageFragmentRoot getPackageFragmentRoot() {
	return (PackageFragmentRoot) getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
}

/*
 * Validates the existence of this openable. Returns a non ok status if it doesn't exist.
 */
abstract protected IStatus validateExistence(IResource underlyingResource);

/*
 * Opens the ancestors of this openable that are not yet opened, validating their existence.
 */
protected void openAncestors(HashMap newElements, IProgressMonitor monitor) throws JavaModelException {
	Openable openableParent = (Openable)getOpenableParent();
	if (openableParent != null && !openableParent.isOpen()) {
		openableParent.generateInfos(openableParent.createElementInfo(), newElements, monitor);
	}
}

}
