blob: f5def68d17e4bf939c02aca178967776a6fef166 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2017 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
* BEA - Daniel R Somerfield - Bug 88939
* Frits Jalvingh - Contribution for Bug 459831 - [launching] Support attaching
* external annotations to a JRE container
*******************************************************************************/
package org.eclipse.jdt.internal.launching;
import java.util.Arrays;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.jdt.core.ClasspathContainerInitializer;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IRuntimeClasspathEntry;
import org.eclipse.jdt.launching.JavaRuntime;
import org.eclipse.osgi.util.NLS;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* An entry on the runtime classpath that the user can manipulate
* and share in a launch configuration.
*
* @see org.eclipse.jdt.launching.IRuntimeClasspathEntry
* @since 2.0
*/
public class RuntimeClasspathEntry implements IRuntimeClasspathEntry {
/**
* This entry's type - must be set on creation.
*/
private int fType = -1;
/**
* This entry's classpath property.
*/
private int fClasspathProperty = -1;
/**
* This entry's associated build path entry.
*/
private IClasspathEntry fClasspathEntry = null;
/**
* The entry's resolved entry (lazily initialized)
*/
private IClasspathEntry fResolvedEntry = null;
/**
* Associated Java project, or <code>null</code>
*/
private IJavaProject fJavaProject = null;
/**
* The path if the entry was invalid and fClasspathEntry is null
*/
private IPath fInvalidPath;
/**
* Constructs a new runtime classpath entry based on the
* (build) classpath entry.
*
* @param entry the associated classpath entry
*/
public RuntimeClasspathEntry(IClasspathEntry entry) {
switch (entry.getEntryKind()) {
case IClasspathEntry.CPE_PROJECT:
setType(PROJECT);
break;
case IClasspathEntry.CPE_LIBRARY:
setType(ARCHIVE);
break;
case IClasspathEntry.CPE_VARIABLE:
setType(VARIABLE);
break;
default:
throw new IllegalArgumentException(NLS.bind(LaunchingMessages.RuntimeClasspathEntry_Illegal_classpath_entry__0__1, new String[] {entry.toString()}));
}
setClasspathEntry(entry);
initializeClasspathProperty();
}
/**
* Constructs a new container entry in the context of the given project
*
* @param entry classpath entry
* @param classpathProperty this entry's classpath property
*/
public RuntimeClasspathEntry(IClasspathEntry entry, int classpathProperty) {
switch (entry.getEntryKind()) {
case IClasspathEntry.CPE_CONTAINER:
setType(CONTAINER);
break;
case IClasspathEntry.CPE_PROJECT:
setType(PROJECT);
break;
case IClasspathEntry.CPE_LIBRARY:
setType(ARCHIVE);
break;
case IClasspathEntry.CPE_VARIABLE:
setType(VARIABLE);
break;
default:
throw new IllegalArgumentException(NLS.bind(LaunchingMessages.RuntimeClasspathEntry_Illegal_classpath_entry__0__1, new String[] {
entry.toString() }));
}
setClasspathEntry(entry);
setClasspathProperty(classpathProperty);
}
/**
* Reconstructs a runtime classpath entry from the given
* XML document root not.
*
* @param root a memento root doc element created by this class
* @exception CoreException if unable to restore from the given memento
*/
public RuntimeClasspathEntry(Element root) throws CoreException {
try {
setType(Integer.parseInt(root.getAttribute("type"))); //$NON-NLS-1$
} catch (NumberFormatException e) {
abort(LaunchingMessages.RuntimeClasspathEntry_Unable_to_recover_runtime_class_path_entry_type_2, e);
}
try {
setClasspathProperty(Integer.parseInt(root.getAttribute("path"))); //$NON-NLS-1$
} catch (NumberFormatException e) {
abort(LaunchingMessages.RuntimeClasspathEntry_Unable_to_recover_runtime_class_path_entry_location_3, e);
}
// source attachment
IPath sourcePath = null;
IPath rootPath = null;
String path = root.getAttribute("sourceAttachmentPath"); //$NON-NLS-1$
if (path != null && path.length() > 0) {
sourcePath = new Path(path);
}
path = root.getAttribute("sourceRootPath"); //$NON-NLS-1$
if (path != null && path.length() > 0) {
rootPath = new Path(path);
}
switch (getType()) {
case PROJECT :
String name = root.getAttribute("projectName"); //$NON-NLS-1$
if (isEmpty(name)) {
abort(LaunchingMessages.RuntimeClasspathEntry_Unable_to_recover_runtime_class_path_entry___missing_project_name_4, null);
} else {
IProject proj = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
setClasspathEntry(JavaCore.newProjectEntry(proj.getFullPath()));
}
break;
case ARCHIVE :
path = root.getAttribute("externalArchive"); //$NON-NLS-1$
if (isEmpty(path)) {
// internal
path = root.getAttribute("internalArchive"); //$NON-NLS-1$
if (isEmpty(path)) {
abort(LaunchingMessages.RuntimeClasspathEntry_Unable_to_recover_runtime_class_path_entry___missing_archive_path_5, null);
} else {
setClasspathEntry(createLibraryEntry(sourcePath, rootPath, path));
}
} else {
// external
setClasspathEntry(createLibraryEntry(sourcePath, rootPath, path));
}
break;
case VARIABLE :
String var = root.getAttribute("containerPath"); //$NON-NLS-1$
if (isEmpty(var)) {
abort(LaunchingMessages.RuntimeClasspathEntry_Unable_to_recover_runtime_class_path_entry___missing_variable_name_6, null);
} else {
setClasspathEntry(JavaCore.newVariableEntry(new Path(var), sourcePath, rootPath));
}
break;
case CONTAINER :
var = root.getAttribute("containerPath"); //$NON-NLS-1$
if (isEmpty(var)) {
abort(LaunchingMessages.RuntimeClasspathEntry_Unable_to_recover_runtime_class_path_entry___missing_variable_name_6, null);
} else {
setClasspathEntry(JavaCore.newContainerEntry(new Path(var)));
}
break;
}
String name = root.getAttribute("javaProject"); //$NON-NLS-1$
if (isEmpty(name)) {
fJavaProject = null;
} else {
IProject project2 = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
fJavaProject = JavaCore.create(project2);
}
}
private IClasspathEntry createLibraryEntry(IPath sourcePath, IPath rootPath, String path) {
Path p = new Path(path);
if (!p.isAbsolute())
{
fInvalidPath = p;
return null;
//abort("There was a problem with path \" " + path + "\": paths must be absolute.", null);
}
return JavaCore.newLibraryEntry(p, sourcePath, rootPath);
}
/**
* Throws an internal error exception
* @param message the message
* @param e the error
* @throws CoreException the new {@link CoreException}
*/
protected void abort(String message, Throwable e) throws CoreException {
IStatus s = new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), IJavaLaunchConfigurationConstants.ERR_INTERNAL_ERROR, message, e);
throw new CoreException(s);
}
/**
* @see IRuntimeClasspathEntry#getType()
*/
@Override
public int getType() {
return fType;
}
/**
* Sets this entry's type
*
* @param type this entry's type
*/
private void setType(int type) {
fType = type;
}
/**
* Sets the classpath entry associated with this runtime classpath entry.
* Clears the cache of the resolved entry.
*
* @param entry the classpath entry associated with this runtime classpath entry
*/
private void setClasspathEntry(IClasspathEntry entry) {
fClasspathEntry = entry;
fResolvedEntry = null;
}
/**
* @see IRuntimeClasspathEntry#getClasspathEntry()
*/
@Override
public IClasspathEntry getClasspathEntry() {
return fClasspathEntry;
}
/**
* @see IRuntimeClasspathEntry#getMemento()
*/
@Override
public String getMemento() throws CoreException {
Document doc = DebugPlugin.newDocument();
Element node = doc.createElement("runtimeClasspathEntry"); //$NON-NLS-1$
doc.appendChild(node);
node.setAttribute("type", (new Integer(getType())).toString()); //$NON-NLS-1$
node.setAttribute("path", (new Integer(getClasspathProperty())).toString()); //$NON-NLS-1$
switch (getType()) {
case PROJECT :
node.setAttribute("projectName", getPath().lastSegment()); //$NON-NLS-1$
break;
case ARCHIVE :
IResource res = getResource();
if (res == null) {
node.setAttribute("externalArchive", getPath().toString()); //$NON-NLS-1$
} else {
node.setAttribute("internalArchive", res.getFullPath().toString()); //$NON-NLS-1$
}
break;
case VARIABLE :
case CONTAINER :
node.setAttribute("containerPath", getPath().toString()); //$NON-NLS-1$
break;
}
if (getSourceAttachmentPath() != null) {
node.setAttribute("sourceAttachmentPath", getSourceAttachmentPath().toString()); //$NON-NLS-1$
}
if (getSourceAttachmentRootPath() != null) {
node.setAttribute("sourceRootPath", getSourceAttachmentRootPath().toString()); //$NON-NLS-1$
}
if (getExternalAnnotationsPath() != null) {
node.setAttribute("externalAnnotationsPath", getExternalAnnotationsPath().toString()); //$NON-NLS-1$
}
if (getJavaProject() != null) {
node.setAttribute("javaProject", getJavaProject().getElementName()); //$NON-NLS-1$
}
return DebugPlugin.serializeDocument(doc);
}
/**
* @see IRuntimeClasspathEntry#getPath()
*/
@Override
public IPath getPath() {
IClasspathEntry entry = getClasspathEntry();
return entry != null ? entry.getPath() : fInvalidPath;
}
/**
* @see IRuntimeClasspathEntry#getResource()
*/
@Override
public IResource getResource() {
switch (getType()) {
case CONTAINER:
case VARIABLE:
return null;
default:
return getResource(getPath());
}
}
/**
* Returns the resource in the workspace associated with the given
* absolute path, or <code>null</code> if none. The path may have
* a device.
*
* @param path absolute path, or <code>null</code>
* @return resource or <code>null</code>
*/
protected IResource getResource(IPath path) {
if (path != null) {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
if (getType() == PROJECT) {
// project entry should always have a workspace relative path, try the fastest lookup first
IResource member = root.findMember(path);
if (member != null) {
return member;
}
}
// look for files or folders with the given path
IFile file = root.getFileForLocation(path);
if (file != null) {
return file;
}
if (getType() != ARCHIVE) {
IContainer container = root.getContainerForLocation(path);
if (container != null) {
return container;
}
}
@SuppressWarnings("deprecation")
IFile[] files = root.findFilesForLocation(path);
if (files.length > 0) {
return files[0];
}
if (getType() != ARCHIVE) {
@SuppressWarnings("deprecation")
IContainer[] containers = root.findContainersForLocation(path);
if (containers.length > 0) {
return containers[0];
}
}
return root.findMember(path);
}
return null;
}
/**
* @see IRuntimeClasspathEntry#getSourceAttachmentPath()
*/
@Override
public IPath getSourceAttachmentPath() {
IClasspathEntry entry = getClasspathEntry();
return entry != null ? entry.getSourceAttachmentPath() : null;
}
/**
* @see IRuntimeClasspathEntry#setSourceAttachmentPath(IPath)
*/
@Override
public void setSourceAttachmentPath(IPath path) {
if (path != null && path.isEmpty()) {
path = null;
}
updateClasspathEntry(getPath(), path, getSourceAttachmentRootPath(), getExternalAnnotationsPath());
}
@Override
public IPath getExternalAnnotationsPath() {
IClasspathEntry entry = getClasspathEntry();
if (null != entry) {
String s = findClasspathAttribute(entry.getExtraAttributes(), IClasspathAttribute.EXTERNAL_ANNOTATION_PATH);
if (null != s) {
return new Path(s);
}
}
return null;
}
private static String findClasspathAttribute(IClasspathAttribute[] attributes, String name) {
for(int i = attributes.length; --i >= 0;) {
if(name.equals(attributes[i].getName())) {
return attributes[i].getValue();
}
}
return null;
}
@Override
public void setExternalAnnotationsPath(IPath path) {
if (path != null && path.isEmpty()) {
path = null;
}
updateClasspathEntry(getPath(), getSourceAttachmentPath(), getSourceAttachmentRootPath(), path);
}
/**
* @see IRuntimeClasspathEntry#getSourceAttachmentRootPath()
*/
@Override
public IPath getSourceAttachmentRootPath() {
IClasspathEntry entry = getClasspathEntry();
IPath path = entry != null ? getClasspathEntry().getSourceAttachmentRootPath() : null;
if (path == null && getSourceAttachmentPath() != null) {
return Path.EMPTY;
}
return path;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeClasspathEntry#setSourceAttachmentRootPath(org.eclipse.core.runtime.IPath)
*/
@Override
public void setSourceAttachmentRootPath(IPath path) {
if (path != null && path.isEmpty()) {
path = null;
}
updateClasspathEntry(getPath(), getSourceAttachmentPath(), path, getExternalAnnotationsPath());
}
/**
* Initializes the classpath property based on this entry's type.
*/
private void initializeClasspathProperty() {
switch (getType()) {
case VARIABLE:
if (getVariableName().equals(JavaRuntime.JRELIB_VARIABLE)) {
setClasspathProperty(STANDARD_CLASSES);
} else {
setClasspathProperty(USER_CLASSES);
}
break;
case PROJECT:
case ARCHIVE:
setClasspathProperty(USER_CLASSES);
break;
default:
break;
}
}
/**
* @see IRuntimeClasspathEntry#setClasspathProperty(int)
*/
@Override
public void setClasspathProperty(int location) {
fClasspathProperty = location;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeClasspathEntry#getClasspathProperty()
*/
@Override
public int getClasspathProperty() {
return fClasspathProperty;
}
/**
* @see IRuntimeClasspathEntry#getLocation()
*/
@Override
public String getLocation() {
IPath path = null;
switch (getType()) {
case PROJECT :
IJavaProject pro = (IJavaProject) JavaCore.create(getResource());
if (pro != null) {
try {
path = pro.getOutputLocation();
} catch (JavaModelException e) {
LaunchingPlugin.log(e);
}
}
break;
case ARCHIVE :
path = getPath();
break;
case VARIABLE :
IClasspathEntry resolved = getResolvedClasspathEntry();
if (resolved != null) {
path = resolved.getPath();
}
break;
case CONTAINER :
break;
}
return resolveToOSPath(path);
}
/**
* Returns the OS path for the given absolute or workspace relative path
* @param path the path
* @return the OS path
*/
protected String resolveToOSPath(IPath path) {
if (path != null) {
IResource res = null;
if (path.getDevice() == null) {
// if there is no device specified, find the resource
res = getResource(path);
}
if (res == null) {
return path.toOSString();
}
IPath location = res.getLocation();
if (location != null) {
return location.toOSString();
}
}
return null;
}
/**
* @see IRuntimeClasspathEntry#getVariableName()
*/
@Override
public String getVariableName() {
if (getType() == IRuntimeClasspathEntry.VARIABLE || getType() == IRuntimeClasspathEntry.CONTAINER) {
return getPath().segment(0);
}
return null;
}
/**
* @see Object#equals(Object)
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof IRuntimeClasspathEntry) {
IRuntimeClasspathEntry r = (IRuntimeClasspathEntry)obj;
if (getType() == r.getType() && getClasspathProperty() == r.getClasspathProperty()) {
if (getType() == IRuntimeClasspathEntry.CONTAINER) {
String id = getPath().segment(0);
ClasspathContainerInitializer initializer = JavaCore.getClasspathContainerInitializer(id);
IJavaProject javaProject1 = getJavaProject();
IJavaProject javaProject2 = r.getJavaProject();
if (initializer == null || javaProject1 == null || javaProject2 == null) {
// containers are equal if their ID is equal by default
return getPath().equals(r.getPath());
}
Object comparisonID1 = initializer.getComparisonID(getPath(), javaProject1);
Object comparisonID2 = initializer.getComparisonID(r.getPath(), javaProject2);
return comparisonID1.equals(comparisonID2);
} else if (getPath() != null && getPath().equals(r.getPath())) {
IPath sa1 = getSourceAttachmentPath();
IPath root1 = getSourceAttachmentRootPath();
IPath sa2 = r.getSourceAttachmentPath();
IPath root2 = r.getSourceAttachmentRootPath();
return equal(sa1, sa2) && equal(root1, root2);
}
}
}
return false;
}
/**
* Returns whether the given objects are equal, accounting for null
* @param one the object to compare
* @param two the object to compare to
* @return the equality of the objects
*/
protected boolean equal(Object one, Object two) {
if (one == null) {
return two == null;
}
return one.equals(two);
}
/**
* @see Object#hashCode()
*/
@Override
public int hashCode() {
if (getType() == CONTAINER) {
return getPath().segment(0).hashCode() + getType();
}
return getPath().hashCode() + getType();
}
/**
* @see IRuntimeClasspathEntry#getSourceAttachmentLocation()
*/
@Override
public String getSourceAttachmentLocation() {
IPath path = null;
switch (getType()) {
case VARIABLE :
case ARCHIVE :
IClasspathEntry resolved = getResolvedClasspathEntry();
if (resolved != null) {
path = resolved.getSourceAttachmentPath();
}
break;
default :
break;
}
return resolveToOSPath(path);
}
/**
* @see IRuntimeClasspathEntry#getSourceAttachmentRootLocation()
*/
@Override
public String getSourceAttachmentRootLocation() {
IPath path = null;
switch (getType()) {
case VARIABLE :
case ARCHIVE :
IClasspathEntry resolved = getResolvedClasspathEntry();
if (resolved != null) {
path = resolved.getSourceAttachmentRootPath();
}
break;
default :
break;
}
if (path != null) {
return path.toOSString();
}
return null;
}
/**
* Creates a new underlying classpath entry for this runtime classpath entry
* with the given paths, due to a change in source attachment.
* @param path the path
* @param sourcePath the source path
* @param rootPath the root path
*/
protected void updateClasspathEntry(IPath path, IPath sourcePath, IPath rootPath, IPath annotationsPath) {
IClasspathEntry entry = null;
IClasspathEntry original = getClasspathEntry();
switch (getType()) {
case ARCHIVE:
IClasspathAttribute[] extraAttributes = original.getExtraAttributes();
if (annotationsPath != null) {
extraAttributes = setClasspathAttribute(extraAttributes, IClasspathAttribute.EXTERNAL_ANNOTATION_PATH, annotationsPath.toPortableString());
}
entry = JavaCore.newLibraryEntry(path, sourcePath, rootPath, original.getAccessRules(), extraAttributes, original.isExported());
break;
case VARIABLE:
entry = JavaCore.newVariableEntry(path, sourcePath, rootPath);
break;
default:
return;
}
setClasspathEntry(entry);
}
private static IClasspathAttribute[] setClasspathAttribute(IClasspathAttribute[] attributes, String name, String value) {
for (int i = attributes.length; --i >= 0;) {
if (name.equals(attributes[i].getName())) {
IClasspathAttribute[] nw = Arrays.copyOf(attributes, attributes.length);
nw[i] = JavaCore.newClasspathAttribute(name, value);
return nw;
}
}
IClasspathAttribute[] nw = Arrays.copyOf(attributes, attributes.length + 1);
nw[attributes.length] = JavaCore.newClasspathAttribute(name, value);
return nw;
}
/**
* Returns the resolved classpath entry associated with this runtime
* entry, resolving if required.
* @return the resolved {@link IClasspathEntry}
*/
protected IClasspathEntry getResolvedClasspathEntry() {
if (fResolvedEntry == null) {
fResolvedEntry = JavaCore.getResolvedClasspathEntry(getClasspathEntry());
}
return fResolvedEntry;
}
protected boolean isEmpty(String string) {
return string == null || string.length() == 0;
}
@Override
public String toString() {
if (fClasspathEntry != null) {
return fClasspathEntry.toString();
}
return super.toString();
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IRuntimeClasspathEntry#getJavaProject()
*/
@Override
public IJavaProject getJavaProject() {
return fJavaProject;
}
/**
* Sets the Java project associated with this classpath entry.
*
* @param project Java project
*/
public void setJavaProject(IJavaProject project) {
fJavaProject = project;
}
@Override
public boolean isAutomodule() {
IClasspathAttribute[] extraAttributes = getClasspathEntry().getExtraAttributes();
for (IClasspathAttribute attribute : extraAttributes) {
if (IClasspathAttribute.MODULE.equals(attribute.getName()) && Boolean.TRUE.toString().equals(attribute.getValue())) {
return true;
}
}
return false;
}
}