1 /*
2  * %CopyrightBegin%
3  *
4  * Copyright Ericsson AB 2000-2016. All Rights Reserved.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * %CopyrightEnd%
19  */
20 package com.ericsson.otp.erlang;
21 
22 import java.util.Iterator;
23 import java.util.NoSuchElementException;
24 
25 /**
26  * Provides a Java representation of Erlang lists. Lists are created from zero
27  * or more arbitrary Erlang terms.
28  *
29  * <p>
30  * The arity of the list is the number of elements it contains.
31  */
32 public class OtpErlangList extends OtpErlangObject implements
33         Iterable<OtpErlangObject> {
34     // don't change this!
35     private static final long serialVersionUID = 5999112769036676548L;
36 
37     private static final OtpErlangObject[] NO_ELEMENTS = new OtpErlangObject[0];
38 
39     final private OtpErlangObject[] elems;
40 
41     private OtpErlangObject lastTail = null;
42 
43     /**
44      * Create an empty list.
45      */
OtpErlangList()46     public OtpErlangList() {
47         elems = NO_ELEMENTS;
48     }
49 
50     /**
51      * Create a list of Erlang integers representing Unicode codePoints. This
52      * method does not check if the string contains valid code points.
53      *
54      * @param str
55      *            the characters from which to create the list.
56      */
OtpErlangList(final String str)57     public OtpErlangList(final String str) {
58         if (str == null || str.length() == 0) {
59             elems = NO_ELEMENTS;
60         } else {
61             final int[] codePoints = OtpErlangString.stringToCodePoints(str);
62             elems = new OtpErlangObject[codePoints.length];
63             for (int i = 0; i < elems.length; i++) {
64                 elems[i] = new OtpErlangInt(codePoints[i]);
65             }
66         }
67     }
68 
69     /**
70      * Create a list containing one element.
71      *
72      * @param elem
73      *            the elememet to make the list from.
74      */
OtpErlangList(final OtpErlangObject elem)75     public OtpErlangList(final OtpErlangObject elem) {
76         elems = new OtpErlangObject[] { elem };
77     }
78 
79     /**
80      * Create a list from an array of arbitrary Erlang terms.
81      *
82      * @param elems
83      *            the array of terms from which to create the list.
84      */
OtpErlangList(final OtpErlangObject[] elems)85     public OtpErlangList(final OtpErlangObject[] elems) {
86         this(elems, 0, elems.length);
87     }
88 
89     /**
90      * Create a list from an array of arbitrary Erlang terms. Tail can be
91      * specified, if not null, the list will not be proper.
92      *
93      * @param elems
94      *            array of terms from which to create the list
95      * @param lastTail
96      * @throws OtpErlangException
97      */
OtpErlangList(final OtpErlangObject[] elems, final OtpErlangObject lastTail)98     public OtpErlangList(final OtpErlangObject[] elems,
99             final OtpErlangObject lastTail) throws OtpErlangException {
100         this(elems, 0, elems.length);
101         if (elems.length == 0 && lastTail != null) {
102             throw new OtpErlangException("Bad list, empty head, non-empty tail");
103         }
104         this.lastTail = lastTail;
105     }
106 
107     /**
108      * Create a list from an array of arbitrary Erlang terms.
109      *
110      * @param elems
111      *            the array of terms from which to create the list.
112      * @param start
113      *            the offset of the first term to insert.
114      * @param count
115      *            the number of terms to insert.
116      */
OtpErlangList(final OtpErlangObject[] elems, final int start, final int count)117     public OtpErlangList(final OtpErlangObject[] elems, final int start,
118             final int count) {
119         if (elems != null && count > 0) {
120             this.elems = new OtpErlangObject[count];
121             System.arraycopy(elems, start, this.elems, 0, count);
122         } else {
123             this.elems = NO_ELEMENTS;
124         }
125     }
126 
127     /**
128      * Create a list from a stream containing an list encoded in Erlang external
129      * format.
130      *
131      * @param buf
132      *            the stream containing the encoded list.
133      *
134      * @exception OtpErlangDecodeException
135      *                if the buffer does not contain a valid external
136      *                representation of an Erlang list.
137      */
OtpErlangList(final OtpInputStream buf)138     public OtpErlangList(final OtpInputStream buf)
139             throws OtpErlangDecodeException {
140         final int arity = buf.read_list_head();
141         if (arity > 0) {
142             elems = new OtpErlangObject[arity];
143             for (int i = 0; i < arity; i++) {
144                 elems[i] = buf.read_any();
145             }
146             /* discard the terminating nil (empty list) or read tail */
147             if (buf.peek1() == OtpExternal.nilTag) {
148                 buf.read_nil();
149             } else {
150                 lastTail = buf.read_any();
151             }
152         } else {
153             elems = NO_ELEMENTS;
154         }
155     }
156 
157     /**
158      * Get the arity of the list.
159      *
160      * @return the number of elements contained in the list.
161      */
arity()162     public int arity() {
163         return elems.length;
164     }
165 
166     /**
167      * Get the specified element from the list.
168      *
169      * @param i
170      *            the index of the requested element. List elements are numbered
171      *            as array elements, starting at 0.
172      *
173      * @return the requested element, of null if i is not a valid element index.
174      */
elementAt(final int i)175     public OtpErlangObject elementAt(final int i) {
176         if (i >= arity() || i < 0) {
177             return null;
178         }
179         return elems[i];
180     }
181 
182     /**
183      * Get all the elements from the list as an array.
184      *
185      * @return an array containing all of the list's elements.
186      */
elements()187     public OtpErlangObject[] elements() {
188         if (arity() == 0) {
189             return NO_ELEMENTS;
190         }
191         final OtpErlangObject[] res = new OtpErlangObject[arity()];
192         System.arraycopy(elems, 0, res, 0, res.length);
193         return res;
194     }
195 
196     /**
197      * Get the string representation of the list.
198      *
199      * @return the string representation of the list.
200      */
201 
202     @Override
toString()203     public String toString() {
204         return toString(0);
205     }
206 
toString(final int start)207     protected String toString(final int start) {
208         final StringBuffer s = new StringBuffer();
209         s.append("[");
210 
211         for (int i = start; i < arity(); i++) {
212             if (i > start) {
213                 s.append(",");
214             }
215             s.append(elems[i].toString());
216         }
217         if (lastTail != null) {
218             s.append("|").append(lastTail.toString());
219         }
220         s.append("]");
221 
222         return s.toString();
223     }
224 
225     /**
226      * Convert this list to the equivalent Erlang external representation. Note
227      * that this method never encodes lists as strings, even when it is possible
228      * to do so.
229      *
230      * @param buf
231      *            An output stream to which the encoded list should be written.
232      *
233      */
234 
235     @Override
encode(final OtpOutputStream buf)236     public void encode(final OtpOutputStream buf) {
237         encode(buf, 0);
238     }
239 
encode(final OtpOutputStream buf, final int start)240     protected void encode(final OtpOutputStream buf, final int start) {
241         final int arity = arity() - start;
242 
243         if (arity > 0) {
244             buf.write_list_head(arity);
245 
246             for (int i = start; i < arity + start; i++) {
247                 buf.write_any(elems[i]);
248             }
249         }
250         if (lastTail == null) {
251             buf.write_nil();
252         } else {
253             buf.write_any(lastTail);
254         }
255     }
256 
257     /**
258      * Determine if two lists are equal. Lists are equal if they have the same
259      * arity and all of the elements are equal.
260      *
261      * @param o
262      *            the list to compare to.
263      *
264      * @return true if the lists have the same arity and all the elements are
265      *         equal.
266      */
267 
268     @Override
equals(final Object o)269     public boolean equals(final Object o) {
270 
271         /*
272          * Be careful to use methods even for "this", so that equals work also
273          * for sublists
274          */
275 
276         if (!(o instanceof OtpErlangList)) {
277             return false;
278         }
279 
280         final OtpErlangList l = (OtpErlangList) o;
281 
282         final int a = arity();
283         if (a != l.arity()) {
284             return false;
285         }
286         for (int i = 0; i < a; i++) {
287             if (!elementAt(i).equals(l.elementAt(i))) {
288                 return false; // early exit
289             }
290         }
291         final OtpErlangObject otherTail = l.getLastTail();
292         if (getLastTail() == null && otherTail == null) {
293             return true;
294         }
295         if (getLastTail() == null) {
296             return false;
297         }
298         return getLastTail().equals(l.getLastTail());
299     }
300 
301     @Override
match(final OtpErlangObject term, final T bindings)302     public <T> boolean match(final OtpErlangObject term, final T bindings) {
303         if (!(term instanceof OtpErlangList)) {
304             return false;
305         }
306         final OtpErlangList that = (OtpErlangList) term;
307 
308         final int thisArity = this.arity();
309         final int thatArity = that.arity();
310         final OtpErlangObject thisTail = this.getLastTail();
311         final OtpErlangObject thatTail = that.getLastTail();
312 
313         if (thisTail == null) {
314             if (thisArity != thatArity || thatTail != null) {
315                 return false;
316             }
317         } else {
318             if (thisArity > thatArity) {
319                 return false;
320             }
321         }
322         for (int i = 0; i < thisArity; i++) {
323             if (!elementAt(i).match(that.elementAt(i), bindings)) {
324                 return false;
325             }
326         }
327         if (thisTail == null) {
328             return true;
329         }
330         return thisTail.match(that.getNthTail(thisArity), bindings);
331     }
332 
333     @Override
bind(final T binds)334     public <T> OtpErlangObject bind(final T binds) throws OtpErlangException {
335         final OtpErlangList list = (OtpErlangList) this.clone();
336 
337         final int a = list.elems.length;
338         for (int i = 0; i < a; i++) {
339             list.elems[i] = list.elems[i].bind(binds);
340         }
341 
342         if (list.lastTail != null) {
343             list.lastTail = list.lastTail.bind(binds);
344         }
345 
346         return list;
347     }
348 
getLastTail()349     public OtpErlangObject getLastTail() {
350         return lastTail;
351     }
352 
353     @Override
doHashCode()354     protected int doHashCode() {
355         final OtpErlangObject.Hash hash = new OtpErlangObject.Hash(4);
356         final int a = arity();
357         if (a == 0) {
358             return (int) 3468870702L;
359         }
360         for (int i = 0; i < a; i++) {
361             hash.combine(elementAt(i).hashCode());
362         }
363         final OtpErlangObject t = getLastTail();
364         if (t != null) {
365             final int h = t.hashCode();
366             hash.combine(h, h);
367         }
368         return hash.valueOf();
369     }
370 
371     @Override
clone()372     public Object clone() {
373         try {
374             return new OtpErlangList(elements(), getLastTail());
375         } catch (final OtpErlangException e) {
376             throw new AssertionError(this);
377         }
378     }
379 
iterator()380     public Iterator<OtpErlangObject> iterator() {
381         return iterator(0);
382     }
383 
iterator(final int start)384     private Iterator<OtpErlangObject> iterator(final int start) {
385         return new Itr(start);
386     }
387 
388     /**
389      * @return true if the list is proper, i.e. the last tail is nil
390      */
isProper()391     public boolean isProper() {
392         return lastTail == null;
393     }
394 
getHead()395     public OtpErlangObject getHead() {
396         if (arity() > 0) {
397             return elems[0];
398         }
399         return null;
400     }
401 
getTail()402     public OtpErlangObject getTail() {
403         return getNthTail(1);
404     }
405 
getNthTail(final int n)406     public OtpErlangObject getNthTail(final int n) {
407         final int arity = arity();
408         if (arity >= n) {
409             if (arity == n && lastTail != null) {
410                 return lastTail;
411             }
412             return new SubList(this, n);
413         }
414         return null;
415     }
416 
417     /**
418      * Convert a list of integers into a Unicode string, interpreting each
419      * integer as a Unicode code point value.
420      *
421      * @return A java.lang.String object created through its constructor
422      *         String(int[], int, int).
423      *
424      * @exception OtpErlangException
425      *                for non-proper and non-integer lists.
426      *
427      * @exception OtpErlangRangeException
428      *                if any integer does not fit into a Java int.
429      *
430      * @exception java.security.InvalidParameterException
431      *                if any integer is not within the Unicode range.
432      *
433      * @see String#String(int[], int, int)
434      *
435      */
436 
stringValue()437     public String stringValue() throws OtpErlangException {
438         if (!isProper()) {
439             throw new OtpErlangException("Non-proper list: " + this);
440         }
441         final int[] values = new int[arity()];
442         for (int i = 0; i < values.length; ++i) {
443             final OtpErlangObject o = elementAt(i);
444             if (!(o instanceof OtpErlangLong)) {
445                 throw new OtpErlangException("Non-integer term: " + o);
446             }
447             final OtpErlangLong l = (OtpErlangLong) o;
448             values[i] = l.intValue();
449         }
450         return new String(values, 0, values.length);
451     }
452 
453     public static class SubList extends OtpErlangList {
454         private static final long serialVersionUID = OtpErlangList.serialVersionUID;
455 
456         private final int start;
457 
458         private final OtpErlangList parent;
459 
SubList(final OtpErlangList parent, final int start)460         private SubList(final OtpErlangList parent, final int start) {
461             super();
462             this.parent = parent;
463             this.start = start;
464         }
465 
466         @Override
arity()467         public int arity() {
468             return parent.arity() - start;
469         }
470 
471         @Override
elementAt(final int i)472         public OtpErlangObject elementAt(final int i) {
473             return parent.elementAt(i + start);
474         }
475 
476         @Override
elements()477         public OtpErlangObject[] elements() {
478             final int n = parent.arity() - start;
479             final OtpErlangObject[] res = new OtpErlangObject[n];
480             for (int i = 0; i < res.length; i++) {
481                 res[i] = parent.elementAt(i + start);
482             }
483             return res;
484         }
485 
486         @Override
isProper()487         public boolean isProper() {
488             return parent.isProper();
489         }
490 
491         @Override
getHead()492         public OtpErlangObject getHead() {
493             return parent.elementAt(start);
494         }
495 
496         @Override
getNthTail(final int n)497         public OtpErlangObject getNthTail(final int n) {
498             return parent.getNthTail(n + start);
499         }
500 
501         @Override
toString()502         public String toString() {
503             return parent.toString(start);
504         }
505 
506         @Override
encode(final OtpOutputStream stream)507         public void encode(final OtpOutputStream stream) {
508             parent.encode(stream, start);
509         }
510 
511         @Override
getLastTail()512         public OtpErlangObject getLastTail() {
513             return parent.getLastTail();
514         }
515 
516         @Override
iterator()517         public Iterator<OtpErlangObject> iterator() {
518             return parent.iterator(start);
519         }
520     }
521 
522     private class Itr implements Iterator<OtpErlangObject> {
523         /**
524          * Index of element to be returned by subsequent call to next.
525          */
526         private int cursor;
527 
Itr(final int cursor)528         private Itr(final int cursor) {
529             this.cursor = cursor;
530         }
531 
hasNext()532         public boolean hasNext() {
533             return cursor < elems.length;
534         }
535 
next()536         public OtpErlangObject next() {
537             try {
538                 return elems[cursor++];
539             } catch (final IndexOutOfBoundsException e) {
540                 throw new NoSuchElementException();
541             }
542         }
543 
remove()544         public void remove() {
545             throw new UnsupportedOperationException(
546                     "OtpErlangList cannot be modified!");
547         }
548     }
549 }
550