text: /*
   * Licensed to the Apache Software Foundation (ASF) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * The ASF licenses this file to You 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.
   */
  
  #include "Properties.h"
  
  #include <map>
  #include <sstream>
  #include <decaf/util/Date.h>
  #include <decaf/util/Map.h>
  #include <decaf/util/StlMap.h>
  #include <decaf/io/BufferedInputStream.h>
  #include <decaf/lang/Character.h>
  #include <decaf/lang/Integer.h>
  #include <decaf/lang/exceptions/UnsupportedOperationException.h>
  
  using namespace decaf;
  using namespace decaf::util;
  using namespace decaf::lang;
  using namespace decaf::lang::exceptions;
  using namespace decaf::io;
  
  ////////////////////////////////////////////////////////////////////////////////
  namespace decaf{
  namespace util{
  
      class PropertiesInternal{
      public:
  
          decaf::util::StlMap< std::string, std::string > properties;
  
          PropertiesInternal() : properties() {}
      };
  
  }}
  
  ////////////////////////////////////////////////////////////////////////////////
  namespace {
  
      /**
       * internal namespace for Properties utility methods, these might change often
       * so we hide them in here to preserve ABI rules on the header.
       */
  
      enum ParsingMode {
          PARSE_MODE_NONE = 0,
          PARSE_MODE_SLASH = 1,
          PARSE_MODE_CONTINUE = 2,
          PARSE_MODE_KEY_DONE = 3,
          PARSE_MODE_IGNORE = 4
      };
  
      void dumpString(std::ostringstream& buffer, const std::string& string, bool key) {
  
          std::size_t i = 0;
          if (!key && i < string.length() && string.at(i) == ' ') {
              buffer << "\\ ";
              i++;
          }
  
          for (; i < string.length(); i++) {
  
              char ch = string.at(i);
  
              switch(ch) {
                  case '\t':
                      buffer << "\\t";
                      break;
                  case '\n':
                      buffer << "\\n";
                      break;
                  case '\f':
                      buffer << "\\f";
                      break;
                  case '\r':
                      buffer << "\\r";
                      break;
                  default:
                      if (std::string("\\#!=:").find(ch) != std::string::npos || (key && ch == ' ')) {
                          buffer << '\\';
                      }
  
                      if (ch >= ' ' && ch <= '~') {
                          buffer << ch;
                      } else {
  
                          std::string hex = Integer::toHexString(ch);
                          buffer << "\\u";
                          for (std::size_t j = 0; j < 4 - hex.length(); j++) {
                              buffer << "0";
                          }
                          buffer << hex;
                      }
                      break;
              }
          }
      }
  
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  Properties::Properties() : internal(new PropertiesInternal()), defaults() {
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  Properties::Properties(const Properties& src) : internal(new PropertiesInternal()), defaults() {
  
      this->internal->properties.copy(src.internal->properties);
  
      if (src.defaults.get() != NULL) {
          this->defaults.reset(src.defaults->clone());
      }
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  Properties::~Properties() {
      try {
          delete this->internal;
      }
      DECAF_CATCH_NOTHROW(Exception)
      DECAF_CATCHALL_NOTHROW()
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  Properties& Properties::operator=(const Properties& source) {
  
      if (this == &source) {
          return *this;
      }
  
      this->copy(source);
  
      return *this;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  bool Properties::isEmpty() const {
      synchronized( &( internal->properties ) ) {
          return internal->properties.isEmpty();
      }
  
      return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  int Properties::size() const {
  
      synchronized( &( internal->properties ) ) {
          return (int) internal->properties.size();
      }
  
      return 0;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  const char* Properties::getProperty(const std::string& name) const {
  
      synchronized( &( internal->properties ) ) {
          if (this->internal->properties.containsKey(name)) {
              return this->internal->properties.get(name).c_str();
          } else if (this->defaults != NULL && this->defaults->hasProperty(name)) {
              return this->defaults->getProperty(name);
          }
      }
  
      return NULL;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  std::string Properties::getProperty(const std::string& name, const std::string& defaultValue) const {
  
      synchronized(&(internal->properties)) {
          if (this->internal->properties.containsKey(name)) {
              return this->internal->properties.get(name);
          } else if (this->defaults != NULL && this->defaults->hasProperty(name)) {
              return this->defaults->getProperty(name);
          }
      }
  
      return defaultValue;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  std::string Properties::setProperty(const std::string& name, const std::string& value) {
  
      std::string oldValue;
      synchronized(&(internal->properties)) {
  
          if (internal->properties.containsKey(name)) {
              oldValue = internal->properties.get(name);
          }
  
          internal->properties.put(name, value);
      }
  
      return oldValue;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  bool Properties::hasProperty(const std::string& name) const {
  
      synchronized( &( internal->properties ) ) {
          return this->internal->properties.containsKey(name);
      }
  
      return false;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  std::string Properties::remove(const std::string& name) {
  
      std::string oldValue;
  
      synchronized( &( internal->properties ) ) {
          if (this->internal->properties.containsKey(name)) {
              oldValue = this->internal->properties.get(name);
              this->internal->properties.remove(name);
          }
      }
  
      return oldValue;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  std::vector<std::pair<std::string, std::string> > Properties::toArray() const {
  
      std::vector<std::pair<std::string, std::string> > result;
  
      synchronized( &( internal->properties ) ) {
          Pointer<Iterator<MapEntry<std::string, std::string> > > entries(
                  this->internal->properties.entrySet().iterator());
          while (entries->hasNext()) {
              MapEntry<std::string, std::string> entry = entries->next();
              result.push_back(std::make_pair(entry.getKey(), entry.getValue()));
          }
      }
  
      return result;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Properties::copy( const Properties& source ){
  
      if (&source == this) {
          return;
      }
  
      synchronized( &( this->internal->properties ) ) {
  
          synchronized( &( source.internal->properties ) ) {
              this->internal->properties.copy(source.internal->properties);
  
              if (source.defaults.get() != NULL) {
                  this->defaults.reset(source.defaults->clone());
              }
          }
      }
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  Properties* Properties::clone() const {
  
      Properties* props = new Properties();
      props->internal->properties.copy(this->internal->properties);
      return props;
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Properties::clear() {
  
      synchronized( &( internal->properties ) ) {
          this->internal->properties.clear();
      }
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  bool Properties::equals(const Properties& source) const {
      return this->internal->properties.equals(source.internal->properties);
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  std::string Properties::toString() const {
  
      std::ostringstream stream;
      std::map<std::string, std::string>::const_iterator iter;
  
      stream << "Begin Class decaf::util::Properties:" << std::endl;
  
      synchronized(&(internal->properties)) {
          Pointer<Iterator<MapEntry<std::string, std::string> > > entries(
                  this->internal->properties.entrySet().iterator());
          while (entries->hasNext()) {
              MapEntry<std::string, std::string> entry = entries->next();
              stream << " property[" << entry.getKey() << "] = " << entry.getValue() << std::endl;
          }
      }
  
      stream << "End Class decaf::util::Properties:" << std::endl;
  
      return stream.str();
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  std::vector<std::string> Properties::propertyNames() const {
      StlMap<std::string, std::string> selectedProperties;
      this->selectProperties(selectedProperties);
      return selectedProperties.keySet().toArray();
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Properties::selectProperties(StlMap<std::string, std::string>& selectProperties) const {
  
      if (this->defaults != NULL) {
          this->defaults->selectProperties(selectProperties);
      }
  
      Pointer<Iterator<MapEntry<std::string, std::string> > > entries(
          this->internal->properties.entrySet().iterator());
      while (entries->hasNext()) {
          MapEntry<std::string, std::string> entry = entries->next();
          selectProperties.put(entry.getKey(), entry.getValue());
      }
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Properties::load(decaf::io::InputStream* stream) {
  
      try{
  
          if (stream == NULL) {
              throw NullPointerException(
                  __FILE__, __LINE__, "The Stream instance passed was Null");
          }
  
          int mode = PARSE_MODE_NONE;
          char nextChar;
          std::vector<char> buf;
          int offset = 0;
          int keyLength = -1;
          int intVal;
          bool firstChar = true;
          BufferedInputStream bis(stream);
  
          while (true) {
  
              intVal = bis.read();
  
              if (intVal == -1) {
                  break;
              }
  
              nextChar = (char) (intVal & 0xFF);
  
              if (mode == PARSE_MODE_SLASH) {
  
                  mode = PARSE_MODE_NONE;
                  switch( nextChar ) {
                      case '\r':
                          mode = PARSE_MODE_CONTINUE; // Look for a following \n
                          continue;
                      case '\n':
                          mode = PARSE_MODE_IGNORE; // Ignore whitespace on the next line
                          continue;
                      case 'b':
                          nextChar = '\b';
                          break;
                      case 'f':
                          nextChar = '\f';
                          break;
                      case 'n':
                          nextChar = '\n';
                          break;
                      case 'r':
                          nextChar = '\r';
                          break;
                      case 't':
                          nextChar = '\t';
                          break;
                  }
  
              } else {
  
                  switch( nextChar ) {
                      case '#':
                      case '!':
                          if (firstChar) {
                              while (true) {
                                  intVal = bis.read();
                                  if (intVal == -1) {
                                      break;
                                  }
  
                                  nextChar = (char) (intVal & 0xFF);
  
                                  if (nextChar == '\r' || nextChar == '\n') {
                                      break;
                                  }
                              }
                              continue;
                          }
                          break;
                      case '\n':
                          if (mode == PARSE_MODE_CONTINUE) { // Part of a \r\n sequence
                              mode = PARSE_MODE_IGNORE; // Ignore whitespace on the next line
                              continue;
                          }
                          // Intentional fall into the next case
                      case '\r':
                          mode = PARSE_MODE_NONE;
                          firstChar = true;
                          if (offset > 0 || (offset == 0 && keyLength == 0)) {
  
                              if (keyLength == -1) {
                                  keyLength = offset;
                              }
                              std::string temp(buf.begin(), buf.begin() + offset);
  
                              this->internal->properties.put(
                                  temp.substr(0, (std::size_t) keyLength), temp.substr((std::size_t) keyLength));
                          }
  
                          keyLength = -1;
                          offset = 0;
                          buf.clear();
                          continue;
                      case '\\':
                          if (mode == PARSE_MODE_KEY_DONE) {
                              keyLength = offset;
                          }
                          mode = PARSE_MODE_SLASH;
                          continue;
                      case ':':
                      case '=':
                          if (keyLength == -1) { // if parsing the key
                              mode = PARSE_MODE_NONE;
                              keyLength = offset;
                              continue;
                          }
                          break;
                  }
  
                  if (Character::isWhitespace(nextChar)) {
                      if (mode == PARSE_MODE_CONTINUE) {
                          mode = PARSE_MODE_IGNORE;
                      }
                      // if key length == 0 or value length == 0
                      if (offset == 0 || offset == keyLength || mode == PARSE_MODE_IGNORE) {
                          continue;
                      }
                      if (keyLength == -1) { // if parsing the key
                          mode = PARSE_MODE_KEY_DONE;
                          continue;
                      }
                  }
  
                  if (mode == PARSE_MODE_IGNORE || mode == PARSE_MODE_CONTINUE) {
                      mode = PARSE_MODE_NONE;
                  }
              }
  
              firstChar = false;
              if (mode == PARSE_MODE_KEY_DONE) {
                  keyLength = offset;
                  mode = PARSE_MODE_NONE;
              }
  
              offset += 1;
              buf.push_back(nextChar);
          }
  
          if (keyLength == -1 && offset > 0) {
              keyLength = offset;
          }
  
          if (keyLength >= 0) {
              std::string temp(buf.begin(), buf.begin() + offset);
              this->internal->properties.put(
                  temp.substr(0, (std::size_t) keyLength), temp.substr((std::size_t) keyLength));
          }
      }
      DECAF_CATCH_RETHROW(IOException)
      DECAF_CATCH_RETHROW(IllegalArgumentException)
      DECAF_CATCH_RETHROW(NullPointerException)
      DECAF_CATCH_EXCEPTION_CONVERT(Exception, IOException)
      DECAF_CATCHALL_THROW(IOException)
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Properties::load(decaf::io::Reader* reader) {
  
      try {
  
          if (reader == NULL) {
              throw NullPointerException(
                  __FILE__, __LINE__, "The Reader instance passed was Null");
          }
  
          throw UnsupportedOperationException(
              __FILE__, __LINE__, "Not yet Implemented.");
      }
      DECAF_CATCH_RETHROW(IOException)
      DECAF_CATCH_RETHROW(IllegalArgumentException)
      DECAF_CATCH_RETHROW(NullPointerException)
      DECAF_CATCH_EXCEPTION_CONVERT(Exception, IOException)
      DECAF_CATCHALL_THROW(IOException)
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Properties::store(decaf::io::OutputStream* out, const std::string& comments DECAF_UNUSED) {
  
      try {
  
          if (out == NULL) {
              throw NullPointerException(
                  __FILE__, __LINE__, "The OutputStream instance passed was Null");
          }
  
          std::ostringstream buffer;
          std::ostringstream writer;
  
          if (comments != "") {
              writer << "#";
              writer << comments;
              writer << std::endl;
          }
  
          writer << "#";
          writer << Date().toString();
          writer << std::endl;
  
          Pointer<Iterator<MapEntry<std::string, std::string> > > entries(
                  this->internal->properties.entrySet().iterator());
          while (entries->hasNext()) {
              MapEntry<std::string, std::string> entry = entries->next();
  
              dumpString(buffer, entry.getKey(), true);
              buffer << "=";
              dumpString(buffer, entry.getValue(), false);
              buffer << std::endl;
  
              writer << buffer.str();
              buffer.str("");
          }
  
          int length = (int) writer.str().length();
  
          out->write((const unsigned char*) writer.str().c_str(), length, 0, length);
          out->flush();
      }
      DECAF_CATCH_RETHROW(IOException)
      DECAF_CATCH_RETHROW(NullPointerException)
      DECAF_CATCH_EXCEPTION_CONVERT(Exception, IOException)
      DECAF_CATCHALL_THROW(IOException)
  }
  
  ////////////////////////////////////////////////////////////////////////////////
  void Properties::store(decaf::io::Writer* writer, const std::string& comments DECAF_UNUSED) {
  
      try {
  
          if (writer == NULL) {
              throw NullPointerException(
                  __FILE__, __LINE__, "The Writer instance passed was Null");
          }
  
          throw UnsupportedOperationException(
              __FILE__, __LINE__, "Not yet Implemented.");
      }
      DECAF_CATCH_RETHROW(IOException)
      DECAF_CATCH_RETHROW(NullPointerException)
      DECAF_CATCH_EXCEPTION_CONVERT(Exception, IOException)
      DECAF_CATCHALL_THROW(IOException)
  }
  
  