# SectionContent.java

    /*
    ** Module   : SectionContent.java
    ** Abstract : Progress 4GL INI file content class representing one section of data
    **
    ** Copyright (c) 2013-2017 Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------Description----------------------------------
    ** 001 EVL 20130701 Created initial version.
    ** 004 MAG 20140908 Make key names case insensitive.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import java.io.*;
    import java.util.*;

    /**
     * A class implementing the INI text file key=value section lines. Can contain several lines with
     * key values and also comment lines end empty lines in any place of the section.
     *
     * @author    EVL
     */
    public class SectionContent
    extends Content
    {
       /** Map to store key=value pars. */
       private Map<String, String> keyMap =
                new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);

       /** The section name associated with the content. */
       private String name = null;

       /** List to control section line ordering. */
       private List<Renderable> ordering = new LinkedList<Renderable>();

       /** The position of the last key=value pair in the ordering list. */
       private int nextKeyNumber = 0;

       /**
        * Constructs an instance of the data section in INI file.
        *
        * @param name
        *        The name of the section corresponding to the key=value pairs of the INI file.
        */
       public SectionContent(String name)
       {
          super();
          this.name = name;
       }

       /**
        * Getting the name of this section.
        *
        * @return The String with the section name.
        */
       public String getName()
       {
          return name;
       }

       /**
        * Writes the content of the data section to the file via Writer object provided.
        *
        * @param pw
        *        The writer will be used to save content to the file.
        */
       public void write(PrintWriter pw)
       {
          if (ordering.size() > 0)
          {
             // Write the section name first
             pw.println("[" + name + "]");
             // Key names iterator
             Iterator<Renderable> iterLines = ordering.iterator();
             while (iterLines.hasNext())
             {
                // Get next available Renderable object
                Renderable lineToRender = (Renderable)iterLines.next();
                // And write them to the file
                String nextLine = lineToRender.render();
                // It is possible to have null pointer here if the key was previously removed
                if (nextLine != null)
                {
                   pw.println(nextLine);
                }
             }
          }
       }

       /**
        * Checks if the content of the section is empty or not.
        *
        * @return <code>true</code> in case of not emptry, <code>false</code> otherwise.
        */
       public boolean notEmpty()
       {
          // Not empty means we have some elements inside allocated map
          return ordering != null && ordering.size() > 0;
       }

       /**
        * Stores the key=value pair into the section internal map.
        *
        * @param key
        *        The String key name to store.
        * @param value
        *        The String value to be associated with the given key name.
        */
       public void store(String key, String value)
       {
          // Find out if the key=value line already in the section
          boolean alreadyInMap = alreadyInSection(key);
          // Put key=value pair into internal map
          keyMap.put(key, value);
          // And refresh ordering list if the Renderer is still not there
          if (!alreadyInMap)
          {
             // We have to insert new key after last existed key, not at the end of the list
             // And increase then the next number
             ordering.add(nextKeyNumber++, new KeyRenderer(keyMap, key));
          }
       }

       /**
        * Removes the key=value pair from the section internal map. After this method the caller
        * must reload the INI file to sync up the internal maps.
        *
        * @param key
        *        The String key name to remove.
        */
       public void remove(String key)
       {
          // Find out if the key=value line already in the section and remove them from map only
          if (alreadyInSection(key))
          {
             keyMap.remove(key);
          }
       }

       /**
        * Adds the key=value pair to the section internals assuming this is the new key.
        *
        * @param key
        *        The String key name to store.
        * @param value
        *        The String value to be associated with the given key name.
        */
       public void add(String key, String value)
       {
          // Put key=value pair into internal map
          keyMap.put(key, value);
          // And refresh ordering list if the Renderer is still not there
          ordering.add(new KeyRenderer(keyMap, key));
          // Remember the position of the next key to add in store() method
          nextKeyNumber = ordering.size();
       }

       /**
        * Stores the empty line or comment iside the section content.
        *
        * @param textLine
        *        The ignored text line to store.
        */
       public void store(String textLine)
       {
          // And refresh ordering list
          ordering.add(new TextRenderer(textLine));
       }

       /**
        * Stores the line iside the section content as pseudo key. The all next key-value pair will
        * be stored after this line. Simulation of 4GL CHUI crazy apprach.
        *
        * @param textLine
        *        The ignored text line to store.
        */
       public void storeAsPseudoKey(String textLine)
       {
          // And refresh ordering list
          ordering.add(nextKeyNumber++, new TextRenderer(textLine));
       }

       /**
        * Gets the value associated with the given key inside the section.
        *
        * @param key
        *        The String key name of the value to be retrieved.
        *
        * @return The String value associate with the given key or <code>null</code> if there is no
        *         key=value pair inside the section.
        */
       public String getValueForKey(String key)
       {
          if (keyMap != null)
          {
             return keyMap.get(key);
          }

          return null;
       }

       /**
        * Checks if the given key already added to the section internals..
        *
        * @param key
        *        The String key name to check.
        *
        * @return <code>true</code> if the key was already added to internal map, <code>false</code>
        *         otherwise.
        */
       public boolean alreadyInSection(String key)
       {
          if (keyMap != null)
          {
             return keyMap.containsKey(key);
          }

          return false;
       }

       /**
        * Getting the comma-separated list of the keys for this section.
        *
        * @return The String with comma-separated key names.
        */
       public String getKeys()
       {
          StringBuilder sb = new StringBuilder();

          Iterator<Renderable> iterLines = ordering.iterator();
          int count = 0;
          while (iterLines.hasNext())
          {
             // Get next available Renderable object
             Renderable lineToCheck = (Renderable)iterLines.next();
             // Get the string internal of the renderable, key or text members
             String keyToCheck = lineToCheck.toString();
             // And check if it is a key from key-value map
             if (alreadyInSection(keyToCheck))
             {
                // This is the comma-separator if there is more than 1 name
                if (count > 0)
                {
                   sb.append(EnvironmentOps.PROPATH_SEPARATOR);
                }
                sb.append(keyToCheck);
                count++;
             }
          }

          return sb.toString();
       }

       /**
        * The interface to present single line and key-value pair in one ordered list.
        */
       public interface Renderable
       {
          /**
           * Gets the string representation of the instance.
           *
           * @return The String value associated with the given key=value line of the file or line
           *         to be ignored.
           */
          public String render();

          /**
           * Gets the string representation of the instance internal string part.
           *
           * @return The String value associated with the given key or whole line.
           */
          public String toString();
       }

       /**
        * The class to present single line in one ordered list.
        */
       private static class TextRenderer
       implements Renderable
       {
          /** The content of the whole line. */
          private String text = null;

          /**
           * Constructs an instance of the single line in INI file.
           *
           * @param text
           *        The text corresponding to the ignored line of the INI file.
           */
          private TextRenderer(String text)
          {
             this.text = text;
          }

          /**
           * Gets the string representation of the ignored line.
           *
           * @return The String value associated with the given to be ignored.
           */
          public String render()
          {
              return text;
          }

          /**
           * Gets the string representation of the instance internal string part.
           *
           * @return The String value associated with the given key or whole line.
           */
          public String toString()
          {
             return render();
          }
       }

       /**
        * The class to present key-value pair in one ordered list.
        */
       private static class KeyRenderer
       implements Renderable
       {
          /** The key name.*/
          private String key = null;

          /** The Map containing key=value pairs.*/
          private Map<String, String> lookup = null;

          /**
           * Constructs an instance of the key entry line in INI file.
           *
           * @param lookup
           *        The Map containing the key-value pairs to look for.
           * @param key
           *        The key part string of the given key=value pair.
           */
          private KeyRenderer(Map<String, String> lookup, String key)
          {
             this.lookup = lookup;
             this.key    = key;
          }

          /**
           * Gets the string representation of the key=value pair.
           *
           * @return The String value associated with the given key=value line of the file.
           */
          public String render()
          {
             // Get the value for the known key
             String value = lookup.get(key);

             if (value != null)
                return key + "=" + value;
             else
                return null;
          }

          /**
           * Gets the string representation of the instance internal string part.
           *
           * @return The String value associated with the given key or whole line.
           */
          public String toString()
          {
             return key;
          }
       }
    }