blob: d24d5316eb380eb4b6aef1dc324aff36d9e1e95e [file] [log] [blame]
/*
* 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)
}