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