content: /*
   * Indenter.java July 2006
   *
   * Copyright (C) 2006, Niall Gallagher <niallg@users.sf.net>
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 
   * implied. See the License for the specific language governing 
   * permissions and limitations under the License.
   */
  
  package com.aliyun.odps.simpleframework.xml.stream;
  
  /**
   * The <code>Indenter</code> is used create indent strings using the
   * stack paradigm. This allows XML documents to be generated by 
   * pushing and popping indents onto the stack. This indenter caches
   * all indent strings created so that when the same position on the
   * stack is encountered the indent can be acquired quickly.
   * <p>
   * The indents created by this are all prefixed with the line feed
   * character, which allows XML tags to span exclusive lines. If the
   * indent size specified is zero or less then no spaces, or line 
   * feed will be added to the generated indent string.
   *
   * @author Niall Gallagher
   */ 
  class Indenter {
  
     /**
      * Provides a quick string cache that caches using by index.
      */         
     private Cache cache;
         
     /**
      * Number of spaces that is used for each of the indents.
      */ 
     private int indent;
     
     /**
      * Represents the current number of spaces in the indent text.
      */ 
     private int count;
  
     /**
      * Represents the index within the cache to get the indent.
      */ 
     private int index;
    
     /**
      * Constructor for the <code>Indenter</code> object. This will
      * create an indent that uses three spaces for each indent that
      * is pushed on to the stack. This also uses a default cache 
      * size of sixteen, which should be sufficient for most files.
      */ 
     public Indenter() {
        this(new Format());
     }
  
     /**
      * Constructor for the <code>Indenter</code> object. This will
      * create an indent that uses the specified number of spaces to
      * create each entry pushed on to the stack. This uses a cache
      * size of sixteen, which should be sufficient for most files.
      *
      * @param format determines the number of spaces per indent
      */ 
     public Indenter(Format format) {
        this(format, 16);           
     }
     
     /**
      * Constructor for the <code>Indenter</code> object. This will
      * create an indent that uses the specified number of spaces to
      * create each entry pushed on to the stack. This uses a cache
      * of the specified size, which is used to optimize the object.
      *
      * @param format determines the number of spaces per indent
      * @param size this is the initial size of the indent cache
      */ 
     private Indenter(Format format, int size) {
        this.indent = format.getIndent();           
        this.cache = new Cache(size);
     }  
     
     /**
      * This returns the current indent for this indenter. This should
      * be used to write elements or comments that should be at the
      * same indentation level as the XML element that will follow. 
      * 
      * @return this returns the current indentation level for this
      */
     public String top() {
        return indent(index);
     }
  
     /**
      * This is used to push an indent on to the cache. The first
      * indent created by this is an empty string, this is because an
      * indent is not required for the start of an XML file. If there
      * are multiple roots written to the same writer then the start
      * and end tags of a root element will exist on the same line.
      * 
      * @return this is used to push an indent on to the stack
      */ 
     public String push() {
        String text = indent(index++);
     
        if(indent > 0) {
           count += indent;
        }         
        return text;
     }                  
  
     /**
      * This is used to pop an indent from the cache. This reduces
      * the length of the current indent and is typically used when
      * an end tag is added to an XML document. If the number of pop
      * requests exceeds the number of push requests then an empty
      * string is returned from this method.
      *
      * @return this is used to pop an indent from the stack
      */ 
     public String pop() {
        String text = indent(--index);
  
        if(indent > 0) {
           count -= indent;
        }      
        return text;
     }
  
     /**
      * This is used to acquire the indent at the specified index. If
      * the indent does not exist at the specified index then on is
      * created using the current value of the indent. The very first
      * indent taken from this will be an empty string value.
      *
      * @param index this is the index to acquire the indent from
      *
      * @return this returns the indent from the specified index
      */ 
     private String indent(int index) {
        if(indent > 0) {
           String text = cache.get(index);
  
           if(text == null){
              text = create();
              cache.set(index, text);         
           }
           if(cache.size() >0) {
              return text;
           }            
        }         
        return "";  
     }
  
     /**
      * This is used to create an indent which can later be pushed on
      * to the stack. If the number of spaces to be added is zero then
      * this will return a single character string with a line feed.
      *
      * @return this will create an indent to be added to the stack
      */ 
     private String create() {
        char[] text = new char[count+1];
  
        if(count > 0) {
           text[0] = '\n';
  
           for(int i = 1; i <= count; i++){
              text[i] = ' ';                 
           }         
           return new String(text);
        }
        return "\n";
     }
  
     /**
      * The <code>Cache</code> object is used create an indexable list
      * which allows the indenter to quickly acquire an indent using
      * a stack position. This ensures that the indenter need only 
      * create an index once for a given stack position. The number of
      * indents held within this cache can also be tracked.
      */ 
     private static class Cache {
  
        /**
         * This is used to track indent strings within the cache.
         */            
        private String[] list;
  
        /**
         * Represents the number of indent strings held by the cache.
         */ 
        private int count;
        
        /**
         * Constructor for the <code>Cache</code> object. This creates
         * a cache of the specified size, the specified size acts as
         * an initial size and the cache can be expanded on demand.
         *
         * @param size the initial number of entries in the cache
         */ 
        public Cache(int size) {
           this.list = new String[size];              
        }
  
        /**
         * This method is used to retrieve the number of indents that
         * have been added to the cache. This is used to determine if
         * an indent request is the first.
         *
         * @return this returns the number of indents in the cache
         */ 
        public int size() {
           return count;              
        }      
  
        /**
         * This method is used to add the specified indent on to the
         * cache. The index allows the cache to act as a stack, when
         * the index is specified it can be used to retrieve the same
         * indent using that index.
         * 
         * @param index this is the position to add the index to
         * @param text this is the indent to add to the position
         */ 
        public void set(int index, String text) {
           if(index >= list.length) {
              resize(index * 2);                             
           }
           if(index > count) {
              count = index;                 
           }
           list[index] = text;         
        }
  
        /**
         * This method is used to retrieve an indent from the given
         * position. This allows the indenter to use the cache as a
         * stack, by increasing and decreasing the index as required.
         * 
         * @param index the position to retrieve the indent from
         *
         * @return this is the indent retrieve from the given index
         */ 
        public String get(int index) {
           if(index < list.length) {
              return list[index];
           }              
           return null;
        }
  
        /**
         * Should the number of indents to be cache grows larger than
         * the default initial size then this will increase the size
         * of the cache. This ensures that the indenter can handle an
         * arbitrary number of indents for a given output.
         *
         * @param size this is the size to expand the cache to
         */ 
        private void resize(int size) {
           String[] temp = new String[size];
  
           for(int i = 0; i < list.length; i++){
              temp[i] = list[i];                 
           }
           list = temp;
        }
     }
  }
  