1package jdk.nashorn.internal.runtime.arrays;
2
3import java.util.Iterator;
4import java.util.List;
5import java.util.SortedMap;
6import java.util.TreeMap;
7import jdk.nashorn.internal.runtime.JSType;
8import jdk.nashorn.internal.runtime.ScriptRuntime;
9
10/**
11 * Filter to use for ArrayData where the length is not writable.
12 * The default behavior is just to ignore {@link ArrayData#setLength}
13 */
14final class LengthNotWritableFilter extends ArrayFilter {
15    private final SortedMap<Long, Object> extraElements; //elements with index >= length
16
17    /**
18     * Constructor
19     * @param underlying array
20     */
21    LengthNotWritableFilter(final ArrayData underlying) {
22        this(underlying, new TreeMap<Long, Object>());
23    }
24
25    private LengthNotWritableFilter(final ArrayData underlying, final SortedMap<Long, Object> extraElements) {
26        super(underlying);
27        this.extraElements = extraElements;
28    }
29
30    @Override
31    public ArrayData copy() {
32        return new LengthNotWritableFilter(underlying.copy(), new TreeMap<>(extraElements));
33    }
34
35    @Override
36    public boolean has(final int index) {
37        return super.has(index) || extraElements.containsKey((long)index);
38    }
39
40    /**
41     * Set the length of the data array
42     *
43     * @param length the new length for the data array
44     */
45    @Override
46    public void setLength(final long length) {
47        //empty - setting length for a LengthNotWritableFilter is always a nop
48    }
49
50    @Override
51    public ArrayData ensure(final long index) {
52        return this;
53    }
54
55    @Override
56    public ArrayData slice(final long from, final long to) {
57        //return array[from...to), or array[from...length] if undefined, in this case not as we are an ArrayData
58        return new LengthNotWritableFilter(underlying.slice(from, to), extraElements.subMap(from, to));
59    }
60
61    private boolean checkAdd(final long index, final Object value) {
62        if (index >= length()) {
63            extraElements.put(index, value);
64            return true;
65        }
66        return false;
67    }
68
69    private Object get(final long index) {
70        final Object obj = extraElements.get(index);
71        if (obj == null) {
72            return ScriptRuntime.UNDEFINED;
73        }
74        return obj;
75    }
76
77    @Override
78    public int getInt(final int index) {
79        if (index >= length()) {
80            return JSType.toInt32(get(index));
81        }
82        return underlying.getInt(index);
83    }
84
85    @Override
86    public int getIntOptimistic(final int index, final int programPoint) {
87        if (index >= length()) {
88            return JSType.toInt32Optimistic(get(index), programPoint);
89        }
90        return underlying.getIntOptimistic(index, programPoint);
91    }
92
93    @Override
94    public double getDouble(final int index) {
95        if (index >= length()) {
96            return JSType.toNumber(get(index));
97        }
98        return underlying.getDouble(index);
99    }
100
101    @Override
102    public double getDoubleOptimistic(final int index, final int programPoint) {
103        if (index >= length()) {
104            return JSType.toNumberOptimistic(get(index), programPoint);
105        }
106        return underlying.getDoubleOptimistic(index, programPoint);
107    }
108
109    @Override
110    public Object getObject(final int index) {
111        if (index >= length()) {
112            return get(index);
113        }
114        return underlying.getObject(index);
115    }
116
117    @Override
118    public ArrayData set(final int index, final Object value, final boolean strict) {
119        if (checkAdd(index, value)) {
120            return this;
121        }
122        underlying = underlying.set(index, value, strict);
123        return this;
124    }
125
126    @Override
127    public ArrayData set(final int index, final int value, final boolean strict) {
128        if (checkAdd(index, value)) {
129            return this;
130        }
131        underlying = underlying.set(index, value, strict);
132        return this;
133    }
134
135    @Override
136    public ArrayData set(final int index, final double value, final boolean strict) {
137        if (checkAdd(index, value)) {
138            return this;
139        }
140        underlying = underlying.set(index, value, strict);
141        return this;
142    }
143
144    @Override
145    public ArrayData delete(final int index) {
146        extraElements.remove(ArrayIndex.toLongIndex(index));
147        underlying = underlying.delete(index);
148        return this;
149    }
150
151    @Override
152    public ArrayData delete(final long fromIndex, final long toIndex) {
153        for (final Iterator<Long> iter = extraElements.keySet().iterator(); iter.hasNext();) {
154            final long next = iter.next();
155            if (next >= fromIndex && next <= toIndex) {
156                iter.remove();
157            }
158            if (next > toIndex) { //ordering guaranteed because TreeSet
159                break;
160            }
161        }
162        underlying = underlying.delete(fromIndex, toIndex);
163        return this;
164    }
165
166    @Override
167    public Iterator<Long> indexIterator() {
168        final List<Long> keys = computeIteratorKeys();
169        keys.addAll(extraElements.keySet()); //even if they are outside length this is fine
170        return keys.iterator();
171    }
172
173}
174