blob: 43213667a0ddded7d2fa88866c83b4488509d0d3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2015 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.eclipse.jdt.internal.debug.eval.ast.engine;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdi.internal.TypeImpl;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.debug.core.IJavaClassObject;
import org.eclipse.jdt.debug.core.IJavaClassType;
import org.eclipse.jdt.debug.core.IJavaFieldVariable;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaReferenceType;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.eval.ast.instructions.InstructionsEvaluationMessages;
import com.ibm.icu.text.MessageFormat;
import com.sun.jdi.InvocationException;
/**
* Common runtime context code for class loading and cache of class
* loader/java.lang.Class.
*
* @since 3.2
*/
public abstract class AbstractRuntimeContext implements IRuntimeContext {
/**
* Cache of class loader for this runtime context
*/
private IJavaObject fClassLoader;
/**
* Cache of java.lang.Class type
*/
private IJavaClassType fJavaLangClass;
/**
* Java project context
*/
protected IJavaProject fProject;
public static final String CLASS = "java.lang.Class"; //$NON-NLS-1$
public static final String FOR_NAME = "forName"; //$NON-NLS-1$
public static final String FOR_NAME_SIGNATURE = "(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"; //$NON-NLS-1$
public AbstractRuntimeContext(IJavaProject project) {
fProject = project;
}
/**
* Returns the class loader used to load classes for this runtime context or
* <code>null</code> when loaded by the bootstrap loader
*
* @return the class loader used to load classes for this runtime context or
* <code>null</code> when loaded by the bootstrap loader
* @throws CoreException
* if unable to resolve a class loader
*/
protected IJavaObject getClassLoaderObject() throws CoreException {
if (fClassLoader == null) {
fClassLoader = getReceivingType().getClassLoaderObject();
}
return fClassLoader;
}
/**
* Return the java.lang.Class type.
*
* @return the java.lang.Class type
* @throws CoreException
* if unable to retrieve the type
*/
protected IJavaClassType getJavaLangClass() throws CoreException {
if (fJavaLangClass == null) {
IJavaType[] types = getVM().getJavaTypes(CLASS);
if (types == null || types.length != 1) {
throw new CoreException(
new Status(
IStatus.ERROR,
JDIDebugPlugin.getUniqueIdentifier(),
IStatus.OK,
MessageFormat
.format(InstructionsEvaluationMessages.Instruction_No_type,
new Object[] { CLASS }), null));
}
fJavaLangClass = (IJavaClassType) types[0];
}
return fJavaLangClass;
}
/**
* Invokes Class.classForName(String, boolean, ClassLoader) on the target to
* force load the specified class.
*
* @param qualifiedName
* name of class to load
* @param loader
* the class loader to use or <code>null</code> if the bootstrap
* loader
* @return the loaded class
* @throws CoreException
* if loading fails
*/
protected IJavaClassObject classForName(String qualifiedName,
IJavaObject loader) throws CoreException {
String tname = qualifiedName;
if (tname.startsWith("[")) { //$NON-NLS-1$
tname = TypeImpl.signatureToName(qualifiedName);
}
IJavaType[] types = getVM().getJavaTypes(tname);
if (types != null && types.length > 0) {
// find the one with the right class loader
for (IJavaType type2 : types) {
if ( type2 instanceof IJavaReferenceType){
IJavaReferenceType type = (IJavaReferenceType) type2;
IJavaObject cloader = type.getClassLoaderObject();
if (isCompatibleLoader(loader, cloader)) {
return type.getClassObject();
}
}
}
}
IJavaValue loaderArg = loader;
if (loader == null) {
loaderArg = getVM().nullValue();
}
// prevent the name string from being collected during the class lookup
// call
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=301412
final IJavaValue name = getVM().newValue(qualifiedName);
((IJavaObject) name).disableCollection();
IJavaValue[] args = new IJavaValue[] { name, getVM().newValue(true),
loaderArg };
try {
return (IJavaClassObject) getJavaLangClass().sendMessage(FOR_NAME,
FOR_NAME_SIGNATURE, args, getThread());
} catch (CoreException e) {
if (e.getStatus().getException() instanceof InvocationException) {
// Don't throw ClassNotFoundException
if (((InvocationException) e.getStatus().getException())
.exception().referenceType().name()
.equals("java.lang.ClassNotFoundException")) { //$NON-NLS-1$
return null;
}
}
throw e;
} finally {
((IJavaObject) name).enableCollection();
}
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.eval.ast.engine.IRuntimeContext#classForName
* (java.lang.String)
*/
@Override
public IJavaClassObject classForName(String name) throws CoreException {
return classForName(name, getClassLoaderObject());
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.jdt.internal.debug.eval.ast.engine.IRuntimeContext#getProject
* ()
*/
@Override
public IJavaProject getProject() {
return fProject;
}
/**
* Returns whether the class loaded by the <code>otherLoader</code> is
* compatible with the receiver's class loader. To be compatible, the
* other's loader must be the same or a parent of the receiver's loader.
*
* @param recLoader
* class loader of receiver
* @param otherLoader
* class loader of other class
* @return whether compatible
*/
private boolean isCompatibleLoader(IJavaObject recLoader,
IJavaObject otherLoader) throws CoreException {
if (recLoader == null || otherLoader == null) {
// if either class is a bootstrap loader, then they are compatible
// since all loaders
// stem from the bootstrap loader
return true;
}
if (recLoader.equals(otherLoader)) {
return true;
}
// check parent loaders
IJavaObject parent = getParentLoader(recLoader);
while (parent != null) {
if (parent.equals(otherLoader)) {
return true;
}
parent = getParentLoader(parent);
}
return false;
}
/**
* Returns the parent class loader of the given class loader object or
* <code>null</code> if none.
*
* @param loader
* class loader object
* @return parent class loader or <code>null</code>
* @throws CoreException
*/
private IJavaObject getParentLoader(IJavaObject loader)
throws CoreException {
// to avoid message send, first check for 'parent' field
IJavaFieldVariable field = loader.getField("parent", false); //$NON-NLS-1$
if (field != null) {
IJavaValue value = (IJavaValue) field.getValue();
if (value.isNull()) {
return null;
}
return (IJavaObject) value;
}
IJavaValue result = loader
.sendMessage(
"getParent", "()Ljava/lang/ClassLoader;", null, getThread(), false); //$NON-NLS-1$ //$NON-NLS-2$
if (result.isNull()) {
return null;
}
return (IJavaObject) result;
}
}