blob: 468d25ae172043c1f235fe57b7a0ac1ed6fb8343 [file] [log] [blame]
/*
* Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* The Original Code is HAT. The Initial Developer of the
* Original Code is Bill Foote, with contributions from others
* at JavaSoft/Sun.
*/
var hatPkg = Packages.com.sun.tools.hat.internal;
/**
* This is JavaScript interface for heap analysis using HAT
* (Heap Analysis Tool). HAT classes are refered from
* this file. In particular, refer to classes in hat.model
* package.
*
* HAT model objects are wrapped as convenient script objects so that
* fields may be accessed in natural syntax. For eg. Java fields can be
* accessed with obj.field_name syntax and array elements can be accessed
* with array[index] syntax.
*/
// returns an enumeration that wraps elements of
// the input enumeration elements.
function wrapperEnumeration(e) {
return new java.util.Enumeration() {
hasMoreElements: function() {
return e.hasMoreElements();
},
nextElement: function() {
return wrapJavaValue(e.nextElement());
}
};
}
// returns an enumeration that filters out elements
// of input enumeration using the filter function.
function filterEnumeration(e, func, wrap) {
var next = undefined;
var index = 0;
function findNext() {
var tmp;
while (e.hasMoreElements()) {
tmp = e.nextElement();
index++;
if (wrap) {
tmp = wrapJavaValue(tmp);
}
if (func(tmp, index, e)) {
next = tmp;
return;
}
}
}
return new java.util.Enumeration() {
hasMoreElements: function() {
findNext();
return next != undefined;
},
nextElement: function() {
if (next == undefined) {
// user may not have called hasMoreElements?
findNext();
}
if (next == undefined) {
throw "NoSuchElementException";
}
var res = next;
next = undefined;
return res;
}
};
}
// enumeration that has no elements ..
var emptyEnumeration = new java.util.Enumeration() {
hasMoreElements: function() {
return false;
},
nextElement: function() {
throw "NoSuchElementException";
}
};
function wrapRoot(root) {
if (root) {
return {
id: root.idString,
description: root.description,
referrer: wrapJavaValue(root.referer),
type: root.typeName
};
} else {
return null;
}
}
function JavaClassProto() {
function jclass(obj) {
return obj['wrapped-object'];
}
// return whether given class is subclass of this class or not
this.isSubclassOf = function(other) {
var tmp = jclass(this);
var otherid = objectid(other);
while (tmp != null) {
if (otherid.equals(tmp.idString)) {
return true;
}
tmp = tmp.superclass;
}
return false;
}
// return whether given class is superclass of this class or not
this.isSuperclassOf = function(other) {
return other.isSubclassOf(this);
}
// includes direct and indirect superclasses
this.superclasses = function() {
var res = new Array();
var tmp = this.superclass;
while (tmp != null) {
res[res.length] = tmp;
tmp = tmp.superclass;
}
return res;
}
/**
* Returns an array containing subclasses of this class.
*
* @param indirect should include indirect subclasses or not.
* default is true.
*/
this.subclasses = function(indirect) {
if (indirect == undefined) indirect = true;
var classes = jclass(this).subclasses;
var res = new Array();
for (var i in classes) {
var subclass = wrapJavaValue(classes[i]);
res[res.length] = subclass;
if (indirect) {
res = res.concat(subclass.subclasses());
}
}
return res;
}
this.toString = function() { return jclass(this).toString(); }
}
var theJavaClassProto = new JavaClassProto();
// Script wrapper for HAT model objects, values.
// wraps a Java value as appropriate for script object
function wrapJavaValue(thing) {
if (thing == null || thing == undefined ||
thing instanceof hatPkg.model.HackJavaValue) {
return null;
}
if (thing instanceof hatPkg.model.JavaValue) {
// map primitive values to closest JavaScript primitives
if (thing instanceof hatPkg.model.JavaBoolean) {
return thing.toString() == "true";
} else if (thing instanceof hatPkg.model.JavaChar) {
return thing.toString() + '';
} else {
return java.lang.Double.parseDouble(thing.toString());
}
} else {
// wrap Java object as script object
return wrapJavaObject(thing);
}
}
// wrap Java object with appropriate script object
function wrapJavaObject(thing) {
// HAT Java model object wrapper. Handles all cases
// (instance, object/primitive array and Class objects)
function javaObject(jobject) {
// FIXME: Do I need this? or can I assume that these would
// have been resolved already?
if (jobject instanceof hatPkg.model.JavaObjectRef) {
jobject = jobject.dereference();
if (jobject instanceof hatPkg.model.HackJavaValue) {
print(jobject);
return null;
}
}
if (jobject instanceof hatPkg.model.JavaObject) {
return new JavaObjectWrapper(jobject);
} else if (jobject instanceof hatPkg.model.JavaClass) {
return new JavaClassWrapper(jobject);
} else if (jobject instanceof hatPkg.model.JavaObjectArray) {
return new JavaObjectArrayWrapper(jobject);
} else if (jobject instanceof hatPkg.model.JavaValueArray) {
return new JavaValueArrayWrapper(jobject);
} else {
print("unknown heap object type: " + jobject.getClass());
return jobject;
}
}
// returns wrapper for Java instances
function JavaObjectWrapper(instance) {
var things = instance.fields;
var fields = instance.clazz.fieldsForInstance;
// instance fields can be accessed in natural syntax
return new JSAdapter() {
__getIds__ : function() {
var res = new Array(fields.length);
for (var i in fields) {
res[i] = fields[i].name;
}
return res;
},
__has__ : function(name) {
for (var i in fields) {
if (name == fields[i].name) return true;
}
return name == 'class' || name == 'toString' ||
name == 'wrapped-object';
},
__get__ : function(name) {
for (var i in fields) {
if(fields[i].name == name) {
return wrapJavaValue(things[i]);
}
}
if (name == 'class') {
return wrapJavaValue(instance.clazz);
} else if (name == 'toString') {
return function() {
return instance.toString();
}
} else if (name == 'wrapped-object') {
return instance;
}
return undefined;
}
}
}
// return wrapper for Java Class objects
function JavaClassWrapper(jclass) {
var fields = jclass.statics;
// to access static fields of given Class cl, use
// cl.statics.<static-field-name> syntax
this.statics = new JSAdapter() {
__getIds__ : function() {
var res = new Array(fields.length);
for (var i in fields) {
res[i] = fields[i].field.name;
}
return res;
},
__has__ : function(name) {
for (var i in fields) {
if (name == fields[i].field.name) {
return true;
}
}
return theJavaClassProto[name] != undefined;
},
__get__ : function(name) {
for (var i in fields) {
if (name == fields[i].field.name) {
return wrapJavaValue(fields[i].value);
}
}
return theJavaClassProto[name];
}
}
if (jclass.superclass != null) {
this.superclass = wrapJavaValue(jclass.superclass);
} else {
this.superclass = null;
}
this.loader = wrapJavaValue(jclass.getLoader());
this.signers = wrapJavaValue(jclass.getSigners());
this.protectionDomain = wrapJavaValue(jclass.getProtectionDomain());
this.instanceSize = jclass.instanceSize;
this.name = jclass.name;
this.fields = jclass.fields;
this['wrapped-object'] = jclass;
this.__proto__ = this.statics;
}
// returns wrapper for Java object arrays
function JavaObjectArrayWrapper(array) {
var elements = array.elements;
// array elements can be accessed in natural syntax
// also, 'length' property is supported.
return new JSAdapter() {
__getIds__ : function() {
var res = new Array(elements.length);
for (var i = 0; i < elements.length; i++) {
res[i] = i;
}
return res;
},
__has__: function(name) {
return (typeof(name) == 'number' &&
name >= 0 && name < elements.length) ||
name == 'length' || name == 'class' ||
name == 'toString' || name == 'wrapped-object';
},
__get__ : function(name) {
if (typeof(name) == 'number' &&
name >= 0 && name < elements.length) {
return wrapJavaValue(elements[name]);
} else if (name == 'length') {
return elements.length;
} else if (name == 'class') {
return wrapJavaValue(array.clazz);
} else if (name == 'toString') {
return function() { return array.toString(); }
} else if (name == 'wrapped-object') {
return array;
} else {
return undefined;
}
}
}
}
// returns wrapper for Java primitive arrays
function JavaValueArrayWrapper(array) {
var type = String(java.lang.Character.toString(array.elementType));
var elements = array.elements;
// array elements can be accessed in natural syntax
// also, 'length' property is supported.
return new JSAdapter() {
__getIds__ : function() {
var r = new Array(array.length);
for (var i = 0; i < array.length; i++) {
r[i] = i;
}
return r;
},
__has__: function(name) {
return (typeof(name) == 'number' &&
name >= 0 && name < array.length) ||
name == 'length' || name == 'class' ||
name == 'toString' || name == 'wrapped-object';
},
__get__: function(name) {
if (typeof(name) == 'number' &&
name >= 0 && name < array.length) {
return elements[name];
}
if (name == 'length') {
return array.length;
} else if (name == 'toString') {
return function() { return array.valueString(true); }
} else if (name == 'wrapped-object') {
return array;
} else if (name == 'class') {
return wrapJavaValue(array.clazz);
} else {
return undefined;
}
}
}
}
return javaObject(thing);
}
// unwrap a script object to corresponding HAT object
function unwrapJavaObject(jobject) {
if (!(jobject instanceof hatPkg.model.JavaHeapObject)) {
try {
jobject = jobject["wrapped-object"];
} catch (e) {
print("unwrapJavaObject: " + jobject + ", " + e);
jobject = undefined;
}
}
return jobject;
}
/**
* readHeapDump parses a heap dump file and returns script wrapper object.
*
* @param file Heap dump file name
* @param stack flag to tell if allocation site traces are available
* @param refs flag to tell if backward references are needed or not
* @param debug debug level for HAT
* @return heap as a JavaScript object
*/
function readHeapDump(file, stack, refs, debug) {
// default value of debug is 0
if (!debug) debug = 0;
// by default, we assume no stack traces
if (!stack) stack = false;
// by default, backward references are resolved
if (!refs) refs = true;
// read the heap dump
var heap = hatPkg.parser.HprofReader.readFile(file, stack, debug);
// resolve it
heap.resolve(refs);
// wrap Snapshot as convenient script object
return wrapHeapSnapshot(heap);
}
/**
* The result object supports the following methods:
*
* forEachClass -- calls a callback for each Java Class
* forEachObject -- calls a callback for each Java object
* findClass -- finds Java Class of given name
* findObject -- finds object from given object id
* objects -- returns all objects of given class as an enumeration
* classes -- returns all classes in the heap as an enumeration
* reachables -- returns all objects reachable from a given object
* livepaths -- returns an array of live paths because of which an
* object alive.
* describeRef -- returns description for a reference from a 'from'
* object to a 'to' object.
*/
function wrapHeapSnapshot(heap) {
function getClazz(clazz) {
if (clazz == undefined) clazz = "java.lang.Object";
var type = typeof(clazz);
if (type == "string") {
clazz = heap.findClass(clazz);
} else if (type == "object") {
clazz = unwrapJavaObject(clazz);
} else {
throw "class expected";;
}
return clazz;
}
// return heap as a script object with useful methods.
return {
snapshot: heap,
/**
* Class iteration: Calls callback function for each
* Java Class in the heap. Default callback function
* is 'print'. If callback returns true, the iteration
* is stopped.
*
* @param callback function to be called.
*/
forEachClass: function(callback) {
if (callback == undefined) callback = print;
var classes = this.snapshot.classes;
while (classes.hasMoreElements()) {
if (callback(wrapJavaValue(classes.nextElement())))
return;
}
},
/**
* Returns an Enumeration of all roots.
*/
roots: function() {
var e = this.snapshot.roots;
return new java.util.Enumeration() {
hasMoreElements: function() {
return e.hasMoreElements();
},
nextElement: function() {
return wrapRoot(e.nextElement());
}
};
},
/**
* Returns an Enumeration for all Java classes.
*/
classes: function() {
return wrapIterator(this.snapshot.classes, true);
},
/**
* Object iteration: Calls callback function for each
* Java Object in the heap. Default callback function
* is 'print'.If callback returns true, the iteration
* is stopped.
*
* @param callback function to be called.
* @param clazz Class whose objects are retrieved.
* Optional, default is 'java.lang.Object'
* @param includeSubtypes flag to tell if objects of subtypes
* are included or not. optional, default is true.
*/
forEachObject: function(callback, clazz, includeSubtypes) {
if (includeSubtypes == undefined) includeSubtypes = true;
if (callback == undefined) callback = print;
clazz = getClazz(clazz);
if (clazz) {
var instances = clazz.getInstances(includeSubtypes);
while (instances.hasNextElements()) {
if (callback(wrapJavaValue(instances.nextElement())))
return;
}
}
},
/**
* Returns an enumeration of Java objects in the heap.
*
* @param clazz Class whose objects are retrieved.
* Optional, default is 'java.lang.Object'
* @param includeSubtypes flag to tell if objects of subtypes
* are included or not. optional, default is true.
* @param where (optional) filter expression or function to
* filter the objects. The expression has to return true
* to include object passed to it in the result array.
* Built-in variable 'it' refers to the current object in
* filter expression.
*/
objects: function(clazz, includeSubtypes, where) {
if (includeSubtypes == undefined) includeSubtypes = true;
if (where) {
if (typeof(where) == 'string') {
where = new Function("it", "return " + where);
}
}
clazz = getClazz(clazz);
if (clazz) {
var instances = clazz.getInstances(includeSubtypes);
if (where) {
return filterEnumeration(instances, where, true);
} else {
return wrapperEnumeration(instances);
}
} else {
return emptyEnumeration;
}
},
/**
* Find Java Class of given name.
*
* @param name class name
*/
findClass: function(name) {
var clazz = this.snapshot.findClass(name + '');
return wrapJavaValue(clazz);
},
/**
* Find Java Object from given object id
*
* @param id object id as string
*/
findObject: function(id) {
return wrapJavaValue(this.snapshot.findThing(id));
},
/**
* Returns an enumeration of objects in the finalizer
* queue waiting to be finalized.
*/
finalizables: function() {
var tmp = this.snapshot.getFinalizerObjects();
return wrapperEnumeration(tmp);
},
/**
* Returns an array that contains objects referred from the
* given Java object directly or indirectly (i.e., all
* transitively referred objects are returned).
*
* @param jobject Java object whose reachables are returned.
*/
reachables: function (jobject) {
return reachables(jobject, this.snapshot.reachableExcludes);
},
/**
* Returns array of paths of references by which the given
* Java object is live. Each path itself is an array of
* objects in the chain of references. Each path supports
* toHtml method that returns html description of the path.
*
* @param jobject Java object whose live paths are returned
* @param weak flag to indicate whether to include paths with
* weak references or not. default is false.
*/
livepaths: function (jobject, weak) {
if (weak == undefined) {
weak = false;
}
function wrapRefChain(refChain) {
var path = new Array();
// compute path array from refChain
var tmp = refChain;
while (tmp != null) {
var obj = tmp.obj;
path[path.length] = wrapJavaValue(obj);
tmp = tmp.next;
}
function computeDescription(html) {
var root = refChain.obj.root;
var desc = root.description;
if (root.referer) {
var ref = root.referer;
desc += " (from " +
(html? toHtml(ref) : ref.toString()) + ')';
}
desc += '->';
var tmp = refChain;
while (tmp != null) {
var next = tmp.next;
var obj = tmp.obj;
desc += html? toHtml(obj) : obj.toString();
if (next != null) {
desc += " (" +
obj.describeReferenceTo(next.obj, heap) +
") ->";
}
tmp = next;
}
return desc;
}
return new JSAdapter() {
__getIds__ : function() {
var res = new Array(path.length);
for (var i = 0; i < path.length; i++) {
res[i] = i;
}
return res;
},
__has__ : function (name) {
return (typeof(name) == 'number' &&
name >= 0 && name < path.length) ||
name == 'length' || name == 'toHtml' ||
name == 'toString';
},
__get__ : function(name) {
if (typeof(name) == 'number' &&
name >= 0 && name < path.length) {
return path[name];
} else if (name == 'length') {
return path.length;
} else if (name == 'toHtml') {
return function() {
return computeDescription(true);
}
} else if (name == 'toString') {
return function() {
return computeDescription(false);
}
} else {
return undefined;
}
},
};
}
jobject = unwrapJavaObject(jobject);
var refChains = this.snapshot.rootsetReferencesTo(jobject, weak);
var paths = new Array(refChains.length);
for (var i in refChains) {
paths[i] = wrapRefChain(refChains[i]);
}
return paths;
},
/**
* Return description string for reference from 'from' object
* to 'to' Java object.
*
* @param from source Java object
* @param to destination Java object
*/
describeRef: function (from, to) {
from = unwrapJavaObject(from);
to = unwrapJavaObject(to);
return from.describeReferenceTo(to, this.snapshot);
},
};
}
// per-object functions
/**
* Returns allocation site trace (if available) of a Java object
*
* @param jobject object whose allocation site trace is returned
*/
function allocTrace(jobject) {
try {
jobject = unwrapJavaObject(jobject);
var trace = jobject.allocatedFrom;
return (trace != null) ? trace.frames : null;
} catch (e) {
print("allocTrace: " + jobject + ", " + e);
return null;
}
}
/**
* Returns Class object for given Java object
*
* @param jobject object whose Class object is returned
*/
function classof(jobject) {
jobject = unwrapJavaObject(jobject);
return wrapJavaValue(jobject.clazz);
}
/**
* Find referers (a.k.a in-coming references). Calls callback
* for each referrer of the given Java object. If the callback
* returns true, the iteration is stopped.
*
* @param callback function to call for each referer
* @param jobject object whose referers are retrieved
*/
function forEachReferrer(callback, jobject) {
jobject = unwrapJavaObject(jobject);
var refs = jobject.referers;
while (refs.hasMoreElements()) {
if (callback(wrapJavaValue(refs.nextElement()))) {
return;
}
}
}
/**
* Compares two Java objects for object identity.
*
* @param o1, o2 objects to compare for identity
*/
function identical(o1, o2) {
return objectid(o1) == objectid(o2);
}
/**
* Returns Java object id as string
*
* @param jobject object whose id is returned
*/
function objectid(jobject) {
try {
jobject = unwrapJavaObject(jobject);
return String(jobject.idString);
} catch (e) {
print("objectid: " + jobject + ", " + e);
return null;
}
}
/**
* Prints allocation site trace of given object
*
* @param jobject object whose allocation site trace is returned
*/
function printAllocTrace(jobject) {
var frames = this.allocTrace(jobject);
if (frames == null || frames.length == 0) {
print("allocation site trace unavailable for " +
objectid(jobject));
return;
}
print(objectid(jobject) + " was allocated at ..");
for (var i in frames) {
var frame = frames[i];
var src = frame.sourceFileName;
if (src == null) src = '<unknown source>';
print('\t' + frame.className + "." +
frame.methodName + '(' + frame.methodSignature + ') [' +
src + ':' + frame.lineNumber + ']');
}
}
/**
* Returns an enumeration of referrers of the given Java object.
*
* @param jobject Java object whose referrers are returned.
*/
function referrers(jobject) {
try {
jobject = unwrapJavaObject(jobject);
return wrapperEnumeration(jobject.referers);
} catch (e) {
print("referrers: " + jobject + ", " + e);
return emptyEnumeration;
}
}
/**
* Returns an array that contains objects referred from the
* given Java object.
*
* @param jobject Java object whose referees are returned.
*/
function referees(jobject) {
var res = new Array();
jobject = unwrapJavaObject(jobject);
if (jobject != undefined) {
try {
jobject.visitReferencedObjects(
new hatPkg.model.JavaHeapObjectVisitor() {
visit: function(other) {
res[res.length] = wrapJavaValue(other);
},
exclude: function(clazz, field) {
return false;
},
mightExclude: function() {
return false;
}
});
} catch (e) {
print("referees: " + jobject + ", " + e);
}
}
return res;
}
/**
* Returns an array that contains objects referred from the
* given Java object directly or indirectly (i.e., all
* transitively referred objects are returned).
*
* @param jobject Java object whose reachables are returned.
* @param excludes optional comma separated list of fields to be
* removed in reachables computation. Fields are
* written as class_name.field_name form.
*/
function reachables(jobject, excludes) {
if (excludes == undefined) {
excludes = null;
} else if (typeof(excludes) == 'string') {
var st = new java.util.StringTokenizer(excludes, ",");
var excludedFields = new Array();
while (st.hasMoreTokens()) {
excludedFields[excludedFields.length] = st.nextToken().trim();
}
if (excludedFields.length > 0) {
excludes = new hatPkg.model.ReachableExcludes() {
isExcluded: function (field) {
for (var index in excludedFields) {
if (field.equals(excludedFields[index])) {
return true;
}
}
return false;
}
};
} else {
// nothing to filter...
excludes = null;
}
} else if (! (excludes instanceof hatPkg.model.ReachableExcludes)) {
excludes = null;
}
jobject = unwrapJavaObject(jobject);
var ro = new hatPkg.model.ReachableObjects(jobject, excludes);
var tmp = ro.reachables;
var res = new Array(tmp.length);
for (var i in tmp) {
res[i] = wrapJavaValue(tmp[i]);
}
return res;
}
/**
* Returns whether 'from' object refers to 'to' object or not.
*
* @param from Java object that is source of the reference.
* @param to Java object that is destination of the reference.
*/
function refers(from, to) {
try {
var tmp = unwrapJavaObject(from);
if (tmp instanceof hatPkg.model.JavaClass) {
from = from.statics;
} else if (tmp instanceof hatPkg.model.JavaValueArray) {
return false;
}
for (var i in from) {
if (identical(from[i], to)) {
return true;
}
}
} catch (e) {
print("refers: " + from + ", " + e);
}
return false;
}
/**
* If rootset includes given jobject, return Root
* object explanining the reason why it is a root.
*
* @param jobject object whose Root is returned
*/
function root(jobject) {
try {
jobject = unwrapJavaObject(jobject);
return wrapRoot(jobject.root);
} catch (e) {
return null;
}
}
/**
* Returns size of the given Java object
*
* @param jobject object whose size is returned
*/
function sizeof(jobject) {
try {
jobject = unwrapJavaObject(jobject);
return jobject.size;
} catch (e) {
print("sizeof: " + jobject + ", " + e);
return null;
}
}
/**
* Returns String by replacing Unicode chars and
* HTML special chars (such as '<') with entities.
*
* @param str string to be encoded
*/
function encodeHtml(str) {
return hatPkg.util.Misc.encodeHtml(str);
}
/**
* Returns HTML string for the given object.
*
* @param obj object for which HTML string is returned.
*/
function toHtml(obj) {
if (obj == null) {
return "null";
}
if (obj == undefined) {
return "undefined";
}
var tmp = unwrapJavaObject(obj);
if (tmp != undefined) {
var id = tmp.idString;
if (tmp instanceof Packages.com.sun.tools.hat.internal.model.JavaClass) {
var name = tmp.name;
return "<a href='/class/" + id + "'>class " + name + "</a>";
} else {
var name = tmp.clazz.name;
return "<a href='/object/" + id + "'>" +
name + "@" + id + "</a>";
}
} else if ((typeof(obj) == 'object') || (obj instanceof JSAdapter)) {
if (obj instanceof java.lang.Object) {
// script wrapped Java object
obj = wrapIterator(obj);
// special case for enumeration
if (obj instanceof java.util.Enumeration) {
var res = "[ ";
while (obj.hasMoreElements()) {
res += toHtml(obj.nextElement()) + ", ";
}
res += "]";
return res;
} else {
return obj;
}
} else if (obj instanceof Array) {
// script array
var res = "[ ";
for (var i in obj) {
res += toHtml(obj[i]);
if (i != obj.length - 1) {
res += ", ";
}
}
res += " ]";
return res;
} else {
// if the object has a toHtml function property
// just use that...
if (typeof(obj.toHtml) == 'function') {
return obj.toHtml();
} else {
// script object
var res = "{ ";
for (var i in obj) {
res += i + ":" + toHtml(obj[i]) + ", ";
}
res += "}";
return res;
}
}
} else {
// JavaScript primitive value
return obj;
}
}
/*
* Generic array/iterator/enumeration [or even object!] manipulation
* functions. These functions accept an array/iteration/enumeration
* and expression String or function. These functions iterate each
* element of array and apply the expression/function on each element.
*/
// private function to wrap an Iterator as an Enumeration
function wrapIterator(itr, wrap) {
if (itr instanceof java.util.Iterator) {
return new java.util.Enumeration() {
hasMoreElements: function() {
return itr.hasNext();
},
nextElement: function() {
return wrap? wrapJavaValue(itr.next()) : itr.next();
}
};
} else {
return itr;
}
}
/**
* Converts an enumeration/iterator/object into an array
*
* @param obj enumeration/iterator/object
* @return array that contains values of enumeration/iterator/object
*/
function toArray(obj) {
obj = wrapIterator(obj);
if (obj instanceof java.util.Enumeration) {
var res = new Array();
while (obj.hasMoreElements()) {
res[res.length] = obj.nextElement();
}
return res;
} else if (obj instanceof Array) {
return obj;
} else {
var res = new Array();
for (var index in obj) {
res[res.length] = obj[index];
}
return res;
}
}
/**
* Returns whether the given array/iterator/enumeration contains
* an element that satisfies the given boolean expression specified
* in code.
*
* @param array input array/iterator/enumeration that is iterated
* @param code expression string or function
* @return boolean result
*
* The code evaluated can refer to the following built-in variables.
*
* 'it' -> currently visited element
* 'index' -> index of the current element
* 'array' -> array that is being iterated
*/
function contains(array, code) {
array = wrapIterator(array);
var func = code;
if (typeof(func) != 'function') {
func = new Function("it", "index", "array", "return " + code);
}
if (array instanceof java.util.Enumeration) {
var index = 0;
while (array.hasMoreElements()) {
var it = array.nextElement();
if (func(it, index, array)) {
return true;
}
index++;
}
} else {
for (var index in array) {
var it = array[index];
if (func(it, index, array)) {
return true;
}
}
}
return false;
}
/**
* concatenates two arrays/iterators/enumerators.
*
* @param array1 array/iterator/enumeration
* @param array2 array/iterator/enumeration
*
* @return concatenated array or composite enumeration
*/
function concat(array1, array2) {
array1 = wrapIterator(array1);
array2 = wrapIterator(array2);
if (array1 instanceof Array && array2 instanceof Array) {
return array1.concat(array2);
} else if (array1 instanceof java.util.Enumeration &&
array2 instanceof java.util.Enumeration) {
return new Packages.com.sun.tools.hat.internal.util.CompositeEnumeration(array1, array2);
} else {
return undefined;
}
}
/**
* Returns the number of array/iterator/enumeration elements
* that satisfy the given boolean expression specified in code.
* The code evaluated can refer to the following built-in variables.
*
* @param array input array/iterator/enumeration that is iterated
* @param code expression string or function
* @return number of elements
*
* 'it' -> currently visited element
* 'index' -> index of the current element
* 'array' -> array that is being iterated
*/
function count(array, code) {
if (code == undefined) {
return length(array);
}
array = wrapIterator(array);
var func = code;
if (typeof(func) != 'function') {
func = new Function("it", "index", "array", "return " + code);
}
var result = 0;
if (array instanceof java.util.Enumeration) {
var index = 0;
while (array.hasMoreElements()) {
var it = array.nextElement();
if (func(it, index, array)) {
result++;
}
index++;
}
} else {
for (var index in array) {
var it = array[index];
if (func(it, index, array)) {
result++;
}
}
}
return result;
}
/**
* filter function returns an array/enumeration that contains
* elements of the input array/iterator/enumeration that satisfy
* the given boolean expression. The boolean expression code can
* refer to the following built-in variables.
*
* @param array input array/iterator/enumeration that is iterated
* @param code expression string or function
* @return array/enumeration that contains the filtered elements
*
* 'it' -> currently visited element
* 'index' -> index of the current element
* 'array' -> array that is being iterated
* 'result' -> result array
*/
function filter(array, code) {
array = wrapIterator(array);
var func = code;
if (typeof(code) != 'function') {
func = new Function("it", "index", "array", "result", "return " + code);
}
if (array instanceof java.util.Enumeration) {
return filterEnumeration(array, func, false);
} else {
var result = new Array();
for (var index in array) {
var it = array[index];
if (func(it, index, array, result)) {
result[result.length] = it;
}
}
return result;
}
}
/**
* Returns the number of elements of array/iterator/enumeration.
*
* @param array input array/iterator/enumeration that is iterated
*/
function length(array) {
array = wrapIterator(array);
if (array instanceof Array) {
return array.length;
} else if (array instanceof java.util.Enumeration) {
var cnt = 0;
while (array.hasMoreElements()) {
array.nextElement();
cnt++;
}
return cnt;
} else {
var cnt = 0;
for (var index in array) {
cnt++;
}
return cnt;
}
}
/**
* Transforms the given object or array by evaluating given code
* on each element of the object or array. The code evaluated
* can refer to the following built-in variables.
*
* @param array input array/iterator/enumeration that is iterated
* @param code expression string or function
* @return array/enumeration that contains mapped values
*
* 'it' -> currently visited element
* 'index' -> index of the current element
* 'array' -> array that is being iterated
* 'result' -> result array
*
* map function returns an array/enumeration of values created
* by repeatedly calling code on each element of the input
* array/iterator/enumeration.
*/
function map(array, code) {
array = wrapIterator(array);
var func = code;
if(typeof(code) != 'function') {
func = new Function("it", "index", "array", "result", "return " + code);
}
if (array instanceof java.util.Enumeration) {
var index = 0;
var result = new java.util.Enumeration() {
hasMoreElements: function() {
return array.hasMoreElements();
},
nextElement: function() {
return func(array.nextElement(), index++, array, result);
}
};
return result;
} else {
var result = new Array();
for (var index in array) {
var it = array[index];
result[result.length] = func(it, index, array, result);
}
return result;
}
}
// private function used by min, max functions
function minmax(array, code) {
if (typeof(code) == 'string') {
code = new Function("lhs", "rhs", "return " + code);
}
array = wrapIterator(array);
if (array instanceof java.util.Enumeration) {
if (! array.hasMoreElements()) {
return undefined;
}
var res = array.nextElement();
while (array.hasMoreElements()) {
var next = array.nextElement();
if (code(next, res)) {
res = next;
}
}
return res;
} else {
if (array.length == 0) {
return undefined;
}
var res = array[0];
for (var index = 1; index < array.length; index++) {
if (code(array[index], res)) {
res = array[index];
}
}
return res;
}
}
/**
* Returns the maximum element of the array/iterator/enumeration
*
* @param array input array/iterator/enumeration that is iterated
* @param code (optional) comparision expression or function
* by default numerical maximum is computed.
*/
function max(array, code) {
if (code == undefined) {
code = function (lhs, rhs) { return lhs > rhs; }
}
return minmax(array, code);
}
/**
* Returns the minimum element of the array/iterator/enumeration
*
* @param array input array/iterator/enumeration that is iterated
* @param code (optional) comparision expression or function
* by default numerical minimum is computed.
*/
function min(array, code) {
if (code == undefined) {
code = function (lhs, rhs) { return lhs < rhs; }
}
return minmax(array, code);
}
/**
* sort function sorts the input array. optionally accepts
* code to compare the elements. If code is not supplied,
* numerical sort is done.
*
* @param array input array/iterator/enumeration that is sorted
* @param code expression string or function
* @return sorted array
*
* The comparison expression can refer to the following
* built-in variables:
*
* 'lhs' -> 'left side' element
* 'rhs' -> 'right side' element
*/
function sort(array, code) {
// we need an array to sort, so convert non-arrays
array = toArray(array);
// by default use numerical comparison
var func = code;
if (code == undefined) {
func = function(lhs, rhs) { return lhs - rhs; };
} else if (typeof(code) == 'string') {
func = new Function("lhs", "rhs", "return " + code);
}
return array.sort(func);
}
/**
* Returns the sum of the elements of the array
*
* @param array input array that is summed.
* @param code optional expression used to map
* input elements before sum.
*/
function sum(array, code) {
array = wrapIterator(array);
if (code != undefined) {
array = map(array, code);
}
var result = 0;
if (array instanceof java.util.Enumeration) {
while (array.hasMoreElements()) {
result += Number(array.nextElement());
}
} else {
for (var index in array) {
result += Number(array[index]);
}
}
return result;
}
/**
* Returns array of unique elements from the given input
* array/iterator/enumeration.
*
* @param array from which unique elements are returned.
* @param code optional expression (or function) giving unique
* attribute/property for each element.
* by default, objectid is used for uniqueness.
*/
function unique(array, code) {
array = wrapIterator(array);
if (code == undefined) {
code = new Function("it", "return objectid(it);");
} else if (typeof(code) == 'string') {
code = new Function("it", "return " + code);
}
var tmp = new Object();
if (array instanceof java.util.Enumeration) {
while (array.hasMoreElements()) {
var it = array.nextElement();
tmp[code(it)] = it;
}
} else {
for (var index in array) {
var it = array[index];
tmp[code(it)] = it;
}
}
var res = new Array();
for (var index in tmp) {
res[res.length] = tmp[index];
}
return res;
}