Add static getCollection() method on HopObject constructors to generate collections programmatically and on the fly. Implement limit and offset collection properties for databases that support it (Postgresql + Mysql)
This commit is contained in:
parent
0a62df7875
commit
a32b4f2c86
4 changed files with 242 additions and 104 deletions
|
@ -36,7 +36,7 @@ public final class DbMapping {
|
|||
private final String typename;
|
||||
|
||||
// properties from where the mapping is read
|
||||
private final ResourceProperties props;
|
||||
private final Properties props;
|
||||
|
||||
// name of data dbSource to which this mapping writes
|
||||
private DbSource dbSource;
|
||||
|
@ -147,7 +147,7 @@ public final class DbMapping {
|
|||
/**
|
||||
* Create a DbMapping from a type.properties property file
|
||||
*/
|
||||
public DbMapping(Application app, String typename, ResourceProperties props) {
|
||||
public DbMapping(Application app, String typename, Properties props) {
|
||||
this.app = app;
|
||||
// create a unique instance of the string. This is useful so
|
||||
// we can compare types just by using == instead of equals.
|
||||
|
@ -169,7 +169,10 @@ public final class DbMapping {
|
|||
* Tell the type manager whether we need update() to be called
|
||||
*/
|
||||
public boolean needsUpdate() {
|
||||
return props.lastModified() != lastTypeChange;
|
||||
if (props instanceof ResourceProperties) {
|
||||
return ((ResourceProperties) props).lastModified() != lastTypeChange;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -233,7 +236,8 @@ public final class DbMapping {
|
|||
parentInfo = null;
|
||||
}
|
||||
|
||||
lastTypeChange = props.lastModified();
|
||||
lastTypeChange = props instanceof ResourceProperties ?
|
||||
((ResourceProperties) props).lastModified() : System.currentTimeMillis();
|
||||
|
||||
// see if this prototype extends (inherits from) any other prototype
|
||||
String extendsProto = props.getProperty("_extends");
|
||||
|
@ -298,13 +302,15 @@ public final class DbMapping {
|
|||
HashMap d2p = new HashMap();
|
||||
ArrayList joinList = new ArrayList();
|
||||
|
||||
for (Enumeration e = props.keys(); e.hasMoreElements();) {
|
||||
String propName = (String) e.nextElement();
|
||||
for (Iterator it = props.entrySet().iterator(); it.hasNext(); ) {
|
||||
Map.Entry entry = (Map.Entry) it.next();
|
||||
|
||||
try {
|
||||
String propName = (String) entry.getKey();
|
||||
|
||||
// ignore internal properties (starting with "_") and sub-options (containing a ".")
|
||||
if (!propName.startsWith("_") && (propName.indexOf(".") < 0)) {
|
||||
String dbField = props.getProperty(propName);
|
||||
Object propValue = entry.getValue();
|
||||
|
||||
// check if a relation for this propery already exists. If so, reuse it
|
||||
Relation rel = (Relation) prop2db.get(propName);
|
||||
|
@ -313,7 +319,7 @@ public final class DbMapping {
|
|||
rel = new Relation(propName, this);
|
||||
}
|
||||
|
||||
rel.update(dbField, props);
|
||||
rel.update(propValue, getSubProperties(propName));
|
||||
p2d.put(propName, rel);
|
||||
|
||||
if ((rel.columnName != null) && rel.isPrimitiveOrReference()) {
|
||||
|
@ -354,7 +360,7 @@ public final class DbMapping {
|
|||
joins = new Relation[joinList.size()];
|
||||
joins = (Relation[]) joinList.toArray(joins);
|
||||
|
||||
String subnodeMapping = props.getProperty("_children");
|
||||
Object subnodeMapping = props.get("_children");
|
||||
|
||||
if (subnodeMapping != null) {
|
||||
try {
|
||||
|
@ -363,7 +369,7 @@ public final class DbMapping {
|
|||
subRelation = new Relation("_children", this);
|
||||
}
|
||||
|
||||
subRelation.update(subnodeMapping, props);
|
||||
subRelation.update(subnodeMapping, getSubProperties("_children"));
|
||||
|
||||
// if subnodes are accessed via access name or group name,
|
||||
// the subnode relation is also the property relation.
|
||||
|
@ -396,6 +402,9 @@ public final class DbMapping {
|
|||
*/
|
||||
private void registerExtension(String extID, String extName) {
|
||||
// lazy initialization of extensionMap
|
||||
if (extID == null) {
|
||||
return;
|
||||
}
|
||||
if (extensionMap == null) {
|
||||
extensionMap = new ResourceProperties();
|
||||
extensionMap.setIgnoreCase(true);
|
||||
|
@ -1469,10 +1478,31 @@ public final class DbMapping {
|
|||
*
|
||||
* @return our properties
|
||||
*/
|
||||
public ResourceProperties getProperties() {
|
||||
public Properties getProperties() {
|
||||
return props;
|
||||
}
|
||||
|
||||
public Properties getSubProperties(String prefix) {
|
||||
if (props.get(prefix) instanceof Properties) {
|
||||
return (Properties) props.get(prefix);
|
||||
} else if (props instanceof ResourceProperties) {
|
||||
return ((ResourceProperties) props).getSubProperties(prefix + ".");
|
||||
} else {
|
||||
Properties subprops = new Properties();
|
||||
prefix = prefix + ".";
|
||||
Iterator it = props.entrySet().iterator();
|
||||
int prefixLength = prefix.length();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry entry = (Map.Entry) it.next();
|
||||
String key = entry.getKey().toString();
|
||||
if (key.regionMatches(false, 0, prefix, 0, prefixLength)) {
|
||||
subprops.put(key.substring(prefixLength), entry.getValue());
|
||||
}
|
||||
}
|
||||
return subprops;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a DbMapping that depends on this DbMapping, so that collections of other mapping
|
||||
* should be reloaded if data on this mapping is updated.
|
||||
|
|
|
@ -102,6 +102,7 @@ public final class Relation {
|
|||
Vector filterFragments;
|
||||
Vector filterPropertyRefs;
|
||||
int maxSize = 0;
|
||||
int offset = 0;
|
||||
|
||||
/**
|
||||
* This constructor makes a copy of an existing relation. Not all fields are copied, just those
|
||||
|
@ -123,6 +124,7 @@ public final class Relation {
|
|||
this.additionalTablesJoined = rel.additionalTablesJoined;
|
||||
this.queryHints = rel.queryHints;
|
||||
this.maxSize = rel.maxSize;
|
||||
this.offset = rel.offset;
|
||||
this.constraints = rel.constraints;
|
||||
this.accessName = rel.accessName;
|
||||
this.logicalOperator = rel.logicalOperator;
|
||||
|
@ -144,39 +146,27 @@ public final class Relation {
|
|||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// parse methods for new file format
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
public void update(String desc, ResourceProperties props) {
|
||||
public void update(Object desc, Properties props) {
|
||||
Application app = ownType.getApplication();
|
||||
|
||||
if ((desc == null) || "".equals(desc.trim())) {
|
||||
if (propName != null) {
|
||||
reftype = PRIMITIVE;
|
||||
columnName = propName;
|
||||
} else {
|
||||
reftype = INVALID;
|
||||
columnName = propName;
|
||||
}
|
||||
} else {
|
||||
desc = desc.trim();
|
||||
|
||||
int open = desc.indexOf("(");
|
||||
int close = desc.indexOf(")");
|
||||
|
||||
if ((open > -1) && (close > open)) {
|
||||
String ref = desc.substring(0, open).trim();
|
||||
String proto = desc.substring(open + 1, close).trim();
|
||||
|
||||
if ("collection".equalsIgnoreCase(ref)) {
|
||||
if (desc instanceof Properties || parseDescriptor(desc, props)) {
|
||||
// new style foo.collectionOf = Bar mapping
|
||||
String proto;
|
||||
if (props.containsKey("collection")) {
|
||||
proto = props.getProperty("collection");
|
||||
virtual = !"_children".equalsIgnoreCase(propName);
|
||||
reftype = COLLECTION;
|
||||
} else if ("mountpoint".equalsIgnoreCase(ref)) {
|
||||
virtual = true;
|
||||
} else if (props.containsKey("mountpoint")) {
|
||||
proto = props.getProperty("mountpoint");
|
||||
reftype = COLLECTION;
|
||||
prototype = proto;
|
||||
} else if ("object".equalsIgnoreCase(ref)) {
|
||||
virtual = false;
|
||||
virtual = true;
|
||||
this.prototype = proto;
|
||||
} else if (props.containsKey("object")) {
|
||||
proto = props.getProperty("object");
|
||||
if (reftype != COMPLEX_REFERENCE) {
|
||||
reftype = REFERENCE;
|
||||
}
|
||||
virtual = false;
|
||||
} else {
|
||||
throw new RuntimeException("Invalid property Mapping: " + desc);
|
||||
}
|
||||
|
@ -193,24 +183,16 @@ public final class Relation {
|
|||
otherType.update();
|
||||
}
|
||||
|
||||
} else {
|
||||
virtual = false;
|
||||
columnName = desc;
|
||||
reftype = PRIMITIVE;
|
||||
}
|
||||
}
|
||||
|
||||
ResourceProperties config = props.getSubProperties(propName + '.');
|
||||
|
||||
readonly = "true".equalsIgnoreCase(config.getProperty("readonly"));
|
||||
|
||||
isPrivate = "true".equalsIgnoreCase(config.getProperty("private"));
|
||||
readonly = "true".equalsIgnoreCase(props.getProperty("readonly"));
|
||||
isPrivate = "true".equalsIgnoreCase(props.getProperty("private"));
|
||||
|
||||
// the following options only apply to object and collection relations
|
||||
if ((reftype != PRIMITIVE) && (reftype != INVALID)) {
|
||||
Vector newConstraints = new Vector();
|
||||
|
||||
parseOptions(newConstraints, config);
|
||||
parseOptions(newConstraints, props);
|
||||
|
||||
constraints = new Constraint[newConstraints.size()];
|
||||
newConstraints.copyInto(constraints);
|
||||
|
@ -256,32 +238,84 @@ public final class Relation {
|
|||
}
|
||||
}
|
||||
|
||||
protected void parseOptions(Vector cnst, Properties config) {
|
||||
String loading = config.getProperty("loadmode");
|
||||
/**
|
||||
* Converts old style foo = collection(Bar) mapping to new style
|
||||
* foo.collection = Bar mappinng and returns true if a non-primitive mapping
|
||||
* was encountered.
|
||||
* @param value the value of the top level property mapping
|
||||
* @param config the sub-map for this property mapping
|
||||
* @return true if the value describes a valid, non-primitive property mapping
|
||||
*/
|
||||
protected boolean parseDescriptor(Object value, Map config) {
|
||||
String desc = value instanceof String ? (String) value : null;
|
||||
|
||||
if ((desc == null) || "".equals(desc.trim())) {
|
||||
if (propName != null) {
|
||||
reftype = PRIMITIVE;
|
||||
columnName = propName;
|
||||
} else {
|
||||
reftype = INVALID;
|
||||
columnName = propName;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
desc = desc.trim();
|
||||
|
||||
int open = desc.indexOf("(");
|
||||
int close = desc.indexOf(")");
|
||||
|
||||
if ((open > -1) && (close > open)) {
|
||||
String ref = desc.substring(0, open).trim();
|
||||
String proto = desc.substring(open + 1, close).trim();
|
||||
|
||||
if ("collection".equalsIgnoreCase(ref)) {
|
||||
config.put("collection", proto);
|
||||
} else if ("mountpoint".equalsIgnoreCase(ref)) {
|
||||
config.put("mountpoint", proto);
|
||||
} else if ("object".equalsIgnoreCase(ref)) {
|
||||
config.put("object", proto);
|
||||
} else {
|
||||
throw new RuntimeException("Invalid property Mapping: " + desc);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
} else {
|
||||
virtual = false;
|
||||
columnName = desc;
|
||||
reftype = PRIMITIVE;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected void parseOptions(Vector cnst, Properties props) {
|
||||
String loading = props.getProperty("loadmode");
|
||||
|
||||
aggressiveLoading = (loading != null) &&
|
||||
"aggressive".equalsIgnoreCase(loading.trim());
|
||||
|
||||
String caching = config.getProperty("cachemode");
|
||||
String caching = props.getProperty("cachemode");
|
||||
|
||||
aggressiveCaching = (caching != null) &&
|
||||
"aggressive".equalsIgnoreCase(caching.trim());
|
||||
|
||||
// get order property
|
||||
order = config.getProperty("order");
|
||||
order = props.getProperty("order");
|
||||
|
||||
if ((order != null) && (order.trim().length() == 0)) {
|
||||
order = null;
|
||||
}
|
||||
|
||||
// get the criteria(s) for updating this collection
|
||||
updateCriteria = config.getProperty("updatecriteria");
|
||||
updateCriteria = props.getProperty("updatecriteria");
|
||||
|
||||
// get the autosorting flag
|
||||
autoSorted = "auto".equalsIgnoreCase(config.getProperty("sortmode"));
|
||||
autoSorted = "auto".equalsIgnoreCase(props.getProperty("sortmode"));
|
||||
|
||||
// get additional filter property
|
||||
filter = config.getProperty("filter");
|
||||
filter = props.getProperty("filter");
|
||||
|
||||
if (filter != null) {
|
||||
if (filter.trim().length() == 0) {
|
||||
|
@ -304,7 +338,7 @@ public final class Relation {
|
|||
}
|
||||
|
||||
// get additional tables
|
||||
additionalTables = config.getProperty("filter.additionalTables");
|
||||
additionalTables = props.getProperty("filter.additionalTables");
|
||||
|
||||
if (additionalTables != null) {
|
||||
if (additionalTables.trim().length() == 0) {
|
||||
|
@ -334,36 +368,31 @@ public final class Relation {
|
|||
}
|
||||
|
||||
// get query hints
|
||||
queryHints = config.getProperty("hints");
|
||||
queryHints = props.getProperty("hints");
|
||||
|
||||
// get max size of collection
|
||||
String max = config.getProperty("maxSize");
|
||||
|
||||
if (max != null) {
|
||||
try {
|
||||
maxSize = Integer.parseInt(max);
|
||||
} catch (NumberFormatException nfe) {
|
||||
maxSize = 0;
|
||||
}
|
||||
} else {
|
||||
maxSize = 0;
|
||||
maxSize = getIntegerProperty("maxSize", props, 0);
|
||||
if (maxSize == 0) {
|
||||
// use limit as alias for maxSize
|
||||
maxSize = getIntegerProperty("limit", props, 0);
|
||||
}
|
||||
offset = getIntegerProperty("offset", props, 0);
|
||||
|
||||
// get group by property
|
||||
groupby = config.getProperty("group");
|
||||
groupby = props.getProperty("group");
|
||||
|
||||
if ((groupby != null) && (groupby.trim().length() == 0)) {
|
||||
groupby = null;
|
||||
}
|
||||
|
||||
if (groupby != null) {
|
||||
groupbyOrder = config.getProperty("group.order");
|
||||
groupbyOrder = props.getProperty("group.order");
|
||||
|
||||
if ((groupbyOrder != null) && (groupbyOrder.trim().length() == 0)) {
|
||||
groupbyOrder = null;
|
||||
}
|
||||
|
||||
groupbyPrototype = config.getProperty("group.prototype");
|
||||
groupbyPrototype = props.getProperty("group.prototype");
|
||||
|
||||
if ((groupbyPrototype != null) && (groupbyPrototype.trim().length() == 0)) {
|
||||
groupbyPrototype = null;
|
||||
|
@ -374,11 +403,11 @@ public final class Relation {
|
|||
}
|
||||
|
||||
// check if subnode condition should be applied for property relations
|
||||
accessName = config.getProperty("accessname");
|
||||
accessName = props.getProperty("accessname");
|
||||
|
||||
// parse contstraints
|
||||
String local = config.getProperty("local");
|
||||
String foreign = config.getProperty("foreign");
|
||||
String local = props.getProperty("local");
|
||||
String foreign = props.getProperty("foreign");
|
||||
|
||||
if ((local != null) && (foreign != null)) {
|
||||
cnst.addElement(new Constraint(local, foreign, false));
|
||||
|
@ -387,8 +416,8 @@ public final class Relation {
|
|||
|
||||
// parse additional contstraints from *.1 to *.9
|
||||
for (int i=1; i<10; i++) {
|
||||
local = config.getProperty("local."+i);
|
||||
foreign = config.getProperty("foreign."+i);
|
||||
local = props.getProperty("local."+i);
|
||||
foreign = props.getProperty("foreign."+i);
|
||||
|
||||
if ((local != null) && (foreign != null)) {
|
||||
cnst.addElement(new Constraint(local, foreign, false));
|
||||
|
@ -397,7 +426,7 @@ public final class Relation {
|
|||
|
||||
// parse constraints logic
|
||||
if (cnst.size() > 1) {
|
||||
String logic = config.getProperty("logicalOperator");
|
||||
String logic = props.getProperty("logicalOperator");
|
||||
if ("and".equalsIgnoreCase(logic)) {
|
||||
logicalOperator = AND;
|
||||
} else if ("or".equalsIgnoreCase(logic)) {
|
||||
|
@ -413,13 +442,24 @@ public final class Relation {
|
|||
|
||||
}
|
||||
|
||||
private int getIntegerProperty(String name, Properties props, int defaultValue) {
|
||||
Object value = props.get(name);
|
||||
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
} else if (value instanceof String) {
|
||||
return Integer.parseInt((String) value);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Get the configuration properties for this relation.
|
||||
*/
|
||||
public ResourceProperties getConfig() {
|
||||
return ownType.getProperties().getSubProperties(propName + '.');
|
||||
public Map getConfig() {
|
||||
return ownType.getSubProperties(propName + '.');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -875,6 +915,13 @@ public final class Relation {
|
|||
q.append(" ORDER BY ").append(order);
|
||||
}
|
||||
|
||||
if (maxSize > 0 && !ownType.isOracle()) {
|
||||
q.append(" LIMIT ").append(maxSize);
|
||||
if (offset > 0) {
|
||||
q.append(" OFFSET ").append(offset);
|
||||
}
|
||||
}
|
||||
|
||||
return q.toString();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ package helma.scripting.rhino;
|
|||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Properties;
|
||||
|
||||
import helma.objectmodel.INode;
|
||||
import helma.objectmodel.db.DbMapping;
|
||||
|
@ -58,6 +59,7 @@ public class HopObjectCtor extends FunctionObject {
|
|||
this.protoProperty = prototype;
|
||||
addAsConstructor(core.global, prototype);
|
||||
defineProperty("getById", new GetById(core.global), attr);
|
||||
defineProperty("getCollection", new HopCollection(core.global), attr);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -180,4 +182,44 @@ public class HopObjectCtor extends FunctionObject {
|
|||
|
||||
}
|
||||
|
||||
class HopCollection extends BaseFunction {
|
||||
|
||||
public HopCollection(Scriptable scope) {
|
||||
ScriptRuntime.setFunctionProtoAndParent(this, scope);
|
||||
}
|
||||
|
||||
public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {
|
||||
if (args.length != 1) {
|
||||
throw new IllegalArgumentException("Wrong number of arguments in definePrototype()");
|
||||
}
|
||||
if (!(args[0] instanceof Scriptable)) {
|
||||
throw new IllegalArgumentException("Second argument to HopObject.definePrototype() must be Object");
|
||||
}
|
||||
|
||||
Scriptable desc = (Scriptable) args[0];
|
||||
Properties childmapping = core.scriptableToProperties(desc);
|
||||
if (!childmapping.containsKey("collection")) {
|
||||
// if contained type isn't defined explicitly limit collection to our own type
|
||||
childmapping.put("collection", HopObjectCtor.this.getFunctionName());
|
||||
}
|
||||
|
||||
Node node = new Node("HopQuery", null, core.app.getWrappedNodeManager());
|
||||
Properties props = new Properties();
|
||||
props.put("_children", childmapping);
|
||||
DbMapping dbmap = new DbMapping(core.app, null, props);
|
||||
dbmap.update();
|
||||
node.setDbMapping(dbmap);
|
||||
node.setState(Node.VIRTUAL);
|
||||
return new HopObject("HopQuery", core, node, core.hopObjectProto);
|
||||
}
|
||||
|
||||
public int getArity() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -23,10 +23,7 @@ import helma.framework.repository.Resource;
|
|||
import helma.objectmodel.*;
|
||||
import helma.objectmodel.db.DbMapping;
|
||||
import helma.scripting.*;
|
||||
import helma.util.CacheMap;
|
||||
import helma.util.SystemMap;
|
||||
import helma.util.WrappedMap;
|
||||
import helma.util.WeakCacheMap;
|
||||
import helma.util.*;
|
||||
import org.mozilla.javascript.*;
|
||||
import org.mozilla.javascript.tools.debugger.ScopeProvider;
|
||||
|
||||
|
@ -216,7 +213,7 @@ public final class RhinoCore implements ScopeProvider {
|
|||
*
|
||||
* @param prototype the prototype to be created
|
||||
*/
|
||||
private synchronized void initPrototype(Prototype prototype) {
|
||||
protected synchronized TypeInfo initPrototype(Prototype prototype) {
|
||||
|
||||
String name = prototype.getName();
|
||||
String lowerCaseName = prototype.getLowerCaseName();
|
||||
|
@ -236,7 +233,7 @@ public final class RhinoCore implements ScopeProvider {
|
|||
} else {
|
||||
op = new HopObject(name, this);
|
||||
}
|
||||
registerPrototype(prototype, op);
|
||||
type = registerPrototype(prototype, op);
|
||||
}
|
||||
|
||||
// Register a constructor for all types except global.
|
||||
|
@ -250,6 +247,8 @@ public final class RhinoCore implements ScopeProvider {
|
|||
app.logError("Error adding ctor for " + name, x);
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -291,7 +290,7 @@ public final class RhinoCore implements ScopeProvider {
|
|||
* @param prototype the prototype spec
|
||||
* @param type the prototype object info
|
||||
*/
|
||||
private void setParentPrototype(Prototype prototype, TypeInfo type) {
|
||||
protected void setParentPrototype(Prototype prototype, TypeInfo type) {
|
||||
String name = prototype.getName();
|
||||
String lowerCaseName = prototype.getLowerCaseName();
|
||||
|
||||
|
@ -748,6 +747,26 @@ public final class RhinoCore implements ScopeProvider {
|
|||
return href;
|
||||
}
|
||||
|
||||
|
||||
Properties scriptableToProperties(Scriptable obj) {
|
||||
Object[] ids = obj.getIds();
|
||||
Properties props = new ResourceProperties(app, null, null, true);
|
||||
for (int i = 0; i < ids.length; i++) {
|
||||
// we ignore non-string keys
|
||||
if (ids[i] instanceof String) {
|
||||
String key = (String) ids[i];
|
||||
Object value = obj.get(key, obj);
|
||||
if (value == Undefined.instance || value == Scriptable.NOT_FOUND) {
|
||||
value = null;
|
||||
} else if (value instanceof Scriptable) {
|
||||
value = scriptableToProperties((Scriptable) value);
|
||||
}
|
||||
props.put(key, value);
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RhinoCore instance associated with the current thread, or null
|
||||
* @return the RhinoCore instance associated with the current thread
|
||||
|
|
Loading…
Add table
Reference in a new issue