/*******************************************************************************
 * 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
 *
 
 *******************************************************************************/
package org.eclipse.dltk.internal.core;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ProjectScope;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.AssertionFailedException;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.IScopeContext;
import org.eclipse.dltk.compiler.util.ObjectVector;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.IBuildpathContainer;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelMarker;
import org.eclipse.dltk.core.IModelStatus;
import org.eclipse.dltk.core.IModelStatusConstants;
import org.eclipse.dltk.core.IProjectFragment;
import org.eclipse.dltk.core.IRegion;
import org.eclipse.dltk.core.IScriptFolder;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.ISearchableEnvironment;
import org.eclipse.dltk.core.ISourceModule;
import org.eclipse.dltk.core.IType;
import org.eclipse.dltk.core.ITypeHierarchy;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.WorkingCopyOwner;
import org.eclipse.dltk.internal.core.util.MementoTokenizer;
import org.eclipse.dltk.internal.core.util.Messages;
import org.eclipse.dltk.internal.core.util.Util;
import org.eclipse.dltk.utils.CorePrinter;
import org.osgi.service.prefs.BackingStoreException;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class ScriptProject extends Openable implements IScriptProject {
	/**
	 * Name of file containing project buildpath
	 */
	public static final String BUILDPATH_FILENAME = ".buildpath"; //$NON-NLS-1$
	/**
	 * Value of the project's raw buildpath if the .buildpath file contains
	 * invalid entries.
	 */
	public static final IBuildpathEntry[] INVALID_BUILDPATH = new IBuildpathEntry[0];
	/**
	 * Whether the underlying file system is case sensitive.
	 */
	protected static final boolean IS_CASE_SENSITIVE = !new File("Temp").equals(new File("temp")); //$NON-NLS-1$ //$NON-NLS-2$
	/**
	 * An empty array of strings indicating that a project doesn't have any
	 * prerequesite projects.
	 */
	protected static final String[] NO_PREREQUISITES = new String[0];
	/*
	 * Value of project's resolved buildpath while it is being resolved
	 */
	private static final IBuildpathEntry[] RESOLUTION_IN_PROGRESS = new IBuildpathEntry[0];
	/**
	 * The platform project this <code>IDylanProject</code> is based on
	 */
	protected IProject project;
	private IDLTKLanguageToolkit toolkit = null;

	public ScriptProject(IProject project, ModelElement parent) {
		super(parent);
		this.project = project;
		toolkit = DLTKLanguageManager.getLanguageToolkit(this);
	}

	public IDLTKLanguageToolkit getLanguageToolkit() {
		return toolkit;
	}

	/**
	 * The path is known to match a source/library folder entry.
	 * 
	 * @param path
	 *            IPath
	 * @return IProjectFragment
	 */
	public IProjectFragment getFolderProjectFragment(IPath path) {
		if (path.segmentCount() == 1) { // default project root
			return getProjectFragment(this.project);
		}
		return getProjectFragment(this.project.getWorkspace().getRoot()
				.getFolder(path));
	}

	public IProject getProject() {
		return this.project;
	}

	public IProjectFragment getProjectFragment(IResource resource) {
		switch (resource.getType()) {
		case IResource.FILE:
			if (org.eclipse.dltk.compiler.util.Util.isArchiveFileName(resource
					.getName())) {
				return createArchiveFragment(resource);
			} else {
				return null;
			}
		case IResource.FOLDER:
			return new ProjectFragment(resource, this);
		case IResource.PROJECT:
			return new ProjectFragment(resource, this);
		default:
			return null;
		}
	}

	private IProjectFragment createArchiveFragment(IResource resource) {
		IDLTKLanguageToolkit toolkit = DLTKLanguageManager
				.getLanguageToolkit(this);
		if (toolkit != null) {
			if (toolkit.languageSupportZIPBuildpath()) {
				return new ArchiveProjectFragment(resource, this);
			}
		}
		return null;
	}

	public IProjectFragment getProjectFragment(String path) {
		return getProjectFragment(canonicalizedPath(new Path(path)));
	}

	/*
	 * no path canonicalization
	 */
	public IProjectFragment getProjectFragment0(IPath archivePath) {
		IDLTKLanguageToolkit toolkit = DLTKLanguageManager
				.getLanguageToolkit(this);
		if (toolkit != null) {
			if (toolkit.languageSupportZIPBuildpath()) {
				return new ArchiveProjectFragment(archivePath, this);
			}
		}
		return null;
	}

	/**
	 * @param path
	 *            IPath
	 * @return A handle to the package fragment root identified by the given
	 *         path. This method is handle-only and the element may or may not
	 *         exist. Returns <code>null</code> if unable to generate a handle
	 *         from the path (for example, an absolute path that has less than 1
	 *         segment. The path may be relative or absolute.
	 */
	public IProjectFragment getProjectFragment(IPath path) {
		boolean isBuiltin = path.toString().startsWith(
				IBuildpathEntry.BUILTIN_EXTERNAL_ENTRY_STR);
		if (!path.isAbsolute() && !isBuiltin) {
			path = getPath().append(path);
		}
		int segmentCount = path.segmentCount();
		switch (segmentCount) {
		case 0:
			return null;
		case 1:
			if (path.equals(getPath())) { // see
				// https://bugs.eclipse.org/bugs/show_bug.cgi?id=75814
				// default root
				return getProjectFragment(this.project);
			}
		default:
			// a path ending with .jar/.zip is still ambiguous and could
			// still
			// resolve to a source/lib folder
			// thus will try to guess based on existing resource
			if (isBuiltin) {
				return new BuiltinProjectFragment(path, this);
			}
			if (org.eclipse.dltk.compiler.util.Util.isArchiveFileName(path
					.lastSegment())) {
				IResource resource = this.project.getWorkspace().getRoot()
						.findMember(path);
				if (resource != null && resource.getType() == IResource.FOLDER) {
					return getProjectFragment(resource);
				}
				return getProjectFragment0(path);
			} else if (segmentCount == 1) {
				// lib being another project
				return getProjectFragment(this.project.getWorkspace().getRoot()
						.getProject(path.lastSegment()));
			} else {
				// lib being a folder
				return getProjectFragment(this.project.getWorkspace().getRoot()
						.getFolder(path));
			}
		}
	}

	// /*
	// * Returns the cached resolved buildpath, or compute it ignoring
	// unresolved
	// * entries and cache it.
	// */
	public IBuildpathEntry[] getResolvedBuildpath() throws ModelException {
		return getResolvedBuildpath(true/* ignoreUnresolvedEntry */,
				false/* don't generateMarkerOnError */, false/*
		 * don't
		 * returnResolutionInProgress
		 */); // force
		// the
		// reverse
		// rawEntry
		// cache
		// to
		// be
		// populated
	}

	/*
	 * Internal variant which can create marker on project for invalid entries
	 * and caches the resolved buildpath on perProjectInfo. If requested, return
	 * a special buildpath (RESOLUTION_IN_PROGRESS) if the buildpath is being
	 * resolved.
	 */
	public IBuildpathEntry[] getResolvedBuildpath(
			boolean ignoreUnresolvedEntry, boolean generateMarkerOnError,
			boolean returnResolutionInProgress) throws ModelException {

		ModelManager manager = ModelManager.getModelManager();
		ModelManager.PerProjectInfo perProjectInfo = null;
		if (ignoreUnresolvedEntry && !generateMarkerOnError) {
			perProjectInfo = getPerProjectInfo();
			if (perProjectInfo != null) {
				// resolved path is cached on its info
				IBuildpathEntry[] infoPath = perProjectInfo.resolvedBuildpath;
				if (infoPath != null) {
					return infoPath;
				} else if (returnResolutionInProgress
						&& manager.isBuildpathBeingResolved(this)) {
					if (ModelManager.BP_RESOLVE_VERBOSE) {
						Util
								.verbose("CPResolution: reentering raw buildpath resolution, will use empty buildpath instead" + //$NON-NLS-1$
										"	project: " + getElementName() + '\n' + //$NON-NLS-1$
										"	invocation stack trace:"); //$NON-NLS-1$
						new Exception("<Fake exception>").printStackTrace(System.out); //$NON-NLS-1$
					}
					return RESOLUTION_IN_PROGRESS;
				}
			}
		}
		Map rawReverseMap = perProjectInfo == null ? null : new HashMap(5);
		IBuildpathEntry[] resolvedPath = null;
		boolean nullOldResolvedCP = perProjectInfo != null
				&& perProjectInfo.resolvedBuildpath == null;
		try {
			// protect against misbehaving clients (see
			// https://bugs.eclipse.org/bugs/show_bug.cgi?id=61040)
			if (nullOldResolvedCP)
				manager.setBuildpathBeingResolved(this, true);
			resolvedPath = getResolvedBuildpath(getRawBuildpath(
					generateMarkerOnError, !generateMarkerOnError),
					ignoreUnresolvedEntry, generateMarkerOnError, rawReverseMap);
		} finally {
			if (nullOldResolvedCP)
				perProjectInfo.resolvedBuildpath = null;
		}

		if (perProjectInfo != null) {
			if (perProjectInfo.rawBuildpath == null // .buildpath file could not
					// be read
					&& generateMarkerOnError
					&& DLTKLanguageManager.hasScriptNature(this.project)) {
				// flush .buildpath format markers (bug 39877), but only when
				// file cannot be read (bug 42366)
				this.flushBuildpathProblemMarkers(false, true);
				this.createBuildpathProblemMarker(new ModelStatus(
						IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT,
						Messages.bind(
								Messages.buildpath_cannotReadBuildpathFile,
								this.getElementName())));
			}

			perProjectInfo.resolvedBuildpath = resolvedPath;
			perProjectInfo.resolvedPathToRawEntries = rawReverseMap;
			manager.setBuildpathBeingResolved(this, false);
		}
		return resolvedPath;
	}

	/**
	 * Internal variant which can process any arbitrary buildpath
	 * 
	 * @param buildpathEntries
	 *            IBuildpathEntry[]
	 * @param projectOutputLocation
	 *            IPath
	 * @param ignoreUnresolvedEntry
	 *            boolean
	 * @param generateMarkerOnError
	 *            boolean
	 * @param rawReverseMap
	 *            Map
	 * @return IBuildpathEntry[]
	 * @throws ModelException
	 */
	public IBuildpathEntry[] getResolvedBuildpath(
			IBuildpathEntry[] buildpathEntries, boolean ignoreUnresolvedEntry, // if
			// unresolved
			// entries
			// are
			// met,
			// should
			// it
			// trigger
			// initializations
			boolean generateMarkerOnError, Map rawReverseMap) // can be null
			// if not
			// interested in
			// reverse
			// mapping
			throws ModelException {

		IModelStatus status;
		if (generateMarkerOnError) {
			flushBuildpathProblemMarkers(false, false);
		}

		int length = buildpathEntries.length;
		ArrayList resolvedEntries = new ArrayList();

		for (int i = 0; i < length; i++) {

			IBuildpathEntry rawEntry = buildpathEntries[i];
			IPath resolvedPath;
			status = null;

			/* validation if needed */
			if (generateMarkerOnError || !ignoreUnresolvedEntry) {
				status = BuildpathEntry.validateBuildpathEntry(this, rawEntry,
						false /*
				 * do not recurse in containers, done later to
				 * accumulate
				 */);
				if (generateMarkerOnError && !status.isOK()) {
					if (status.getCode() == IModelStatusConstants.INVALID_PATH
							&& ((BuildpathEntry) rawEntry).isOptional())
						continue; // ignore this entry
					createBuildpathProblemMarker(status);
				}
			}

			switch (rawEntry.getEntryKind()) {

			case IBuildpathEntry.BPE_CONTAINER:

				IBuildpathContainer container = DLTKCore.getBuildpathContainer(
						rawEntry.getPath(), this);
				if (container == null) {
					if (!ignoreUnresolvedEntry)
						throw new ModelException(status);
					break;
				}

				IBuildpathEntry[] containerEntries = container
						.getBuildpathEntries(this);
				if (containerEntries == null)
					break;

				// container was bound
				for (int j = 0, containerLength = containerEntries.length; j < containerLength; j++) {
					BuildpathEntry cEntry = (BuildpathEntry) containerEntries[j];
					if (generateMarkerOnError) {
						IModelStatus containerStatus = BuildpathEntry
								.validateBuildpathEntry(this, cEntry, true /* recurse */);
						if (!containerStatus.isOK())
							createBuildpathProblemMarker(containerStatus);
					}
					// if container is exported or restricted, then its nested
					// entries must in turn be exported (21749) and/or propagate
					// restrictions
					cEntry = cEntry.combineWith((BuildpathEntry) rawEntry);
					if (rawReverseMap != null) {
						if (rawReverseMap.get(resolvedPath = cEntry.getPath()) == null)
							rawReverseMap.put(resolvedPath, rawEntry);
					}
					resolvedEntries.add(cEntry);
				}
				break;

			default:

				if (rawReverseMap != null) {
					if (rawReverseMap.get(resolvedPath = rawEntry.getPath()) == null)
						rawReverseMap.put(resolvedPath, rawEntry);
				}
				resolvedEntries.add(rawEntry);

			}
		}

		IBuildpathEntry[] resolvedPath = new IBuildpathEntry[resolvedEntries
				.size()];
		resolvedEntries.toArray(resolvedPath);

		if (generateMarkerOnError) {
			status = BuildpathEntry.validateBuildpath(this, resolvedPath);
			if (!status.isOK())
				createBuildpathProblemMarker(status);
		}
		return resolvedPath;
	}

	/*
	 * Internal variant which can create marker on project for invalid entries
	 * and caches the resolved buildpath on perProjectInfo. If requested, return
	 * a special buildpath (RESOLUTION_IN_PROGRESS) if the buildpath is being
	 * resolved.
	 */
	public IBuildpathEntry[] getResolvedBuildpath(boolean ignoreUnresolvedEntry)
			throws ModelException {
		return getResolvedBuildpath(ignoreUnresolvedEntry, false, // don't
				// generateMarkerOnError
				true // returnResolutionInProgress
		);
	}

	public IBuildpathEntry[] getResolvedBuildpath(
			boolean ignoreUnresolvedEntry, boolean generateMarkerOnError)
			throws ModelException {

		return getResolvedBuildpath(ignoreUnresolvedEntry,
				generateMarkerOnError, true // returnResolutionInProgress
		);
	}

	/**
	 * This is a helper method returning the expanded buildpath for the project,
	 * as a list of buildpath entries, where all buildpath variable entries have
	 * been resolved and substituted with their final target entries. All
	 * project exports have been appended to project entries.
	 * 
	 * @param ignoreUnresolvedVariable
	 *            boolean
	 * @return IBuildpathEntry[]
	 * @throws ModelException
	 */
	public IBuildpathEntry[] getExpandedBuildpath(
			boolean ignoreUnresolvedVariable) throws ModelException {

		return getExpandedBuildpath(ignoreUnresolvedVariable,
				false/* don't create markers */, null);
	}

	/**
	 * Internal variant which can create marker on project for invalid entries,
	 * it will also perform buildpath expansion in presence of project
	 * prerequisites exporting their entries.
	 * 
	 * @param ignoreUnresolvedVariable
	 *            boolean
	 * @param generateMarkerOnError
	 *            boolean
	 * @param preferredBuildpaths
	 *            Map
	 * @param preferredOutputs
	 *            Map
	 * @return IBuildpathEntry[]
	 * @throws ModelException
	 */
	public IBuildpathEntry[] getExpandedBuildpath(
			boolean ignoreUnresolvedVariable, boolean generateMarkerOnError,
			Map preferredBuildpaths) throws ModelException {

		ObjectVector accumulatedEntries = new ObjectVector();
		computeExpandedBuildpath(null, ignoreUnresolvedVariable,
				generateMarkerOnError, new HashSet(5), accumulatedEntries,
				preferredBuildpaths);

		IBuildpathEntry[] expandedPath = new IBuildpathEntry[accumulatedEntries
				.size()];
		accumulatedEntries.copyInto(expandedPath);

		return expandedPath;
	}

	/**
	 * Internal computation of an expanded buildpath. It will eliminate
	 * duplicates, and produce copies of exported or restricted buildpath
	 * entries to avoid possible side-effects ever after.
	 */
	private void computeExpandedBuildpath(BuildpathEntry referringEntry,
			boolean ignoreUnresolvedVariable, boolean generateMarkerOnError,
			HashSet rootIDs, ObjectVector accumulatedEntries,
			Map preferredBuildpaths) throws ModelException {

		String projectRootId = this.rootID();
		if (rootIDs.contains(projectRootId)) {
			return; // break cycles if any
		}
		rootIDs.add(projectRootId);

		IBuildpathEntry[] preferredBuildpath = preferredBuildpaths != null ? (IBuildpathEntry[]) preferredBuildpaths
				.get(this)
				: null;
		IBuildpathEntry[] immediateBuildpath = preferredBuildpath != null ? getResolvedBuildpath(
				preferredBuildpath, ignoreUnresolvedVariable,
				generateMarkerOnError, null /* no reverse map */)
				: getResolvedBuildpath(ignoreUnresolvedVariable,
						generateMarkerOnError, false/*
				 * don't
				 * returnResolutionInProgress
				 */);

		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		boolean isInitialProject = referringEntry == null;
		for (int i = 0, length = immediateBuildpath.length; i < length; i++) {
			BuildpathEntry entry = (BuildpathEntry) immediateBuildpath[i];
			if (isInitialProject || entry.isExported()) {
				String rootID = entry.rootID();
				if (rootIDs.contains(rootID)) {
					continue;
				}
				// combine restrictions along the project chain
				BuildpathEntry combinedEntry = entry
						.combineWith(referringEntry);
				accumulatedEntries.add(combinedEntry);

				// recurse in project to get all its indirect exports (only
				// consider exported entries from there on)
				if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) {
					IResource member = workspaceRoot
							.findMember(entry.getPath());
					if (member != null && member.getType() == IResource.PROJECT) { // double
						// check
						// if
						// bound
						// to
						// project
						// (23977)
						IProject projRsc = (IProject) member;
						if (ScriptProject.hasScriptNature(projRsc)) {
							ScriptProject scriptProject = (ScriptProject) DLTKCore
									.create(projRsc);
							scriptProject.computeExpandedBuildpath(
									combinedEntry, ignoreUnresolvedVariable,
									false /*
									 * no marker when recursing in
									 * prereq
									 */, rootIDs, accumulatedEntries,
									preferredBuildpaths);
						}
					}
				} else {
					rootIDs.add(rootID);
				}
			}
		}
	}

	/**
	 * Computes the project fragments identified by the given entry. Only works
	 * with resolved entry
	 * 
	 * @param resolvedEntry
	 *            IBuildpathEntry
	 * @return IProjectFragment[]
	 */
	public IProjectFragment[] computeProjectFragments(
			IBuildpathEntry resolvedEntry) {
		try {
			return computeProjectFragments(
					new IBuildpathEntry[] { resolvedEntry }, false, // don't
					// retrieve
					// exported
					// roots
					null /* no reverse map */
			);
		} catch (ModelException e) {
			return new IProjectFragment[] {};
		}
	}

	public int getElementType() {
		return SCRIPT_PROJECT;
	}

	protected boolean buildStructure(OpenableElementInfo info,
			IProgressMonitor pm, Map newElements, IResource underlyingResource)
			throws ModelException {
		// check whether the dltk project can be opened

		if (!underlyingResource.isAccessible()) {
			throw newNotPresentException();
		}

		// cannot refresh bp markers on opening (emulate cp check on startup)
		// since can create deadlocks (see bug 37274)
		IBuildpathEntry[] resolvedBuildpath = getResolvedBuildpath();
		// compute the project fragements
		info
				.setChildren(computeProjectFragments(resolvedBuildpath, false,
						null));
		// remember the timestamps of external libraries the first time they are
		// looked up
		getPerProjectInfo().rememberExternalLibTimestamps();
		return true;
	}

	public ModelManager.PerProjectInfo getPerProjectInfo()
			throws ModelException {
		return ModelManager.getModelManager().getPerProjectInfoCheckExistence(
				this.project);
	}

	/**
	 * Returns (local/all) the project fragments identified by the given
	 * project's buildpath. Note: this follows project buildpath references to
	 * find required project contributions, eliminating duplicates silently.
	 * Only works with resolved entries
	 * 
	 * @param resolvedBuildpath
	 *            IBuildpathEntry[]
	 * @param retrieveExportedRoots
	 *            boolean
	 * @return IProjectFragment[]
	 * @throws ModelException
	 */
	public IProjectFragment[] computeProjectFragments(
			IBuildpathEntry[] resolvedBuildpath, boolean retrieveExportedRoots,
			Map rootToResolvedEntries) throws ModelException {
		ObjectVector accumulatedRoots = new ObjectVector();
		computeProjectFragments(resolvedBuildpath, accumulatedRoots,
				new HashSet(5), // rootIDs
				null, // inside original project
				true, // check existency
				retrieveExportedRoots, rootToResolvedEntries);
		IProjectFragment[] rootArray = new IProjectFragment[accumulatedRoots
				.size()];
		accumulatedRoots.copyInto(rootArray);
		return rootArray;
	}

	/**
	 * Returns (local/all) the package fragment roots identified by the given
	 * project's buildpath. Note: this follows project buildpath references to
	 * find required project contributions, eliminating duplicates silently.
	 * Only works with resolved entries
	 * 
	 * @param resolvedBuildpath
	 *            IBuildpathEntry[]
	 * @param accumulatedRoots
	 *            List
	 * @param rootIDs
	 *            HashSet
	 * @param referringEntry
	 *            project entry referring to this CP or null if initial project
	 * @param checkExistency
	 *            boolean
	 * @param retrieveExportedRoots
	 *            boolean
	 * @throws ModelException
	 */
	public void computeProjectFragments(IBuildpathEntry[] resolvedBuildpath,
			ObjectVector accumulatedRoots, HashSet rootIDs,
			IBuildpathEntry referringEntry, boolean checkExistency,
			boolean retrieveExportedRoots, Map rootToResolvedEntries)
			throws ModelException {
		if (referringEntry == null) {
			rootIDs.add(rootID());
		}
		for (int i = 0, length = resolvedBuildpath.length; i < length; i++) {
			computeProjectFragments(resolvedBuildpath[i], accumulatedRoots,
					rootIDs, referringEntry, checkExistency,
					retrieveExportedRoots, rootToResolvedEntries);
		}
	}

	/**
	 * Returns the package fragment roots identified by the given entry. In case
	 * it refers to a project, it will follow its buildpath so as to find
	 * exported roots as well. Only works with resolved entry
	 * 
	 * @param resolvedEntry
	 *            IBuildpathEntry
	 * @param accumulatedRoots
	 *            List
	 * @param rootIDs
	 *            HashSet
	 * @param referringEntry
	 *            the BP entry (project) referring to this entry, or null if
	 *            initial project
	 * @param checkExistency
	 *            boolean
	 * @param retrieveExportedRoots
	 *            boolean
	 * @throws ModelException
	 */
	public void computeProjectFragments(IBuildpathEntry resolvedEntry,
			ObjectVector accumulatedRoots, HashSet rootIDs,
			IBuildpathEntry referringEntry, boolean checkExistency,
			boolean retrieveExportedRoots, Map rootToResolvedEntries)
			throws ModelException {
		String rootID = ((BuildpathEntry) resolvedEntry).rootID();
		if (rootIDs.contains(rootID))
			return;
		IPath projectPath = this.project.getFullPath();
		IPath entryPath = resolvedEntry.getPath();
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		IProjectFragment root = null;
		switch (resolvedEntry.getEntryKind()) {
		// source folder
		case IBuildpathEntry.BPE_SOURCE:
			if (projectPath.isPrefixOf(entryPath)) {
				if (checkExistency) {
					Object target = Model.getTarget(workspaceRoot, entryPath,
							checkExistency);
					if (target == null)
						return;
					if (target instanceof IFolder || target instanceof IProject) {
						root = getProjectFragment((IResource) target);
					}
				} else {
					root = getFolderProjectFragment(entryPath);
				}
			}
			break;
		// internal/external ZIP or folder
		case IBuildpathEntry.BPE_LIBRARY:
			if (referringEntry != null && !resolvedEntry.isExported())
				return;
			if (checkExistency) {
				if (entryPath.toString().startsWith(
						IBuildpathEntry.BUILTIN_EXTERNAL_ENTRY_STR)
						&& BuiltinProjectFragment.isSupported(this)) {
					root = new BuiltinProjectFragment(entryPath, this);
					break;
				}
				Object target = Model.getTarget(workspaceRoot, entryPath,
						checkExistency);
				if (target == null)
					return;
				if (!resolvedEntry.isExternal()) {
					if (target instanceof IResource) {
						// internal target
						root = getProjectFragment((IResource) target);
					}
				} else {// external target
					// This is external folder or zip.
					if (Model.isFile(target)
							&& (org.eclipse.dltk.compiler.util.Util
									.isArchiveFileName(entryPath.lastSegment()))) {
						// root = new ArchiveProjectFragment(entryPath, this);
						root = getProjectFragment0(entryPath);
					} else {
						if (resolvedEntry.isContainerEntry()) {
							root = new ExternalProjectFragment(entryPath, this,
									true, true);
						} else {
							root = new ExternalProjectFragment(entryPath, this,
									true, true);
						}
					}
				}
			} else {
				root = getProjectFragment(entryPath);
			}
			break;
		// recurse into required project
		case IBuildpathEntry.BPE_PROJECT:
			if (!retrieveExportedRoots)
				return;
			if (referringEntry != null && !resolvedEntry.isExported())
				return;
			IResource member = workspaceRoot.findMember(entryPath);
			if (member != null && member.getType() == IResource.PROJECT) {// double
				/*
				 * check if bound to project (23977)
				 */
				IProject requiredProjectRsc = (IProject) member;
				rootIDs.add(rootID);
				ScriptProject requiredProject = (ScriptProject) DLTKCore
						.create(requiredProjectRsc);
				requiredProject
						.computeProjectFragments(
								requiredProject.getResolvedBuildpath(),
								accumulatedRoots,
								rootIDs,
								rootToResolvedEntries == null ? resolvedEntry
										: ((BuildpathEntry) resolvedEntry)
												.combineWith((BuildpathEntry) referringEntry),
								checkExistency, retrieveExportedRoots,
								rootToResolvedEntries);
			}
			break;
		}
		if (root != null) {
			accumulatedRoots.add(root);
			rootIDs.add(rootID);
			if (rootToResolvedEntries != null)
				rootToResolvedEntries.put(root,
						((BuildpathEntry) resolvedEntry)
								.combineWith((BuildpathEntry) referringEntry));
		}
	}

	public String[] projectPrerequisites(IBuildpathEntry[] entries)
			throws ModelException {
		ArrayList prerequisites = new ArrayList();
		// need resolution
		entries = getResolvedBuildpath(entries, true, false, null/*
		 * no
		 * reverse
		 * map
		 */);
		for (int i = 0, length = entries.length; i < length; i++) {
			IBuildpathEntry entry = entries[i];
			if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) {
				prerequisites.add(entry.getPath().lastSegment());
			}
		}
		int size = prerequisites.size();
		if (size == 0) {
			return NO_PREREQUISITES;
		} else {
			String[] result = new String[size];
			prerequisites.toArray(result);
			return result;
		}
	}

	/**
	 * Returns a default build path. This is the root of the project
	 */
	protected IBuildpathEntry[] defaultBuildpath() {
		return new IBuildpathEntry[] { DLTKCore.newSourceEntry(this.project
				.getFullPath()) };
	}

	/**
	 * Answers an ID which is used to distinguish project/entries during project
	 * fragment root computations
	 * 
	 * @return String
	 */
	public String rootID() {
		return "[PRJ]" + this.project.getFullPath(); //$NON-NLS-1$
	}

	protected Object createElementInfo() {
		return new ProjectElementInfo();
	}

	public IResource getResource() {
		return project;
	}

	public IPath getPath() {
		return project.getFullPath();
	}

	/**
	 * Returns a canonicalized path from the given external path. Note that the
	 * return path contains the same number of segments and it contains a device
	 * only if the given path contained one.
	 * 
	 * @param externalPath
	 *            IPath
	 * @see java.io.File for the definition of a canonicalized path
	 * @return IPath
	 */
	public static IPath canonicalizedPath(IPath externalPath) {
		if (externalPath == null)
			return null;
		if (IS_CASE_SENSITIVE) {
			return externalPath;
		}
		// if not external path, return original path
		IWorkspace workspace = ResourcesPlugin.getWorkspace();
		if (workspace == null)
			return externalPath; // protection during shutdown (30487)
		if (workspace.getRoot().findMember(externalPath) != null) {
			return externalPath;
		}
		// TODO Check this
		return externalPath;
		//		
		// IPath canonicalPath = null;
		// try {
		// canonicalPath = new Path(new File(externalPath.toOSString())
		// .getCanonicalPath());
		// } catch (IOException e) {
		// // default to original path
		// return externalPath;
		// }
		// IPath result;
		// int canonicalLength = canonicalPath.segmentCount();
		// if (canonicalLength == 0) {
		// // the java.io.File canonicalization failed
		// return externalPath;
		// } else if (externalPath.isAbsolute()) {
		// result = canonicalPath;
		// } else {
		// // if path is relative, remove the first segments that were added by
		// // the java.io.File canonicalization
		// // e.g. 'lib/classes.zip' was converted to
		// // 'd:/myfolder/lib/classes.zip'
		// int externalLength = externalPath.segmentCount();
		// if (canonicalLength >= externalLength) {
		// result = canonicalPath.removeFirstSegments(canonicalLength
		// - externalLength);
		// } else {
		// return externalPath;
		// }
		// }
		// // keep device only if it was specified (this is because
		// // File.getCanonicalPath() converts '/lib/classed.zip' to
		// // 'd:/lib/classes/zip')
		// if (externalPath.getDevice() == null) {
		// result = result.setDevice(null);
		// }
		// return result;
	}

	/**
	 * Convenience method that returns the specific type of info for a project.
	 */
	protected ProjectElementInfo getProjectElementInfo() throws ModelException {
		return (ProjectElementInfo) getElementInfo();
	}

	/*
	 * Resets this project's caches
	 */
	public void resetCaches() {
		ProjectElementInfo info = (ProjectElementInfo) ModelManager
				.getModelManager().peekAtInfo(this);
		if (info != null) {
			info.resetCaches();
		}
	}

	/**
	 * Reset the collection of project fragments (local ones) - only if opened.
	 */
	public void updateProjectFragments() {
		if (this.isOpen()) {
			try {
				ProjectElementInfo info = getProjectElementInfo();
				computeChildren(info);
				info.resetCaches(); // discard caches (hold onto roots and pkg
				// fragments)
			} catch (ModelException e) {
				try {
					close(); // could not do better
				} catch (ModelException ex) {
					// ignore
				}
			}
		}
	}

	/**
	 * Computes the collection of project fragments (local ones) and set it on
	 * the given info. Need to check *all* project fragments in order to reset
	 * NameLookup
	 * 
	 * @param info
	 *            ProjectElementInfo
	 * @throws ModelException
	 */
	public void computeChildren(ProjectElementInfo info) throws ModelException {
		IBuildpathEntry[] buildpath = getResolvedBuildpath();
		ProjectElementInfo.ProjectCache projectCache = info.projectCache;
		if (projectCache != null) {
			IProjectFragment[] newRoots = computeProjectFragments(buildpath,
					true, null /*
			 * no reverse map
			 */);
			checkIdentical: { // compare all pkg fragment root lists
				IProjectFragment[] oldRoots = projectCache.allProjectFragmentCache;
				if (oldRoots.length == newRoots.length) {
					for (int i = 0, length = oldRoots.length; i < length; i++) {
						if (!oldRoots[i].equals(newRoots[i])) {
							break checkIdentical;
						}
					}
					return; // no need to update
				}
			}
		}
		info.setForeignResources(null);
		info.setChildren(computeProjectFragments(buildpath, false, null /*
		 * no
		 * reverse
		 * map
		 */));
	}

	public IBuildpathEntry[] getRawBuildpath() throws ModelException {
		// Do not create marker but log problems while getting raw buildpath
		return getRawBuildpath(false, true);
	}

	/*
	 * Internal variant allowing to parameterize problem creation/logging
	 */
	public IBuildpathEntry[] getRawBuildpath(boolean createMarkers,
			boolean logProblems) throws ModelException {
		ModelManager.PerProjectInfo perProjectInfo = null;
		IBuildpathEntry[] buildpath;
		if (createMarkers) {
			flushBuildpathProblemMarkers(false/* cycle */, true/* format */);
			buildpath = readBuildpathFile(createMarkers, logProblems);
		} else {
			perProjectInfo = getPerProjectInfo();
			buildpath = perProjectInfo.rawBuildpath;
			if (buildpath != null)
				return buildpath;
			buildpath = readBuildpathFile(createMarkers, logProblems);
		}
		if (buildpath == null) {
			return defaultBuildpath();
		}
		if (!createMarkers) {
			perProjectInfo.rawBuildpath = buildpath;
		}
		return buildpath;
	}

	/**
	 * Reads the .buildpath file from disk and returns the list of entries it
	 * contains (including output location entry) Returns null if .classfile is
	 * not present. Returns INVALID_CLASSPATH if it has a format problem.
	 */
	protected IBuildpathEntry[] readBuildpathFile(boolean createMarker,
			boolean logProblems) {
		return readBuildpathFile(createMarker, logProblems, null/*
		 * not
		 * interested in
		 * unknown
		 * elements
		 */);
	}

	protected IBuildpathEntry[] readBuildpathFile(boolean createMarker,
			boolean logProblems, Map unknownElements) {
		try {
			String xmlBuildpath = getSharedProperty(BUILDPATH_FILENAME);
			if (xmlBuildpath == null) {
				if (createMarker && this.project.isAccessible()) {
					createBuildpathProblemMarker(new ModelStatus(
							IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT,
							Messages.bind(
									Messages.buildpath_cannotReadBuildpathFile,
									this.getElementName())));
				}
				return null;
			}
			return decodeBuildpath(xmlBuildpath, createMarker, logProblems,
					unknownElements);
		} catch (CoreException e) {
			// file does not exist (or not accessible)
			if (createMarker && this.project.isAccessible()) {
				createBuildpathProblemMarker(new ModelStatus(
						IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT,
						Messages.bind(
								Messages.buildpath_cannotReadBuildpathFile,
								this.getElementName())));
			}
			if (logProblems) {
				Util.log(e, "Exception while retrieving " + this.getPath() //$NON-NLS-1$
						+ "/.buildpath, will revert to default buildpath"); //$NON-NLS-1$
			}
		}
		return null;
	}

	/**
	 * Record a new marker denoting a buildpath problem
	 */
	void createBuildpathProblemMarker(IModelStatus status) {
		IMarker marker = null;
		int severity;
		String[] arguments = new String[0];
		boolean isCycleProblem = false, isBuildpathFileFormatProblem = false;
		switch (status.getCode()) {
		case IModelStatusConstants.BUILDPATH_CYCLE:
			isCycleProblem = true;
			if (DLTKCore.ERROR.equals(getOption(
					DLTKCore.CORE_CIRCULAR_BUILDPATH, true))) {
				severity = IMarker.SEVERITY_ERROR;
			} else {
				severity = IMarker.SEVERITY_WARNING;
			}
			break;
		case IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT:
			isBuildpathFileFormatProblem = true;
			severity = IMarker.SEVERITY_ERROR;
			break;
		default:
			IPath path = status.getPath();
			if (path != null) {
				arguments = new String[] { path.toString() };
			}
			if (DLTKCore.ERROR.equals(getOption(
					DLTKCore.CORE_INCOMPLETE_BUILDPATH, true))) {
				severity = IMarker.SEVERITY_ERROR;
			} else {
				severity = IMarker.SEVERITY_WARNING;
			}
			break;
		}
		try {
			marker = this.project
					.createMarker(IModelMarker.BUILDPATH_PROBLEM_MARKER);
			marker.setAttributes(new String[] { IMarker.MESSAGE,
					IMarker.SEVERITY, IMarker.LOCATION,
					IModelMarker.CYCLE_DETECTED,
					IModelMarker.BUILDPATH_FILE_FORMAT, IModelMarker.ID,
					IModelMarker.ARGUMENTS, }, new Object[] {
					status.getMessage(),
					new Integer(severity),
					Messages.buildpath_buildPath,
					isCycleProblem ? "true" : "false",//$NON-NLS-1$ //$NON-NLS-2$
					isBuildpathFileFormatProblem ? "true" : "false",//$NON-NLS-1$ //$NON-NLS-2$
					new Integer(status.getCode()),
					Util.getProblemArgumentsForMarker(arguments), });
		} catch (CoreException e) {
			// could not create marker: cannot do much
			if (ModelManager.VERBOSE) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Remove all markers denoting buildpath problems
	 */
	protected void flushBuildpathProblemMarkers(boolean flushCycleMarkers,
			boolean flushBuildpathFormatMarkers) {
		try {
			if (this.project.isAccessible()) {
				IMarker[] markers = this.project.findMarkers(
						IModelMarker.BUILDPATH_PROBLEM_MARKER, false,
						IResource.DEPTH_ZERO);
				for (int i = 0, length = markers.length; i < length; i++) {
					IMarker marker = markers[i];
					if (flushCycleMarkers && flushBuildpathFormatMarkers) {
						marker.delete();
					} else {
						String cycleAttr = (String) marker
								.getAttribute(IModelMarker.CYCLE_DETECTED);
						String buildpathFileFormatAttr = (String) marker
								.getAttribute(IModelMarker.BUILDPATH_FILE_FORMAT);
						if ((flushCycleMarkers == (cycleAttr != null && cycleAttr
								.equals("true"))) //$NON-NLS-1$
								&& (flushBuildpathFormatMarkers == (buildpathFileFormatAttr != null && buildpathFileFormatAttr
										.equals("true")))) { //$NON-NLS-1$
							marker.delete();
						}
					}
				}
			}
		} catch (CoreException e) {
			// could not flush markers: not much we can do
			if (ModelManager.VERBOSE) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * Reads and decode an XML buildpath string
	 */
	protected IBuildpathEntry[] decodeBuildpath(String xmlBuildpath,
			boolean createMarker, boolean logProblems) {
		return decodeBuildpath(xmlBuildpath, createMarker, logProblems, null/*
		 * not
		 * interested
		 * in
		 * unknown
		 * elements
		 */);
	}

	/**
	 * Reads and decode an XML buildpath string
	 */
	protected IBuildpathEntry[] decodeBuildpath(String xmlBuildpath,
			boolean createMarker, boolean logProblems, Map unknownElements) {
		ArrayList paths = new ArrayList();
		try {
			if (xmlBuildpath == null)
				return null;
			StringReader reader = new StringReader(xmlBuildpath);
			Element cpElement;
			try {
				DocumentBuilder parser = DocumentBuilderFactory.newInstance()
						.newDocumentBuilder();
				cpElement = parser.parse(new InputSource(reader))
						.getDocumentElement();
			} catch (SAXException e) {
				throw new IOException(Messages.file_badFormat);
			} catch (ParserConfigurationException e) {
				throw new IOException(Messages.file_badFormat);
			} finally {
				reader.close();
			}
			if (!cpElement.getNodeName().equalsIgnoreCase("buildpath")) { //$NON-NLS-1$
				throw new IOException(Messages.file_badFormat);
			}
			NodeList list = cpElement.getElementsByTagName("buildpathentry"); //$NON-NLS-1$
			int length = list.getLength();
			for (int i = 0; i < length; ++i) {
				Node node = list.item(i);
				if (node.getNodeType() == Node.ELEMENT_NODE) {
					IBuildpathEntry entry = BuildpathEntry.elementDecode(
							(Element) node, this, unknownElements);
					if (entry != null)
						paths.add(entry);
				}
			}
		} catch (IOException e) {
			// bad format
			if (createMarker && this.project.isAccessible()) {
				this.createBuildpathProblemMarker(new ModelStatus(
						IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT,
						Messages.bind(Messages.buildpath_xmlFormatError,
								new String[] { this.getElementName(),
										e.getMessage() })));
			}
			if (logProblems) {
				Util.log(e, "Exception while retrieving " + this.getPath() //$NON-NLS-1$
						+ "/.buildpath, will mark buildpath as invalid"); //$NON-NLS-1$
			}
			return INVALID_BUILDPATH;
		} catch (AssertionFailedException e) {
			// failed creating CP entries from file
			if (createMarker && this.project.isAccessible()) {
				this.createBuildpathProblemMarker(new ModelStatus(
						IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT,
						Messages.bind(
								Messages.buildpath_illegalEntryInBuildpathFile,
								new String[] { this.getElementName(),
										e.getMessage() })));
			}
			if (logProblems) {
				Util.log(e, "Exception while retrieving " + this.getPath() //$NON-NLS-1$
						+ "/.buildpath, will mark buildpath as invalid"); //$NON-NLS-1$
			}
			return INVALID_BUILDPATH;
		}
		IBuildpathEntry[] entries = new IBuildpathEntry[paths.size()];
		paths.toArray(entries);
		return entries;
	}

	/*
	 * Reads and decode an XML buildpath string
	 */
	public IBuildpathEntry[] decodeBuildpath(String xmlBuildpath,
			Map unknownElements) throws IOException, AssertionFailedException {
		ArrayList paths = new ArrayList();
		StringReader reader = new StringReader(xmlBuildpath);
		Element cpElement;
		try {
			DocumentBuilder parser = DocumentBuilderFactory.newInstance()
					.newDocumentBuilder();
			cpElement = parser.parse(new InputSource(reader))
					.getDocumentElement();
		} catch (SAXException e) {
			throw new IOException(Messages.file_badFormat);
		} catch (ParserConfigurationException e) {
			throw new IOException(Messages.file_badFormat);
		} finally {
			reader.close();
		}
		if (!cpElement.getNodeName().equalsIgnoreCase("buildpath")) { //$NON-NLS-1$
			throw new IOException(Messages.file_badFormat);
		}
		NodeList list = cpElement.getElementsByTagName("buildpathentry"); //$NON-NLS-1$
		int length = list.getLength();
		for (int i = 0; i < length; ++i) {
			Node node = list.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				IBuildpathEntry entry = BuildpathEntry.elementDecode(
						(Element) node, this, unknownElements);
				if (entry != null) {
					paths.add(entry);
				}
			}
		}
		// return a new empty buildpath is it size is 0, to differenciate from
		// an INVALID_BUILDPATH
		int pathSize = paths.size();
		IBuildpathEntry[] entries = new IBuildpathEntry[pathSize];
		paths.toArray(entries);
		return entries;
	}

	public IBuildpathEntry decodeBuildpathEntry(String encodedEntry) {
		try {
			if (encodedEntry == null)
				return null;
			StringReader reader = new StringReader(encodedEntry);
			Element node;
			try {
				DocumentBuilder parser = DocumentBuilderFactory.newInstance()
						.newDocumentBuilder();
				node = parser.parse(new InputSource(reader))
						.getDocumentElement();
			} catch (SAXException e) {
				return null;
			} catch (ParserConfigurationException e) {
				return null;
			} finally {
				reader.close();
			}
			if (!node.getNodeName().equalsIgnoreCase("buildpathentry") //$NON-NLS-1$
					|| node.getNodeType() != Node.ELEMENT_NODE) {
				return null;
			}
			return BuildpathEntry.elementDecode(node, this, null/*
			 * not
			 * interested in
			 * unknown
			 * elements
			 */);
		} catch (IOException e) {
			// bad format
			return null;
		}
	}

	/**
	 * Retrieve a shared property on a project. If the property is not defined,
	 * answers null. Note that it is orthogonal to IResource persistent
	 * properties, and client code has to decide which form of storage to use
	 * appropriately. Shared properties produce real resource files which can be
	 * shared through a VCM onto a server. Persistent properties are not
	 * shareable.
	 * 
	 * @param key
	 *            String
	 * @see IScriptProject#setSharedProperty(String, String)
	 * @return String
	 * @throws CoreException
	 */
	public String getSharedProperty(String key) throws CoreException {
		String property = null;
		IFile rscFile = this.project.getFile(key);
		if (rscFile.exists()) {
			byte[] bytes = Util.getResourceContentsAsByteArray(rscFile);
			try {
				property = new String(bytes,
						org.eclipse.dltk.compiler.util.Util.UTF_8); // .buildpath
				// always
				// encoded
				// with
				// UTF-8
			} catch (UnsupportedEncodingException e) {
				Util.log(e, "Could not read .buildpath with UTF-8 encoding"); //$NON-NLS-1$
				// fallback to default
				property = new String(bytes);
			}
		} else {
			// when a project is imported, we get a first delta for the addition
			// of the .project, but the .buildpath is not accessible
			// so default to using java.io.File
			// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96258
			URI location = rscFile.getLocationURI();
			if (location != null) {
				File file = Util.toLocalFile(location, null/*
				 * no progress
				 * monitor available
				 */);
				if (file != null && file.exists()) {
					byte[] bytes;
					try {
						bytes = org.eclipse.dltk.compiler.util.Util
								.getFileByteContent(file);
					} catch (IOException e) {
						return null;
					}
					try {
						property = new String(bytes,
								org.eclipse.dltk.compiler.util.Util.UTF_8); // .buildpath
						// always
						// encoded
						// with
						// UTF-8
					} catch (UnsupportedEncodingException e) {
						Util
								.log(e,
										"Could not read .buildpath with UTF-8 encoding"); //$NON-NLS-1$
						// fallback to default
						property = new String(bytes);
					}
				}
			}
		}
		return property;
	}

	public IProjectFragment[] getProjectFragments() throws ModelException {
		Object[] children;
		int length;
		IProjectFragment[] roots;
		System.arraycopy(children = getChildren(), 0,
				roots = new IProjectFragment[length = children.length], 0,
				length);
		return roots;
	}

	/**
	 * Saves the buildpath in a shareable format (VCM-wise) only when necessary,
	 * that is, if it is semantically different from the existing one in file.
	 * Will never write an identical one.
	 * 
	 * @param newBuildpath
	 *            IBuildpathEntry[]
	 * @param newOutputLocation
	 *            IPath
	 * @return boolean Return whether the .buildpath file was modified.
	 * @throws ModelException
	 */
	public boolean saveBuildpath(IBuildpathEntry[] newBuildpath)
			throws ModelException {
		if (!this.project.isAccessible())
			return false;
		Map unknownElements = new HashMap();
		IBuildpathEntry[] fileEntries = readBuildpathFile(false /*
		 * don't create
		 * markers
		 */, false/*
		 * don't
		 * log
		 * problems
		 */, unknownElements);
		if (fileEntries != null
				&& isBuildpathEqualsTo(newBuildpath, fileEntries)) {
			// no need to save it, it is the same
			return false;
		}
		// actual file saving
		try {
			setSharedProperty(ScriptProject.BUILDPATH_FILENAME,
					encodeBuildpath(newBuildpath, true, unknownElements));
			return true;
		} catch (CoreException e) {
			throw new ModelException(e);
		} catch (NullPointerException e) {
			e.printStackTrace();
			throw e;
		}
	}

	/**
	 * Record a shared persistent property onto a project. Note that it is
	 * orthogonal to IResource persistent properties, and client code has to
	 * decide which form of storage to use appropriately. Shared properties
	 * produce real resource files which can be shared through a VCM onto a
	 * server. Persistent properties are not shareable.
	 * 
	 * shared properties end up in resource files, and thus cannot be modified
	 * during delta notifications (a CoreException would then be thrown).
	 * 
	 * @param key
	 *            String
	 * @param value
	 *            String
	 * @see IScriptProject#getSharedProperty(String key)
	 * @throws CoreException
	 */
	public void setSharedProperty(String key, String value)
			throws CoreException {
		IFile rscFile = this.project.getFile(key);
		byte[] bytes = null;
		try {
			bytes = value.getBytes(org.eclipse.dltk.compiler.util.Util.UTF_8); // .buildpath
			// always
			// encoded
			// with
			// UTF-8
		} catch (UnsupportedEncodingException e) {
			Util.log(e, "Could not write .buildpath with UTF-8 encoding "); //$NON-NLS-1$
			// fallback to default
			bytes = value.getBytes();
		}
		InputStream inputStream = new ByteArrayInputStream(bytes);
		// update the resource content
		if (rscFile.exists()) {
			if (rscFile.isReadOnly()) {
				// provide opportunity to checkout read-only .buildpath file
				// (23984)
				ResourcesPlugin.getWorkspace().validateEdit(
						new IFile[] { rscFile }, null);
			}
			rscFile.setContents(inputStream, IResource.FORCE, null);
		} else {
			rscFile.create(inputStream, IResource.FORCE, null);
		}
	}

	/**
	 * Compare current buildpath with given one to see if any different. Note
	 * that the argument buildpath contains its binary output.
	 * 
	 * @param newBuildpath
	 *            IBuildpathEntry[]
	 * @param newOutputLocation
	 *            IPath
	 * @param otherBuildpathWithOutput
	 *            IBuildpathEntry[]
	 * @return boolean
	 */
	public boolean isBuildpathEqualsTo(IBuildpathEntry[] newBuildpath,
			IBuildpathEntry[] otherBuildpath) {
		if (otherBuildpath == null || otherBuildpath.length == 0)
			return false;
		int length = otherBuildpath.length;
		if (length != newBuildpath.length)
			// output is amongst file entries (last one)
			return false;
		// compare buildpath entries
		for (int i = 0; i < length; i++) {
			if (!otherBuildpath[i].equals(newBuildpath[i]))
				return false;
		}
		return true;
	}

	/**
	 * Returns the XML String encoding of the buildpath.
	 */
	protected String encodeBuildpath(IBuildpathEntry[] buildpath,
			boolean indent, Map unknownElements) throws ModelException {
		try {
			ByteArrayOutputStream s = new ByteArrayOutputStream();
			OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$
			XMLWriter xmlWriter = new XMLWriter(writer, this, true/*
			 * print XML
			 * version
			 */);
			xmlWriter.startTag(BuildpathEntry.TAG_BUILDPATH, indent);
			for (int i = 0; i < buildpath.length; ++i) {
				((BuildpathEntry) buildpath[i]).elementEncode(xmlWriter,
						this.project.getFullPath(), indent, true,
						unknownElements);
			}
			xmlWriter.endTag(BuildpathEntry.TAG_BUILDPATH, indent, true/*
			 * insert
			 * new
			 * line
			 */);
			writer.flush();
			writer.close();
			return s.toString("UTF8");//$NON-NLS-1$
		} catch (IOException e) {
			throw new ModelException(e, IModelStatusConstants.IO_EXCEPTION);
		}
	}

	public String encodeBuildpathEntry(IBuildpathEntry buildpathEntry) {
		try {
			ByteArrayOutputStream s = new ByteArrayOutputStream();
			OutputStreamWriter writer = new OutputStreamWriter(s, "UTF8"); //$NON-NLS-1$
			XMLWriter xmlWriter = new XMLWriter(writer, this, false/*
			 * don't
			 * print XML
			 * version
			 */);
			((BuildpathEntry) buildpathEntry).elementEncode(xmlWriter,
					this.project.getFullPath(), true/* indent */, true/*
					 * insert
					 * new
					 * line
					 */, null/*
			 * not
			 * interested
			 * in
			 * unknown
			 * elements
			 */);
			writer.flush();
			writer.close();
			return s.toString("UTF8");//$NON-NLS-1$
		} catch (IOException e) {
			return null; // never happens since all is done in memory
		}
	}

	/**
	 * @see IScriptProject
	 */
	public boolean hasBuildpathCycle(IBuildpathEntry[] preferredBuildpath) {
		HashSet cycleParticipants = new HashSet();
		HashMap preferredBuildpaths = new HashMap(1);
		preferredBuildpaths.put(this, preferredBuildpath);
		updateCycleParticipants(new ArrayList(2), cycleParticipants,
				ResourcesPlugin.getWorkspace().getRoot(), new HashSet(2),
				preferredBuildpaths);
		return !cycleParticipants.isEmpty();
	}

	public boolean hasCycleMarker() {
		return this.getCycleMarker() != null;
	}

	public int hashCode() {
		return this.project.hashCode();
	}

	/*
	 * Returns the cycle marker associated with this project or null if none.
	 */
	public IMarker getCycleMarker() {
		try {
			if (this.project.isAccessible()) {
				IMarker[] markers = this.project.findMarkers(
						IModelMarker.BUILDPATH_PROBLEM_MARKER, false,
						IResource.DEPTH_ZERO);
				for (int i = 0, length = markers.length; i < length; i++) {
					IMarker marker = markers[i];
					String cycleAttr = (String) marker
							.getAttribute(IModelMarker.CYCLE_DETECTED);
					if (cycleAttr != null && cycleAttr.equals("true")) { //$NON-NLS-1$
						return marker;
					}
				}
			}
		} catch (CoreException e) {
			// could not get markers: return null
		}
		return null;
	}

	/**
	 * Update cycle markers for all projects
	 * 
	 * @param preferredBuildpaths
	 *            Map
	 * @throws ModelException
	 */
	public static void updateAllCycleMarkers(Map preferredBuildpaths)
			throws ModelException {
		// long start = System.currentTimeMillis();
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		IProject[] rscProjects = workspaceRoot.getProjects();
		int length = rscProjects.length;
		ScriptProject[] projects = new ScriptProject[length];
		HashSet cycleParticipants = new HashSet();
		HashSet traversed = new HashSet();
		// compute cycle participants
		ArrayList prereqChain = new ArrayList();
		for (int i = 0; i < length; i++) {
			ScriptProject project = (projects[i] = (ScriptProject) DLTKCore
					.create(rscProjects[i]));
			if (!traversed.contains(project.getPath())) {
				prereqChain.clear();
				project.updateCycleParticipants(prereqChain, cycleParticipants,
						workspaceRoot, traversed, preferredBuildpaths);
			}
		}
		for (int i = 0; i < length; i++) {
			ScriptProject project = projects[i];
			if (project != null) {
				if (cycleParticipants.contains(project.getPath())) {
					IMarker cycleMarker = project.getCycleMarker();
					String circularCPOption = project.getOption(
							DLTKCore.CORE_CIRCULAR_BUILDPATH, true);
					int circularCPSeverity = DLTKCore.ERROR
							.equals(circularCPOption) ? IMarker.SEVERITY_ERROR
							: IMarker.SEVERITY_WARNING;
					if (cycleMarker != null) {
						// update existing cycle marker if needed
						try {
							int existingSeverity = ((Integer) cycleMarker
									.getAttribute(IMarker.SEVERITY)).intValue();
							if (existingSeverity != circularCPSeverity) {
								cycleMarker.setAttribute(IMarker.SEVERITY,
										circularCPSeverity);
							}
						} catch (CoreException e) {
							throw new ModelException(e);
						}
					} else {
						// create new marker
						project
								.createBuildpathProblemMarker(new ModelStatus(
										IModelStatusConstants.BUILDPATH_CYCLE,
										project));
					}
				} else {
					project.flushBuildpathProblemMarkers(true, false);
				}
			}
		}
	}

	public String getOption(String optionName, boolean inheritCoreOptions) {
		String propertyName = optionName;
		if (ModelManager.getModelManager().optionNames.contains(propertyName)) {
			IEclipsePreferences projectPreferences = getEclipsePreferences();
			String javaCoreDefault = inheritCoreOptions ? DLTKCore
					.getOption(propertyName) : null;
			if (projectPreferences == null)
				return javaCoreDefault;
			String value = projectPreferences
					.get(propertyName, javaCoreDefault);
			return value == null ? null : value.trim();
		}
		return null;
	}

	public Map getOptions(boolean inheritCoreOptions) {
		// initialize to the defaults from DLTKCore options pool
		Map options = inheritCoreOptions ? DLTKCore.getOptions()
				: new Hashtable(5);
		// Get project specific options
		ModelManager.PerProjectInfo perProjectInfo = null;
		Hashtable projectOptions = null;
		HashSet optionNames = ModelManager.getModelManager().optionNames;
		try {
			perProjectInfo = getPerProjectInfo();
			projectOptions = perProjectInfo.options;
			if (projectOptions == null) {
				// get eclipse preferences
				IEclipsePreferences projectPreferences = getEclipsePreferences();
				if (projectPreferences == null)
					return options; // cannot do better (non-script project)
				// create project options
				String[] propertyNames = projectPreferences.keys();
				projectOptions = new Hashtable(propertyNames.length);
				for (int i = 0; i < propertyNames.length; i++) {
					String propertyName = propertyNames[i];
					String value = projectPreferences.get(propertyName, null);
					if (value != null && optionNames.contains(propertyName)) {
						projectOptions.put(propertyName, value.trim());
					}
				}
				// cache project options
				perProjectInfo.options = projectOptions;
			}
		} catch (ModelException jme) {
			projectOptions = new Hashtable();
		} catch (BackingStoreException e) {
			projectOptions = new Hashtable();
		}
		// Inherit from DLTKCore options if specified
		if (inheritCoreOptions) {
			Iterator propertyNames = projectOptions.keySet().iterator();
			while (propertyNames.hasNext()) {
				String propertyName = (String) propertyNames.next();
				String propertyValue = (String) projectOptions
						.get(propertyName);
				if (propertyValue != null && optionNames.contains(propertyName)) {
					options.put(propertyName, propertyValue.trim());
				}
			}
			return options;
		}
		return projectOptions;
	}

	public void setOption(String optionName, String optionValue) {
		if (!ModelManager.getModelManager().optionNames.contains(optionName))
			return; // unrecognized option
		if (optionValue == null)
			return; // invalid value
		IEclipsePreferences projectPreferences = getEclipsePreferences();
		String defaultValue = DLTKCore.getOption(optionName);
		if (optionValue.equals(defaultValue)) {
			// set default value => remove preference
			projectPreferences.remove(optionName);
		} else {
			projectPreferences.put(optionName, optionValue);
		}
		// Dump changes
		try {
			projectPreferences.flush();
		} catch (BackingStoreException e) {
			// problem with pref store - quietly ignore
		}
	}

	public void setOptions(Map newOptions) {
		IEclipsePreferences projectPreferences = getEclipsePreferences();
		try {
			if (newOptions == null) {
				projectPreferences.clear();
			} else {
				Iterator keys = newOptions.keySet().iterator();
				while (keys.hasNext()) {
					String key = (String) keys.next();
					if (!ModelManager.getModelManager().optionNames
							.contains(key))
						continue; // unrecognized option
					// no filtering for encoding (custom encoding for project is
					// allowed)
					String value = (String) newOptions.get(key);
					projectPreferences.put(key, value);
				}
				// reset to default all options not in new map
				// @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=26255
				// @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=49691
				String[] pNames = projectPreferences.keys();
				int ln = pNames.length;
				for (int i = 0; i < ln; i++) {
					String key = pNames[i];
					if (!newOptions.containsKey(key)) {
						projectPreferences.remove(key); // old preferences =>
						// remove from
						// preferences table
					}
				}
			}
			// persist options
			projectPreferences.flush();
			// flush cache immediately
			try {
				getPerProjectInfo().options = null;
			} catch (ModelException e) {
				// do nothing
			}
		} catch (BackingStoreException e) {
			// problem with pref store - quietly ignore
		}
	}

	/**
	 * @see IScriptProject#setRawBuildpath(IBuildpathEntry[],IPath,boolean,IProgressMonitor)
	 */
	public void setRawBuildpath(IBuildpathEntry[] entries,
			boolean canModifyResources, IProgressMonitor monitor)
			throws ModelException {

		setRawBuildpath(entries, monitor, canModifyResources,
				getResolvedBuildpath(true/* ignoreUnresolvedEntry */,
						false/* don't generateMarkerOnError */, false/*
				 * don't
				 * returnResolutionInProgress
				 */), true, // needValidation
				canModifyResources); // save only if modifying resources is
		// allowed
	}

	/**
	 * @see IScriptProject#setRawClasspath(IClasspathEntry[],IPath,IProgressMonitor)
	 */
	public void setRawBuildpath(IBuildpathEntry[] entries,
			IProgressMonitor monitor) throws ModelException {

		setRawBuildpath(entries, monitor, true, // canChangeResource (as per API
				// contract)
				getResolvedBuildpath(true/* ignoreUnresolvedEntry */,
						false/* don't generateMarkerOnError */, false/*
				 * don't
				 * returnResolutionInProgress
				 */), true, // needValidation
				true); // need to save
	}

	public void setRawBuildpath(IBuildpathEntry[] newEntries,
			IProgressMonitor monitor, boolean canChangeResource,
			IBuildpathEntry[] oldResolvedPath, boolean needValidation,
			boolean needSave) throws ModelException {

		ModelManager manager = ModelManager.getModelManager();
		try {
			IBuildpathEntry[] newRawPath = newEntries;
			if (newRawPath == null) { // are we already with the default
				// buildpath
				newRawPath = defaultBuildpath();
			}
			SetBuildpathOperation op = new SetBuildpathOperation(this,
					oldResolvedPath, newRawPath, canChangeResource,
					needValidation, needSave);
			op.runOperation(monitor);

		} catch (ModelException e) {
			manager.getDeltaProcessor().flush();
			throw e;
		}
	}

	/**
	 * Returns the project custom preference pool. Project preferences may
	 * include custom encoding.
	 * 
	 * @return IEclipsePreferences
	 */
	public IEclipsePreferences getEclipsePreferences() {
		// Get cached preferences if exist
		ModelManager.PerProjectInfo perProjectInfo = ModelManager
				.getModelManager().getPerProjectInfo(this.project, true);
		if (perProjectInfo.preferences != null)
			return perProjectInfo.preferences;
		// Init project preferences
		IScopeContext context = new ProjectScope(getProject());
		final IEclipsePreferences eclipsePreferences = context
				.getNode(DLTKCore.PLUGIN_ID);
		perProjectInfo.preferences = eclipsePreferences;
		// Listen to node removal from parent in order to reset cache (see bug
		// 68993)
		IEclipsePreferences.INodeChangeListener nodeListener = new IEclipsePreferences.INodeChangeListener() {
			public void added(IEclipsePreferences.NodeChangeEvent event) {
				// do nothing
			}

			public void removed(IEclipsePreferences.NodeChangeEvent event) {
				if (event.getChild() == eclipsePreferences) {
					ModelManager.getModelManager().resetProjectPreferences(
							ScriptProject.this);
				}
			}
		};
		((IEclipsePreferences) eclipsePreferences.parent())
				.addNodeChangeListener(nodeListener);
		// Listen to preference changes
		IEclipsePreferences.IPreferenceChangeListener preferenceListener = new IEclipsePreferences.IPreferenceChangeListener() {
			public void preferenceChange(
					IEclipsePreferences.PreferenceChangeEvent event) {
				ModelManager.getModelManager().resetProjectOptions(
						ScriptProject.this);
			}
		};
		eclipsePreferences.addPreferenceChangeListener(preferenceListener);
		return eclipsePreferences;
	}

	/*
	 * Update .buildpath format markers.
	 */
	public void updateBuildpathMarkers(Map preferredBuildpaths) {

		this.flushBuildpathProblemMarkers(false/* cycle */, true/* format */);
		this
				.flushBuildpathProblemMarkers(false/* cycle */, false/* format */);

		IBuildpathEntry[] buildpath = this.readBuildpathFile(true/* marker */,
				false/* log */);
		// remember invalid path so as to avoid reupdating it again later on
		if (preferredBuildpaths != null) {
			preferredBuildpaths.put(this, buildpath == null ? INVALID_BUILDPATH
					: buildpath);
		}

		// force buildpath marker refresh
		if (buildpath != null) {
			for (int i = 0; i < buildpath.length; i++) {
				IModelStatus status = BuildpathEntry.validateBuildpathEntry(
						this, buildpath[i], true /* recurse in container */);
				if (!status.isOK()) {
					if (status.getCode() == IModelStatusConstants.INVALID_BUILDPATH
							&& ((BuildpathEntry) buildpath[i]).isOptional())
						continue; // ignore this entry
					this.createBuildpathProblemMarker(status);
				}
			}
			IModelStatus status = BuildpathEntry.validateBuildpath(this,
					buildpath);
			if (!status.isOK())
				this.createBuildpathProblemMarker(status);
		}
	}

	/**
	 * If a cycle is detected, then cycleParticipants contains all the paths of
	 * projects involved in this cycle (directly and indirectly), no cycle if
	 * the set is empty (and started empty)
	 * 
	 * @param prereqChain
	 *            ArrayList
	 * @param cycleParticipants
	 *            HashSet
	 * @param workspaceRoot
	 *            IWorkspaceRoot
	 * @param traversed
	 *            HashSet
	 * @param preferredBuildpaths
	 *            Map
	 */
	public void updateCycleParticipants(ArrayList prereqChain,
			HashSet cycleParticipants, IWorkspaceRoot workspaceRoot,
			HashSet traversed, Map preferredBuildpaths) {
		IPath path = this.getPath();
		prereqChain.add(path);
		traversed.add(path);
		try {
			IBuildpathEntry[] buildpath = null;
			if (preferredBuildpaths != null)
				buildpath = (IBuildpathEntry[]) preferredBuildpaths.get(this);
			if (buildpath == null)
				buildpath = getResolvedBuildpath();
			for (int i = 0, length = buildpath.length; i < length; i++) {
				IBuildpathEntry entry = buildpath[i];
				if (entry.getEntryKind() == IBuildpathEntry.BPE_PROJECT) {
					IPath prereqProjectPath = entry.getPath();
					int index = cycleParticipants.contains(prereqProjectPath) ? 0
							: prereqChain.indexOf(prereqProjectPath);
					if (index >= 0) { // refer to cycle, or in cycle itself
						for (int size = prereqChain.size(); index < size; index++) {
							cycleParticipants.add(prereqChain.get(index));
						}
					} else {
						if (!traversed.contains(prereqProjectPath)) {
							IResource member = workspaceRoot
									.findMember(prereqProjectPath);
							if (member != null
									&& member.getType() == IResource.PROJECT) {
								ScriptProject scriptProject = (ScriptProject) DLTKCore
										.create((IProject) member);
								scriptProject.updateCycleParticipants(
										prereqChain, cycleParticipants,
										workspaceRoot, traversed,
										preferredBuildpaths);
							}
						}
					}
				}
			}
		} catch (ModelException e) {
			// project doesn't exist: ignore
		}
		prereqChain.remove(path);
	}

	/**
	 * Returns true if this handle represents the same project as the given
	 * handle. Two handles represent the same project if they are identical or
	 * if they represent a project with the same underlying resource and
	 * occurrence counts.
	 * 
	 * @see ModelElement#equals(Object)
	 */
	public boolean equals(Object o) {
		if (this == o)
			return true;
		if (!(o instanceof ScriptProject))
			return false;
		ScriptProject other = (ScriptProject) o;
		return this.project.equals(other.getProject());
	}

	public boolean exists() {
		return project.exists() && DLTKLanguageManager.hasScriptNature(project);
	}

	public String getElementName() {
		return project.getName();
	}

	/*
	 * Returns whether the given resource is accessible through the children or
	 * the non-script resources of this project. Returns true if the resource is
	 * not in the project. Assumes that the resource is a folder or a file.
	 */
	public boolean contains(IResource resource) {
		IBuildpathEntry[] buildpath;
		try {
			buildpath = getResolvedBuildpath();
		} catch (ModelException e) {
			return false;
		}
		IPath fullPath = resource.getFullPath();
		IBuildpathEntry innerMostEntry = null;
		for (int j = 0, cpLength = buildpath.length; j < cpLength; j++) {
			IBuildpathEntry entry = buildpath[j];
			IPath entryPath = entry.getPath();
			if ((innerMostEntry == null || innerMostEntry.getPath().isPrefixOf(
					entryPath))
					&& entryPath.isPrefixOf(fullPath)) {
				innerMostEntry = entry;
			}
		}
		if (innerMostEntry != null) {
			return true;
		}
		return false;
	}

	public boolean isValid() {
		IDLTKLanguageToolkit toolkit = DLTKLanguageManager
				.getLanguageToolkit(this);
		return toolkit != null;
	}

	public void printNode(CorePrinter output) {
		output.formatPrint("ScriptProject:" + getElementName()); //$NON-NLS-1$
		output.indent();
		try {
			IModelElement modelElements[] = this.getChildren();
			for (int i = 0; i < modelElements.length; ++i) {
				IModelElement element = modelElements[i];
				if (element instanceof ModelElement) {
					((ModelElement) element).printNode(output);
				} else {
					output.print("Unknown element:" + element); //$NON-NLS-1$
				}
			}
		} catch (ModelException ex) {
			output.formatPrint(ex.getLocalizedMessage());
		}
		output.dedent();
	}

	/*
	 * Reads the buildpath file entries of this project's .buildpath file. This
	 * includes the output entry. As a side effect, unknown elements are stored
	 * in the given map (if not null) Throws exceptions if the file cannot be
	 * accessed or is malformed.
	 */
	public IBuildpathEntry[] readFileEntriesWithException(Map unknownElements)
			throws CoreException, IOException, AssertionFailedException {
		String xmlBuildpath;
		IFile rscFile = this.project.getFile(ScriptProject.BUILDPATH_FILENAME);
		if (rscFile.exists()) {
			byte[] bytes = Util.getResourceContentsAsByteArray(rscFile);
			try {
				xmlBuildpath = new String(bytes, Util.UTF_8); // .buildpath
				// always
				// encoded with
				// UTF-8
			} catch (UnsupportedEncodingException e) {
				Util.log(e, "Could not read .buildpath with UTF-8 encoding"); //$NON-NLS-1$
				// fallback to default
				xmlBuildpath = new String(bytes);
			}
		} else {
			// when a project is imported, we get a first delta for the addition
			// of the .project, but the .buildpath is not accessible
			// so default to using java.io.File
			// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96258
			URI location = rscFile.getLocationURI();
			if (location == null)
				throw new IOException(
						"Cannot obtain a location URI for " + rscFile); //$NON-NLS-1$
			File file = Util.toLocalFile(location, null/*
			 * no progress monitor
			 * available
			 */);
			if (file == null)
				throw new IOException("Unable to fetch file from " + location); //$NON-NLS-1$
			byte[] bytes;
			try {
				bytes = org.eclipse.dltk.compiler.util.Util
						.getFileByteContent(file);
			} catch (IOException e) {
				if (!file.exists())
					return defaultBuildpath();
				throw e;
			}
			try {
				xmlBuildpath = new String(bytes, Util.UTF_8); // .buildpath
				// always
				// encoded with
				// UTF-8
			} catch (UnsupportedEncodingException e) {
				Util.log(e, "Could not read .buildpath with UTF-8 encoding"); //$NON-NLS-1$
				// fallback to default
				xmlBuildpath = new String(bytes);
			}
		}
		return decodeBuildpath(xmlBuildpath, unknownElements);
	}

	public static boolean areBuildpathsEqual(IBuildpathEntry[] firstBuildpath,
			IBuildpathEntry[] secondBuildpath) {
		if (secondBuildpath == null || secondBuildpath.length == 0)
			return false;
		int length = firstBuildpath.length;
		if (length != secondBuildpath.length)
			return false;
		for (int i = 0; i < length; i++) {
			if (!firstBuildpath[i].equals(secondBuildpath[i]))
				return false;
		}
		return true;
	}

	/*
	 * Detect cycles in the buildpath of the workspace's projects and create
	 * markers if necessary. @param preferredBuildpaths Map @throws
	 * ModelException
	 */
	public static void validateCycles(Map preferredBuildpaths)
			throws ModelException {
		// long start = System.currentTimeMillis();
		IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
		IProject[] rscProjects = workspaceRoot.getProjects();
		int length = rscProjects.length;
		ScriptProject[] projects = new ScriptProject[length];
		HashSet cycleParticipants = new HashSet();
		HashSet traversed = new HashSet();
		// compute cycle participants
		ArrayList prereqChain = new ArrayList();
		for (int i = 0; i < length; i++) {
			if (DLTKLanguageManager.hasScriptNature(rscProjects[i])) {
				ScriptProject project = (projects[i] = (ScriptProject) DLTKCore
						.create(rscProjects[i]));
				if (!traversed.contains(project.getPath())) {
					prereqChain.clear();
					project.updateCycleParticipants(prereqChain,
							cycleParticipants, workspaceRoot, traversed,
							preferredBuildpaths);
				}
			}
		}
		// System.out.println("updateAllCycleMarkers: " +
		// (System.currentTimeMillis() - start) + " ms");
		for (int i = 0; i < length; i++) {
			ScriptProject project = projects[i];
			if (project != null) {
				if (cycleParticipants.contains(project.getPath())) {
					IMarker cycleMarker = project.getCycleMarker();
					String circularCPOption = project.getOption(
							DLTKCore.CORE_CIRCULAR_BUILDPATH, true);
					int circularCPSeverity = DLTKCore.ERROR
							.equals(circularCPOption) ? IMarker.SEVERITY_ERROR
							: IMarker.SEVERITY_WARNING;
					if (cycleMarker != null) {
						// update existing cycle marker if needed
						try {
							int existingSeverity = ((Integer) cycleMarker
									.getAttribute(IMarker.SEVERITY)).intValue();
							if (existingSeverity != circularCPSeverity) {
								cycleMarker.setAttribute(IMarker.SEVERITY,
										circularCPSeverity);
							}
						} catch (CoreException e) {
							throw new ModelException(e);
						}
					} else {
						// create new marker
						project
								.createBuildpathProblemMarker(new ModelStatus(
										IModelStatusConstants.BUILDPATH_CYCLE,
										project));
					}
				} else {
					project.flushBuildpathProblemMarkers(true, false);
				}
			}
		}
	}

	// Search operations
	/**
	 * @see IScriptProject
	 */
	public IModelElement findElement(IPath path) throws ModelException {
		return findElement(path, DefaultWorkingCopyOwner.PRIMARY);
	}

	/**
	 * @see IScriptProject
	 */
	public IModelElement findElement(IPath path, WorkingCopyOwner owner)
			throws ModelException {
		if (path == null) {
			// throw new ModelException(
			// new ModelStatus(IModelStatusConstants.INVALID_PATH, path));
			// }
			return null;
		}
		try {

			String extension = path.getFileExtension();
			if (extension == null) {
				String packageName = path.toString();// .replace(IPath.SEPARATOR,
				// '.');

				NameLookup lookup = newNameLookup((WorkingCopyOwner) null/*
				 * no
				 * need
				 * to
				 * look
				 * at
				 * working
				 * copies
				 * for
				 * pkgs
				 */);
				IScriptFolder[] pkgFragments = lookup.findScriptFolders(
						packageName, false);
				if (pkgFragments == null) {
					return null;

				} else {
					// try to return one that is a child of this project
					for (int i = 0, length = pkgFragments.length; i < length; i++) {

						IScriptFolder pkgFragment = pkgFragments[i];
						if (this.equals(pkgFragment.getParent().getParent())) {
							return pkgFragment;
						}
					}
					// default to the first one
					return pkgFragments[0];
				}
			} else if (Util.isValidSourceModule(this, path)) {
				IPath packagePath = path.removeLastSegments(1);
				String packageName = packagePath.toString();// .replace(IPath.SEPARATOR,
				// '.');
				String typeName = path.lastSegment();
				typeName = typeName.substring(0, typeName.length()
						- extension.length() - 1);
				String qualifiedName = null;
				if (packageName.length() > 0) {
					qualifiedName = packageName
							+ IScriptFolder.PACKAGE_DELIMETER_STR + typeName;
				} else {
					qualifiedName = typeName;
				}

				// lookup type
				NameLookup lookup = newNameLookup(owner);
				NameLookup.Answer answer = lookup.findType(qualifiedName,
						false, NameLookup.ACCEPT_ALL,
						true/* consider secondary types */,
						false/* do NOT wait for indexes */,
						false/* don't check restrictions */, null);

				if (answer != null) {
					return answer.type.getParent();
				} else {
					return null;
				}
			} else {
				// unsupported extension
				return null;
			}
		} catch (ModelException e) {
			if (e.getStatus().getCode() == IModelStatusConstants.ELEMENT_DOES_NOT_EXIST) {
				return null;
			} else {
				throw e;
			}
		}
	}

	/**
	 * @see IScriptProject
	 */
	public IScriptFolder findScriptFolder(IPath path) throws ModelException {
		return findScriptFolder0(ScriptProject.canonicalizedPath(path));
	}

	/*
	 * non path canonicalizing version
	 */
	private IScriptFolder findScriptFolder0(IPath path) throws ModelException {
		if (DLTKCore.DEBUG) {
			System.err.println("Search Need to be implemented"); //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * @see IScriptProject
	 */
	public IProjectFragment findProjectFragment(IPath path)
			throws ModelException {
		return findProjectFragment0(ScriptProject.canonicalizedPath(path));
	}

	/*
	 * no path canonicalization
	 */
	public IProjectFragment findProjectFragment0(IPath path)
			throws ModelException {
		IProjectFragment[] allRoots = this.getAllProjectFragments();
		if (!path.isAbsolute()) {
			throw new IllegalArgumentException(Messages.path_mustBeAbsolute);
		}
		for (int i = 0; i < allRoots.length; i++) {
			IProjectFragment buildpathRoot = allRoots[i];
			if (buildpathRoot.getPath().equals(path)) {
				return buildpathRoot;
			}
		}
		return null;
	}

	/**
	 * @see IScriptProject
	 */
	public IProjectFragment[] findProjectFragments(IBuildpathEntry entry) {
		try {
			IBuildpathEntry[] buildpath = this.getRawBuildpath();
			for (int i = 0, length = buildpath.length; i < length; i++) {
				if (buildpath[i].equals(entry)) { // entry may need to be
					// resolved
					return computeProjectFragments(
							getResolvedBuildpath(
									new IBuildpathEntry[] { entry }, true,
									false, null/* no reverse map */), false, // don't
							// retrieve
							// exported
							// roots
							null); /* no reverse map */
				}
			}
		} catch (ModelException e) {
			// project doesn't exist: return an empty array
		}
		return new IProjectFragment[] {};
	}

	/**
	 * @see IScriptProject#findType(String)
	 */
	public IType findType(String fullyQualifiedName) throws ModelException {
		return findType(fullyQualifiedName, DefaultWorkingCopyOwner.PRIMARY);
	}

	/**
	 * @see IScriptProject#findType(String, IProgressMonitor)
	 */
	public IType findType(String fullyQualifiedName,
			IProgressMonitor progressMonitor) throws ModelException {
		return findType(fullyQualifiedName, DefaultWorkingCopyOwner.PRIMARY,
				progressMonitor);
	}

	/*
	 * Internal findType with instanciated name lookup
	 */
	IType findType(String fullyQualifiedName, Object lookup,
			boolean considerSecondaryTypes, IProgressMonitor progressMonitor)
			throws ModelException {
		if (DLTKCore.DEBUG) {
			System.err.println("Search Need to be implemented"); //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * @see IScriptProject#findType(String, String)
	 */
	public IType findType(String packageName, String typeQualifiedName)
			throws ModelException {
		return findType(packageName, typeQualifiedName,
				DefaultWorkingCopyOwner.PRIMARY);
	}

	/**
	 * @see IScriptProject#findType(String, String, IProgressMonitor)
	 */
	public IType findType(String packageName, String typeQualifiedName,
			IProgressMonitor progressMonitor) throws ModelException {
		return findType(packageName, typeQualifiedName,
				DefaultWorkingCopyOwner.PRIMARY, progressMonitor);
	}

	/*
	 * Internal findType with instanciated name lookup
	 */
	IType findType(String packageName, String typeQualifiedName, Object lookup,
			boolean considerSecondaryTypes, IProgressMonitor progressMonitor)
			throws ModelException {
		if (DLTKCore.DEBUG) {
			System.err.println("Search Need to be implemented"); //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * @see IScriptProject#findType(String, String, WorkingCopyOwner)
	 */
	public IType findType(String packageName, String typeQualifiedName,
			WorkingCopyOwner owner) throws ModelException {
		if (DLTKCore.DEBUG) {
			System.err.println("Search Need to be implemented"); //$NON-NLS-1$
		}
		return null;
	}

	/**
	 * @see IScriptProject#findType(String, String, WorkingCopyOwner,
	 *      IProgressMonitor)
	 */
	public IType findType(String packageName, String typeQualifiedName,
			WorkingCopyOwner owner, IProgressMonitor progressMonitor)
			throws ModelException {
		if (DLTKCore.DEBUG) {
			System.err.println("Search Need to be implemented"); //$NON-NLS-1$
		}
		return null;
	}

	public IType findType(String fullyQualifiedName, WorkingCopyOwner owner)
			throws ModelException {
		if (DLTKCore.DEBUG) {
			System.err.println("Search Need to be implemented"); //$NON-NLS-1$
		}
		return null;
	}

	public IType findType(String fullyQualifiedName, WorkingCopyOwner owner,
			IProgressMonitor progressMonitor) throws ModelException {
		if (DLTKCore.DEBUG) {
			System.err.println("Search Need to be implemented"); //$NON-NLS-1$
		}
		return null;
	}

	public IBuildpathEntry[] readRawBuildpath() {
		// Read buildpath file without creating markers nor logging problems
		IBuildpathEntry[] buildpath = readFileEntries(null/*
		 * not interested in
		 * unknown elements
		 */);
		if (buildpath == ScriptProject.INVALID_BUILDPATH)
			return defaultBuildpath();
		return buildpath;
	}

	/*
	 * Reads the buildpath file entries of this project's .buildpath file. This
	 * includes the output entry. As a side effect, unknown elements are stored
	 * in the given map (if not null)
	 */
	private IBuildpathEntry[] readFileEntries(Map unkwownElements) {
		try {
			return readFileEntriesWithException(unkwownElements);
		} catch (CoreException e) {
			Util
					.log(
							e,
							"Exception while reading " + getPath().append(ScriptProject.BUILDPATH_FILENAME)); //$NON-NLS-1$
			return ScriptProject.INVALID_BUILDPATH;
		} catch (IOException e) {
			Util
					.log(
							e,
							"Exception while reading " + getPath().append(ScriptProject.BUILDPATH_FILENAME)); //$NON-NLS-1$
			return ScriptProject.INVALID_BUILDPATH;
		} catch (AssertionFailedException e) {
			Util
					.log(
							e,
							"Exception while reading " + getPath().append(ScriptProject.BUILDPATH_FILENAME)); //$NON-NLS-1$
			return ScriptProject.INVALID_BUILDPATH;
		}
	}

	/**
	 * Returns an array of non-java resources contained in the receiver.
	 */
	public Object[] getForeignResources() throws ModelException {
		return ((ProjectElementInfo) getElementInfo())
				.getForeignResources(this);
	}

	public boolean isOnBuildpath(IModelElement element) {
		IBuildpathEntry[] rawBuildpath;
		try {
			rawBuildpath = getRawBuildpath();
		} catch (ModelException e) {
			return false; // not a script project
		}
		int elementType = element.getElementType();
		boolean isFolderPath = false;
		// boolean isSource = false;
		boolean isProjectFragment = false;
		switch (elementType) {
		case IModelElement.SCRIPT_MODEL:
			return false;
		case IModelElement.SCRIPT_PROJECT:
			break;
		case IModelElement.PROJECT_FRAGMENT:
			isProjectFragment = true;
			break;
		case IModelElement.SCRIPT_FOLDER:
			isFolderPath = !((IProjectFragment) element.getParent())
					.isArchive();
			break;
		case IModelElement.SOURCE_MODULE:
			// isSource = true;
			break;
		default:
			// isSource = element.getAncestor(IModelElement.SOURCE_MODULE) !=
			// null;
			break;
		}
		IPath elementPath = element.getPath();
		// first look at unresolved entries
		int length = rawBuildpath.length;
		for (int i = 0; i < length; i++) {
			IBuildpathEntry entry = rawBuildpath[i];
			switch (entry.getEntryKind()) {
			case IBuildpathEntry.BPE_LIBRARY:
			case IBuildpathEntry.BPE_PROJECT:
			case IBuildpathEntry.BPE_SOURCE:
				if (isOnBuildpathEntry(elementPath, isFolderPath,
						isProjectFragment, entry))
					return true;
				break;
			}
		}
		// no need to go further for compilation units and elements inside a
		// compilation unit
		// it can only be in a source folder, thus on the raw buildpath
		// if (isSource)
		// return false;
		// then look at resolved entries
		for (int i = 0; i < length; i++) {
			IBuildpathEntry rawEntry = rawBuildpath[i];
			switch (rawEntry.getEntryKind()) {
			case IBuildpathEntry.BPE_CONTAINER:
				IBuildpathContainer container;
				try {
					container = DLTKCore.getBuildpathContainer(rawEntry
							.getPath(), this);
				} catch (ModelException e) {
					break;
				}
				if (container == null)
					break;
				IBuildpathEntry[] containerEntries = container
						.getBuildpathEntries(this);
				if (containerEntries == null)
					break;
				// container was bound
				for (int j = 0, containerLength = containerEntries.length; j < containerLength; j++) {
					IBuildpathEntry resolvedEntry = containerEntries[j];
					if (isOnBuildpathEntry(elementPath, isFolderPath,
							isProjectFragment, resolvedEntry))
						return true;
				}
				break;
			}
		}
		return false;
	}

	/*
	 * @see IScriptProject
	 */
	public boolean isOnBuildpath(IResource resource) {
		IPath exactPath = resource.getFullPath();
		IPath path = exactPath;
		// ensure that folders are only excluded if all of their children are
		// excluded
		int resourceType = resource.getType();
		boolean isFolderPath = resourceType == IResource.FOLDER
				|| resourceType == IResource.PROJECT;
		IBuildpathEntry[] buildpath;
		try {
			buildpath = this.getResolvedBuildpath();
		} catch (ModelException e) {
			return false; // not a script project
		}
		for (int i = 0; i < buildpath.length; i++) {
			IBuildpathEntry entry = buildpath[i];
			IPath entryPath = entry.getPath();
			if (entryPath.equals(exactPath)) { // package fragment roots must
				// match exactly entry pathes
				// (no exclusion there)
				return true;
			}
			if (entryPath.isPrefixOf(path)
					&& !Util.isExcluded(path, ((BuildpathEntry) entry)
							.fullInclusionPatternChars(),
							((BuildpathEntry) entry)
									.fullExclusionPatternChars(), isFolderPath)) {
				return true;
			}
		}
		return false;
	}

	private boolean isOnBuildpathEntry(IPath elementPath, boolean isFolderPath,
			boolean isProjectFragment, IBuildpathEntry entry) {
		IPath entryPath = entry.getPath();
		if (isProjectFragment) {
			// package fragment roots must match exactly entry pathes (no
			// exclusion there)
			if (entryPath.equals(elementPath))
				return true;
		} else {
			if (entryPath.isPrefixOf(elementPath)
					&& !Util.isExcluded(elementPath, ((BuildpathEntry) entry)
							.fullInclusionPatternChars(),
							((BuildpathEntry) entry)
									.fullExclusionPatternChars(), isFolderPath))
				return true;
		}
		return false;
	}

	public IModelElement getHandleFromMemento(String token,
			MementoTokenizer memento, WorkingCopyOwner owner) {
		switch (token.charAt(0)) {
		case JEM_PROJECTFRAGMENT:
			String rootPath = IProjectFragment.DEFAULT_PACKAGE_ROOT;
			token = null;
			while (memento.hasMoreTokens()) {
				token = memento.nextToken();
				char firstChar = token.charAt(0);
				if (firstChar != JEM_SCRIPTFOLDER && firstChar != JEM_COUNT) {
					rootPath += token;
				} else {
					break;
				}
			}
			ModelElement root = null;
			if (rootPath.indexOf(JEM_SKIP_DELIMETER) != -1) {
				root = getExternalScriptFolderOrContainerFolder(rootPath);
			} else {
				root = (ModelElement) getProjectFragment(new Path(rootPath));
			}
			if (token != null && token.charAt(0) == JEM_SCRIPTFOLDER) {
				return root.getHandleFromMemento(token, memento, owner);
			} else {
				return root.getHandleFromMemento(memento, owner);
			}
		}
		return null;
	}

	private ModelElement getExternalScriptFolderOrContainerFolder(
			String rootPath) {
		IProjectFragment[] allRoots;
		try {
			allRoots = this.getProjectFragments();
		} catch (ModelException e) {
			if (DLTKCore.DEBUG) {
				e.printStackTrace();
			}
			return null;
		}
		for (int i = 0; i < allRoots.length; i++) {
			IProjectFragment buildpathRoot = allRoots[i];
			if (buildpathRoot.getElementName().equals(rootPath)) {
				return (ModelElement) buildpathRoot;
			}
		}
		return null;
	}

	protected char getHandleMementoDelimiter() {
		return JEM_SCRIPTPROJECT;
	}

	public IResource getUnderlyingResource() throws ModelException {
		if (!exists())
			throw newNotPresentException();
		return this.project;
	}

	/*
	 * Returns a new search name environment for this project. This name
	 * environment first looks in the given working copies.
	 */
	public ISearchableEnvironment newSearchableNameEnvironment(
			ISourceModule[] workingCopies) throws ModelException {
		return new SearchableEnvironment(this, workingCopies);
	}

	/*
	 * Returns a new search name environment for this project. This name
	 * environment first looks in the working copies of the given owner.
	 */
	public SearchableEnvironment newSearchableNameEnvironment(
			WorkingCopyOwner owner) throws ModelException {
		return new SearchableEnvironment(this, owner);
	}

	/**
	 * @see IJavaProject
	 */
	public ITypeHierarchy newTypeHierarchy(IRegion region,
			IProgressMonitor monitor) throws ModelException {

		return newTypeHierarchy(region, DefaultWorkingCopyOwner.PRIMARY,
				monitor);
	}

	/**
	 * @see IJavaProject
	 */
	public ITypeHierarchy newTypeHierarchy(IRegion region,
			WorkingCopyOwner owner, IProgressMonitor monitor)
			throws ModelException {

		if (region == null) {
			throw new IllegalArgumentException(Messages.hierarchy_nullRegion);
		}
		ISourceModule[] workingCopies = ModelManager.getModelManager()
				.getWorkingCopies(owner, true/* add primary working copies */);
		CreateTypeHierarchyOperation op = new CreateTypeHierarchyOperation(
				region, workingCopies, null, true);
		op.runOperation(monitor);
		return op.getResult();
	}

	/**
	 * @see IJavaProject
	 */
	public ITypeHierarchy newTypeHierarchy(IType type, IRegion region,
			IProgressMonitor monitor) throws ModelException {

		return newTypeHierarchy(type, region, DefaultWorkingCopyOwner.PRIMARY,
				monitor);
	}

	/**
	 * @see IJavaProject
	 */
	public ITypeHierarchy newTypeHierarchy(IType type, IRegion region,
			WorkingCopyOwner owner, IProgressMonitor monitor)
			throws ModelException {

		if (type == null) {
			throw new IllegalArgumentException(Messages.hierarchy_nullFocusType);
		}
		if (region == null) {
			throw new IllegalArgumentException(Messages.hierarchy_nullRegion);
		}
		ISourceModule[] workingCopies = ModelManager.getModelManager()
				.getWorkingCopies(owner, true/* add primary working copies */);
		CreateTypeHierarchyOperation op = new CreateTypeHierarchyOperation(
				region, workingCopies, type, true/* compute subtypes */);
		op.runOperation(monitor);
		return op.getResult();
	}

	/**
	 * Returns the buildpath entry that refers to the given path or
	 * <code>null</code> if there is no reference to the path.
	 * 
	 * @param path
	 *            IPath
	 * @return IBuildpathEntry
	 * @throws ModelException
	 */
	public IBuildpathEntry getBuildpathEntryFor(IPath path)
			throws ModelException {
		IBuildpathEntry[] entries = getExpandedBuildpath(true);
		for (int i = 0; i < entries.length; i++) {
			if (entries[i].getPath().equals(path)) {
				return entries[i];
			}
		}
		return null;
	}

	/*
	 * Returns a new name lookup. This name lookup first looks in the given
	 * working copies.
	 */
	public NameLookup newNameLookup(ISourceModule[] workingCopies)
			throws ModelException {
		return getProjectElementInfo().newNameLookup(this, workingCopies);
	}

	/*
	 * Returns a new name lookup. This name lookup first looks in the working
	 * copies of the given owner.
	 */
	public NameLookup newNameLookup(WorkingCopyOwner owner)
			throws ModelException {
		ModelManager manager = ModelManager.getModelManager();
		ISourceModule[] workingCopies = owner == null ? null : manager
				.getWorkingCopies(owner, true/*
				 * add primary WCs
				 */);
		return newNameLookup(workingCopies);
	}

	/*
	 * Force the project to reload its <code>.buildpath</code> file from disk
	 * and update the buildpath accordingly. Usually, a change to the <code>.buildpath</code>
	 * file is automatically noticed and reconciled at the next resource change
	 * notification event. If required to consider such a change prior to the
	 * next automatic refresh, then this functionnality should be used to
	 * trigger a refresh. In particular, if a change to the file is performed,
	 * during an operation where this change needs to be reflected before the
	 * operation ends, then an explicit refresh is necessary. Note that
	 * buildpath markers are NOT created.
	 * 
	 * @param monitor a progress monitor for reporting operation progress
	 * @exception ModelException if the buildpath could not be updated. Reasons
	 * include: <ul> <li> This Script element does not exist
	 * (ELEMENT_DOES_NOT_EXIST)</li> <li> Two or more entries specify source
	 * roots with the same or overlapping paths (NAME_COLLISION) <li> A entry of
	 * kind <code>CPE_PROJECT</code> refers to this project (INVALID_PATH)
	 * <li>This Script element does not exist (ELEMENT_DOES_NOT_EXIST)</li>
	 * <li>The output location path refers to a location not contained in this
	 * project (<code>PATH_OUTSIDE_PROJECT</code>) <li>The output location
	 * path is not an absolute path (<code>RELATIVE_PATH</code>) <li>The
	 * output location path is nested inside a package fragment root of this
	 * project (<code>INVALID_PATH</code>) <li> The buildpath is being
	 * modified during resource change event notification (CORE_EXCEPTION) </ul>
	 */
	protected void forceBuildpathReload(IProgressMonitor monitor)
			throws ModelException {

		if (monitor != null && monitor.isCanceled())
			return;

		// check if any actual difference
		boolean wasSuccessful = false; // flag recording if .buildpath file
		// change got reflected
		try {
			// force to (re)read the property file
			IBuildpathEntry[] fileEntries = readBuildpathFile(
					false/* don't create markers */, false/*
			 * don't log
			 * problems
			 */);
			if (fileEntries == null) {
				return; // could not read, ignore
			}
			ModelManager.PerProjectInfo info = getPerProjectInfo();
			if (info.rawBuildpath != null) { // if there is an in-memory
				// buildpath
				if (isBuildpathEqualsTo(info.rawBuildpath, fileEntries)) {
					wasSuccessful = true;
					return;
				}
			}

			// will force an update of the buildpath/output location based on
			// the file information
			// extract out the output location
			IBuildpathEntry[] oldResolvedBuildpath = info.resolvedBuildpath;
			setRawBuildpath(
					fileEntries,
					monitor,
					!ResourcesPlugin.getWorkspace().isTreeLocked(), // canChangeResource
					oldResolvedBuildpath != null ? oldResolvedBuildpath
							: getResolvedBuildpath(
									true/* ignoreUnresolvedEntry */,
									false/* don't generateMarkerOnError */,
									false/* don't returnResolutionInProgress */),
					true, // needValidation
					false); // no need to save

			// if reach that far, the buildpath file change got absorbed
			wasSuccessful = true;
		} catch (RuntimeException e) {
			// setRawBuildpath might fire a delta, and a listener may throw an
			// exception
			if (this.project.isAccessible()) {
				Util.log(e, "Could not set buildpath for " + getPath()); //$NON-NLS-1$
			}
			throw e; // rethrow
		} catch (ModelException e) { // CP failed validation
			if (!ResourcesPlugin.getWorkspace().isTreeLocked()) {
				if (this.project.isAccessible()) {
					if (e.getModelStatus().getException() instanceof CoreException) {
						// happens if the .buildpath could not be written to
						// disk
						createBuildpathProblemMarker(new ModelStatus(
								IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT,
								Messages
										.bind(
												Messages.buildpath_couldNotWriteBuildpathFile,
												new String[] {
														getElementName(),
														e.getMessage() })));
					} else {
						createBuildpathProblemMarker(new ModelStatus(
								IModelStatusConstants.INVALID_BUILDPATH_FILE_FORMAT,
								Messages
										.bind(
												Messages.buildpath_invalidBuildpathInBuildpathFile,
												new String[] {
														getElementName(),
														e.getMessage() })));
					}
				}
			}
			throw e; // rethrow
		} finally {
			if (!wasSuccessful) {
				try {
					this.getPerProjectInfo().updateBuildpathInformation(
							ScriptProject.INVALID_BUILDPATH);
					updateProjectFragments();
				} catch (ModelException e) {
					// ignore
				}
			}
		}
	}

	public IProjectFragment[] getAllProjectFragments() throws ModelException {
		return getAllProjectFragments(null /* no reverse map */);
	}

	public IProjectFragment[] getAllProjectFragments(Map rootToResolvedEntries)
			throws ModelException {
		return computeProjectFragments(getResolvedBuildpath(
				true/* ignoreUnresolvedEntry */,
				false/* don't generateMarkerOnError */, false/*
		 * don't
		 * returnResolutionInProgress
		 */), true/* retrieveExportedRoots */, rootToResolvedEntries);
	}

	public static boolean hasScriptNature(IProject p) {
		return DLTKLanguageManager.hasScriptNature(p);
	}

	public IScriptFolder[] getScriptFolders() throws ModelException {

		IProjectFragment[] roots = getProjectFragments();
		return getScriptFoldersInFragments(roots);
	}

	public IScriptFolder[] getScriptFoldersInFragments(IProjectFragment[] roots) {

		ArrayList frags = new ArrayList();
		for (int i = 0; i < roots.length; i++) {
			IProjectFragment root = roots[i];
			try {
				IModelElement[] rootFragments = root.getChildren();
				for (int j = 0; j < rootFragments.length; j++) {
					frags.add(rootFragments[j]);
				}
			} catch (ModelException e) {
				// do nothing
			}
		}
		IScriptFolder[] fragments = new IScriptFolder[frags.size()];
		frags.toArray(fragments);
		return fragments;
	}
}
