/* Copyright (c) Jython Developers */
package org.python.core.util;
import java.io.IOException;
import java.io.InputStream;
import java.util.EnumSet;
import org.python.core.BytecodeLoader;
import org.python.core.Py;
import org.python.core.PyCode;
import org.python.core.PyList;
import org.python.core.PyModule;
import org.python.core.PyObject;
import org.python.core.PyType;
import org.python.core.imp;
/**
* A base class for PEP-302 path hooks. Handles looking through source, compiled, package and module
* items in the right order, and creating and filling in modules.
*/
public abstract class importer<T> extends PyObject {
static enum EntryType {
IS_SOURCE, IS_BYTECODE, IS_PACKAGE
};
/** SearchOrder defines how we search for a module. */
final SearchOrderEntry[] searchOrder;
/** Module information */
protected static enum ModuleInfo {
ERROR, NOT_FOUND, MODULE, PACKAGE
};
public importer(PyType subType) {
super(subType);
searchOrder = makeSearchOrder();
}
public importer() {
searchOrder = makeSearchOrder();
}
/**
* Returns the separator between directories and files used by this type of importer.
*/
protected abstract String getSeparator();
/**
* Returns the value to fill in __path__ on a module with the given full module name created by
* this importer.
*/
protected abstract String makePackagePath(String fullname);
/**
* Given a full module name, return the potential file path in the archive (without extension).
*/
protected abstract String makeFilename(String fullname);
/**
* Given a full module name, return the potential file path including the archive (without
* extension).
*/
protected abstract String makeFilePath(String fullname);
/**
* Returns an entry for a filename from makeFilename with a potential suffix such that this
* importer can make a bundle with it, or null if fullFilename doesn't exist in this importer.
*/
protected abstract T makeEntry(String filenameAndSuffix);
/**
* Returns a Bundle for fullFilename and entry, the result from a makeEntry call for
* fullFilename.
*/
protected abstract Bundle makeBundle(String filenameAndSuffix, T entry);
private SearchOrderEntry[] makeSearchOrder(){
return new SearchOrderEntry[] {
new SearchOrderEntry(getSeparator() + "__init__$py.class",
EnumSet.of(EntryType.IS_PACKAGE, EntryType.IS_BYTECODE)),
new SearchOrderEntry(getSeparator() + "__init__.py",
EnumSet.of(EntryType.IS_PACKAGE, EntryType.IS_SOURCE)),
new SearchOrderEntry("$py.class", EnumSet.of(EntryType.IS_BYTECODE)),
new SearchOrderEntry(".py", EnumSet.of(EntryType.IS_SOURCE)),};
}
protected final PyObject importer_find_module(String fullname, String path) {
ModuleInfo moduleInfo = getModuleInfo(fullname);
if (moduleInfo == ModuleInfo.ERROR || moduleInfo == ModuleInfo.NOT_FOUND) {
return Py.None;
}
return this;
}
protected final PyObject importer_load_module(String fullname) {
ModuleCodeData moduleCodeData = getModuleCode(fullname);
if (moduleCodeData == null) {
return Py.None;
}
// the module *must* be in sys.modules before the loader executes the module code; the
// module code may (directly or indirectly) import itself
PyModule mod = imp.addModule(fullname);
mod.__dict__.__setitem__("__loader__", this);
if (moduleCodeData.isPackage) {
// add __path__ to the module *before* the code gets executed
PyList pkgpath = new PyList();
pkgpath.add(makePackagePath(fullname));
mod.__dict__.__setitem__("__path__", pkgpath);
}
imp.createFromCode(fullname, moduleCodeData.code, moduleCodeData.path);
Py.writeDebug("import", "import " + fullname + " # loaded from " + moduleCodeData.path);
return mod;
}
/**
* Bundle is an InputStream, bundled together with a method that can close the input stream and
* whatever resources are associated with it when the resource is imported.
*/
protected abstract static class Bundle {
public InputStream inputStream;
public Bundle(InputStream inputStream) {
this.inputStream = inputStream;
}
/**
* Close the underlying resource if necessary. Raises an IOError if a problem occurs.
*/
public abstract void close();
}
/**
* Given a path to a compiled file in the archive, return the modification time of the
* matching .py file.
*
* @param path to the compiled file
* @return long mtime of the .py, or -1 if no source is available
*/
protected abstract long getSourceMtime(String path);
/**
* Return module information for the module with the fully qualified name.
*
* @param fullname
* the fully qualified name of the module
* @return the module's information
*/
protected final ModuleInfo getModuleInfo(String fullname) {
String path = makeFilename(fullname);
for (SearchOrderEntry entry : searchOrder) {
T importEntry = makeEntry(path + entry.suffix);
if (importEntry == null) {
continue;
}
if (entry.type.contains(EntryType.IS_PACKAGE)) {
return ModuleInfo.PACKAGE;
}
return ModuleInfo.MODULE;
}
return ModuleInfo.NOT_FOUND;
}
/**
* Return the code object and its associated data for the module with the fully qualified name.
*
* @param fullname
* the fully qualified name of the module
* @return the module's ModuleCodeData object
*/
protected final ModuleCodeData getModuleCode(String fullname) {
String path = makeFilename(fullname);
String fullPath = makeFilePath(fullname);
if (path.length() < 0) {
return null;
}
for (SearchOrderEntry entry : searchOrder) {
String suffix = entry.suffix;
String searchPath = path + suffix;
String fullSearchPath = fullPath + suffix;
Py.writeDebug("import", "# trying " + searchPath);
T tocEntry = makeEntry(searchPath);
if (tocEntry == null) {
continue;
}
boolean isPackage = entry.type.contains(EntryType.IS_PACKAGE);
boolean isBytecode = entry.type.contains(EntryType.IS_BYTECODE);
long mtime = -1;
if (isBytecode) {
mtime = getSourceMtime(searchPath);
}
Bundle bundle = makeBundle(searchPath, tocEntry);
byte[] codeBytes;
try {
if (isBytecode) {
try {
codeBytes = imp.readCode(fullname, bundle.inputStream, true, mtime);
} catch (IOException ioe) {
throw Py.ImportError(ioe.getMessage() + "[path=" + fullSearchPath + "]");
}
if (codeBytes == null) {
// bad magic number or non-matching mtime in byte code, try next
continue;
}
} else {
codeBytes = imp.compileSource(fullname, bundle.inputStream, fullSearchPath);
}
} finally {
bundle.close();
}
PyCode code = BytecodeLoader.makeCode(fullname + "$py", codeBytes, fullSearchPath);
return new ModuleCodeData(code, isPackage, fullSearchPath);
}
return null;
}
/**
* Container for PyModule code - whether or not it's a package - and its path.
*/
protected class ModuleCodeData {
public PyCode code;
public boolean isPackage;
public String path;
public ModuleCodeData(PyCode code, boolean isPackage, String path) {
this.code = code;
this.isPackage = isPackage;
this.path = path;
}
}
/**
* A step in the module search order: the file suffix and its entry type.
*/
protected static class SearchOrderEntry {
public String suffix;
public EnumSet<EntryType> type;
public SearchOrderEntry(String suffix, EnumSet<EntryType> type) {
this.suffix = suffix;
this.type = type;
}
}
}