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 */ 25 26 package jdk.nashorn.internal.runtime; 27 28 import java.lang.invoke.MethodHandle; 29 import java.util.AbstractList; 30 import java.util.Deque; 31 import java.util.Iterator; 32 import java.util.ListIterator; 33 import java.util.NoSuchElementException; 34 import java.util.Objects; 35 import java.util.RandomAccess; 36 import java.util.concurrent.Callable; 37 import jdk.nashorn.api.scripting.JSObject; 38 import jdk.nashorn.api.scripting.ScriptObjectMirror; 39 import jdk.nashorn.internal.objects.Global; 40 import jdk.nashorn.internal.runtime.linker.Bootstrap; 41 42 /** 43 * An adapter that can wrap any ECMAScript Array-like object (that adheres to the array rules for the property 44 * {@code length} and having conforming {@code push}, {@code pop}, {@code shift}, {@code unshift}, and {@code splice} 45 * methods) and expose it as both a Java list and double-ended queue. While script arrays aren't necessarily efficient 46 * as dequeues, it's still slightly more efficient to be able to translate dequeue operations into pushes, pops, shifts, 47 * and unshifts, than to blindly translate all list's add/remove operations into splices. Also, it is conceivable that a 48 * custom script object that implements an Array-like API can have a background data representation that is optimized 49 * for dequeue-like access. Note that with ECMAScript arrays, {@code push} and {@code pop} operate at the end of the 50 * array, while in Java {@code Deque} they operate on the front of the queue and as such the Java dequeue 51 * {@link #push(Object)} and {@link #pop()} operations will translate to {@code unshift} and {@code shift} script 52 * operations respectively, while {@link #addLast(Object)} and {@link #removeLast()} will translate to {@code push} and 53 * {@code pop}. 54 */ 55 public class ListAdapter extends AbstractList<Object> implements RandomAccess, Deque<Object> { 56 // Invoker creator for methods that add to the start or end of the list: PUSH and UNSHIFT. Takes fn, this, and value, returns void. 57 private static final Callable<MethodHandle> ADD_INVOKER_CREATOR = invokerCreator(void.class, Object.class, JSObject.class, Object.class); 58 59 // PUSH adds to the start of the list 60 private static final Object PUSH = new Object(); 61 // UNSHIFT adds to the end of the list 62 private static final Object UNSHIFT = new Object(); 63 64 // Invoker creator for methods that remove from the tail or head of the list: POP and SHIFT. Takes fn, this, returns Object. 65 private static final Callable<MethodHandle> REMOVE_INVOKER_CREATOR = invokerCreator(Object.class, Object.class, JSObject.class); 66 67 // POP removes from the start of the list 68 private static final Object POP = new Object(); 69 // SHIFT removes from the end of the list 70 private static final Object SHIFT = new Object(); 71 72 // SPLICE can be used to add a value in the middle of the list. 73 private static final Object SPLICE_ADD = new Object(); 74 private static final Callable<MethodHandle> SPLICE_ADD_INVOKER_CREATOR = invokerCreator(void.class, Object.class, JSObject.class, int.class, int.class, Object.class); 75 76 // SPLICE can also be used to remove values from the middle of the list. 77 private static final Object SPLICE_REMOVE = new Object(); 78 private static final Callable<MethodHandle> SPLICE_REMOVE_INVOKER_CREATOR = invokerCreator(void.class, Object.class, JSObject.class, int.class, int.class); 79 80 /** wrapped object */ 81 final JSObject obj; 82 private final Global global; 83 84 // allow subclasses only in this package 85 ListAdapter(final JSObject obj, final Global global) { 86 if (global == null) { 87 throw new IllegalStateException(ECMAErrors.getMessage("list.adapter.null.global")); 88 } 89 90 this.obj = obj; 91 this.global = global; 92 } 93 94 /** 95 * Factory to create a ListAdapter for a given script object. 96 * 97 * @param obj script object to wrap as a ListAdapter 98 * @return A ListAdapter wrapper object 99 */ 100 public static ListAdapter create(final Object obj) { 101 final Global global = Context.getGlobal(); 102 return new ListAdapter(getJSObject(obj, global), global); 103 } 104 105 private static JSObject getJSObject(final Object obj, final Global global) { 106 if (obj instanceof ScriptObject) { 107 return (JSObject)ScriptObjectMirror.wrap(obj, global); 108 } else if (obj instanceof JSObject) { 109 return (JSObject)obj; 110 } 111 throw new IllegalArgumentException("ScriptObject or JSObject expected"); 112 } 113 114 @Override 115 public final Object get(final int index) { 116 checkRange(index); 117 return getAt(index); 118 } 119 120 private Object getAt(final int index) { 121 return obj.getSlot(index); 122 } 123 124 @Override 125 public Object set(final int index, final Object element) { 126 checkRange(index); 127 final Object prevValue = getAt(index); 128 obj.setSlot(index, element); 129 return prevValue; 130 } 131 132 private void checkRange(final int index) { 133 if(index < 0 || index >= size()) { 134 throw invalidIndex(index); 135 } 136 } 137 138 @Override 139 public int size() { 140 return JSType.toInt32(obj.getMember("length")); 141 } 142 143 @Override 144 public final void push(final Object e) { 145 addFirst(e); 146 } 147 148 @Override 149 public final boolean add(final Object e) { 150 addLast(e); 151 return true; 152 } 153 154 @Override 155 public final void addFirst(final Object e) { 156 try { 157 getDynamicInvoker(UNSHIFT, ADD_INVOKER_CREATOR).invokeExact(getFunction("unshift"), obj, e); 158 } catch(RuntimeException | Error ex) { 159 throw ex; 160 } catch(final Throwable t) { 161 throw new RuntimeException(t); 162 } 163 } 164 165 @Override 166 public final void addLast(final Object e) { 167 try { 168 getDynamicInvoker(PUSH, ADD_INVOKER_CREATOR).invokeExact(getFunction("push"), obj, e); 169 } catch(RuntimeException | Error ex) { 170 throw ex; 171 } catch(final Throwable t) { 172 throw new RuntimeException(t); 173 } 174 } 175 176 @Override 177 public final void add(final int index, final Object e) { 178 try { 179 if(index < 0) { 180 throw invalidIndex(index); 181 } else if(index == 0) { 182 addFirst(e); 183 } else { 184 final int size = size(); 185 if(index < size) { 186 getDynamicInvoker(SPLICE_ADD, SPLICE_ADD_INVOKER_CREATOR).invokeExact(obj.getMember("splice"), obj, index, 0, e); 187 } else if(index == size) { 188 addLast(e); 189 } else { 190 throw invalidIndex(index); 191 } 192 } 193 } catch(final RuntimeException | Error ex) { 194 throw ex; 195 } catch(final Throwable t) { 196 throw new RuntimeException(t); 197 } 198 } 199 private Object getFunction(final String name) { 200 final Object fn = obj.getMember(name); 201 if(!(Bootstrap.isCallable(fn))) { 202 throw new UnsupportedOperationException("The script object doesn't have a function named " + name); 203 } 204 return fn; 205 } 206 207 private static IndexOutOfBoundsException invalidIndex(final int index) { 208 return new IndexOutOfBoundsException(String.valueOf(index)); 209 } 210 211 @Override 212 public final boolean offer(final Object e) { 213 return offerLast(e); 214 } 215 216 @Override 217 public final boolean offerFirst(final Object e) { 218 addFirst(e); 219 return true; 220 } 221 222 @Override 223 public final boolean offerLast(final Object e) { 224 addLast(e); 225 return true; 226 } 227 228 @Override 229 public final Object pop() { 230 return removeFirst(); 231 } 232 233 @Override 234 public final Object remove() { 235 return removeFirst(); 236 } 237 238 @Override 239 public final Object removeFirst() { 240 checkNonEmpty(); 241 return invokeShift(); 242 } 243 244 @Override 245 public final Object removeLast() { 246 checkNonEmpty(); 247 return invokePop(); 248 } 249 250 private void checkNonEmpty() { 251 if(isEmpty()) { 252 throw new NoSuchElementException(); 253 } 254 } 255 256 @Override 257 public final Object remove(final int index) { 258 if(index < 0) { 259 throw invalidIndex(index); 260 } else if (index == 0) { 261 return invokeShift(); 262 } else { 263 final int maxIndex = size() - 1; 264 if(index < maxIndex) { 265 final Object prevValue = get(index); 266 invokeSpliceRemove(index, 1); 267 return prevValue; 268 } else if(index == maxIndex) { 269 return invokePop(); 270 } else { 271 throw invalidIndex(index); 272 } 273 } 274 } 275 276 private Object invokeShift() { 277 try { 278 return getDynamicInvoker(SHIFT, REMOVE_INVOKER_CREATOR).invokeExact(getFunction("shift"), obj); 279 } catch(RuntimeException | Error ex) { 280 throw ex; 281 } catch(final Throwable t) { 282 throw new RuntimeException(t); 283 } 284 } 285 286 private Object invokePop() { 287 try { 288 return getDynamicInvoker(POP, REMOVE_INVOKER_CREATOR).invokeExact(getFunction("pop"), obj); 289 } catch(RuntimeException | Error ex) { 290 throw ex; 291 } catch(final Throwable t) { 292 throw new RuntimeException(t); 293 } 294 } 295 296 @Override 297 protected final void removeRange(final int fromIndex, final int toIndex) { 298 invokeSpliceRemove(fromIndex, toIndex - fromIndex); 299 } 300 301 private void invokeSpliceRemove(final int fromIndex, final int count) { 302 try { 303 getDynamicInvoker(SPLICE_REMOVE, SPLICE_REMOVE_INVOKER_CREATOR).invokeExact(getFunction("splice"), obj, fromIndex, count); 304 } catch(RuntimeException | Error ex) { 305 throw ex; 306 } catch(final Throwable t) { 307 throw new RuntimeException(t); 308 } 309 } 310 311 @Override 312 public final Object poll() { 313 return pollFirst(); 314 } 315 316 @Override 317 public final Object pollFirst() { 318 return isEmpty() ? null : invokeShift(); 319 } 320 321 @Override 322 public final Object pollLast() { 323 return isEmpty() ? null : invokePop(); 324 } 325 326 @Override 327 public final Object peek() { 328 return peekFirst(); 329 } 330 331 @Override 332 public final Object peekFirst() { 333 return isEmpty() ? null : get(0); 334 } 335 336 @Override 337 public final Object peekLast() { 338 return isEmpty() ? null : get(size() - 1); 339 } 340 341 @Override 342 public final Object element() { 343 return getFirst(); 344 } 345 346 @Override 347 public final Object getFirst() { 348 checkNonEmpty(); 349 return get(0); 350 } 351 352 @Override 353 public final Object getLast() { 354 checkNonEmpty(); 355 return get(size() - 1); 356 } 357 358 @Override 359 public final Iterator<Object> descendingIterator() { 360 final ListIterator<Object> it = listIterator(size()); 361 return new Iterator<Object>() { 362 @Override 363 public boolean hasNext() { 364 return it.hasPrevious(); 365 } 366 367 @Override 368 public Object next() { 369 return it.previous(); 370 } 371 372 @Override 373 public void remove() { 374 it.remove(); 375 } 376 }; 377 } 378 379 @Override 380 public final boolean removeFirstOccurrence(final Object o) { 381 return removeOccurrence(o, iterator()); 382 } 383 384 @Override 385 public final boolean removeLastOccurrence(final Object o) { 386 return removeOccurrence(o, descendingIterator()); 387 } 388 389 private static boolean removeOccurrence(final Object o, final Iterator<Object> it) { 390 while(it.hasNext()) { 391 if(Objects.equals(o, it.next())) { 392 it.remove(); 393 return true; 394 } 395 } 396 return false; 397 } 398 399 private static Callable<MethodHandle> invokerCreator(final Class<?> rtype, final Class<?>... ptypes) { 400 return new Callable<MethodHandle>() { 401 @Override 402 public MethodHandle call() { 403 return Bootstrap.createDynamicCallInvoker(rtype, ptypes); 404 } 405 }; 406 } 407 408 private MethodHandle getDynamicInvoker(final Object key, final Callable<MethodHandle> creator) { 409 return global.getDynamicInvoker(key, creator); 410 } 411 }