org.python.core.PyType Maven / Gradle / Ivy
Show all versions of jython-slim Show documentation
/* Copyright (c) Jython Developers */
package org.python.core;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.python.antlr.ast.cmpopType;
import org.python.expose.ExposeAsSuperclass;
import org.python.expose.ExposedDelete;
import org.python.expose.ExposedGet;
import org.python.expose.ExposedMethod;
import org.python.expose.ExposedNew;
import org.python.expose.ExposedSet;
import org.python.expose.ExposedType;
import org.python.expose.MethodType;
import org.python.expose.TypeBuilder;
import org.python.modules._weakref.WeakrefModule;
import org.python.util.Generic;
/**
* This class implements the Python type
object and the static methods and data
* structures that support the Python type system in Jython (the type registry).
*
* The class PyType
contains static data that describes Python types, that are
* consulted and modified through its static API (notably {@link #fromClass(Class)}). The data
* structures are guarded against modification by concurrent threads (or consultation while being
* modified). They support construction of type
objects that are visible to Python.
*
* Bootstrapping of the type system: The first attempt to construct or get a
* PyObject
(or instance of a subclass of PyObject
), to use the {@link Py}
* class utilities, or to call {@link PyType#fromClass(Class)}, causes the Jython type system to be
* initialised. By the time that call returns, the type system will be in working order: any
* PyType
s the application sees will be fully valid. Also, provided that the static
* initialisation of the PyObject
subclass in question is not obstructed for any other
* reason, the instance returned will also be fully functional. Note that it is possible to refer to
* C.class
, and (if it is not an exposed type) to produce a PyType
for it,
* without causing the static initialisation of C
.
*
* This may be no less than the reader expected, but we mention it because, for classes encountered
* during the bootstrapping of the type system, this guarantee is not offered. The (static)
* initialisation of the type system is highly reentrant. Classes that are used by the type system
* itself, and their instances, do encounter defective PyType
objects. Instances
* of these classes, which include mundane classes like PyNone
and
* PyString
, may exist before their class is statically initialised in the JVM sense.
* The type system has been implemented with this fact constantly in mind. The PyType
* encountered is always the "right" one — the unique instance representing the Python type of
* that class — but it may not be completely filled in. Debugging that enters these classes
* during bootstrapping will take surprising turns. Changes to these classes should also take this
* into account.
*/
/*
* As a Java object, PyType and its intimate subclass PyJavaType are not strongly encapsulated,
* internal structures are, on the whole, open to package access. A set of constructors is provided
* for different types of target class that allow us to set builtin, underlying_class and the Java
* proxy at construction time. PyType.fromClass, uses these constructors, and then calls init() to
* build the rest of the PyType state. After these steps, the PyType should be effectively
* immutable.
*
* type___new__ and PyType.newType use "blank" PyType constructors and do their own initialisation
* of the instance data.
*/
@ExposedType(name = "type", doc = BuiltinDocs.type_doc)
public class PyType extends PyObject implements Serializable, Traverseproc {
/**
* Constants (singletons) for PyType
s that we use repeatedly in the logic of
* PyType
and PyJavaType
. This avoids repeated calls to
* {@link PyType#fromClass(Class)}. The inner class pattern ensures they can be constructed
* before PyType
is initialised, and a unique instance of type(type)
* exists for {@link PyType#PyType(Class)} to use.
*/
protected static class Constant {
// Identify an object to be type(type). Warning: not initialised until fromClass is called.
static final PyType PYTYPE = new PyType(false);
static final PyType PYOBJECT = fromClass(PyObject.class);
static final PyType PYSTRING = fromClass(PyString.class);
}
/** The PyType
of PyType
(or type(type)
). */
public static final PyType TYPE = fromClass(PyType.class);
/**
* The type's name. builtin types include their fully qualified name, e.g.: time.struct_time.
*/
protected String name;
/** __base__, the direct base type or null. */
protected PyType base;
/** __bases__, the base classes. */
protected PyObject[] bases = Registry.EMPTY_PYOBJECT_ARRAY;
/** The real, internal __dict__. */
protected PyObject dict;
/** __mro__, the method resolution. order */
protected PyObject[] mro;
/** __flags__, the type's options. */
private long tp_flags;
/**
* The Java Class that instances of this type represent (when that is a PyObject
),
* or null
if the type is not a PyObject
, in which case the class is
* indicated through a {@link JyAttribute#JAVA_PROXY_ATTR}.
*/
protected Class underlying_class;
/** Whether this is a builtin type. */
protected boolean builtin;
/** Whether new instances of this type can be instantiated */
protected boolean instantiable = true;
/** Whether this type implements descriptor __get/set/delete__ methods. */
boolean hasGet;
boolean hasSet;
boolean hasDelete;
/** Whether this type allows subclassing. */
private boolean isBaseType = true;
/** Whether this type has a __dict__. */
protected boolean needs_userdict;
/** Whether this type has a __weakref__ slot (however all types are weakrefable). */
protected boolean needs_weakref;
/** Whether finalization is required for this type's instances (implements __del__). */
protected boolean needs_finalizer;
/** Whether this type's __getattribute__ is object.__getattribute__. */
private volatile boolean usesObjectGetattribute;
/** MethodCacheEntry version tag. */
private volatile Object versionTag = new Object();
/** The number of __slots__ defined by this type + bases. */
private int numSlots;
/** The number of __slots__ defined by this type itself. */
private int ownSlots = 0;
private transient ReferenceQueue subclasses_refq = new ReferenceQueue();
private Set> subclasses = Generic.linkedHashSet();
/** Brevity for JyAttribute.hasAttr(obj, JyAttribute.JAVA_PROXY_ATTR)
. */
static boolean hasProxyAttr(PyObject obj) {
return JyAttribute.hasAttr(obj, JyAttribute.JAVA_PROXY_ATTR);
}
/** Brevity for JyAttribute.getAttr(obj, JyAttribute.JAVA_PROXY_ATTR)
. */
static Object getProxyAttr(PyObject obj) {
return JyAttribute.getAttr(obj, JyAttribute.JAVA_PROXY_ATTR);
}
/** Brevity for JyAttribute.setAttr(obj, JyAttribute.JAVA_PROXY_ATTR, value)
. */
static void setProxyAttr(PyObject obj, Object value) {
JyAttribute.setAttr(obj, JyAttribute.JAVA_PROXY_ATTR, value);
}
/**
* The class PyType
contains a registry that describes Python types through a
* system of indexes and PyType
objects. Each PyObject
calls
* {@link PyType#fromClass(Class)}, which adds to this data, as it is being initialised. When
* the first descriptor is created, as the first exposed type is initialised, the registry has
* to be in a consistent, working state.
*
* PyType
itself is a PyObject
. In order to guarantee that the
* registry exists when we need it, we use a separate class whose initialisation is not hostage
* to the type system itself, as it would be it it were static data in PyType
. See
* the classic
* "The
* double-checked locking problem", which we use here to be ready rather than lazy.
*
* A further requirement is that the registry data structures be guarded against concurrent
* access. This class also contains the methods that manipulate the state, and they synchronise
* access to it on behalf of PyType
.
*/
private static final class Registry {
/** Mapping of Java classes to their PyTypes, under construction. */
private static final Map, PyType> classToNewType = new IdentityHashMap<>();
/** Mapping of Java classes to their TypeBuilders, until these are used. */
private static final Map, TypeBuilder> classToBuilder = new HashMap<>();
/** Acts as a non-blocking cache for PyType look-up. */
static ClassValue classToType = new ClassValue() {
@Override
protected PyType computeValue(Class c) throws IncompleteType {
synchronized (Registry.class) {
// Competing threads will block and this thread will win the return.
return resolveType(c);
}
}
};
/**
* An exception used internally to signal a result from {@code classToType.get()} when the
* type is still under construction.
*/
private static class IncompleteType extends RuntimeException {
/** The incompletely constructed {@code PyType}. */
final PyType type;
IncompleteType(PyType type) {
this.type = type;
}
}
/**
* Mapping of Java classes to their PyTypes that have been thrown as {@link IncompleteType}
* exceptions. That action causes the {@code computeValue()} in progress to be re-tried, so
* we have to have the answer ready.
*/
private static final Map, PyType> classToThrownType = new IdentityHashMap<>();
/**
* A list of PyObject
sub-classes, instances of which are used in the type
* system itself. We require to reference their PyType
s before Java static
* initialisation can produce a builder, because that initialisation itself depends on
* PyType
s of classes in this list, circularly. These classes must, however, be
* Java initialised and have complete PyType
s by the time the static
* initialisation of PyObject
completes.
*/
// @formatter:off
private static final Class[] BOOTSTRAP_TYPES = {
PyObject.class,
PyType.class,
PyBuiltinCallable.class,
PyDataDescr.class,
PyString.class,
};
// @formatter:on
/**
* The set of classes that we should not, at the time we create their PyType
,
* try to Java-initialise if they do not have a builder. The PyType for such a class is
* partially initialised, and needs to be completed when a builder appears. Classes come off
* this list when fully initialised, and for bootstrap types, we'll check that in the static
* initialisation of PyObject.
*/
static final Set> deferredInit = new HashSet<>(Arrays.asList(BOOTSTRAP_TYPES));
/**
* True if the current {@link #resolveType(Class)} call is a top-level call; false if
* {@link #resolveType(Class)} is called reentrantly, while attempting to type some other
* class.
*/
private static int depth = 0;
/** Java types awaiting processing of their inner classes. */
private static Set needsInners = new HashSet<>();
/** Handy constant: zero-length array. */
static final PyObject[] EMPTY_PYOBJECT_ARRAY = {};
/**
* Test whether a given class is an exposed PyObject
. Amongst other things,
* this may be used to decide that type(c)
should be created as a
* PyType
instead of a PyJavaType
.
*
* @param c class to test
* @return whether an exposed type
*/
private static boolean isExposed(Class c) {
if (c.getAnnotation(ExposedType.class) != null) {
return true;
} else {
return ExposeAsSuperclass.class.isAssignableFrom(c);
}
}
/**
* Return the PyType
for the given target Java class. During the execution of
* this method, {@link #classToNewType} will hold any incompletely constructed
* {@link PyType}s, and a second attempt to resolve the same class will throw an
* {@link IncompleteType}, holding the incomplete {@code PyType}. This supports
* {@link PyType#fromClass(Class)} in the case where a PyType
has not already
* been published. See there for caveats. If the making of a type (it will be a
* PyJavaType
) might require the processing of inner classes,
* {@link PyType#fromClass(Class)} is called recursively for each.
*
* The caller guarantees that this thread holds the lock on the registry.
*
* @param c for which a PyType is to be created
* @throws IncompleteType to signal an incompletely constructed result
*/
static PyType resolveType(Class c) throws IncompleteType {
// log("resolve", c);
PyType type = classToNewType.get(c);
if (type != null) {
/*
* The type for c is still under construction, so we cannot return it normally,
* which would publish it immediately to other threads. The client must be our
* thread, calling re-entrantly, so we sneak it a reference in an exception, and
* remember we have done this.
*/
classToThrownType.put(c, type);
// log("> ", type);
throw new IncompleteType(type);
} else if ((type = classToThrownType.remove(c)) != null) {
/*
* The type for c has been fully constructed, but was ignored because an exception
* was raised between the call to computeValue() and the return. .
*/
} else if (depth > 0) {
/*
* This is a nested call about a class c we haven't seen before. Create (or choose
* an existing) type for c.
*/
depth += 1;
addFromClass(c);
depth -= 1;
type = classToNewType.remove(c);
} else {
/*
* This is a top-level call about a class c we haven't seen before. Create (or
* choose an existing) type for c. (In rare circumstances a thread that saw the
* cache miss will arrive here after some other thread populates classToNewType. The
* duplicate will be discarded, but the implementation must ensure this is harmless.
*/
assert needsInners.isEmpty();
try {
// Signal to further invocations that they are nested.
depth = 1;
// Create (or choose an existing) type for c, accumulating classes in
// needsInners.
addFromClass(c);
// Process inner classes too, if necessary. (This invalidates needsInners.)
if (!needsInners.isEmpty()) {
processInners();
}
} finally {
// Guarantee subsequent calls are top-level and needsInners is empty.
depth = 0;
needsInners.clear();
}
type = classToNewType.remove(c);
}
/*
* Return the PyType we made for c, which is now complete. (We may still be part way
* through making others.)
*/
// log("+-->", type);
return type;
}
/**
* Make the PyType
of each inner class of the classes in {@link #needsInners}
* into an attribute of its Python representation (except where shadowed). This will
* normally mean creating a PyType
for each inner class.
*
* Calling this method sets {@link #topLevel} = true
and may replace the
* contents of {@link #needsInners}.
*/
private static void processInners() {
// Take a copy for iteration since needsInners gets modified in the loop.
PyJavaType[] ni = needsInners.toArray(new PyJavaType[needsInners.size()]);
// Ensure calls to fromClass are top-level.
depth = 0;
needsInners.clear();
for (PyJavaType javaType : ni) {
Class forClass = javaType.getProxyType();
for (Class inner : forClass.getClasses()) {
/*
* Only add the class if there isn't something else with that name and it came
* from this class.
*/
if (inner.getDeclaringClass() == forClass
&& javaType.dict.__finditem__(inner.getSimpleName()) == null) {
// This call is a top-level fromClass and destroys needsInners.
PyType innerType = fromClass(inner);
javaType.dict.__setitem__(inner.getSimpleName(), innerType);
}
}
}
}
/**
* Add a new PyType
or {@link PyJavaType} entry for the given class to
* {@link #classToNewType}, respecting the {@link ExposeAsSuperclass} marker interface. It
* is known at the point this method is called that the class is not already registered.
*
* The caller guarantees that this thread holds the lock on the registry.
*
* @param c for which a PyType is to be created in {@link #classToNewType}
*/
private static void addFromClass(Class c) {
if (ExposeAsSuperclass.class.isAssignableFrom(c)) {
// Expose c with the Python type of its superclass, recursively as necessary.
PyType exposedAs = fromClass(c.getSuperclass());
classToNewType.put(c, exposedAs);
} else {
// Create and add a new Python type for this Java class
createType(c);
}
}
/**
* Create a new PyType
or {@link PyJavaType} for the given class in
* {@link #classToNewType}. It is known at the point this method is called that the class is
* not already registered.
*
* The caller guarantees that this thread holds the lock on the registry.
*
* @param c for which a PyType is to be added to {@link #classToNewType}
*/
private static void createType(Class c) {
PyType newtype;
if (PyObject.class.isAssignableFrom(c)) {
// c is a PyObject
if (isExposed(c)) {
// c is an exposed type (therefore should be a PyObject of some kind).
if (c != PyType.class) {
newtype = new PyType(c);
} else {
// The one PyType that PyType(Class) can't make.
newtype = Constant.PYTYPE;
}
} else {
// c is a non-exposed PyObject: expose reflectively.
newtype = new PyJavaType(c, true);
}
} else {
// c is not a PyObject: expose reflectively via a proxy.
if (c != Class.class) {
newtype = new PyJavaType(c, false);
} else {
// The proxy that PyJavaType(Class, boolean) can't make.
newtype = PyJavaType.Constant.CLASS;
}
}
// Enter the new PyType in the registry
classToNewType.put(c, newtype);
/*
* This is new to the registry, and we must initialise it. Note that the if c is a
* bootstrap type init() is called, but cut short, filling only the MRO. The work will
* be completed later as a side effect of addBuilder(). Until bootstrap types are all
* fully initialised, the type system demands care in use.
*/
newtype.init(needsInners);
newtype.invalidateMethodCache();
}
/**
* Register the {@link TypeBuilder} for the given class. This only really makes sense for a
* PyObject
. Initialising a properly-formed PyObject will usually result in a
* call to addBuilder
, thanks to code inserted by the Jython type exposer.
*
* @param c class for which this is the builder
* @param builder to register
*/
static synchronized void addBuilder(Class c, TypeBuilder builder) {
/*
* If c is not registered in the type system (e.g. c is being JVM-init'd by a "use" of
* c), the next fromClass will create, register, and fully init() type(c), because we're
* giving it a builder. In that case, we'll be done.
*/
classToBuilder.put(c, builder);
PyType type = fromClass(c);
/*
* The PyTypes of bootstrap classes may be registered "half-initialised", until a
* builder is available. They end up on the deferred list.
*/
if (deferredInit.remove(c)) {
/*
* c was on the deferred list, so it was registered without a builder, and type(c)
* was "half-initialised". We need to complete the initialisation.
*/
type.init(null);
}
}
/**
* Force static initialisation of the given sub-class of PyObject
in order to
* supply a builder (via {@link #addBuilder(Class, TypeBuilder)}, if the class wants to. If
* this is a class we haven't seen before, many reentrant calls to
* {@link PyType#fromClass(Class)} are produced here, for the descriptor classes that expose
* methods and attributes.
*
* @param c target class
*/
static synchronized void staticJavaInit(Class c) throws SecurityException {
try {
// Call Class.forName to force static initialisation.
Class.forName(c.getName(), true, c.getClassLoader());
} catch (ClassNotFoundException e) {
// Well, this is certainly surprising.
String msg = "Got ClassNotFound calling Class.forName on a class already found ";
throw new RuntimeException(msg, e);
} catch (ExceptionInInitializerError e) {
throw Py.JavaError(e);
}
}
/**
* For each class listed as a bootstrap type, try to make sure it fully initialised a
* PyType
.
*
* @return classes that did not completely initialise
*/
static synchronized Set> bootstrap() {
Set> missing = new HashSet<>();
for (Class c : BOOTSTRAP_TYPES) {
// Look-up should force at least first-stage initialisation (if not done before).
PyType type = fromClass(c);
if (deferredInit.contains(c)) {
// Only the first stage happened: encourage the rest to happen.
staticJavaInit(c);
}
// Check various symptoms
if (type.name == null || deferredInit.contains(c)) {
missing.add(c);
}
}
return missing;
}
// -------- Debugging for the registry --------
private static void log(String where, Class c) {
String name = abbr(c.getName());
System.err.printf("%s%s: %s %s thr=%s\n", pad(), where, name, names(classToNewType),
names(classToThrownType));
// logger.log(Level.INFO, "{0}{1}: {2}", new Object[] {pad, where, name});
}
private static void log(String kind, PyType result) {
String r = result.toString();
System.err.printf("%s%s %s %s thr=%s\n", pad(), kind, r, names(classToNewType),
names(classToThrownType));
}
/** For logging formatting. */
private static final String PAD = " ";
private static String abbr(String name) {
return name.replace("java.lang.", "j.l.").replace("org.python.core.", "");
}
private static String pad() {
int d = Math.min(Math.max(2 * depth, 0), PAD.length());
return PAD.substring(0, d);
}
private static List names(Map, PyType> map) {
ArrayList names = new ArrayList<>(map.size());
for (Class k : map.keySet()) {
names.add(abbr(k.getName()));
}
return names;
}
}
/**
* Create a "blank" PyType
instance, for a Python subclass of type
,
* that is, for a Python metatype. The {@link #underlying_class} is null
and
* {@link #builtin} is false
. This form is used by PyTypeDerived
.
*
* @param subtype Python subclass of this object
*/
protected PyType(PyType subtype) {
super(subtype);
}
/**
* Create a "blank" PyType
instance. The {@link #underlying_class} is
* null
and it is a {@link #builtin} is false
. This form is used in
* {@link #newType(PyNewWrapper, PyType, String, PyTuple, PyObject)}, which takes responsibility
* for filling in the other fields.
*/
private PyType() {}
/**
* Create the PyType
instance for type
itself, or for a subclass of
* it. Depending upon the argument, the {@link #underlying_class} is either this class, or
* null
, indicating a proxy. Either way, {@link #builtin} is true
. In
* practice this is used to create the PyType
s for PyType
itself and
* for java.lang.Class
.
*
* @param isProxy if true, this is a proxy
*/
protected PyType(boolean isProxy) {
super(false);
builtin = true;
underlying_class = isProxy ? null : this.getClass();
}
/**
* As {@link #PyType(Class)}, but also specifying the sub-type of Python type for which it is
* created.
*/
protected PyType(PyType subtype, Class c) {
super(subtype);
builtin = true;
underlying_class = c;
}
/**
* Create a built-in type for the given Java class. This is the class to be returned by
* __tojava__(Object.class)
(see {@link #__tojava__(Class)}), and by name in the
* string representation of the type.
*
* @param c the underlying class or null
.
*/
protected PyType(Class c) {
// PyType.TYPE is the object type, but that may still be null so use the "advance copy".
this(Constant.PYTYPE, c);
}
@ExposedNew
static final PyObject type___new__(PyNewWrapper new_, boolean init, PyType subtype,
PyObject[] args, String[] keywords) {
// Special case: type(x) should return x.getType()
if (args.length == 1 && keywords.length == 0) {
PyObject obj = args[0];
PyType objType = obj.getType();
// special case for PyStringMap so that it types as a dict
PyType psmType = fromClass(PyStringMap.class);
if (objType == psmType) {
return PyDictionary.TYPE;
}
return objType;
}
/*
* If that didn't trigger, we need 3 arguments. but ArgParser below may give a msg saying
* type() needs exactly 3.
*/
if (args.length + keywords.length != 3) {
throw Py.TypeError("type() takes 1 or 3 arguments");
}
ArgParser ap = new ArgParser("type()", args, keywords, "name", "bases", "dict");
String name = ap.getString(0);
PyTuple bases = (PyTuple) ap.getPyObjectByType(1, PyTuple.TYPE);
PyObject dict = ap.getPyObject(2);
if (!(dict instanceof AbstractDict)) {
throw Py.TypeError("type(): argument 3 must be dict, not " + dict.getType());
}
return newType(new_, subtype, name, bases, dict);
}
@ExposedMethod(doc = BuiltinDocs.type___init___doc)
final void type___init__(PyObject[] args, String[] kwds) {
if (kwds.length > 0) {
throw Py.TypeError("type.__init__() takes no keyword arguments");
}
if (args.length != 1 && args.length != 3) {
throw Py.TypeError("type.__init__() takes 1 or 3 arguments");
}
object___init__(Py.EmptyObjects, Py.NoKeywords);
}
public static PyObject newType(PyNewWrapper new_, PyType metatype, String name, PyTuple bases,
PyObject dict) {
PyObject[] tmpBases = bases.getArray();
PyType winner = findMostDerivedMetatype(tmpBases, metatype);
if (winner != metatype) {
PyObject winnerNew = winner.lookup("__new__");
if (winnerNew != null && winnerNew != new_) {
return invokeNew(winnerNew, winner, false,
new PyObject[] {new PyString(name), bases, dict}, Py.NoKeywords);
}
metatype = winner;
}
/*
* Use PyType as the metaclass for Python subclasses of Java classes rather than PyJavaType.
* Using PyJavaType as metaclass exposes the java.lang.Object methods on the type, which
* doesn't make sense for python subclasses.
*/
if (metatype == PyJavaType.Constant.CLASS) {
metatype = TYPE;
}
PyType type;
if (new_.for_type == metatype) {
// XXX: set metatype
type = new PyType();
} else {
type = new PyTypeDerived(metatype);
}
dict = ((AbstractDict) dict).copy();
type.name = name;
type.bases = tmpBases.length == 0 ? new PyObject[] {PyObject.TYPE} : tmpBases;
type.dict = dict;
type.tp_flags = Py.TPFLAGS_HEAPTYPE | Py.TPFLAGS_BASETYPE;
// Enable defining a custom __dict__ via a property, method, or other descriptor
boolean defines_dict = dict.__finditem__("__dict__") != null;
// immediately setup the javaProxy if applicable. may modify bases
List> interfaces = Generic.list();
Class baseProxyClass = getJavaLayout(type.bases, interfaces);
type.setupProxy(baseProxyClass, interfaces);
PyType base = type.base = best_base(type.bases);
if (!base.isBaseType) {
throw Py.TypeError(
String.format("type '%.100s' is not an acceptable base type", base.name));
}
type.createAllSlots(!(base.needs_userdict || defines_dict), !base.needs_weakref);
type.ensureAttributes();
type.invalidateMethodCache();
for (PyObject cur : type.bases) {
if (cur instanceof PyType) {
((PyType) cur).attachSubclass(type);
}
}
return type;
}
/**
* Used internally by {@link #createAllSlots()}. Builds a naive pseudo mro used to collect all
* slot names relevant for this type.
*
* @param tp type to be investigated
* @param dest list collecting all ancestors
* @param slotsMap map linking each type to its slots
* @return position of first ancestor that is not equal to or ancestor of primary base
*/
private static int findSlottedAncestors(PyType tp, List dest,
Map slotsMap) {
int baseEnd = 0;
if (tp.base != null && tp.base.numSlots > 0 && !slotsMap.containsKey(tp.base)) {
findSlottedAncestors(tp.base, dest, slotsMap);
}
baseEnd = dest.size();
PyObject slots = tp.dict.__finditem__("__slots__");
if (slots != null) {
dest.add(tp); // to keep track of order
slotsMap.put(tp, slots);
}
if (tp.bases.length > 1) {
for (PyObject base : tp.bases) {
if (base == tp.base || !(base instanceof PyType) || ((PyType) base).numSlots == 0
|| slotsMap.containsKey((PyType) base)) {
continue;
}
findSlottedAncestors((PyType) base, dest, slotsMap);
}
}
return baseEnd;
}
/**
* Used internally by {@link #createAllSlots()}. Adds all names in {@code slots} to
* {@code dest}.
*
* @param slots names to be added as slots
* @param dest set collecting all slots
*/
private static void insertSlots(PyObject slots, Set dest) {
if (slots instanceof PyString) {
slots = new PyTuple(slots);
}
// Check for valid slot names and create them.
for (PyObject slot : slots.asIterable()) {
String slotName = confirmIdentifier(slot);
if (slotName.equals("__dict__") || slotName.equals("__weakref__")) {
continue;
}
dest.add(slotName);
}
}
/**
* Create all slots and related descriptors.
*
* @param mayAddDict whether a __dict__ descriptor is allowed on this type
* @param mayAddWeak whether a __weakref__ descriptor is allowed on this type
*/
private void createAllSlots(boolean mayAddDict, boolean mayAddWeak) {
List slottedAncestors = Generic.list(base.mro.length + (bases.length - 1) * 3 + 1);
Map slotsMap = Generic.identityHashMap(slottedAncestors.size());
/*
* Here we would need the mro to search for slots (also in secondary bases) properly, but
* mro hasn't been set up yet. So we quickly (?) build a pseudo mro sufficient to find all
* slots.
*/
int baseEnd = findSlottedAncestors(this, slottedAncestors, slotsMap);
// baseEnd is the first position of an ancestor not equal to or ancestor of primary base
int slots_tmp = 0; // used for various purpose, first to accumulate maximal slot count
for (PyType anc : slottedAncestors) {
slots_tmp += anc.numSlots;
}
/*
* In allSlots we collect slots of primary base first, then of this type, then of secondary
* bases. At any time we prevent it from containing __dict__ or __weakref__. we know the
* required capacity, so the set likely won't be resized.
*/
Set allSlots = Generic.linkedHashSet(2 * slots_tmp);
if (baseEnd > 0) {
for (int i = 0; i < baseEnd; ++i) {
insertSlots(slotsMap.get(slottedAncestors.get(i)), allSlots);
}
}
assert allSlots.size() == base.numSlots;
boolean wantDict = false;
boolean wantWeak = false;
PyObject slots = dict.__finditem__("__slots__");
ownSlots = 0; // to keep track of slots defined by this type itself for isSolidBase
/*
* from now on, slots_tmp stores position where other ancestors than primary base begin
* (points to this type if it defines own slots)
*/
if (slots == null) {
wantDict = mayAddDict;
wantWeak = mayAddWeak;
slots_tmp = baseEnd;
} else {
if (slots instanceof PyString) {
slots = new PyTuple(slots);
}
// Check for valid slot names and create them. Handle two special cases
for (PyObject slot : slots.asIterable()) {
String slotName = confirmIdentifier(slot);
if (slotName.equals("__dict__")) {
if (!mayAddDict || wantDict) {
/*
* CPython is stricter here, but this seems arbitrary. To reproduce CPython
* behavior:
*/
// if (base != PyObject.TYPE) {
// throw Py.TypeError("__dict__ slot disallowed: we already got one");
// }
} else {
wantDict = true;
continue;
}
} else if (slotName.equals("__weakref__")) {
if ((!mayAddWeak || wantWeak) && base != PyObject.TYPE) {
/*
* CPython is stricter here, but this seems arbitrary. To reproduce CPython
* behavior:
*/
// if (base != PyObject.TYPE) {
// throw Py.TypeError("__weakref__ slot disallowed: we already got one");
// }
} else {
wantWeak = true;
continue;
}
}
if (allSlots.add(slotName)) {
++ownSlots;
}
}
if (bases.length > 1 && ((mayAddDict && !wantDict) || (mayAddWeak && !wantWeak))) {
// Secondary bases may provide weakrefs or dict
for (PyObject base : bases) {
if (base == this.base) {
// Skip primary base
continue;
}
if (base instanceof PyClass) {
// Classic base class provides both
if (mayAddDict && !wantDict) {
wantDict = true;
}
if (mayAddWeak && !wantWeak) {
wantWeak = true;
}
break;
}
PyType baseType = (PyType) base;
if (mayAddDict && !wantDict && baseType.needs_userdict) {
wantDict = true;
}
if (mayAddWeak && !wantWeak && baseType.needs_weakref) {
wantWeak = true;
}
if ((!mayAddDict || wantDict) && (!mayAddWeak || wantWeak)) {
// Nothing more to check
break;
}
}
}
slots_tmp = baseEnd + 1;
}
for (int i = slots_tmp; i < slottedAncestors.size(); ++i) {
insertSlots(slotsMap.get(slottedAncestors.get(i)), allSlots);
}
numSlots = allSlots.size();
int slotPos = 0;
Iterator slotIter = allSlots.iterator();
// skip slot names belonging to primary base (i.e. first base.numSlots ones)
for (; slotPos < base.numSlots; ++slotPos) {
slotIter.next();
}
while (slotIter.hasNext()) {
String slotName = slotIter.next();
slotName = mangleName(name, slotName);
if (dict.__finditem__(slotName) == null) {
dict.__setitem__(slotName, new PySlot(this, slotName, slotPos++));
} else {
--numSlots;
}
}
assert slotPos == numSlots;
if (wantDict) {
createDictSlot();
}
if (wantWeak) {
createWeakrefSlot();
}
needs_finalizer = needsFinalizer();
}
/**
* Create the __dict__ descriptor.
*/
private void createDictSlot() {
String doc = "dictionary for instance variables (if defined)";
dict.__setitem__("__dict__", new PyDataDescr(this, "__dict__", PyObject.class, doc) {
@Override
public boolean implementsDescrGet() {
return true;
}
@Override
public Object invokeGet(PyObject obj) {
return obj.getDict();
}
@Override
public boolean implementsDescrSet() {
return true;
}
@Override
public void invokeSet(PyObject obj, Object value) {
obj.setDict((PyObject) value);
}
@Override
public boolean implementsDescrDelete() {
return true;
}
@Override
public void invokeDelete(PyObject obj) {
obj.delDict();
}
});
needs_userdict = true;
}
/**
* Create the __weakref__ descriptor.
*/
private void createWeakrefSlot() {
String doc = "list of weak references to the object (if defined)";
dict.__setitem__("__weakref__", new PyDataDescr(this, "__weakref__", PyObject.class, doc) {
private static final String writeMsg = "attribute '%s' of '%s' objects is not writable";
private void notWritable(PyObject obj) {
throw Py.AttributeError(
String.format(writeMsg, "__weakref__", obj.getType().fastGetName()));
}
@Override
public boolean implementsDescrGet() {
return true;
}
@Override
public Object invokeGet(PyObject obj) {
PyList weakrefs = WeakrefModule.getweakrefs(obj);
switch (weakrefs.size()) {
case 0:
return Py.None;
case 1:
return weakrefs.pyget(0);
default:
return weakrefs;
}
}
@Override
public boolean implementsDescrSet() {
return true;
}
@Override
public void invokeSet(PyObject obj, Object value) {
// XXX: Maybe have PyDataDescr do notWritable() for us
notWritable(obj);
}
@Override
public boolean implementsDescrDelete() {
return true;
}
@Override
public void invokeDelete(PyObject obj) {
notWritable(obj);
}
});
needs_weakref = true;
}
/**
* Setup this type's special attributes.
*/
private void ensureAttributes() {
inheritSpecial();
// special case __new__, if function => static method
PyObject new_ = dict.__finditem__("__new__");
// XXX: java functions?
if (new_ != null && new_ instanceof PyFunction) {
dict.__setitem__("__new__", new PyStaticMethod(new_));
}
ensureDoc(dict);
ensureModule(dict);
// Calculate method resolution order
mro_internal();
cacheDescrBinds();
}
/**
* Inherit special attributes from the dominant base.
*/
private void inheritSpecial() {
if (!needs_userdict && base.needs_userdict) {
needs_userdict = true;
}
if (!needs_weakref && base.needs_weakref) {
needs_weakref = true;
}
}
/**
* Ensure dict contains a __doc__.
*
* @param dict a PyObject mapping
*/
public static void ensureDoc(PyObject dict) {
if (dict.__finditem__("__doc__") == null) {
dict.__setitem__("__doc__", Py.None);
}
}
/**
* Ensure dict contains a __module__, retrieving it from the current frame if it doesn't exist.
*
* @param dict a PyObject mapping
*/
public static void ensureModule(PyObject dict) {
if (dict.__finditem__("__module__") != null) {
return;
}
PyFrame frame = Py.getFrame();
if (frame == null) {
return;
}
PyObject name = frame.f_globals.__finditem__("__name__");
if (name != null) {
dict.__setitem__("__module__", name);
}
}
private static PyObject invokeNew(PyObject new_, PyType type, boolean init, PyObject[] args,
String[] keywords) {
PyObject obj;
if (new_ instanceof PyNewWrapper) {
obj = ((PyNewWrapper) new_).new_impl(init, type, args, keywords);
} else {
int n = args.length;
PyObject[] typePrepended = new PyObject[n + 1];
System.arraycopy(args, 0, typePrepended, 1, n);
typePrepended[0] = type;
obj = new_.__get__(null, type).__call__(typePrepended, keywords);
}
return obj;
}
/**
* Complete the initialisation of the PyType
for an exposed PyObject
.
* If the type is one of the deferred types (types used in bootstrapping the type system,
* predominantly), this will only fill in {@link #mro}, {@link #base} and {@link #bases}, and
* only provisionally. In that case, a second call is made as soon as
* {@link #addBuilder(Class, TypeBuilder)} is called by the initialisation of the type. In most
* cases, and on the second visit for deferred types, this call will fill {@link #dict},
* {@link #name} and all other descriptive state using the exposed characteristics.
*
* The caller guarantees that this thread holds the lock on the registry.
*
* @param needsInners ignored in the base implementation (see {@link PyJavaType#init(Set)}
*/
protected void init(Set needsInners) {
Class forClass = underlying_class;
/*
* We will have a builder already if the class has Java-initialised. We remove builder from
* list as we don't need it anymore, and it holds a reference to the class c.
*/
TypeBuilder builder = Registry.classToBuilder.remove(forClass);
if (builder == null) {
// Consider forcing static initialisation in order to get a builder.
if (!Registry.deferredInit.contains(forClass)) {
Registry.staticJavaInit(forClass);
builder = Registry.classToBuilder.remove(forClass);
}
}
if (builder == null) {
/*
* No builder has been supplied yet. Be content with partial initialisation of the
* PyType. When we have a builder, we'll init this again.
*/
Registry.deferredInit.add(forClass);
computeLinearMro(PyObject.class);
} else {
/* We have a builder so we can go the whole way. Signal c is no longer deferred. */
Registry.deferredInit.remove(forClass);
Class baseClass = builder.getBase();
if (baseClass == Object.class) {
// Base was not explicitly declared: default is Java super-class.
baseClass = underlying_class.getSuperclass();
}
computeLinearMro(baseClass);
// The builder supplies the name and collection of exposed methods and properties
name = builder.getName();
dict = builder.getDict(this);
String doc = builder.getDoc();
// Create a doc string if we don't have one already.
if (dict.__finditem__("__doc__") == null) {
PyObject docObj;
if (doc != null) {
// Not PyString(doc) as PyString.TYPE may be null during bootstrapping.
docObj = new PyString(Constant.PYSTRING, doc);
} else {
// Not Py.None to avoid load & init of Py module and all its constants.
docObj = PyNone.getInstance();
}
dict.__setitem__("__doc__", docObj);
}
setIsBaseType(builder.getIsBaseType());
needs_userdict = dict.__finditem__("__dict__") != null;
instantiable = dict.__finditem__("__new__") != null;
cacheDescrBinds();
}
}
/**
* Fills the base and bases of this type with the type of baseClass as sets its mro to this type
* followed by the mro of baseClass.
*/
protected void computeLinearMro(Class baseClass) {
if (underlying_class == PyObject.class) {
// Special case PyObject: there is no ancestor base: MRO is just {this}.
mro = new PyType[1];
} else {
// MRO of base, with this PyType at the front.
base = fromClass(baseClass);
mro = new PyType[base.mro.length + 1];
System.arraycopy(base.mro, 0, mro, 1, base.mro.length);
bases = new PyObject[] {base};
}
mro[0] = this;
}
/**
* Determine if this type is a descriptor, and if so what kind.
*/
private void cacheDescrBinds() {
hasGet = lookup_mro("__get__") != null;
hasSet = lookup_mro("__set__") != null;
hasDelete = lookup_mro("__delete__") != null;
}
public PyObject getStatic() {
PyType cur = this;
while (cur.underlying_class == null) {
cur = cur.base;
}
return cur;
}
/**
* Offers public read-only access to the protected field needs_finalizer.
*
* @return a boolean indicating whether the type implements __del__
*/
public final boolean needsFinalizer() {
/*
* It might be sluggish to assume that if a finalizer was needed once, this would never
* change. However since an expensive FinalizeTrigger was created anyway, it won't hurt to
* keep it. Whether there actually is a __del__ in the dict, will be checked again when the
* finalizer runs.
*/
if (needs_finalizer) {
return true;
} else {
needs_finalizer = lookup_mro("__del__") != null;
return needs_finalizer;
}
}
/**
* Ensures that the physical layout between this type and other
are compatible.
* Raises a TypeError if not.
*/
public void compatibleForAssignment(PyType other, String attribute) {
if (!getLayout().equals(other.getLayout()) || needs_userdict != other.needs_userdict
|| needs_finalizer != other.needs_finalizer) {
throw Py.TypeError(String.format("%s assignment: '%s' object layout differs from '%s'",
attribute, other.fastGetName(), fastGetName()));
}
}
/**
* Gets the most parent PyType that determines the layout of this type, ie it has slots or an
* underlying_class. Can be this PyType.
*/
private PyType getLayout() {
if (underlying_class != null) {
return this;
} else if (numSlots != base.numSlots) {
return this;
}
return base.getLayout();
}
/**
* Get the most parent Java proxy Class from bases, tallying any encountered Java interfaces.
*
* @param bases array of base Jython classes
* @param interfaces List for collecting interfaces to
* @return base Java proxy Class
* @raises Py.TypeError if multiple Java inheritance was attempted
*/
private static Class getJavaLayout(PyObject[] bases, List> interfaces) {
Class baseProxy = null;
for (PyObject base : bases) {
if (!(base instanceof PyType)) {
continue;
}
Class proxy = ((PyType) base).getProxyType();
if (proxy == null) {
continue;
}
if (proxy.isInterface()) {
interfaces.add(proxy);
} else {
if (baseProxy != null) {
String msg = "no multiple inheritance for Java classes: %s and %s";
throw Py.TypeError(String.format(msg, proxy.getName(), baseProxy.getName()));
}
baseProxy = proxy;
}
}
return baseProxy;
}
/**
* Setup the javaProxy for this type.
*
* @param baseProxyClass this type's base proxyClass
* @param interfaces a list of Java interfaces in bases
*/
private void setupProxy(Class baseProxyClass, List> interfaces) {
if (baseProxyClass == null && interfaces.size() == 0) {
// javaProxy not applicable
return;
}
String proxyName = name;
PyObject module = dict.__finditem__("__module__");
if (module != null) {
proxyName = module.toString() + "$" + proxyName;
}
Class proxyClass =
MakeProxies.makeProxy(baseProxyClass, interfaces, name, proxyName, dict);
setProxyAttr(this, proxyClass);
PyType proxyType = fromClass(proxyClass);
List cleanedBases = Generic.list();
boolean addedProxyType = false;
for (PyObject base : bases) {
if (!(base instanceof PyType)) {
cleanedBases.add(base);
continue;
}
Class proxy = ((PyType) base).getProxyType();
if (proxy == null) {
// non-proxy types go straight into our lookup
cleanedBases.add(base);
} else {
if (!(base instanceof PyJavaType)) {
/*
* python subclasses of proxy types need to be added as a base so their version
* of methods will show up.
*/
cleanedBases.add(base);
} else if (!addedProxyType) {
/*
* Only add a single Java type, since everything's going to go through the proxy
* type.
*/
cleanedBases.add(proxyType);
addedProxyType = true;
}
}
}
bases = cleanedBases.toArray(new PyObject[cleanedBases.size()]);
}
protected PyObject richCompare(PyObject other, cmpopType op) {
// Make sure the other object is a type
if (!(other instanceof PyType) && other != this) {
return null;
}
/*
* If there is a __cmp__ method defined, let it be called instead of our dumb function
* designed merely to warn. See CPython bug #7491.
*/
if (__findattr__("__cmp__") != null || ((PyType) other).__findattr__("__cmp__") != null) {
return null;
}
// Py3K warning if comparison isn't == or !=
if (Options.py3k_warning && op != cmpopType.Eq && op != cmpopType.NotEq) {
Py.warnPy3k("type inequality comparisons not supported in 3.x");
return null;
}
// Compare hashes
int hash1 = object___hash__();
int hash2 = other.object___hash__();
switch (op) {
case Lt:
return hash1 < hash2 ? Py.True : Py.False;
case LtE:
return hash1 <= hash2 ? Py.True : Py.False;
case Eq:
return hash1 == hash2 ? Py.True : Py.False;
case NotEq:
return hash1 != hash2 ? Py.True : Py.False;
case Gt:
return hash1 > hash2 ? Py.True : Py.False;
case GtE:
return hash1 >= hash2 ? Py.True : Py.False;
default:
return null;
}
}
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.type___eq___doc)
public PyObject type___eq__(PyObject other) {
return richCompare(other, cmpopType.Eq);
}
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.type___ne___doc)
public PyObject type___ne__(PyObject other) {
return richCompare(other, cmpopType.NotEq);
}
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.type___le___doc)
public PyObject type___le__(PyObject other) {
return richCompare(other, cmpopType.LtE);
}
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.type___lt___doc)
public PyObject type___lt__(PyObject other) {
return richCompare(other, cmpopType.Lt);
}
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.type___ge___doc)
public PyObject type___ge__(PyObject other) {
return richCompare(other, cmpopType.GtE);
}
@ExposedMethod(type = MethodType.BINARY, doc = BuiltinDocs.type___gt___doc)
public PyObject type___gt__(PyObject other) {
return richCompare(other, cmpopType.Gt);
}
@ExposedGet(name = "__base__")
public PyObject getBase() {
if (base == null) {
return Py.None;
}
return base;
}
@ExposedGet(name = "__bases__")
public PyObject getBases() {
return new PyTuple(bases);
}
@ExposedDelete(name = "__bases__")
public void delBases() {
throw Py.TypeError("Can't delete __bases__ attribute");
}
@ExposedSet(name = "__bases__")
public void setBases(PyObject newBasesTuple) {
if (!(newBasesTuple instanceof PyTuple)) {
throw Py.TypeError("bases must be a tuple");
}
PyObject[] newBases = ((PyTuple) newBasesTuple).getArray();
if (newBases.length == 0) {
throw Py.TypeError(
"can only assign non-empty tuple to __bases__, not " + newBasesTuple);
}
for (int i = 0; i < newBases.length; i++) {
if (!(newBases[i] instanceof PyType)) {
if (!(newBases[i] instanceof PyClass)) {
throw Py.TypeError(name + ".__bases__ must be a tuple of old- or new-style "
+ "classes, not " + newBases[i]);
}
} else {
if (((PyType) newBases[i]).isSubType(this)) {
throw Py.TypeError("a __bases__ item causes an inheritance cycle");
}
}
}
PyType newBase = best_base(newBases);
base.compatibleForAssignment(newBase, "__bases__");
PyObject[] savedBases = bases;
PyType savedBase = base;
PyObject[] savedMro = mro;
List