/*******************************************************************************
 * Copyright (c) 2000, 2018 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aspectj.org.eclipse.jdt.internal.core;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Map;
import java.util.jar.Manifest;

import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.aspectj.org.eclipse.jdt.core.*;
import org.aspectj.org.eclipse.jdt.core.compiler.CharOperation;
import org.aspectj.org.eclipse.jdt.internal.compiler.env.AutomaticModuleNaming;
import org.aspectj.org.eclipse.jdt.internal.compiler.lookup.TypeConstants;
import org.aspectj.org.eclipse.jdt.internal.core.util.MementoTokenizer;
import org.aspectj.org.eclipse.jdt.internal.core.util.Messages;
import org.aspectj.org.eclipse.jdt.internal.core.util.Util;

/**
 * @see IPackageFragmentRoot
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class PackageFragmentRoot extends Openable implements IPackageFragmentRoot {

	/**
	 * The delimiter between the source path and root path in the
	 * attachment server property.
	 */
	protected final static char ATTACHMENT_PROPERTY_DELIMITER= '*';
	/*
	 * No source attachment property
	 */
	public final static String NO_SOURCE_ATTACHMENT = ""; //$NON-NLS-1$

	/**
	 * The resource associated with this root (null for external jar)
	 */
	protected IResource resource;

/**
 * Constructs a package fragment root which is the root of the java package
 * directory hierarchy.
 */
protected PackageFragmentRoot(IResource resource, JavaProject project) {
	super(project);
	this.resource = resource;
}

/**
 * @see IPackageFragmentRoot
 */
@Override
public void attachSource(IPath sourcePath, IPath rootPath, IProgressMonitor monitor) throws JavaModelException {
	try {
		verifyAttachSource(sourcePath);
		if (monitor != null) {
			monitor.beginTask(Messages.element_attachingSource, 2);
		}
		SourceMapper oldMapper= getSourceMapper();
		boolean rootNeedsToBeClosed= false;

		if (sourcePath == null) {
			//source being detached
			rootNeedsToBeClosed= true;
			setSourceMapper(null);
		/* Disable deltas (see 1GDTUSD)
			// fire a delta to notify the UI about the source detachement.
			JavaModelManager manager = (JavaModelManager) JavaModelManager.getJavaModelManager();
			JavaModel model = (JavaModel) getJavaModel();
			JavaElementDelta attachedSourceDelta = new JavaElementDelta(model);
			attachedSourceDelta .sourceDetached(this); // this would be a PackageFragmentRoot
			manager.registerResourceDelta(attachedSourceDelta );
			manager.fire(); // maybe you want to fire the change later. Let us know about it.
		*/
		} else {
		/*
			// fire a delta to notify the UI about the source attachment.
			JavaModelManager manager = (JavaModelManager) JavaModelManager.getJavaModelManager();
			JavaModel model = (JavaModel) getJavaModel();
			JavaElementDelta attachedSourceDelta = new JavaElementDelta(model);
			attachedSourceDelta .sourceAttached(this); // this would be a PackageFragmentRoot
			manager.registerResourceDelta(attachedSourceDelta );
			manager.fire(); // maybe you want to fire the change later. Let us know about it.
		 */

			//check if different from the current attachment
			IPath storedSourcePath= getSourceAttachmentPath();
			IPath storedRootPath= getSourceAttachmentRootPath();
			if (monitor != null) {
				monitor.worked(1);
			}
			if (storedSourcePath != null) {
				if (!(storedSourcePath.equals(sourcePath) && (rootPath != null && rootPath.equals(storedRootPath)) || storedRootPath == null)) {
					rootNeedsToBeClosed= true;
				}
			}
			// check if source path is valid
			Object target = JavaModel.getTarget(sourcePath, false);
			if (target == null) {
				throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INVALID_PATH, sourcePath));
			}
			SourceMapper mapper = createSourceMapper(sourcePath, rootPath);
			if (rootPath == null && mapper.rootPath != null) {
				// as a side effect of calling the SourceMapper constructor, the root path was computed
				rootPath = new Path(mapper.rootPath);
			}
			setSourceMapper(mapper);
		}
		if (sourcePath == null) {
			Util.setSourceAttachmentProperty(getPath(), null); //remove the property
		} else {
			//set the property to the path of the mapped source
			Util.setSourceAttachmentProperty(
				getPath(),
				sourcePath.toString()
				+ (rootPath == null ? "" : (ATTACHMENT_PROPERTY_DELIMITER + rootPath.toString()))); //$NON-NLS-1$
		}
		if (rootNeedsToBeClosed) {
			if (oldMapper != null) {
				oldMapper.close();
			}
			BufferManager manager= BufferManager.getDefaultBufferManager();
			Enumeration openBuffers= manager.getOpenBuffers();
			while (openBuffers.hasMoreElements()) {
				IBuffer buffer= (IBuffer) openBuffers.nextElement();
				IOpenable possibleMember= buffer.getOwner();
				if (isAncestorOf((IJavaElement) possibleMember)) {
					buffer.close();
				}
			}
			if (monitor != null) {
				monitor.worked(1);
			}
		}
	} catch (JavaModelException e) {
		Util.setSourceAttachmentProperty(getPath(), null); // loose info - will be recomputed
		throw e;
	} finally {
		if (monitor != null) {
			monitor.done();
		}
	}
}

/**
 * @see Openable
 */
@Override
protected boolean buildStructure(OpenableElementInfo info, IProgressMonitor pm, Map newElements, IResource underlyingResource) throws JavaModelException {
	((PackageFragmentRootInfo) info).setRootKind(determineKind(underlyingResource));
	return computeChildren(info, underlyingResource);
}

SourceMapper createSourceMapper(IPath sourcePath, IPath rootPath) throws JavaModelException {
	IClasspathEntry entry = ((JavaProject) getParent()).getClasspathEntryFor(getPath());
	String encoding = (entry== null) ? null : ((ClasspathEntry) entry).getSourceAttachmentEncoding();
	SourceMapper mapper = new SourceMapper(
		sourcePath,
		rootPath == null ? null : rootPath.toOSString(),
		getJavaProject().getOptions(true),// cannot use workspace options if external jar is 1.5 jar and workspace options are 1.4 options
		encoding);

	return mapper;
}

@Override
public void delete(
	int updateResourceFlags,
	int updateModelFlags,
	IProgressMonitor monitor)
	throws JavaModelException {

	DeletePackageFragmentRootOperation op = new DeletePackageFragmentRootOperation(this, updateResourceFlags, updateModelFlags);
	op.runOperation(monitor);
}

/**
 * Compute the package fragment children of this package fragment root.
 *
 * @exception JavaModelException  The resource associated with this package fragment root does not exist
 */
protected boolean computeChildren(OpenableElementInfo info, IResource underlyingResource) throws JavaModelException {
	// Note the children are not opened (so not added to newElements) for a regular package fragment root
	// However they are opened for a Jar package fragment root (see JarPackageFragmentRoot#computeChildren)
	try {
		// the underlying resource may be a folder or a project (in the case that the project folder
		// is actually the package fragment root)
		if (underlyingResource.getType() == IResource.FOLDER || underlyingResource.getType() == IResource.PROJECT) {
			ArrayList vChildren = new ArrayList(5);
			IContainer rootFolder = (IContainer) underlyingResource;
			char[][] inclusionPatterns = fullInclusionPatternChars();
			char[][] exclusionPatterns = fullExclusionPatternChars();
			computeFolderChildren(rootFolder, !Util.isExcluded(rootFolder, inclusionPatterns, exclusionPatterns), CharOperation.NO_STRINGS, vChildren, inclusionPatterns, exclusionPatterns);
//			char[] suffix = getKind() == K_SOURCE ? SuffixConstants.SUFFIX_java : SuffixConstants.SUFFIX_class;
//			char[] moduleInfoName = CharOperation.concat(TypeConstants.MODULE_INFO_NAME, suffix);
//			IResource module = rootFolder.findMember(String.valueOf(moduleInfoName), true);
//			if (module != null && module.exists()) {
//				vChildren.add(new ClassFile(getPackageFragment(CharOperation.NO_STRINGS), String.valueOf(TypeConstants.MODULE_INFO_NAME)));
//			}
			if (!vChildren.isEmpty()) {
				IJavaElement[] children = new IJavaElement[vChildren.size()];
				vChildren.toArray(children);
				info.setChildren(children);
			} else {
				info.setChildren(JavaElement.NO_ELEMENTS);
			}
		}
	} catch (JavaModelException e) {
		//problem resolving children; structure remains unknown
		info.setChildren(new IJavaElement[]{});
		throw e;
	}
	return true;
}

/**
 * Starting at this folder, create package fragments and add the fragments that are not excluded
 * to the collection of children.
 *
 * @exception JavaModelException  The resource associated with this package fragment does not exist
 */
protected void computeFolderChildren(IContainer folder, boolean isIncluded, String[] pkgName, ArrayList vChildren, char[][] inclusionPatterns, char[][] exclusionPatterns) throws JavaModelException {

	if (isIncluded) {
	    IPackageFragment pkg = getPackageFragment(pkgName);
		vChildren.add(pkg);
	}
	try {
		IResource[] members = folder.members();
		boolean hasIncluded = isIncluded;
		int length = members.length;
		if (length > 0) {
			// if package fragment root refers to folder in another IProject, then
			// folder.getProject() is different than getJavaProject().getProject()
			// use the other java project's options to verify the name
			IJavaProject otherJavaProject = JavaCore.create(folder.getProject());
			String sourceLevel = otherJavaProject.getOption(JavaCore.COMPILER_SOURCE, true);
			String complianceLevel = otherJavaProject.getOption(JavaCore.COMPILER_COMPLIANCE, true);
			JavaProject javaProject = (JavaProject) getJavaProject();
			JavaModelManager manager = JavaModelManager.getJavaModelManager();
			for (int i = 0; i < length; i++) {
				IResource member = members[i];
				String memberName = member.getName();

				switch(member.getType()) {

			    	case IResource.FOLDER:
			    		// recurse into sub folders even even parent not included as a sub folder could be included
			    		// (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=65637)
			    		if (Util.isValidFolderNameForPackage(memberName, sourceLevel, complianceLevel)) {
			    			// eliminate binary output only if nested inside direct subfolders
			    			if (javaProject.contains(member)) {
			    				String[] newNames = Util.arrayConcat(pkgName, manager.intern(memberName));
			    				boolean isMemberIncluded = !Util.isExcluded(member, inclusionPatterns, exclusionPatterns);
			    				computeFolderChildren((IFolder) member, isMemberIncluded, newNames, vChildren, inclusionPatterns, exclusionPatterns);
			    			}
			    		}
			    		break;
			    	case IResource.FILE:
			    		// inclusion filter may only include files, in which case we still want to include the immediate parent package (lazily)
			    		if (!hasIncluded
			    				&& Util.isValidCompilationUnitName(memberName, sourceLevel, complianceLevel)
								&& !Util.isExcluded(member, inclusionPatterns, exclusionPatterns)) {
			    			hasIncluded = true;
			    			IPackageFragment pkg = getPackageFragment(pkgName);
			    			vChildren.add(pkg);
			    		}
			    		break;
				}
			}
		}
	} catch(IllegalArgumentException e){
		throw new JavaModelException(e, IJavaModelStatusConstants.ELEMENT_DOES_NOT_EXIST); // could be thrown by ElementTree when path is not found
	} catch (CoreException e) {
		throw new JavaModelException(e);
	}
}

@Override
public void copy(
	IPath destination,
	int updateResourceFlags,
	int updateModelFlags,
	IClasspathEntry sibling,
	IProgressMonitor monitor)
	throws JavaModelException {

	CopyPackageFragmentRootOperation op =
		new CopyPackageFragmentRootOperation(this, destination, updateResourceFlags, updateModelFlags, sibling);
	op.runOperation(monitor);
}

/**
 * Returns a new element info for this element.
 */
@Override
protected Object createElementInfo() {
	return new PackageFragmentRootInfo();
}

/**
 * @see IPackageFragmentRoot
 */
@Override
public IPackageFragment createPackageFragment(String pkgName, boolean force, IProgressMonitor monitor) throws JavaModelException {
	CreatePackageFragmentOperation op = new CreatePackageFragmentOperation(this, pkgName, force);
	op.runOperation(monitor);
	return getPackageFragment(op.pkgName);
}

/**
 * Returns the root's kind - K_SOURCE or K_BINARY, defaults
 * to K_SOURCE if it is not on the classpath.
 *
 * @exception JavaModelException if the project and root do
 * 		not exist.
 */
protected int determineKind(IResource underlyingResource) throws JavaModelException {
	IClasspathEntry entry = ((JavaProject)getJavaProject()).getClasspathEntryFor(underlyingResource.getFullPath());
	if (entry != null) {
		return entry.getContentKind();
	}
	return IPackageFragmentRoot.K_SOURCE;
}

/**
 * Compares two objects for equality;
 * for <code>PackageFragmentRoot</code>s, equality is having the
 * same parent, same resources, and occurrence count.
 *
 */
@Override
public boolean equals(Object o) {
	if (this == o)
		return true;
	if (!(o instanceof PackageFragmentRoot))
		return false;
	PackageFragmentRoot other = (PackageFragmentRoot) o;
	return resource().equals(other.resource()) &&
			this.parent.equals(other.parent);
}

private IClasspathEntry findSourceAttachmentRecommendation() {
	try {
		IPath rootPath = getPath();
		IClasspathEntry entry;

		// try on enclosing project first
		JavaProject parentProject = (JavaProject) getJavaProject();
		try {
			entry = parentProject.getClasspathEntryFor(rootPath);
			if (entry != null) {
				Object target = JavaModel.getTarget(entry.getSourceAttachmentPath(), true);
				if (target != null) {
					return entry;
				}
			}
		} catch(JavaModelException e){
			// ignore
		}

		// iterate over all projects
		IJavaModel model = getJavaModel();
		IJavaProject[] jProjects = model.getJavaProjects();
		for (int i = 0, max = jProjects.length; i < max; i++){
			JavaProject jProject = (JavaProject) jProjects[i];
			if (jProject == parentProject) continue; // already done
			try {
				entry = jProject.getClasspathEntryFor(rootPath);
				if (entry != null){
					Object target = JavaModel.getTarget(entry.getSourceAttachmentPath(), true);
					if (target != null) {
						return entry;
					}
				}
			} catch(JavaModelException e){
				// ignore
			}
		}
	} catch(JavaModelException e){
		// ignore
	}

	return null;
}

/*
 * Returns the exclusion patterns from the classpath entry associated with this root.
 */
public char[][] fullExclusionPatternChars() {
	try {
		if (isOpen() && getKind() != IPackageFragmentRoot.K_SOURCE) return null;
		ClasspathEntry entry = (ClasspathEntry) getRawClasspathEntry();
		if (entry == null) {
			return null;
		} else {
			return entry.fullExclusionPatternChars();
		}
	} catch (JavaModelException e) {
		return null;
	}
}

/*
 * Returns the inclusion patterns from the classpath entry associated with this root.
 */
public char[][] fullInclusionPatternChars() {
	try {
		if (isOpen() && getKind() != IPackageFragmentRoot.K_SOURCE) return null;
		ClasspathEntry entry = (ClasspathEntry)getRawClasspathEntry();
		if (entry == null) {
			return null;
		} else {
			return entry.fullInclusionPatternChars();
		}
	} catch (JavaModelException e) {
		return null;
	}
}
@Override
public String getElementName() {
	IResource res = resource();
	if (res instanceof IFolder)
		return ((IFolder) res).getName();
	return ""; //$NON-NLS-1$
}
/**
 * @see IJavaElement
 */
@Override
public int getElementType() {
	return PACKAGE_FRAGMENT_ROOT;
}
/**
 * @see JavaElement#getHandleMemento()
 */
@Override
protected char getHandleMementoDelimiter() {
	return JavaElement.JEM_PACKAGEFRAGMENTROOT;
}
/*
 * @see JavaElement
 */
@Override
public IJavaElement getHandleFromMemento(String token, MementoTokenizer memento, WorkingCopyOwner owner) {
	switch (token.charAt(0)) {
		case JEM_PACKAGEFRAGMENT:
			String[] pkgName;
			if (memento.hasMoreTokens()) {
				token = memento.nextToken();
				char firstChar = token.charAt(0);
				if (firstChar == JEM_CLASSFILE || firstChar == JEM_MODULAR_CLASSFILE || firstChar == JEM_COMPILATIONUNIT || firstChar == JEM_COUNT) {
					pkgName = CharOperation.NO_STRINGS;
				} else {
					pkgName = Util.splitOn('.', token, 0, token.length());
					token = null;
				}
			} else {
				pkgName = CharOperation.NO_STRINGS;
				token = null;
			}
			JavaElement pkg = getPackageFragment(pkgName);
			if (token == null) {
				return pkg.getHandleFromMemento(memento, owner);
			} else {
				return pkg.getHandleFromMemento(token, memento, owner);
			}
	}
	return null;
}
/**
 * @see JavaElement#getHandleMemento(StringBuffer)
 */
@Override
protected void getHandleMemento(StringBuffer buff) {
	IPath path;
	IResource underlyingResource = getResource();
	if (underlyingResource != null) {
		// internal jar or regular root
		if (resource().getProject().equals(getJavaProject().getProject())) {
			path = underlyingResource.getProjectRelativePath();
		} else {
			path = underlyingResource.getFullPath();
		}
	} else {
		// external jar
		path = getPath();
	}
	((JavaElement)getParent()).getHandleMemento(buff);
	buff.append(getHandleMementoDelimiter());
	escapeMementoName(buff, path.toString());
	if (org.aspectj.org.eclipse.jdt.internal.compiler.util.Util.isJrt(path.toOSString())) {
		buff.append(JavaElement.JEM_MODULE);
		escapeMementoName(buff, getElementName());
	}
}
/**
 * @see IPackageFragmentRoot
 */
@Override
public int getKind() throws JavaModelException {
	return ((PackageFragmentRootInfo)getElementInfo()).getRootKind();
}

/*
 * A version of getKind() that doesn't update the timestamp of the info in the Java model cache
 * to speed things up
 */
int internalKind() throws JavaModelException {
	JavaModelManager manager = JavaModelManager.getJavaModelManager();
	PackageFragmentRootInfo info = (PackageFragmentRootInfo) manager.peekAtInfo(this);
	if (info == null) {
		info = (PackageFragmentRootInfo) openWhenClosed(createElementInfo(), false, null);
	}
	return info.getRootKind();
}

/**
 * Returns an array of non-java resources contained in the receiver.
 */
@Override
public Object[] getNonJavaResources() throws JavaModelException {
	return ((PackageFragmentRootInfo) getElementInfo()).getNonJavaResources(getJavaProject(), resource(), this);
}

/**
 * @see IPackageFragmentRoot
 */
@Override
public IPackageFragment getPackageFragment(String packageName) {
	// tolerate package names with spaces (e.g. 'x . y') (http://bugs.eclipse.org/bugs/show_bug.cgi?id=21957)
	String[] pkgName = Util.getTrimmedSimpleNames(packageName);
	return getPackageFragment(pkgName);
}
public PackageFragment getPackageFragment(String[] pkgName) {
	return new PackageFragment(this, pkgName);
}
public PackageFragment getPackageFragment(String[] pkgName, String mod) {
	return new PackageFragment(this, pkgName); // Overridden in JImageModuleFragmentBridge
}
/**
 * Returns the package name for the given folder
 * (which is a decendent of this root).
 */
protected String getPackageName(IFolder folder) {
	IPath myPath= getPath();
	IPath pkgPath= folder.getFullPath();
	int mySegmentCount= myPath.segmentCount();
	int pkgSegmentCount= pkgPath.segmentCount();
	StringBuffer pkgName = new StringBuffer(IPackageFragment.DEFAULT_PACKAGE_NAME);
	for (int i= mySegmentCount; i < pkgSegmentCount; i++) {
		if (i > mySegmentCount) {
			pkgName.append('.');
		}
		pkgName.append(pkgPath.segment(i));
	}
	return pkgName.toString();
}

/**
 * @see IJavaElement
 */
@Override
public IPath getPath() {
	return internalPath();
}

public IPath internalPath() {
	return resource().getFullPath();
}
/*
 * @see IPackageFragmentRoot
 */
@Override
public IClasspathEntry getRawClasspathEntry() throws JavaModelException {

	IClasspathEntry rawEntry = null;
	JavaProject project = (JavaProject)getJavaProject();
	project.getResolvedClasspath(); // force the reverse rawEntry cache to be populated
	Map rootPathToRawEntries = project.getPerProjectInfo().rootPathToRawEntries;
	if (rootPathToRawEntries != null) {
		rawEntry = (IClasspathEntry) rootPathToRawEntries.get(getPath());
	}
	if (rawEntry == null) {
		throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_NOT_ON_CLASSPATH, this));
	}
	return rawEntry;
}
/*
 * @see IPackageFragmentRoot
 */
@Override
public IClasspathEntry getResolvedClasspathEntry() throws JavaModelException {
	IClasspathEntry resolvedEntry = null;
	JavaProject project = (JavaProject)getJavaProject();
	project.getResolvedClasspath(); // force the resolved entry cache to be populated
	Map rootPathToResolvedEntries = project.getPerProjectInfo().rootPathToResolvedEntries;
	if (rootPathToResolvedEntries != null) {
		resolvedEntry = (IClasspathEntry) rootPathToResolvedEntries.get(getPath());
	}
	if (resolvedEntry == null) {
		throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_NOT_ON_CLASSPATH, this));
	}
	return resolvedEntry;
}


@Override
public IResource resource() {
	if (this.resource != null) // perf improvement to avoid message send in resource()
		return this.resource;
	return super.resource();
}
/*
 * @see IJavaElement
 */
@Override
public IResource resource(PackageFragmentRoot root) {
	return this.resource;
}

/**
 * @see IPackageFragmentRoot
 */
@Override
public IPath getSourceAttachmentPath() throws JavaModelException {
	if (getKind() != K_BINARY) return null;

	// 1) look source attachment property (set iff attachSource(...) was called
	IPath path = getPath();
	String serverPathString= Util.getSourceAttachmentProperty(path);
	if (serverPathString != null) {
		int index= serverPathString.lastIndexOf(ATTACHMENT_PROPERTY_DELIMITER);
		if (index < 0) {
			// no root path specified
			return new Path(serverPathString);
		} else {
			String serverSourcePathString= serverPathString.substring(0, index);
			return new Path(serverSourcePathString);
		}
	}

	// 2) look at classpath entry
	IClasspathEntry entry = ((JavaProject) getParent()).getClasspathEntryFor(path);
	IPath sourceAttachmentPath;
	if (entry != null && (sourceAttachmentPath = entry.getSourceAttachmentPath()) != null)
		return sourceAttachmentPath;

	// 3) look for a recommendation
	entry = findSourceAttachmentRecommendation();
	if (entry != null && (sourceAttachmentPath = entry.getSourceAttachmentPath()) != null) {
		return sourceAttachmentPath;
	}

	return null;
}

/**
 * For use by <code>AttachSourceOperation</code> only.
 * Sets the source mapper associated with this root.
 */
public void setSourceMapper(SourceMapper mapper) throws JavaModelException {
	((PackageFragmentRootInfo) getElementInfo()).setSourceMapper(mapper);
}



/**
 * @see IPackageFragmentRoot
 */
@Override
public IPath getSourceAttachmentRootPath() throws JavaModelException {
	if (getKind() != K_BINARY) return null;

	// 1) look source attachment property (set iff attachSource(...) was called
	IPath path = getPath();
	String serverPathString= Util.getSourceAttachmentProperty(path);
	if (serverPathString != null) {
		int index = serverPathString.lastIndexOf(ATTACHMENT_PROPERTY_DELIMITER);
		if (index == -1) return null;
		String serverRootPathString= IPackageFragmentRoot.DEFAULT_PACKAGEROOT_PATH;
		if (index != serverPathString.length() - 1) {
			serverRootPathString= serverPathString.substring(index + 1);
		}
		return new Path(serverRootPathString);
	}

	// 2) look at classpath entry
	IClasspathEntry entry = ((JavaProject) getParent()).getClasspathEntryFor(path);
	IPath sourceAttachmentRootPath;
	if (entry != null && (sourceAttachmentRootPath = entry.getSourceAttachmentRootPath()) != null)
		return sourceAttachmentRootPath;

	// 3) look for a recomendation
	entry = findSourceAttachmentRecommendation();
	if (entry != null && (sourceAttachmentRootPath = entry.getSourceAttachmentRootPath()) != null)
		return sourceAttachmentRootPath;

	return null;
}

/**
 * @see JavaElement
 */
@Override
public SourceMapper getSourceMapper() {
	SourceMapper mapper;
	try {
		PackageFragmentRootInfo rootInfo = (PackageFragmentRootInfo) getElementInfo();
		mapper = rootInfo.getSourceMapper();
		if (mapper == null) {
			// first call to this method
			IPath sourcePath= getSourceAttachmentPath();
			IPath rootPath= getSourceAttachmentRootPath();
			if (sourcePath == null)
				mapper = createSourceMapper(getPath(), rootPath); // attach root to itself
			else
				mapper = createSourceMapper(sourcePath, rootPath);
			rootInfo.setSourceMapper(mapper);
		}
	} catch (JavaModelException e) {
		// no source can be attached
		mapper = null;
	}
	return mapper;
}

/**
 * @see IJavaElement
 */
@Override
public IResource getUnderlyingResource() throws JavaModelException {
	if (!exists()) throw newNotPresentException();
	return resource();
}

/**
 * @see IParent
 */
@Override
public boolean hasChildren() throws JavaModelException {
	// a package fragment root always has the default package as a child
	return true;
}

@Override
public int hashCode() {
	return resource().hashCode();
}

public boolean ignoreOptionalProblems() {
	try {
		return ((PackageFragmentRootInfo) getElementInfo()).ignoreOptionalProblems(this);
	} catch (JavaModelException e) {
		return false;
	}
}

/**
 * @see IPackageFragmentRoot
 */
@Override
public boolean isArchive() {
	return false;
}

/**
 * @see IPackageFragmentRoot
 */
@Override
public boolean isExternal() {
	return false;
}

/*
 * Validate whether this package fragment root is on the classpath of its project.
 */
protected IStatus validateOnClasspath() {

	IPath path = getPath();
	try {
		// check package fragment root on classpath of its project
		JavaProject project = (JavaProject) getJavaProject();
		IClasspathEntry entry = project.getClasspathEntryFor(path);
		if (entry != null) {
			return Status.OK_STATUS;
		}
	} catch(JavaModelException e){
		// could not read classpath, then assume it is outside
		return e.getJavaModelStatus();
	}
	return new JavaModelStatus(IJavaModelStatusConstants.ELEMENT_NOT_ON_CLASSPATH, this);
}

@Override
public void move(
	IPath destination,
	int updateResourceFlags,
	int updateModelFlags,
	IClasspathEntry sibling,
	IProgressMonitor monitor)
	throws JavaModelException {

	MovePackageFragmentRootOperation op =
		new MovePackageFragmentRootOperation(this, destination, updateResourceFlags, updateModelFlags, sibling);
	op.runOperation(monitor);
}

/**
 * @private Debugging purposes
 */
@Override
protected void toStringInfo(int tab, StringBuffer buffer, Object info, boolean showResolvedInfo) {
	buffer.append(tabString(tab));
	IPath path = getPath();
	if (isExternal()) {
		buffer.append(path.toOSString());
	} else if (getJavaProject().getElementName().equals(path.segment(0))) {
	    if (path.segmentCount() == 1) {
			buffer.append("<project root>"); //$NON-NLS-1$
	    } else {
			buffer.append(path.removeFirstSegments(1).makeRelative());
	    }
	} else {
		buffer.append(path);
	}
	if (info == null) {
		buffer.append(" (not open)"); //$NON-NLS-1$
	}
}

@Override
protected IStatus validateExistence(IResource underlyingResource) {
	// check whether this pkg fragment root can be opened
	IStatus status = validateOnClasspath();
	if (!status.isOK())
		return status;
	if (!resourceExists(underlyingResource))
		return newDoesNotExistStatus();
	return JavaModelStatus.VERIFIED_OK;
}

/**
 * Possible failures: <ul>
 *  <li>ELEMENT_NOT_PRESENT - the root supplied to the operation
 *      does not exist
 *  <li>INVALID_ELEMENT_TYPES - the root is not of kind K_BINARY
 *   <li>RELATIVE_PATH - the path supplied to this operation must be
 *      an absolute path
 *  </ul>
 */
protected void verifyAttachSource(IPath sourcePath) throws JavaModelException {
	if (!exists()) {
		throw newNotPresentException();
	} else if (getKind() != K_BINARY) {
		throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.INVALID_ELEMENT_TYPES, this));
	} else if (sourcePath != null && !sourcePath.isAbsolute()) {
		throw new JavaModelException(new JavaModelStatus(IJavaModelStatusConstants.RELATIVE_PATH, sourcePath));
	}
}
/**
 * Returns the relative path within an archive for the given class file name. In certain
 * kind of archives, such as a JMOD file, class files are stored in a nested folder, as opposed
 * to directly under the root. It is the responsibility of such package fragment roots to
 * provide the custom behavior.
 *
 * @param classname
 * @return the relative path for the class file within the archive
 */
public String getClassFilePath(String classname) {
	return classname;
}
@Override
public IModuleDescription getModuleDescription() {
	try {
		IJavaElement[] pkgs = getChildren();
		for (int j = 0, length = pkgs.length; j < length; j++) {
			// only look in the default package
			if (pkgs[j].getElementName().length() == 0) {
				OpenableElementInfo info = null;
				if (getKind() == IPackageFragmentRoot.K_SOURCE) {
					ICompilationUnit unit = ((PackageFragment) pkgs[j])
							.getCompilationUnit(TypeConstants.MODULE_INFO_FILE_NAME_STRING);
					if (unit instanceof CompilationUnit && unit.exists()) {
						info = (CompilationUnitElementInfo) ((CompilationUnit) unit)
								.getElementInfo();
						if (info != null)
							return info.getModule();
					}
				} else {
					IModularClassFile classFile = ((IPackageFragment)pkgs[j]).getModularClassFile();
					if (classFile.exists()) {
						return classFile.getModule();
					}
				}
				break;
			}
		}
	} catch (JavaModelException e) {
		Util.log(e);
	}
	return null;
}

public IModuleDescription getAutomaticModuleDescription() throws JavaModelException {
	return getAutomaticModuleDescription(getResolvedClasspathEntry());
}

IModuleDescription getAutomaticModuleDescription(IClasspathEntry classpathEntry) {
	String elementName = getElementName();
	Manifest manifest = null;
	switch (classpathEntry.getEntryKind()) {
		case IClasspathEntry.CPE_SOURCE:
			manifest = ((JavaProject) getJavaProject()).getManifest();
			elementName = getJavaProject().getElementName();
			break;
		case IClasspathEntry.CPE_LIBRARY:
			manifest = getManifest();
			break;
		case IClasspathEntry.CPE_PROJECT:
			JavaProject javaProject = (JavaProject) getJavaModel().getJavaProject(classpathEntry.getPath().lastSegment());
			manifest = javaProject.getManifest();
			elementName = javaProject.getElementName();
			break;
	}
	boolean nameFromManifest = true;
	char[] moduleName = AutomaticModuleNaming.determineAutomaticModuleNameFromManifest(manifest);
	if (moduleName == null) {
		nameFromManifest = false;
		moduleName = AutomaticModuleNaming.determineAutomaticModuleNameFromFileName(elementName, true, isArchive());
	}
	return new AbstractModule.AutoModule(this, String.valueOf(moduleName), nameFromManifest);
}

/** @see org.aspectj.org.eclipse.jdt.internal.compiler.env.IModulePathEntry#hasCompilationUnit(String, String) */
public boolean hasCompilationUnit(String qualifiedPackageName, String moduleName) {
	IPackageFragment fragment = getPackageFragment(qualifiedPackageName.replace('/', '.'));
	try {
		if (fragment.exists())
			return fragment.containsJavaResources();
	} catch (JavaModelException e) {
		// silent
	}
	return false;
}
/** Convenience lookup, though currently only JarPackageFragmentRoot is searched for a manifest. */
public Manifest getManifest() {
	return null;
}
}
