package org.python.core;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Helper class handling the VM specific java package detection.
*/
public class JavaImportHelper {
private static final String DOT = ".";
/**
* Try to add the java package.
* <p>
* This is handy in cases where the package scan cannot run, or when the initial classpath does not contain all .jar
* files (such as in J2EE containers).
* <p>
* There is some self-healing in the sense that a correct, explicit import of a java class will succeed even if
* sys.modules already contains a Py.None entry for the corresponding java package.
*
* @param packageName The dotted name of the java package
* @param fromlist A tuple with the from names to import. Can be null or empty.
*
* @return <code>true</code> if a java package was doubtlessly identified and added, <code>false</code>
* otherwise.
*/
protected static boolean tryAddPackage(final String packageName, PyObject fromlist) {
// make sure we do not turn off the added flag, once it is set
boolean packageAdded = false;
if (packageName != null) {
// check explicit imports first (performance optimization)
// handle 'from java.net import URL' like explicit imports
List stringFromlist = getFromListAsStrings(fromlist);
Iterator fromlistIterator = stringFromlist.iterator();
while (fromlistIterator.hasNext()) {
String fromName = (String) fromlistIterator.next();
if (isJavaClass(packageName, fromName)) {
packageAdded = addPackage(packageName, packageAdded);
}
}
// handle 'import java.net.URL' style explicit imports
int dotPos = packageName.lastIndexOf(DOT);
if (dotPos > 0) {
String lastDottedName = packageName.substring(dotPos + 1);
String packageCand = packageName.substring(0, dotPos);
if (isJavaClass(packageCand, lastDottedName)) {
packageAdded = addPackage(packageCand, packageAdded);
}
}
// if all else fails, check already loaded packages
if (!packageAdded) {
// build the actual map with the packages known to the VM
Map packages = buildLoadedPackages();
// add known packages
String parentPackageName = packageName;
if (isLoadedPackage(packageName, packages)) {
packageAdded = addPackage(packageName, packageAdded);
}
dotPos = 0;
do {
dotPos = parentPackageName.lastIndexOf(DOT);
if (dotPos > 0) {
parentPackageName = parentPackageName.substring(0, dotPos);
if (isLoadedPackage(parentPackageName, packages)) {
packageAdded = addPackage(parentPackageName, packageAdded);
}
}
} while (dotPos > 0);
// handle package imports like 'from java import math'
fromlistIterator = stringFromlist.iterator();
while (fromlistIterator.hasNext()) {
String fromName = (String) fromlistIterator.next();
String fromPackageName = packageName + DOT + fromName;
if (isLoadedPackage(fromPackageName, packages)) {
packageAdded = addPackage(fromPackageName, packageAdded);
}
}
}
}
return packageAdded;
}
/**
* Check if a java package is already known to the VM.
* <p>
* May return <code>false</code> even if the given package name is a valid java package !
*
* @param packageName
*
* @return <code>true</code> if the package with the given name is already loaded by the VM, <code>false</code>
* otherwise.
*/
protected static boolean isLoadedPackage(String packageName) {
return isLoadedPackage(packageName, buildLoadedPackages());
}
/**
* Convert the fromlist into a java.lang.String based list.
* <p>
* Do some sanity checks: filter out '*' and empty tuples, as well as non tuples.
*
* @param fromlist
* @return a list containing java.lang.String entries
*/
private static final List getFromListAsStrings(PyObject fromlist) {
List stringFromlist = new ArrayList();
if (fromlist != null && fromlist != Py.EmptyTuple && fromlist instanceof PyTuple) {
Iterator iterator = ((PyTuple) fromlist).iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
if (obj instanceof String) {
String fromName = (String) obj;
if (!"*".equals(fromName)) {
stringFromlist.add(fromName);
}
}
}
}
return stringFromlist;
}
/**
* Faster way to check if a java package is already known to the VM.
* <p>
* May return <code>false</code> even if the given package name is a valid java package !
*
* @param packageName
* @param packages A Map containing all packages actually known to the VM. Such a Map can be obtained using
* {@link JavaImportHelper.buildLoadedPackagesTree()}
*
* @return <code>true</code> if the package with the given name is already loaded by the VM, <code>false</code>
* otherwise.
*/
private static boolean isLoadedPackage(String javaPackageName, Map packages) {
boolean isLoaded = false;
if (javaPackageName != null) {
isLoaded = packages.containsKey(javaPackageName);
}
return isLoaded;
}
/**
* Build a <code>Map</code> of the currently known packages to the VM.
* <p>
* All parent packages appear as single entries like python modules, e.g. <code>java</code>,
* <code>java.lang</code>, <code>java.lang.reflect</code>,
*/
private static Map buildLoadedPackages() {
TreeMap packageMap = new TreeMap();
Package[] packages = Package.getPackages();
for (int i = 0; i < packages.length; i++) {
String packageName = packages[i].getName();
packageMap.put(packageName, "");
int dotPos = 0;
do {
dotPos = packageName.lastIndexOf(DOT);
if (dotPos > 0) {
packageName = packageName.substring(0, dotPos);
packageMap.put(packageName, "");
}
} while (dotPos > 0);
}
return packageMap;
}
/**
* @return <code>true</code> if the java class can be found by the current
* Py classloader setup
*/
private static boolean isJavaClass(String packageName, String className) {
return className != null && className.length() > 0 && Py.findClass(packageName + "." + className) != null;
}
/**
* Add a java package to sys.modules, if not already done
*
* @return <code>true</code> if something was really added, <code>false</code> otherwise
*/
private static boolean addPackage(String packageName, boolean packageAdded) {
PyObject modules = Py.getSystemState().modules;
String internedPackageName = packageName.intern();
PyObject module = modules.__finditem__(internedPackageName);
// a previously failed import could have created a Py.None entry in sys.modules
if (module == null || module == Py.None) {
int dotPos = 0;
do {
PyJavaPackage p = PySystemState.add_package(packageName);
if (dotPos == 0) {
modules.__setitem__(internedPackageName, p);
} else {
module = modules.__finditem__(internedPackageName);
if (module == null || module == Py.None) {
modules.__setitem__(internedPackageName, p);
}
}
dotPos = packageName.lastIndexOf(DOT);
if (dotPos > 0) {
packageName = packageName.substring(0, dotPos);
internedPackageName = packageName.intern();
}
} while (dotPos > 0);
// make sure not to turn off the packageAdded flag
packageAdded = true;
}
return packageAdded;
}
}