summaryrefslogblamecommitdiff
path: root/src/library/scala/collection/mutable/StringBuilder.scala
blob: f43acdc796407eeb193c6f7272b939ac3af26642 (plain) (tree)
1
2
3
4
5
6
7
8
9

                                                                          
                                                                          




                                                                          
       

 

                        
 
                
                                
                             

                           
 


                                                                                    



                              
                



                                                                           
                                   
                                
 
                            
 

                             



































                                                                       


                                                                         
    

                                                                    
     



                                    

   

                                                                              
    
                           


                                  
                                                                                    


                                                    












                                                               
     
 




                                                                            






                                                      

                           

                                     

                                                               
    


                                                                       



                                                      
                                                                



              




                                                                        






                                                      

                              

                                                 

                                                                            
    




                                                                       
     
                                                              
 




                                                                                       


                                                    


                                                                               












                                                                                       
                                                        
     
                                                  
 

                                            

                                             

                                                              

                                             
                                     
     
                                                               
 
                                                
    

                                     

                                          
                      

















                                                            
                                                 



                  
                                                                    
    

                                                
     
                                                                                   
 
                                                                      
    

                                                
     
                                                                             
 







                                                                          
                               
                                            



                





                                                                   
     
                                                                   
                                                                




                                                                  






                                        


                                                                              


                                                    

                                                                           






                                                      
                                                               




                  

                                                                                


                                                    


                                                                                          









                                                      
                                                          




                                      










                                                                           








                                                                                       

                                                              



                






                                                                             
     
                                                                                  
 
                                                      
    



                                                                             
     
                                                                                
 







                                                                                        
 
                                                                          
    



                                                                             
     



                                                               
                               

                                                              



                












































                                                                                              




                                                                              
                                                                         
    

                                                                                             
     
                                                 
 
                                                                         
    


                                                                                             
     
                                                                                           
 
                                                                        
    

                                                                                            
     
                                                             
 
                                                                        
    


                                                                                            
     
                                                                                                   
 


                                                                                 
    
                                          
     

                                                                                                          
 
                                                                       
    
                                                                             
     



                                                          
 





                                      
 




                                                                
 
                
     
                                      


        
                                                                  
    
                                                                
     


                                                             


 

                    


                                                                       
                                                        

        


                                                                     
 
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2006-2010, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

// $Id$


package scala.collection
package mutable

import generic._
import compat.Platform.arraycopy
import scala.reflect.Manifest
import annotation.migration
import StringBuilder._

/** A builder for mutable sequence of characters.  This class provides an API
 *  mostly compatible with java.lang.StringBuilder, except where there are conflicts
 *  with the Scala collections API (such as the `reverse` method.)
 *
 *  @author Stephane Micheloud
 *  @author Martin Odersky
 *  @version 2.8
 *  @since   2.7
 */
@serializable
@SerialVersionUID(0 - 8525408645367278351L)
final class StringBuilder(initCapacity: Int, private val initValue: String)
      extends Builder[Char, String]
         with IndexedSeq[Char] {

  require(initCapacity >= 0)

  import scala.collection.Seq

  /** The value is used for character storage. */
  private var array = new Array[Char](initCapacity + initValue.length)

  /** The count is the number of characters used. */
  private var count: Int = 0

  /** Constructs a string builder with no characters in it and an
   *  initial capacity of 16 characters.
   */
  def this() = this(16, "")

  /** Constructs a string builder with no characters in it and an
   *  initial capacity specified by the <code>capacity</code> argument.
   *
   *  @param  capacity  the initial capacity.
   *  @throws NegativeArraySizeException  if the <code>capacity</code>
   *                    argument is less than <code>0</code>.
   */
  def this(capacity: Int) = this(capacity, "")

  /** Constructs a string builder with initial characters
   *  equal to characters of `str`.
   */
  def this(str: String) = this(16, str)

  append(initValue)

  def toArray: Array[Char] = array

  def length: Int = count
  def length_=(n: Int) { setLength(n) }

  /** Clears the builder contents.
   */
  def clear(): Unit = setLength(0)

  /** Sets the length of the character sequence.  If the current sequence
   *  is shorter than the given length, it is padded with nulls; if it is
   *  longer, it is truncated.
   *
   *  @param  len  the new length
   *  @throws IndexOutOfBoundsException if the argument is negative.
   */
  def setLength(len: Int) {
    require(len >= 0, len)
    while (count < len) append('\0')
    count = len
  }

  /** Returns the current capacity, which is the size of the underlying array.
   *  A new array will be allocated if the current capacity is exceeded.
   *
   *  @return  the capacity
   */
  def capacity: Int = array.length

  @deprecated("Use `ensureCapacity' instead. An assignment is misleading because\n"+
              "it can never decrease the capacity.")
  def capacity_=(n: Int) { ensureCapacity(n) }

  /** Ensure that the capacity is at least the given argument.
   *  If the argument is greater than the current capacity, new
   *  storage will be allocated with size equal to the given
   *  argument or to (2 * capacity + 2), whichever is larger.
   *
   *  @param newCapacity    the minimum desired capacity.
   */
  def ensureCapacity(newCapacity: Int): Unit =
    if (newCapacity > array.length) {
      val newSize   = (array.length * 2 + 2) max newCapacity
      val newArray  = new Array[Char](newSize)
      arraycopy(array, 0, newArray, 0, count)
      array = newArray
    }

  /** Returns the Char at the specified index, counting from 0 as in Arrays.
   *
   *  @param  index   the index to look up
   *  @return         the Char at the given index.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def charAt(index: Int): Char = {
    if (index < 0 || index >= count)
      throw new StringIndexOutOfBoundsException(index)
    array(index)
  }

  /** Equivalent to charAt.
   */
  def apply(i: Int): Char = charAt(i)

  /** Removes the Char at the specified index.  The sequence is
   *  shortened by one.
   *
   *  @param  index  The index to remove.
   *  @return        This StringBuilder.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def deleteCharAt(index: Int): StringBuilder = {
    if (index < 0 || index >= count)
      throw new StringIndexOutOfBoundsException(index)
    arraycopy(array, index + 1, array, index, count - index - 1)
    count -= 1
    this
  }

  /** Update the sequence at the given index to hold the specified Char.
   *
   *  @param  index   the index to modify.
   *  @param  ch      the new Char.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def setCharAt(index: Int, ch: Char) {
    if (index < 0 || index >= count)
      throw new StringIndexOutOfBoundsException(index)
    array(index) = ch
  }

  /** Equivalent to setCharAt.
   */
  def update(i: Int, c: Char) { setCharAt(i, c) }

  /** Returns a new String made up of a subsequence of this sequence,
   *  beginning at the given index and extending to the end of the sequence.
   *
   *  target.substring(start)  is equivalent to  target.drop(start)
   *
   *  @param  start  The starting index, inclusive.
   *  @return        The new String.
   *  @throws IndexOutOfBoundsException  if the index is out of bounds.
   */
  def substring(start: Int): String = substring(start, length)

  /** Returns a new String made up of a subsequence of this sequence,
   *  beginning at the start index (inclusive) and extending to the
   *  end index (exclusive).
   *
   *  target.substring(start, end)  is equivalent to  target.slice(start, end).mkString
   *
   *  @param  start  The beginning index, inclusive.
   *  @param  end    The ending index, exclusive.
   *  @return The new String.
   *  @throws StringIndexOutOfBoundsException If either index is out of bounds,
   *          or if start > end.
   */
  def substring(start: Int, end: Int): String = {
    if (start < 0)
      throw new StringIndexOutOfBoundsException(start)
    if (end > count)
      throw new StringIndexOutOfBoundsException(end)
    if (start > end)
      throw new StringIndexOutOfBoundsException(end - start)
    new String(array, start, end - start)
  }

  def subSequence(start: Int, end: Int): java.lang.CharSequence = substring(start, end)

  /** Appends the given Char to the end of the sequence.
   */
  def +=(x: Char): this.type = { append(x); this }

  /** !!! This should create a new sequence.
   */
  def +(x: Char): this.type = { +=(x); this }

  /** Appends the string representation of the given argument,
   *  which is converted to a String with String.valueOf.
   *
   *  @param  x   an <code>Any</code> object.
   *  @return     this StringBuilder.
   */
  def append(x: Any): StringBuilder = append(String.valueOf(x))

  /** Appends the given String to this sequence.
   *
   *  @param  s   a String.
   *  @return     this StringBuilder.
   */
  def append(s: String): StringBuilder = {
    val str = onull(s)
    val len = str.length
    ensureCapacity(count + len)
    str.getChars(0, len, array, count)
    count += len
    this
  }

  /** Appends the specified string builder to this sequence.
   *
   *  @param sb
   *  @return
   */
  def append(sb: StringBuilder): StringBuilder =
    if (sb == null)
      append("null")
    else {
      val len = sb.length
      ensureCapacity(count + len)
      arraycopy(sb.toArray, 0, array, count, len)
      count += len
      this
    }

  /** Appends all the Chars in the given Seq[Char] to this sequence.
   *
   *  @param  xs  the characters to be appended.
   *  @return     this StringBuilder.
   */
  def appendAll(xs: Seq[Char]): StringBuilder = appendAll(xs.toArray, 0, xs.length)

  /** Appends all the Chars in the given Array[Char] to this sequence.
   *
   *  @param  xs  the characters to be appended.
   *  @return     a reference to this object.
   */
  def appendAll(xs: Array[Char]): StringBuilder = appendAll(xs, 0, xs.length)

  /** Appends a portion of the given Array[Char] to this sequence.
   *
   *  @param  xs      the Array containing Chars to be appended.
   *  @param  offset  the index of the first Char to append.
   *  @param  len     the numbers of Chars to append.
   *  @return         this StringBuilder.
   */
  def appendAll(xs: Array[Char], offset: Int, len: Int): StringBuilder = {
    ensureCapacity(count + len)
    arraycopy(xs, offset, array, count, len)
    count += len
    this
  }

  /** Append the String representation of the given primitive type
   *  to this sequence.  The argument is converted to a String with
   *  String.valueOf.
   *
   *  @param   x  a primitive value
   *  @return     This StringBuilder.
   */
  def append(x: Boolean): StringBuilder = append(String.valueOf(x))
  def append(x: Byte): StringBuilder = append(String.valueOf(x))
  def append(x: Short): StringBuilder = append(String.valueOf(x))
  def append(x: Int): StringBuilder = append(String.valueOf(x))
  def append(x: Long): StringBuilder = append(String.valueOf(x))
  def append(x: Float): StringBuilder = append(String.valueOf(x))
  def append(x: Double): StringBuilder = append(String.valueOf(x))
  def append(x: Char): StringBuilder = {
    ensureCapacity(count + 1)
    array(count) = x
    count += 1
    this
  }

  /** Remove a subsequence of Chars from this sequence, starting at the
   *  given start index (inclusive) and extending to the end index (exclusive)
   *  or to the end of the String, whichever comes first.
   *
   *  @param  start  The beginning index, inclusive.
   *  @param  end    The ending index, exclusive.
   *  @return        This StringBuilder.
   *  @throws StringIndexOutOfBoundsException   if start < 0 || start > end
   */
  def delete(start: Int, end: Int): StringBuilder = {
    if (start < 0 || start > end)
      throw new StringIndexOutOfBoundsException(start)
    val end0 = if (end > count) count else end
    val len = end0 - start
    if (len > 0) {
      arraycopy(array, start + len, array, start, count - end0)
      count -= len
    }
    this
  }

  /** Replaces a subsequence of Chars with the given String.  The semantics
   *  are as in delete, with the String argument then inserted at index 'start'.
   *
   *  @param  start  The beginning index, inclusive.
   *  @param  end    The ending index, exclusive.
   *  @param  str    The String to be inserted at the start index.
   *  @return        This StringBuilder.
   *  @throws StringIndexOutOfBoundsException if start < 0, start > length, or start > end
   */
  def replace(start: Int, end: Int, str: String) {
    if (start < 0 || start > count || start > end)
      throw new StringIndexOutOfBoundsException(start)

    val end0 = if (end > count) count else end
    val len = str.length()
    val newCount = count + len - (end0 - start)
    ensureCapacity(newCount)

    arraycopy(array, end, array, start + len, count - end)
    str.getChars(0, len, array, start)
    count = newCount
    this
  }

  /** Inserts a subarray of the given Array[Char] at the given index
   *  of this sequence.
   *
   * @param  index   index at which to insert the subarray.
   * @param  str     the Array from which Chars will be taken.
   * @param  offset  the index of the first Char to insert.
   * @param  len     the number of Chars from 'str' to insert.
   * @return         This StringBuilder.
   *
   * @throws StringIndexOutOfBoundsException  if index < 0, index > length,
   *         offset < 0, len < 0, or (offset + len) > str.length.
   */
  def insertAll(index: Int, str: Array[Char], offset: Int, len: Int): StringBuilder = {
    if (index < 0 || index > count)
      throw new StringIndexOutOfBoundsException(index)
    if (offset < 0 || len < 0 || offset > str.length - len)
      throw new StringIndexOutOfBoundsException(
                "offset " + offset + ", len " + len +
                ", str.length " + str.length)
    ensureCapacity(count + len)
    arraycopy(array, index, array, index + len, count - index)
    arraycopy(str, offset, array, index, len)
    count += len
    this
  }

  /** Inserts the String representation (via String.valueOf) of the given
   *  argument into this sequence at the given index.
   *
   *  @param  index   the index at which to insert.
   *  @param  x       a value.
   *  @return         this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insert(index: Int, x: Any): StringBuilder = insert(index, String.valueOf(x))

  /** Inserts the String into this character sequence.
   *
   *  @param  index the index at which to insert.
   *  @param  x     a String.
   *  @return       this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insert(index: Int, x: String): StringBuilder = insertAll(index, x.toArray)

  /** Inserts the given Seq[Char] into this sequence at the given index.
   *
   *  @param  index the index at which to insert.
   *  @param  xs    the Seq[Char].
   *  @return       this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insertAll(index: Int, xs: Seq[Char]): StringBuilder = insertAll(index, xs.toArray)

  /** Inserts the given Array[Char] into this sequence at the given index.
   *
   *  @param  index the index at which to insert.
   *  @param  xs    the Array[Char].
   *  @return       this StringBuilder.
   *  @throws StringIndexOutOfBoundsException  if the index is out of bounds.
   */
  def insertAll(index: Int, xs: Array[Char]): StringBuilder = {
    if (index < 0 || index > count)
      throw new StringIndexOutOfBoundsException(index)
    val len = xs.length
    ensureCapacity(count + len)
    arraycopy(array, index, array, index + len, count - index)
    arraycopy(xs, 0, array, index, len)
    count += len
    this
  }

  /** Calls String.valueOf on the given primitive value, and inserts the
   *  String at the given index.
   *
   *  @param  index the offset position.
   *  @param  x     a primitive value.
   *  @return       this StringBuilder.
   */
  def insert(index: Int, x: Boolean): StringBuilder = insert(index, String.valueOf(x))
  def insert(index: Int, x: Byte): StringBuilder    = insert(index, String.valueOf(x))
  def insert(index: Int, x: Short): StringBuilder   = insert(index, String.valueOf(x))
  def insert(index: Int, x: Int): StringBuilder     = insert(index, String.valueOf(x))
  def insert(index: Int, x: Long): StringBuilder    = insert(index, String.valueOf(x))
  def insert(index: Int, x: Float): StringBuilder   = insert(index, String.valueOf(x))
  def insert(index: Int, x: Double): StringBuilder  = insert(index, String.valueOf(x))
  def insert(index: Int, x: Char): StringBuilder = {
    if (index < 0 || index > count)
      throw new StringIndexOutOfBoundsException(index)
    ensureCapacity(count + 1)
    arraycopy(array, index, array, index + 1, count - index)
    array(index) = x
    count += 1
    this
  }

  @deprecated("Use appendAll instead. This method is deprecated because of the\n"+
              "possible confusion with `append(Any)'.")
  def append(x: Seq[Char]): StringBuilder = appendAll(x)

  @deprecated("use appendAll instead. This method is deprecated because\n"+
              "of the possible confusion with `append(Any)'.")
  def append(x: Array[Char]): StringBuilder = appendAll(x)

  @deprecated("use appendAll instead. This method is deprecated because\n"+
              "of the possible confusion with `append(Any, Int, Int)'.")
  def append(x: Array[Char], offset: Int, len: Int): StringBuilder = appendAll(x, offset, len)

  @deprecated("use insertAll instead. This method is deprecated because of the\n"+
              "possible confusion with `insert(Int, Any, Int, Int)'.")
  def insert(index: Int, str: Array[Char], offset: Int, len: Int): StringBuilder =
    insertAll(index, str, offset, len)

  @deprecated("use insertAll instead. This method is deprecated because of\n"+
              "the possible confusion with `insert(Int, Any)'.")
  def insert(at: Int, x: Seq[Char]): StringBuilder = insertAll(at, x)

  @deprecated("use insertAll instead. This method is deprecated because of\n"+
              "the possible confusion with `insert(Int, Any)'.")
  def insert(at: Int, x: Array[Char]): StringBuilder =
    insertAll(at, x)

  /** Finds the index of the first occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @return             the first applicable index where target occurs, or -1 if not found.
   */
  def indexOf(str: String): Int = indexOf(str, 0)

  /** Finds the index of the first occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @param    fromIndex the smallest index in the source string to consider
   *  @return             the first applicable index where target occurs, or -1 if not found.
   */
  def indexOf(str: String, fromIndex: Int): Int = indexOfSlice(str.toIndexedSeq, fromIndex)

  /** Finds the index of the last occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @return             the last applicable index where target occurs, or -1 if not found.
   */
  def lastIndexOf(str: String): Int = lastIndexOf(str, count)

  /** Finds the index of the last occurrence of the specified substring.
   *
   *  @param    str       the target string to search for
   *  @param    fromIndex the smallest index in the source string to consider
   *  @return             the last applicable index where target occurs, or -1 if not found.
   */
  def lastIndexOf(str: String, fromIndex: Int): Int = lastIndexOfSlice(str.toIndexedSeq, fromIndex)

  /** Creates a new StringBuilder with the reversed contents of this one.
   *  If surrogate pairs are present, they are treated as indivisible units: each
   *  pair will appear in the same order in the updated sequence.
   *
   *  @return   the reversed StringBuilder
   */
  @migration(2, 8, "Since 2.8 reverse returns a new instance.  Use 'reverseContents' to update in place.")
  override def reverse: StringBuilder = new StringBuilder(this.toString).reverseContents()

  /** Like reverse, but destructively updates the target StringBuilder.
   *
   *  @return   the reversed StringBuilder (same as the target StringBuilder)
   */
  def reverseContents(): StringBuilder = {
    // record of indices of pairs which need to be swapped
    val surrogates = new ListBuffer[(Int, Int)]
    val half  = count / 2

    def mirror(x: Int) = count - 1 - x
    def swap(i1: Int, i2: Int) {
      val tmp = array(i2)
      array(i2) = array(i1)
      array(i1) = tmp
    }

    for ((i, j) <- 0 until half zip (count - 1 to half by -1)) {
      if (array(i).isSurrogate && array(i + 1).isSurrogate)
        surrogates += ((j - 1, j))
      if (array(j).isSurrogate && array(j - 1).isSurrogate)
        surrogates += ((i, i + 1))

      swap(i, j)
    }
    surrogates foreach (swap _).tupled
    this
  }

  /** Returns a new String representing the data in this sequence.
   *
   *  @return  the current contents of this sequence as a String
   */
  override def toString: String = new String(array, 0, count)

  def result(): String = toString
}


object StringBuilder
{
  // method <code>java.util.Arrays.copyOf</code> exists since 1.6
  private def copyOf(src: Array[Char], newLength: Int): Array[Char] = {
    val dest = new Array[Char](newLength)
    arraycopy(src, 0, dest, 0, src.length min newLength)
    dest
  }

  // for mimicking java's propensity to make null into "null"
  private def onull(s: String): String = if (s == null) "null" else s
}