001 
002 /*
003  *  JScripter Emulation 1.0 - To Script Java
004  *  Copyright (C) 2008-2011  J.J.Liu<jianjunliu@126.com> <http://www.jscripter.org>
005  *  
006  *  This program is free software: you can redistribute it and/or modify
007  *  it under the terms of the GNU Affero General Public License as published by
008  *  the Free Software Foundation, either version 3 of the License, or
009  *  (at your option) any later version.
010  *  
011  *  This program is distributed in the hope that it will be useful,
012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014  *  GNU Affero General Public License for more details.
015  *  
016  *  You should have received a copy of the GNU Affero General Public License
017  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
018  */
019 
020 package org.jscripter.emu.gc;
021 
022 import org.jscripter.emu.java.JavaInterface;
023 
024 import js.ArrayLike;
025 import js.Disposable;
026 import js.Id;
027 import js.Index;
028 import js.Js;
029 import js.ObjectLike;
030 import js.Vars;
031 import js.core.JsFunction;
032 import js.user.JsNode;
033 import jsx.Timeout;
034 import jsx.client.Document;
035 import jsx.client.Global;
036 import jsx.client.Win;
037 import jsx.core.ArrayLikes;
038 import jsx.core.ObjectLikes;
039 import jsx.dom.Nodes;
040 
041 /**
042  * <p>An <tt>internal</tt> abstract base class for DOM-enabled visitors emulating garbage collection 
043  * based finalization in JavaScript.</p>
044  * <p>This class is only used internally by JS re-compiler implementations.</p>
045  * 
046  * @author <a href="mailto:jianjunliu@126.com">J.J.Liu (Jianjun Liu)</a> at <a href="http://www.jscripter.org" target="_blank">http://www.jscripter.org</a>
047  * 
048  * @javascript This class is only loaded and resolved by re-compiler implementations.
049  */
050 public abstract class GC extends Disposable
051 {
052     /**
053      * <p>Internally constructs an object visitor of this type.</p>
054      * @since 1.0
055      * @javascript Re-compilers must report error on end-users directly using this constructor.
056      */
057     protected GC() {}
058 
059     /**
060      * <p>Internally traverses all children of an object.</p>
061      * @param o An object to traverse.
062      * @since 1.0
063      * @javascript Re-compilers must report error on end-users directly using this constructor.
064      */
065     protected void traverse(ObjectLike o) {
066         JsNode c = Nodes.firstChild((JsNode)o, 1);
067         while (isObjectLike(c)) {
068             visit(c);
069             c = Nodes.nextSibling(c, 1);
070         }
071     }
072 
073     /**
074      * <p>Internally visits an object.</p>
075      * @param o An object to visit.
076      * @since 1.0
077      * @javascript Re-compilers must report error on end-users directly using this method.
078      */
079     protected abstract void visit(ObjectLike o);
080 
081     /**
082      * <p>Internally checks if a variable represents an object constructed with a Java class.</p>
083      * @param o A value or object.
084      * @return <tt>true</tt> if <tt>o</tt> is Java; <tt>false</tt>, otherwise.
085      * @since 1.0
086      * @javascript Re-compilers must report error on end-users directly using this method.
087      */
088     protected static final boolean isJavaObject(Object o) {
089         return Js.isJavaObject(o);
090     }
091 
092     /**
093      * <p>Internally checks if a variable is a general object that is neither Java nor DOM.</p>
094      * @param o A value or object.
095      * @return <tt>true</tt> if <tt>o</tt> is general; <tt>false</tt>, otherwise.
096      * @since 1.0
097      * @javascript Re-compilers must report error on end-users directly using this method.
098      */
099     protected static final boolean isObjectLike(Object o) {
100         return Js.instanceOf(o, Global.Object.var()) &&
101             !(o instanceof JavaInterface);
102     }
103 
104     /**
105      * <p>Internally checks if an object is relevant to the emulation of finalization.</p>
106      * @param o An object.
107      * @return <tt>true</tt> if it is relevant; <tt>false</tt>, otherwise.
108      * @since 1.0
109      * @javascript Re-compilers must report error on end-users directly using this method.
110      */
111     protected static final boolean isRelevant(ObjectLike o) {
112         return !(o instanceof Disposable) && o != Js.win() &&
113             o != Win.document.var() && o != Document.body.var();
114     }
115 
116     /**
117      * <p>A global identifier for a configurable property of an object.</p>
118      * <p>The identified configurable property of an object is a reference 
119      * to an integer number that indicates how many counts the object are 
120      * currently referenced by other live objects.</p>
121      * @javascript Re-compilers must report error on end-users directly using this identifier.
122      * @since 1.0
123      */
124     protected final static Index<Integer> REFS = new Index<Integer>("$GC.REFS");
125 
126     /**
127      * <p>Internally checks if an object is allocated.</p>
128      * @param o An object.
129      * @return <tt>true</tt> if it is allocated; <tt>false</tt>, otherwise.
130      * @since 1.0
131      * @javascript Re-compilers must report error on end-users directly using this method.
132      */
133     protected static final boolean isAllocated(ObjectLike o) {
134         return Js.gt(ObjectLikes.get(o, REFS), 0);
135     }
136 
137     /**
138      * <p>Internal logical OR operation, resembling that of JavaScript, performs the Boolean OR 
139      * operation on the two values: it returns <tt>true</tt> if either the first operand or 
140      * the second operand is <tt>true</tt>, or if both are <tt>true</tt>. If both operands 
141      * are <tt>false</tt>, it returns <tt>false</tt>.</p>
142      * @param a A value or object.
143      * @param b A value or object.
144      * @return The logical OR of the two operands.
145      * @since 1.0
146      * @javascript Re-compilers must report error on end-users directly using this method.
147      */
148     protected static final boolean or(Object a, Object b) {
149         return Js.be(Js.or(a, b));
150     }
151 
152     /**
153      * <p>Internally inverts the boolean value of its operand, resembling the logical NOT operator 
154      * in JavaScript.</p>
155      * @param o A value or object.
156      * @return The inverted boolean value.
157      * @since 1.0
158      * @javascript Re-compilers must report error on end-users directly using this method.
159      */
160     protected static final boolean not(Object o) {
161         return or(Js.not(o), Js.eq(o, "undefined"));
162     }
163 
164     private final static Index<String> DBG = new Index<String>("$GC.DBG");
165 
166     private static final String getString(ObjectLike o, Id<String> id) {
167         String s = o.var(id);
168         return Js.cond(
169                 Js.be(s),
170                 s,
171                 "none"
172         );
173     }
174 
175     static final void debug(String d, String s, ObjectLike o) {
176         if (Js.be(o) && Js.eq(d, getString(o, DBG))) {
177             debug(s, o);
178         }
179     }
180 
181     static final void debug(String s, ObjectLike o) {
182         if (Js.be(o)) {
183             ArrayLike<String> msg = new Vars<String>()
184                 .add(s)
185                 .add(":")
186                 .add(getString(o, DBG))
187                 .add(":")
188                 .add((String)(Object)ObjectLikes.get(o, REFS))
189                 .var();
190             Js.alert(msg.join(""));
191         }
192     }
193 
194     private static ArrayLike<?> CACHE;
195 
196     private static void clear() {
197         dec(CACHE);
198         CACHE = inc(new Vars<Object>().var());
199     }
200 
201     /**
202      * <p>Internally caches a value if it is an object relevant to the finalization emulation.</p>
203      * @param o A value or object.
204      * @return The cached object.
205      * @since 1.0
206      * @javascript Re-compilers must report error on end-users directly using this method.
207      */
208     public static <T> T cache(T o) {
209         if (isObjectLike(o) && isRelevant((ObjectLike)o)) {
210             if (Js.not(CACHE)) {
211                 clear();
212             }
213             CACHE.push(o);
214         }
215         return o;
216     }
217 
218     private static ObjectLike HEAD;
219     private final static Index<ObjectLike> THIS = new Index<ObjectLike>("$GC.THIS");
220     private final static Index<ObjectLike> NEXT = new Index<ObjectLike>("$GC.NEXT");
221     private final static Index<ObjectLike> PREV = new Index<ObjectLike>("$GC.PREV");
222 
223     /**
224      * <p>Internally creates a calling context for the finalization emulation.</p>
225      * @param arguments The arguments of the invocation.
226      * @return The newly created context object.
227      * @since 1.0
228      * @javascript Re-compilers must report error on end-users directly using this method.
229      */
230     public static ObjectLike context(ObjectLike arguments) {
231         ObjectLike c = (ObjectLike)incElements(
232                 (ArrayLike<?>)arguments
233         );
234         c.var(THIS, (ObjectLike)inc(Js.eval("this")));
235         c.var(NEXT, HEAD);
236         if (Js.be(HEAD)) {
237             HEAD.var(PREV, inc(c));
238         }
239         HEAD = inc(c);
240         return HEAD;
241     }
242 
243     /**
244      * <p>Internally clears and releases a calling context for the finalization emulation
245      * optionally with a return value.</p>
246      * @param c A calling context object.
247      * @param r An optional return value.
248      * @return The returned value or <tt>undefined</tt> for none.
249      * @since 1.0
250      * @javascript Re-compilers must report error on end-users directly using this method.
251      */
252     public static <T> T release(ObjectLike c, T r) {
253         inc(r);
254         clear();
255         ObjectLike next = c.var(NEXT);
256         ObjectLike prev = c.var(PREV);
257         if (Js.be(next)) {
258             next.var(PREV, prev);
259             ObjectLikes.dec(c, REFS);
260             ObjectLikes.delete(c, NEXT);
261         }
262         if (Js.be(prev)) {
263             prev.var(NEXT, next);
264             ObjectLikes.delete(c, PREV);
265         } else {
266             HEAD = next;
267         }
268         dec(c);
269         return r;
270     }
271 
272     private static <T> T asg(T o, T r) {
273         inc(r);
274         dec(o);
275         return r;
276     }
277 
278     /**
279      * <p>Internally assigns a value to a variable with the finalization emulation.</p>
280      * @param o A variable to assign the value.
281      * @param r A value to replace the value.
282      * @return The new value.
283      * @since 1.0
284      * @javascript Re-compilers must report error on end-users directly using this method.
285      */
286     public static <T> T replace(T o, T r) {
287         if (r != o) {
288             asg(o, r);
289         }
290         clear();
291         return r;
292     }
293 
294     /**
295      * <p>Internally sets a property value of an object with the finalization emulation.</p>
296      * @param o An object to set property value.
297      * @param r A name of a property.
298      * @param v A new value for the property of the object.
299      * @return The new value.
300      * @since 1.0
301      * @javascript Re-compilers must report error on end-users directly using this method.
302      */
303     public static Object set(ObjectLike o, String r, Object v) {
304         return Js.cond(
305                 Js.eq(o, v),
306                 o.var(r, v),
307                 o.var(r, asg(o.var(r), v))
308         );
309     }
310 
311     /**
312      * <p>Internally increases the reference count of an object.</p>
313      * @param o An object to increase reference count.
314      * @return The object itself.
315      * @see #REFS
316      * @see #dec(Object)
317      * @see #inc(Object)
318      * @see #incAll(ObjectLike)
319      * @see #incElements(ArrayLike)
320      * @since 1.0
321      * @javascript Re-compilers must report error on end-users directly using this method.
322      */
323     public static <T> T inc(T o) {
324         if (isObjectLike(o) && isRelevant((ObjectLike)(o))) {
325             ObjectLikes.inc((ObjectLike)o, REFS);
326         }
327         return o;
328     }
329 
330     /**
331      * <p>Internally increases the reference counts of all elements of an array.</p>
332      * @param a An array to increase reference count for elements.
333      * @return The array itself.
334      * @see #REFS
335      * @see #dec(Object)
336      * @see #inc(Object)
337      * @see #incAll(ObjectLike)
338      * @since 1.0
339      * @javascript Re-compilers must report error on end-users directly using this method.
340      */
341     public static <T> ArrayLike<T> incElements(ArrayLike<T> a) {
342         ArrayLike<T> r = new Vars<T>().var();
343         for (int i = 0, len = a.length(); i < len; i++) {
344             r.set(i, inc(a.get(i)));
345         }
346         return r;
347     }
348 
349     /**
350      * <p>Internally increases the reference counts of all elements of an object.</p>
351      * @param o An object to increase reference count for elements.
352      * @return The object itself.
353      * @see #REFS
354      * @see #dec(Object)
355      * @see #inc(Object)
356      * @see #incElements(ArrayLike)
357      * @since 1.0
358      * @javascript Re-compilers must report error on end-users directly using this method.
359      */
360     public static ObjectLike incAll(ObjectLike o) {
361         ArrayLike<String> keys = Js.keys(o);
362         for (int i = 0, len = keys.length(); i < len; i++) {
363             inc(o.var(keys.get(i)));
364         }
365         return o;
366     }
367 
368     /**
369      * <p>Internally decreases the reference count of an object.</p>
370      * @param o An object to decrease reference count.
371      * @return The object itself.
372      * @see #REFS
373      * @see #inc(Object)
374      * @see #incAll(ObjectLike)
375      * @see #incElements(ArrayLike)
376      * @since 1.0
377      * @javascript Re-compilers must report error on end-users directly using this method.
378      */
379     protected static <T> T dec(T o) {
380         if (isObjectLike(o) && isRelevant((ObjectLike)o) &&
381                 isAllocated((ObjectLike)o)) {
382             ObjectLikes.dec((ObjectLike)o, REFS);
383             DFS.start((ObjectLike)o);
384         }
385         return o;
386     }
387 
388     /**
389      * <p>Internally executes code at periodic intervals, with finalization support 
390      * simulating the JavaScript global function of the same name.</p>
391      * <p>Note that the specified function is executed in the context of the Window object, 
392      * that is, the Window object is the value of the <tt>this</tt> keyword of the 
393      * executing context of the function. This is <tt>true</tt> even if the call to 
394      * {@link #setTimeout(JsFunction, Number)} occurred within a function with a longer 
395      * scope chain.</p>
396      * @param f A function to be periodically invoked.
397      * @param interval The interval, in milliseconds, between invocations of the function.
398      * @return A value that can be passed to {@link #clearInterval(ArrayLike)} method to 
399      * cancel the periodic execution of the function.
400      * @see #clearInterval(ArrayLike)
401      * @see js.Js#setInterval(JsFunction)
402      * @see js.Js#setInterval(JsFunction, Number)
403      * @see jsx.client.Global#setInterval(JsFunction)
404      * @see jsx.client.Global#setInterval(JsFunction, Number)
405      * @see jsx.client.Client#setInterval(JsFunction)
406      * @see jsx.client.Client#setInterval(JsFunction, Number)
407      * @see jsx.Interval
408      * @since 1.0
409      * @javascript Re-compilers must report error on end-users directly using this method.
410      */
411     public static final ArrayLike<?> setInterval(JsFunction<?> f, Number interval) {
412         ArrayLike<?> id = new Vars<Object>().add(inc(f)).var();
413         ArrayLikes.push(id, Global.setInterval(f, interval));
414         return id;
415     }
416 
417     /**
418      * <p>Internally cancels periodic execution of code, with finalization support
419      * simulating the JavaScript global function of the same name.</p>
420      * <p>This method stops the repeated execution of code that was started by a call to 
421      * {@link #setInterval(JsFunction, Number)}. <tt>intervalId</tt> must be the value 
422      * that was returned by a call to {@link #setInterval(JsFunction, Number)}.</p>
423      * @param intervalId The value returned by the corresponding call to {@link #setInterval(JsFunction, Number)}.
424      * @see #setInterval(JsFunction, Number)
425      * @see js.Js#setInterval(JsFunction, Number)
426      * @see jsx.client.Global#clearInterval(Object)
427      * @see jsx.client.Client#clearInterval(Object)
428      * @see jsx.Interval
429      * @since 1.0
430      * @javascript Re-compilers must report error on end-users directly using this method.
431      */
432     public static final void clearInterval(ArrayLike<?> intervalId) {
433         Global.clearInterval(intervalId.pop());
434         dec(intervalId.pop());
435     }
436 
437     /**
438      * <p>Internally executes code after a specified amount of time elapses, with finalization support 
439      * simulating the JavaScript global function of the same name.</p>
440      * <p>Note that this method executes the specified function only once. The function is 
441      * executed in the context of the Window object, that is, the Window object is the 
442      * value of the <tt>this</tt> keyword of the executing context of the function. 
443      * This is <tt>true</tt> even if the call to {@link #setTimeout(JsFunction, Number)} 
444      * occurred within a function with a longer scope chain.</p>
445      * @param f A function to be invoked after the <tt>delay</tt> has elapsed.
446      * @param delay The amount of time, in milliseconds, before the function should be executed.
447      * @return A value that can be passed to the {@link #clearTimeout(ArrayLike)} method to 
448      * cancel the execution of the function.
449      * @see #clearTimeout(ArrayLike)
450      * @see js.Js#setTimeout(JsFunction)
451      * @see js.Js#setTimeout(JsFunction, Number)
452      * @see jsx.client.Global#setTimeout(JsFunction)
453      * @see jsx.client.Global#setTimeout(JsFunction, Number)
454      * @see jsx.client.Client#setTimeout(JsFunction)
455      * @see jsx.client.Client#setTimeout(JsFunction, Number)
456      * @see jsx.Timeout
457      * @since 1.0
458      * @javascript Re-compilers must report error on end-users directly using this method.
459      */
460     public static ArrayLike<?> setTimeout(final JsFunction<?> f, Number delay) {
461         final ArrayLike<?> id = new Vars<Object>().add(inc(f)).var();
462         Timeout t = new Timeout() {
463             @Override
464             public void run() {
465                 f.invoke();
466                 clearTimeout(id);
467             }
468         };
469         id.push(t);
470         t.set(delay);
471         return id;
472     }
473 
474     /**
475      * <p>Internally cancels a pending timeout operation, with finalization support
476      * simulating the JavaScript global function of the same name.</p>
477      * <p>This method cancels the execution of code that has been deferred with the 
478      * {@link #setTimeout(JsFunction, Number)} method. The <tt>timeoutId</tt> argument 
479      * is a value returned by the call to {@link #setTimeout(JsFunction, Number)} and 
480      * identifies which deferred code to cancel.</p>
481      * @param timeoutId A value returned by {@link #setTimeout(JsFunction, Number)} that 
482      * identifies the timeout to be canceled.
483      * @see #setTimeout(JsFunction, Number)
484      * @see js.Js#setTimeout(JsFunction, Number)
485      * @see js.Js#clearTimeout(Object)
486      * @see jsx.client.Global#clearTimeout(Object)
487      * @see jsx.client.Client#clearTimeout(Object)
488      * @see jsx.Timeout
489      * @since 1.0
490      * @javascript Re-compilers must report error on end-users directly using this method.
491      */
492     public static final void clearTimeout(ArrayLike<?> timeoutId) {
493         ((Timeout)timeoutId.pop()).clear();
494         dec(timeoutId.pop());
495     }
496 }