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

Thu, 23 May 2013 12:01:35 +0200

author
attila
date
Thu, 23 May 2013 12:01:35 +0200
changeset 286
1c1453863ea8
child 325
9374c04f38fe
permissions
-rw-r--r--

8015267: Allow conversion of JS arrays to Java List/Deque
Reviewed-by: lagergren, sundar

attila@286 1 package jdk.nashorn.internal.runtime;
attila@286 2
attila@286 3 import java.util.AbstractList;
attila@286 4 import java.util.Deque;
attila@286 5 import java.util.Iterator;
attila@286 6 import java.util.ListIterator;
attila@286 7 import java.util.NoSuchElementException;
attila@286 8 import java.util.RandomAccess;
attila@286 9 import jdk.nashorn.internal.runtime.linker.InvokeByName;
attila@286 10
attila@286 11 /**
attila@286 12 * An adapter that can wrap any ECMAScript Array-like object (that adheres to the array rules for the property
attila@286 13 * {@code length} and having conforming {@code push}, {@code pop}, {@code shift}, {@code unshift}, and {@code splice}
attila@286 14 * methods) and expose it as both a Java list and double-ended queue. While script arrays aren't necessarily efficient
attila@286 15 * as dequeues, it's still slightly more efficient to be able to translate dequeue operations into pushes, pops, shifts,
attila@286 16 * and unshifts, than to blindly translate all list's add/remove operations into splices. Also, it is conceivable that a
attila@286 17 * custom script object that implements an Array-like API can have a background data representation that is optimized
attila@286 18 * for dequeue-like access. Note that with ECMAScript arrays, {@code push} and {@pop} operate at the end of the array,
attila@286 19 * while in Java {@code Deque} they operate on the front of the queue and as such the Java dequeue {@link #push(Object)}
attila@286 20 * and {@link #pop()} operations will translate to {@code unshift} and {@code shift} script operations respectively,
attila@286 21 * while {@link #addLast(Object)} and {@link #removeLast()} will translate to {@code push} and {@code pop}.
attila@286 22 */
attila@286 23 public class ListAdapter extends AbstractList<Object> implements RandomAccess, Deque<Object> {
attila@286 24 // These add to the back and front of the list
attila@286 25 private static final InvokeByName PUSH = new InvokeByName("push", ScriptObject.class, void.class, Object.class);
attila@286 26 private static final InvokeByName UNSHIFT = new InvokeByName("unshift", ScriptObject.class, void.class, Object.class);
attila@286 27
attila@286 28 // These remove from the back and front of the list
attila@286 29 private static final InvokeByName POP = new InvokeByName("pop", ScriptObject.class, Object.class);
attila@286 30 private static final InvokeByName SHIFT = new InvokeByName("shift", ScriptObject.class, Object.class);
attila@286 31
attila@286 32 // These insert and remove in the middle of the list
attila@286 33 private static final InvokeByName SPLICE_ADD = new InvokeByName("splice", ScriptObject.class, void.class, int.class, int.class, Object.class);
attila@286 34 private static final InvokeByName SPLICE_REMOVE = new InvokeByName("splice", ScriptObject.class, void.class, int.class, int.class);
attila@286 35
attila@286 36 private final ScriptObject obj;
attila@286 37
attila@286 38 /**
attila@286 39 * Creates a new list wrapper for the specified script object.
attila@286 40 * @param obj script the object to wrap
attila@286 41 */
attila@286 42 public ListAdapter(ScriptObject obj) {
attila@286 43 this.obj = obj;
attila@286 44 }
attila@286 45
attila@286 46 @Override
attila@286 47 public int size() {
attila@286 48 return JSType.toInt32(obj.getLength());
attila@286 49 }
attila@286 50
attila@286 51 @Override
attila@286 52 public Object get(int index) {
attila@286 53 checkRange(index);
attila@286 54 return obj.get(index);
attila@286 55 }
attila@286 56
attila@286 57 @Override
attila@286 58 public Object set(int index, Object element) {
attila@286 59 checkRange(index);
attila@286 60 final Object prevValue = get(index);
attila@286 61 obj.set(index, element, false);
attila@286 62 return prevValue;
attila@286 63 }
attila@286 64
attila@286 65 private void checkRange(int index) {
attila@286 66 if(index < 0 || index >= size()) {
attila@286 67 throw invalidIndex(index);
attila@286 68 }
attila@286 69 }
attila@286 70
attila@286 71 @Override
attila@286 72 public void push(Object e) {
attila@286 73 addFirst(e);
attila@286 74 }
attila@286 75
attila@286 76 @Override
attila@286 77 public boolean add(Object e) {
attila@286 78 addLast(e);
attila@286 79 return true;
attila@286 80 }
attila@286 81
attila@286 82 @Override
attila@286 83 public void addFirst(Object e) {
attila@286 84 try {
attila@286 85 final Object fn = UNSHIFT.getGetter().invokeExact(obj);
attila@286 86 checkFunction(fn, UNSHIFT);
attila@286 87 UNSHIFT.getInvoker().invokeExact(fn, obj, e);
attila@286 88 } catch(RuntimeException | Error ex) {
attila@286 89 throw ex;
attila@286 90 } catch(Throwable t) {
attila@286 91 throw new RuntimeException(t);
attila@286 92 }
attila@286 93 }
attila@286 94
attila@286 95 @Override
attila@286 96 public void addLast(Object e) {
attila@286 97 try {
attila@286 98 final Object fn = PUSH.getGetter().invokeExact(obj);
attila@286 99 checkFunction(fn, PUSH);
attila@286 100 PUSH.getInvoker().invokeExact(fn, obj, e);
attila@286 101 } catch(RuntimeException | Error ex) {
attila@286 102 throw ex;
attila@286 103 } catch(Throwable t) {
attila@286 104 throw new RuntimeException(t);
attila@286 105 }
attila@286 106 }
attila@286 107
attila@286 108 @Override
attila@286 109 public void add(int index, Object e) {
attila@286 110 try {
attila@286 111 if(index < 0) {
attila@286 112 throw invalidIndex(index);
attila@286 113 } else if(index == 0) {
attila@286 114 addFirst(e);
attila@286 115 } else {
attila@286 116 final int size = size();
attila@286 117 if(index < size) {
attila@286 118 final Object fn = SPLICE_ADD.getGetter().invokeExact(obj);
attila@286 119 checkFunction(fn, SPLICE_ADD);
attila@286 120 SPLICE_ADD.getInvoker().invokeExact(fn, obj, index, 0, e);
attila@286 121 } else if(index == size) {
attila@286 122 addLast(e);
attila@286 123 } else {
attila@286 124 throw invalidIndex(index);
attila@286 125 }
attila@286 126 }
attila@286 127 } catch(RuntimeException | Error ex) {
attila@286 128 throw ex;
attila@286 129 } catch(Throwable t) {
attila@286 130 throw new RuntimeException(t);
attila@286 131 }
attila@286 132 }
attila@286 133 private static void checkFunction(Object fn, InvokeByName invoke) {
attila@286 134 if(!(fn instanceof ScriptFunction)) {
attila@286 135 throw new UnsupportedOperationException("The script object doesn't have a function named " + invoke.getName());
attila@286 136 }
attila@286 137 }
attila@286 138
attila@286 139 private static IndexOutOfBoundsException invalidIndex(int index) {
attila@286 140 return new IndexOutOfBoundsException(String.valueOf(index));
attila@286 141 }
attila@286 142
attila@286 143 @Override
attila@286 144 public boolean offer(Object e) {
attila@286 145 return offerLast(e);
attila@286 146 }
attila@286 147
attila@286 148 @Override
attila@286 149 public boolean offerFirst(Object e) {
attila@286 150 addFirst(e);
attila@286 151 return true;
attila@286 152 }
attila@286 153
attila@286 154 @Override
attila@286 155 public boolean offerLast(Object e) {
attila@286 156 addLast(e);
attila@286 157 return true;
attila@286 158 }
attila@286 159
attila@286 160 @Override
attila@286 161 public Object pop() {
attila@286 162 return removeFirst();
attila@286 163 }
attila@286 164
attila@286 165 @Override
attila@286 166 public Object remove() {
attila@286 167 return removeFirst();
attila@286 168 }
attila@286 169
attila@286 170 @Override
attila@286 171 public Object removeFirst() {
attila@286 172 checkNonEmpty();
attila@286 173 return invokeShift();
attila@286 174 }
attila@286 175
attila@286 176 @Override
attila@286 177 public Object removeLast() {
attila@286 178 checkNonEmpty();
attila@286 179 return invokePop();
attila@286 180 }
attila@286 181
attila@286 182 private void checkNonEmpty() {
attila@286 183 if(isEmpty()) {
attila@286 184 throw new NoSuchElementException();
attila@286 185 }
attila@286 186 }
attila@286 187
attila@286 188 @Override
attila@286 189 public Object remove(int index) {
attila@286 190 if(index < 0) {
attila@286 191 throw invalidIndex(index);
attila@286 192 } else if (index == 0) {
attila@286 193 return invokeShift();
attila@286 194 } else {
attila@286 195 final int maxIndex = size() - 1;
attila@286 196 if(index < maxIndex) {
attila@286 197 final Object prevValue = get(index);
attila@286 198 invokeSpliceRemove(index, 1);
attila@286 199 return prevValue;
attila@286 200 } else if(index == maxIndex) {
attila@286 201 return invokePop();
attila@286 202 } else {
attila@286 203 throw invalidIndex(index);
attila@286 204 }
attila@286 205 }
attila@286 206 }
attila@286 207
attila@286 208 private Object invokeShift() {
attila@286 209 try {
attila@286 210 final Object fn = SHIFT.getGetter().invokeExact(obj);
attila@286 211 checkFunction(fn, SHIFT);
attila@286 212 return SHIFT.getInvoker().invokeExact(fn, obj);
attila@286 213 } catch(RuntimeException | Error ex) {
attila@286 214 throw ex;
attila@286 215 } catch(Throwable t) {
attila@286 216 throw new RuntimeException(t);
attila@286 217 }
attila@286 218 }
attila@286 219
attila@286 220 private Object invokePop() {
attila@286 221 try {
attila@286 222 final Object fn = POP.getGetter().invokeExact(obj);
attila@286 223 checkFunction(fn, POP);
attila@286 224 return POP.getInvoker().invokeExact(fn, obj);
attila@286 225 } catch(RuntimeException | Error ex) {
attila@286 226 throw ex;
attila@286 227 } catch(Throwable t) {
attila@286 228 throw new RuntimeException(t);
attila@286 229 }
attila@286 230 }
attila@286 231
attila@286 232 @Override
attila@286 233 protected void removeRange(int fromIndex, int toIndex) {
attila@286 234 invokeSpliceRemove(fromIndex, toIndex - fromIndex);
attila@286 235 }
attila@286 236
attila@286 237 private void invokeSpliceRemove(int fromIndex, int count) {
attila@286 238 try {
attila@286 239 final Object fn = SPLICE_REMOVE.getGetter().invokeExact(obj);
attila@286 240 checkFunction(fn, SPLICE_REMOVE);
attila@286 241 SPLICE_REMOVE.getInvoker().invokeExact(fn, obj, fromIndex, count);
attila@286 242 } catch(RuntimeException | Error ex) {
attila@286 243 throw ex;
attila@286 244 } catch(Throwable t) {
attila@286 245 throw new RuntimeException(t);
attila@286 246 }
attila@286 247 }
attila@286 248
attila@286 249 @Override
attila@286 250 public Object poll() {
attila@286 251 return pollFirst();
attila@286 252 }
attila@286 253
attila@286 254 @Override
attila@286 255 public Object pollFirst() {
attila@286 256 return isEmpty() ? null : invokeShift();
attila@286 257 }
attila@286 258
attila@286 259 @Override
attila@286 260 public Object pollLast() {
attila@286 261 return isEmpty() ? null : invokePop();
attila@286 262 }
attila@286 263
attila@286 264 @Override
attila@286 265 public Object peek() {
attila@286 266 return peekFirst();
attila@286 267 }
attila@286 268
attila@286 269 @Override
attila@286 270 public Object peekFirst() {
attila@286 271 return isEmpty() ? null : get(0);
attila@286 272 }
attila@286 273
attila@286 274 @Override
attila@286 275 public Object peekLast() {
attila@286 276 return isEmpty() ? null : get(size() - 1);
attila@286 277 }
attila@286 278
attila@286 279 @Override
attila@286 280 public Object element() {
attila@286 281 return getFirst();
attila@286 282 }
attila@286 283
attila@286 284 @Override
attila@286 285 public Object getFirst() {
attila@286 286 checkNonEmpty();
attila@286 287 return get(0);
attila@286 288 }
attila@286 289
attila@286 290 @Override
attila@286 291 public Object getLast() {
attila@286 292 checkNonEmpty();
attila@286 293 return get(size() - 1);
attila@286 294 }
attila@286 295
attila@286 296 @Override
attila@286 297 public Iterator<Object> descendingIterator() {
attila@286 298 final ListIterator<Object> it = listIterator(size());
attila@286 299 return new Iterator<Object>() {
attila@286 300 @Override
attila@286 301 public boolean hasNext() {
attila@286 302 return it.hasPrevious();
attila@286 303 }
attila@286 304
attila@286 305 @Override
attila@286 306 public Object next() {
attila@286 307 return it.previous();
attila@286 308 }
attila@286 309
attila@286 310 @Override
attila@286 311 public void remove() {
attila@286 312 it.remove();
attila@286 313 }
attila@286 314 };
attila@286 315 }
attila@286 316
attila@286 317 @Override
attila@286 318 public boolean removeFirstOccurrence(Object o) {
attila@286 319 return removeOccurrence(o, iterator());
attila@286 320 }
attila@286 321
attila@286 322 @Override
attila@286 323 public boolean removeLastOccurrence(Object o) {
attila@286 324 return removeOccurrence(o, descendingIterator());
attila@286 325 }
attila@286 326
attila@286 327 private static boolean removeOccurrence(Object o, Iterator<Object> it) {
attila@286 328 while(it.hasNext()) {
attila@286 329 final Object e = it.next();
attila@286 330 if(o == null ? e == null : o.equals(e)) {
attila@286 331 it.remove();
attila@286 332 return true;
attila@286 333 }
attila@286 334 }
attila@286 335 return false;
attila@286 336 }
attila@286 337 }

mercurial