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