src/jdk/nashorn/internal/runtime/ListAdapter.java

Mon, 16 Sep 2013 15:08:36 +0530

author
sundar
date
Mon, 16 Sep 2013 15:08:36 +0530
changeset 554
38378024a332
parent 489
dd79c04ef7df
child 610
ed3da7a574a0
permissions
-rw-r--r--

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 }

mercurial