/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.javascript; /** * <p>This class represents a string composed of two components, each of which * may be a <code>java.lang.String</code> or another ConsString.</p> * * <p>This string representation is optimized for concatenation using the "+" * operator. Instead of immediately copying both components to a new character * array, ConsString keeps references to the original components and only * converts them to a String if either toString() is called or a certain depth * level is reached.</p> * * <p>Note that instances of this class are only immutable if both parts are * immutable, i.e. either Strings or ConsStrings that are ultimately composed * of Strings.</p> * * <p>Both the name and the concept are borrowed from V8.</p> */ public final class ConsString implements CharSequence { private final int length; private int depth; private CharSequence s1, s2; public ConsString(CharSequence str1, CharSequence str2) { int length = 0, depth = 1; if (str1 instanceof ConsString) { ConsString s = (ConsString) str1; length += s.length; depth += s.depth; if (s.depth == 0) { // Directly access string if ConsString is flattened str1 = s.s1; } } else { length += ((String) str1).length(); } if (str2 instanceof ConsString) { ConsString s = (ConsString) str2; length += s.length; depth += s.depth; if (s.depth == 0) { // Directly access string if ConsString is flattened str2 = s.s1; } } else { length += ((String) str2).length(); } this.length = length; this.depth = depth; this.s1 = str1; this.s2 = str2; // Don't let it grow too deep, can cause stack overflows if (depth > 200 && str1 instanceof ConsString && str2 instanceof ConsString) { flatten(); } } @Override public int length() { return length; } @Override public char charAt(int index) { return toString().charAt(index); } @Override public CharSequence subSequence(int start, int end) { if (start < 0 || end < start || end > length) { throw new IndexOutOfBoundsException(); } if (start == end) { return ""; } if (start == 0 && end == length) { return this; } return toString().substring(start, end); } @Override public String toString() { return depth == 0 ? (String) s1 : flatten(); } public char[] toCharArray() { if (depth == 0) { return ((String) s1).toCharArray(); } char[] ca = new char[length()]; appendTo(this, ca, 0); return ca; } public byte[] toByteArray(byte[] ba) { if (ba == null || ba.length < length() * 2) { ba = new byte[length() * 2]; } if (depth == 0) { appendTo((String) s1, ba, 0); } else { appendTo(this, ba, 0); } return ba; } private String flatten() { if (depth > 0) { s1 = flatten(this); s2 = ""; depth = 0; } return (String) s1; } private static String flatten(ConsString s) { char[] ca = new char[s.length()]; appendTo(s, ca, 0); return new String(ca); } private static void appendTo(ConsString s, char[] ca, int offset) { for (;;) { // Flattened ConsString or both parts are simple Strings, just append and return. if (s.depth <= 1) { String s1 = (String) s.s1, s2 = (String) s.s2; appendTo(s1, ca, offset); appendTo(s2, ca, offset + s1.length()); return; } // At least one part is a ConsString. if (s.s1 instanceof String) { // Left is String and right is ConsString, append left and continue with right. String s1 = (String) s.s1; s = (ConsString) s.s2; appendTo(s1, ca, offset); offset += s1.length(); } else if (s.s2 instanceof String) { // Left is ConsString and right is String, append right and continue with left. String s2 = (String) s.s2; s = (ConsString) s.s1; appendTo(s2, ca, offset + s.length()); } else { // Both are ConsStrings, descend into less deeper one and continue with the other. ConsString s1 = (ConsString) s.s1, s2 = (ConsString) s.s2; if (s1.depth < s2.depth) { s = s2; appendTo(s1, ca, offset); offset += s1.length(); } else { s = s1; appendTo(s2, ca, offset + s.length()); } } } } private static void appendTo(String s, char[] ca, int offset) { s.getChars(0, s.length(), ca, offset); } private static void appendTo(ConsString s, byte[] ba, int offset) { for (;;) { // Flattened ConsString or both parts are simple Strings, just append and return. if (s.depth <= 1) { String s1 = (String) s.s1, s2 = (String) s.s2; appendTo(s1, ba, offset); appendTo(s2, ba, offset + s1.length()); return; } // At least one part is a ConsString. if (s.s1 instanceof String) { // Left is String and right is ConsString, append left and continue with right. String s1 = (String) s.s1; s = (ConsString) s.s2; appendTo(s1, ba, offset); offset += s1.length(); } else if (s.s2 instanceof String) { // Left is ConsString and right is String, append right and continue with left. String s2 = (String) s.s2; s = (ConsString) s.s1; appendTo(s2, ba, offset + s.length()); } else { // Both are ConsStrings, descend into less deeper one and continue with the other. ConsString s1 = (ConsString) s.s1, s2 = (ConsString) s.s2; if (s1.depth < s2.depth) { s = s2; appendTo(s1, ba, offset); offset += s1.length(); } else { s = s1; appendTo(s2, ba, offset + s.length()); } } } } private static void appendTo(String s, byte[] ba, int offset) { for (int i = 0, j = offset, len = s.length(); i < len; ++i) { char c = s.charAt(i); ba[j++] = (byte) ((c >>> 8) & 0xff); ba[j++] = (byte) ((c >>> 0) & 0xff); } } }