Mon, 16 Sep 2013 15:08:36 +0530
8024847: Java.to should accept mirror and external JSObjects as array-like objects as well
Reviewed-by: hannesw, attila, lagergren
1 /*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package jdk.nashorn.internal.runtime;
28 import java.util.AbstractList;
29 import java.util.Deque;
30 import java.util.Iterator;
31 import java.util.ListIterator;
32 import java.util.NoSuchElementException;
33 import java.util.RandomAccess;
34 import java.util.concurrent.Callable;
35 import jdk.nashorn.api.scripting.JSObject;
36 import jdk.nashorn.internal.runtime.linker.Bootstrap;
37 import jdk.nashorn.internal.runtime.linker.InvokeByName;
39 /**
40 * An adapter that can wrap any ECMAScript Array-like object (that adheres to the array rules for the property
41 * {@code length} and having conforming {@code push}, {@code pop}, {@code shift}, {@code unshift}, and {@code splice}
42 * methods) and expose it as both a Java list and double-ended queue. While script arrays aren't necessarily efficient
43 * as dequeues, it's still slightly more efficient to be able to translate dequeue operations into pushes, pops, shifts,
44 * and unshifts, than to blindly translate all list's add/remove operations into splices. Also, it is conceivable that a
45 * custom script object that implements an Array-like API can have a background data representation that is optimized
46 * for dequeue-like access. Note that with ECMAScript arrays, {@code push} and {@code pop} operate at the end of the
47 * array, while in Java {@code Deque} they operate on the front of the queue and as such the Java dequeue
48 * {@link #push(Object)} and {@link #pop()} operations will translate to {@code unshift} and {@code shift} script
49 * operations respectively, while {@link #addLast(Object)} and {@link #removeLast()} will translate to {@code push} and
50 * {@code pop}.
51 */
52 public abstract class ListAdapter extends AbstractList<Object> implements RandomAccess, Deque<Object> {
53 // These add to the back and front of the list
54 private static final Object PUSH = new Object();
55 private static InvokeByName getPUSH() {
56 return ((GlobalObject)Context.getGlobal()).getInvokeByName(PUSH,
57 new Callable<InvokeByName>() {
58 @Override
59 public InvokeByName call() {
60 return new InvokeByName("push", Object.class, void.class, Object.class);
61 }
62 });
63 }
65 private static final Object UNSHIFT = new Object();
66 private static InvokeByName getUNSHIFT() {
67 return ((GlobalObject)Context.getGlobal()).getInvokeByName(UNSHIFT,
68 new Callable<InvokeByName>() {
69 @Override
70 public InvokeByName call() {
71 return new InvokeByName("unshift", Object.class, void.class, Object.class);
72 }
73 });
74 }
76 // These remove from the back and front of the list
77 private static final Object POP = new Object();
78 private static InvokeByName getPOP() {
79 return ((GlobalObject)Context.getGlobal()).getInvokeByName(POP,
80 new Callable<InvokeByName>() {
81 @Override
82 public InvokeByName call() {
83 return new InvokeByName("pop", Object.class, Object.class);
84 }
85 });
86 }
88 private static final Object SHIFT = new Object();
89 private static InvokeByName getSHIFT() {
90 return ((GlobalObject)Context.getGlobal()).getInvokeByName(SHIFT,
91 new Callable<InvokeByName>() {
92 @Override
93 public InvokeByName call() {
94 return new InvokeByName("shift", Object.class, Object.class);
95 }
96 });
97 }
99 // These insert and remove in the middle of the list
100 private static final Object SPLICE_ADD = new Object();
101 private static InvokeByName getSPLICE_ADD() {
102 return ((GlobalObject)Context.getGlobal()).getInvokeByName(SPLICE_ADD,
103 new Callable<InvokeByName>() {
104 @Override
105 public InvokeByName call() {
106 return new InvokeByName("splice", Object.class, void.class, int.class, int.class, Object.class);
107 }
108 });
109 }
111 private static final Object SPLICE_REMOVE = new Object();
112 private static InvokeByName getSPLICE_REMOVE() {
113 return ((GlobalObject)Context.getGlobal()).getInvokeByName(SPLICE_REMOVE,
114 new Callable<InvokeByName>() {
115 @Override
116 public InvokeByName call() {
117 return new InvokeByName("splice", Object.class, void.class, int.class, int.class);
118 }
119 });
120 }
122 protected final Object obj;
124 // allow subclasses only in this package
125 ListAdapter(Object obj) {
126 this.obj = obj;
127 }
129 /**
130 * Factory to create a ListAdapter for a given script object.
131 *
132 * @param obj script object to wrap as a ListAdapter
133 * @return A ListAdapter wrapper object
134 */
135 public static ListAdapter create(final Object obj) {
136 if (obj instanceof ScriptObject) {
137 return new ScriptObjectListAdapter((ScriptObject)obj);
138 } else if (obj instanceof JSObject) {
139 return new JSObjectListAdapter((JSObject)obj);
140 } else {
141 throw new IllegalArgumentException("ScriptObject or JSObject expected");
142 }
143 }
145 @Override
146 public final Object get(int index) {
147 checkRange(index);
148 return getAt(index);
149 }
151 protected abstract Object getAt(final int index);
153 @Override
154 public Object set(int index, Object element) {
155 checkRange(index);
156 final Object prevValue = getAt(index);
157 setAt(index, element);
158 return prevValue;
159 }
161 protected abstract void setAt(int index, Object element);
163 private void checkRange(int index) {
164 if(index < 0 || index >= size()) {
165 throw invalidIndex(index);
166 }
167 }
169 @Override
170 public final void push(Object e) {
171 addFirst(e);
172 }
174 @Override
175 public final boolean add(Object e) {
176 addLast(e);
177 return true;
178 }
180 @Override
181 public final void addFirst(Object e) {
182 try {
183 final InvokeByName unshiftInvoker = getUNSHIFT();
184 final Object fn = unshiftInvoker.getGetter().invokeExact(obj);
185 checkFunction(fn, unshiftInvoker);
186 unshiftInvoker.getInvoker().invokeExact(fn, obj, e);
187 } catch(RuntimeException | Error ex) {
188 throw ex;
189 } catch(Throwable t) {
190 throw new RuntimeException(t);
191 }
192 }
194 @Override
195 public final void addLast(Object e) {
196 try {
197 final InvokeByName pushInvoker = getPUSH();
198 final Object fn = pushInvoker.getGetter().invokeExact(obj);
199 checkFunction(fn, pushInvoker);
200 pushInvoker.getInvoker().invokeExact(fn, obj, e);
201 } catch(RuntimeException | Error ex) {
202 throw ex;
203 } catch(Throwable t) {
204 throw new RuntimeException(t);
205 }
206 }
208 @Override
209 public final void add(int index, Object e) {
210 try {
211 if(index < 0) {
212 throw invalidIndex(index);
213 } else if(index == 0) {
214 addFirst(e);
215 } else {
216 final int size = size();
217 if(index < size) {
218 final InvokeByName spliceAddInvoker = getSPLICE_ADD();
219 final Object fn = spliceAddInvoker.getGetter().invokeExact(obj);
220 checkFunction(fn, spliceAddInvoker);
221 spliceAddInvoker.getInvoker().invokeExact(fn, obj, index, 0, e);
222 } else if(index == size) {
223 addLast(e);
224 } else {
225 throw invalidIndex(index);
226 }
227 }
228 } catch(RuntimeException | Error ex) {
229 throw ex;
230 } catch(Throwable t) {
231 throw new RuntimeException(t);
232 }
233 }
234 private static void checkFunction(Object fn, InvokeByName invoke) {
235 if(!(Bootstrap.isCallable(fn))) {
236 throw new UnsupportedOperationException("The script object doesn't have a function named " + invoke.getName());
237 }
238 }
240 private static IndexOutOfBoundsException invalidIndex(int index) {
241 return new IndexOutOfBoundsException(String.valueOf(index));
242 }
244 @Override
245 public final boolean offer(Object e) {
246 return offerLast(e);
247 }
249 @Override
250 public final boolean offerFirst(Object e) {
251 addFirst(e);
252 return true;
253 }
255 @Override
256 public final boolean offerLast(Object e) {
257 addLast(e);
258 return true;
259 }
261 @Override
262 public final Object pop() {
263 return removeFirst();
264 }
266 @Override
267 public final Object remove() {
268 return removeFirst();
269 }
271 @Override
272 public final Object removeFirst() {
273 checkNonEmpty();
274 return invokeShift();
275 }
277 @Override
278 public final Object removeLast() {
279 checkNonEmpty();
280 return invokePop();
281 }
283 private void checkNonEmpty() {
284 if(isEmpty()) {
285 throw new NoSuchElementException();
286 }
287 }
289 @Override
290 public final Object remove(int index) {
291 if(index < 0) {
292 throw invalidIndex(index);
293 } else if (index == 0) {
294 return invokeShift();
295 } else {
296 final int maxIndex = size() - 1;
297 if(index < maxIndex) {
298 final Object prevValue = get(index);
299 invokeSpliceRemove(index, 1);
300 return prevValue;
301 } else if(index == maxIndex) {
302 return invokePop();
303 } else {
304 throw invalidIndex(index);
305 }
306 }
307 }
309 private Object invokeShift() {
310 try {
311 final InvokeByName shiftInvoker = getSHIFT();
312 final Object fn = shiftInvoker.getGetter().invokeExact(obj);
313 checkFunction(fn, shiftInvoker);
314 return shiftInvoker.getInvoker().invokeExact(fn, obj);
315 } catch(RuntimeException | Error ex) {
316 throw ex;
317 } catch(Throwable t) {
318 throw new RuntimeException(t);
319 }
320 }
322 private Object invokePop() {
323 try {
324 final InvokeByName popInvoker = getPOP();
325 final Object fn = popInvoker.getGetter().invokeExact(obj);
326 checkFunction(fn, popInvoker);
327 return popInvoker.getInvoker().invokeExact(fn, obj);
328 } catch(RuntimeException | Error ex) {
329 throw ex;
330 } catch(Throwable t) {
331 throw new RuntimeException(t);
332 }
333 }
335 @Override
336 protected final void removeRange(int fromIndex, int toIndex) {
337 invokeSpliceRemove(fromIndex, toIndex - fromIndex);
338 }
340 private void invokeSpliceRemove(int fromIndex, int count) {
341 try {
342 final InvokeByName spliceRemoveInvoker = getSPLICE_REMOVE();
343 final Object fn = spliceRemoveInvoker.getGetter().invokeExact(obj);
344 checkFunction(fn, spliceRemoveInvoker);
345 spliceRemoveInvoker.getInvoker().invokeExact(fn, obj, fromIndex, count);
346 } catch(RuntimeException | Error ex) {
347 throw ex;
348 } catch(Throwable t) {
349 throw new RuntimeException(t);
350 }
351 }
353 @Override
354 public final Object poll() {
355 return pollFirst();
356 }
358 @Override
359 public final Object pollFirst() {
360 return isEmpty() ? null : invokeShift();
361 }
363 @Override
364 public final Object pollLast() {
365 return isEmpty() ? null : invokePop();
366 }
368 @Override
369 public final Object peek() {
370 return peekFirst();
371 }
373 @Override
374 public final Object peekFirst() {
375 return isEmpty() ? null : get(0);
376 }
378 @Override
379 public final Object peekLast() {
380 return isEmpty() ? null : get(size() - 1);
381 }
383 @Override
384 public final Object element() {
385 return getFirst();
386 }
388 @Override
389 public final Object getFirst() {
390 checkNonEmpty();
391 return get(0);
392 }
394 @Override
395 public final Object getLast() {
396 checkNonEmpty();
397 return get(size() - 1);
398 }
400 @Override
401 public final Iterator<Object> descendingIterator() {
402 final ListIterator<Object> it = listIterator(size());
403 return new Iterator<Object>() {
404 @Override
405 public boolean hasNext() {
406 return it.hasPrevious();
407 }
409 @Override
410 public Object next() {
411 return it.previous();
412 }
414 @Override
415 public void remove() {
416 it.remove();
417 }
418 };
419 }
421 @Override
422 public final boolean removeFirstOccurrence(Object o) {
423 return removeOccurrence(o, iterator());
424 }
426 @Override
427 public final boolean removeLastOccurrence(Object o) {
428 return removeOccurrence(o, descendingIterator());
429 }
431 private static boolean removeOccurrence(Object o, Iterator<Object> it) {
432 while(it.hasNext()) {
433 final Object e = it.next();
434 if(o == null ? e == null : o.equals(e)) {
435 it.remove();
436 return true;
437 }
438 }
439 return false;
440 }
441 }