Treat circular references and malformed expressions as errors.
Add Set() method for setting values.
Renamed GetDefault() to GetString().
Added GetFloat, GetInt, GetUint and GetBool convenience methods.
diff --git a/doc.go b/doc.go
index a11d46b..c140a35 100644
--- a/doc.go
+++ b/doc.go
@@ -2,14 +2,50 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// goproperties provides functions for reading
-// ISO-8859-1 (Java) and UTF-8 encoded .properties files and has
-// support for Spring-like property expansion.
+// goproperties provides functions for reading and writing
+// ISO-8859-1 and UTF-8 encoded .properties files and has
+// support for recursive property expansion.
 //
+// Java properties files are ISO-8859-1 encoded and use Unicode
+// literals for characters outside the ISO character set. Unicode
+// literals can be used in UTF-8 encoded properties files but
+// aren't necessary.
+//
+// All of the different key/value delimiters " :=" are supported
+// as well as the comment characters '!' and '#' and multi-line
+// values.
+//
+//   ! this is a comment
+//   # and so is this
+//
+//   # the following expressions are equal
+//   key value
+//   key=value
+//   key:value
 //   key = value
-//   # key2 = value
+//   key : value
+//   key = val\
+//         ue
+//
+// Property expansion is recursive and circular references 
+// and malformed expressions are not allowed and cause an 
+// error.
+//
+//   # standard property
+//   key = value
+//
+//   # property expansion: key2 = value
 //   key2 = ${key}
 //
+//   # recursive property expansion: key3 = value
+//   key3 = ${key2}
+//
+//   # circular reference (error)
+//   key = ${key}
+//
+//   # malformed expression (error)
+//   key = ${ke
+//
 // The default property expansion format is ${key} but can be
 // changed by setting different pre- and postfix values on the
 // Properties object.
@@ -18,20 +54,7 @@
 //   p.Prefix = "#["
 //   p.Postfix = "]#"
 //
-// Property expansion is recursive and circular references are not allowed.
-// If a circular reference is detected an error is logged and the
-// unexpanded value is returned.
-//
-//   # Circular reference
-//   key = ${key}
-//
-//   # Malformed expression
-//   key = ${ke
-//
-// When writing properties to a writer currently only ISO-8859-1 encoding
-// is supported.
-//
-// See one of the following links for a description of the properties
+// The following documents provide a description of the properties
 // file format.
 //
 // http://en.wikipedia.org/wiki/.properties
diff --git a/example_test.go b/example_test.go
index 18081aa..14811d5 100644
--- a/example_test.go
+++ b/example_test.go
@@ -31,9 +31,9 @@
 	// UTF-8 value with unicode character ⌘ and umlaut ä
 }
 
-func Example_Properties_GetDefault() {
+func Example_Properties_GetString() {
 	p, _ := Load([]byte("key=value"), ISO_8859_1)
-	v := p.GetDefault("another key", "default value")
+	v := p.GetString("another key", "default value")
 	fmt.Println(v)
 	// Output:
 	// default value
@@ -57,7 +57,7 @@
 	}
 
 	// Get a key with a default value
-	v := p.GetDefault("does not exist", "some value")
+	v := p.GetString("does not exist", "some value")
 	fmt.Println(v)
 
 	// Dump the expanded key/value pairs of the Properties
diff --git a/lex.go b/lex.go
index 562b85b..b1e71bf 100644
--- a/lex.go
+++ b/lex.go
@@ -322,7 +322,7 @@
 		return l.scanUnicodeLiteral()
 
 	case isEOF(r):
-		return fmt.Errorf("premature EOF")
+		return fmt.Errorf("Premature EOF")
 
 	// silently drop the escape character and append the rune as is
 	default:
@@ -339,7 +339,7 @@
 	for i := 0; i < 4; i++ {
 		d[i] = l.next()
 		if d[i] == eof || !strings.ContainsRune("0123456789abcdefABCDEF", d[i]) {
-			return fmt.Errorf("invalid unicode literal")
+			return fmt.Errorf("Invalid unicode literal")
 		}
 	}
 
diff --git a/load.go b/load.go
index b77da41..a0fad7f 100644
--- a/load.go
+++ b/load.go
@@ -50,7 +50,12 @@
 )
 
 func loadBuf(buf []byte, enc Encoding) (*Properties, error) {
-	return parse(convert(buf, enc))
+	p, err := parse(convert(buf, enc))
+	if err != nil {
+		return nil, err
+	}
+	
+	return p, p.check()
 }
 
 func loadFiles(filenames []string, enc Encoding, ignoreMissing bool) (*Properties, error) {
@@ -96,6 +101,6 @@
 		}
 		return string(runes)
 	default:
-		panic(fmt.Sprintf("unsupported encoding %v", enc))
+		panic(fmt.Sprintf("Unsupported encoding %v", enc))
 	}
 }
diff --git a/properties.go b/properties.go
index 4b3b5d1..f87af49 100644
--- a/properties.go
+++ b/properties.go
@@ -7,7 +7,7 @@
 import (
 	"fmt"
 	"io"
-	"log"
+	"strconv"
 	"strings"
 	"unicode/utf8"
 )
@@ -39,22 +39,75 @@
 
 	expanded, err := p.expand(v)
 
-	// if there is an error then this is a format exception which we just log
-	// and return the input unchanged.
+	// we guarantee that the expanded value is free of
+	// circular references and malformed expressions
+	// so we panic if we still get an error here.
 	if err != nil {
-		log.Printf("%s in %q", err, key+" = "+v)
-		return v, true
+		panic(fmt.Errorf("%s in %q", err, key+" = "+v))
 	}
 
 	return expanded, true
 }
 
-// GetDefault returns the expanded value for the given key if exists or the default value otherwise.
-func (p *Properties) GetDefault(key, defaultValue string) (value string) {
+// GetBool checks if the expanded value is one of '1', 'yes',
+// 'true' or 'on' if the key exists. The comparison is case-insensitive.
+// If the key does not exist the default value is returned.
+func (p *Properties) GetBool(key string, def bool) bool {
+	if v, ok := p.Get(key); ok {
+		v = strings.ToLower(v)
+		return v == "1" || v == "true" || v == "yes" || v == "on"
+	}
+	return def
+}
+
+// GetFloat parses the expanded value as a float64 if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned.
+func (p *Properties) GetFloat(key string, def float64) float64 {
+	if v, ok := p.Get(key); ok {
+		n, err := strconv.ParseFloat(v, 64)
+		if err != nil {
+			return def
+		}
+		return n
+	}
+	return def
+}
+
+// GetInt parses the expanded value as an int if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned.
+func (p *Properties) GetInt(key string, def int64) int64 {
+	if v, ok := p.Get(key); ok {
+		n, err := strconv.ParseInt(v, 10, 64)
+		if err != nil {
+			return def
+		}
+		return n
+	}
+	return def
+}
+
+// GetUint parses the expanded value as an uint64 if the key exists.
+// If key does not exist or the value cannot be parsed the default
+// value is returned.
+func (p *Properties) GetUint(key string, def uint64) uint64 {
+	if v, ok := p.Get(key); ok {
+		n, err := strconv.ParseUint(v, 10, 64)
+		if err != nil {
+			return def
+		}
+		return n
+	}
+	return def
+}
+
+// GetString returns the expanded value for the given key if exists or the default value otherwise.
+func (p *Properties) GetString(key, def string) string {
 	if v, ok := p.Get(key); ok {
 		return v
 	}
-	return defaultValue
+	return def
 }
 
 // Len returns the number of keys.
@@ -62,6 +115,23 @@
 	return len(p.m)
 }
 
+// Set sets the property key to the corresponding value.
+// If a value for key existed before then ok is true and prev
+// contains the previous value. If the value contains a
+// circular reference or a malformed expression then
+// an error is returned.
+func (p *Properties) Set(key, value string) (prev string, ok bool, err error) {
+	// TODO(frank): Check for invalid unicode literals since this is currently done in the lexer.
+	_, err = p.expand(value)
+	if err != nil {
+		return "", false, err
+	}
+
+	v, ok := p.Get(key)
+	p.m[key] = value
+	return v, ok, nil
+}
+
 // Dump returns a string of all unexpanded 'key = value' pairs.
 func (p *Properties) Dump() string {
 	var s string
@@ -81,11 +151,11 @@
 	return s
 }
 
-// Write writes all unexpanded 'key = value' pairs as ISO-8859-1 to the given writer.
-func (p *Properties) Write(w io.Writer) (int, error) {
+// Write writes all unexpanded 'key = value' pairs to the given writer.
+func (p *Properties) Write(w io.Writer, enc Encoding) (int, error) {
 	total := 0
 	for key, value := range p.m {
-		s := fmt.Sprintf("%s = %s\n", encode(key, " :"), encode(value, ""))
+		s := fmt.Sprintf("%s = %s\n", encode(key, " :", enc), encode(value, "", enc))
 		n, err := w.Write([]byte(s))
 		if err != nil {
 			return total, err
@@ -95,6 +165,19 @@
 	return total, nil
 }
 
+// ----------------------------------------------------------------------------
+
+// check expands all values and returns an error if a circular reference or
+// a malformed expression was found.
+func (p *Properties) check() error {
+	for _, value := range p.m {
+		if _, err := p.expand(value); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // expand recursively expands expressions of '(prefix)key(postfix)' to their corresponding values.
 // The function keeps track of the keys that were already expanded and stops if it
 // detects a circular reference or a malformed expression.
@@ -136,7 +219,28 @@
 }
 
 // encode encodes a UTF-8 string to ISO-8859-1 and escapes some characters.
-func encode(s string, special string) string {
+func encode(s string, special string, enc Encoding) string {
+	switch enc {
+	case UTF8:
+		return encodeUtf8(s, special)
+	case ISO_8859_1:
+		return encodeIso(s, special)
+	default:
+		panic(fmt.Sprintf("Unsupported encoding %v", enc))
+	}
+}
+
+func encodeUtf8(s string, special string) string {
+	v := ""
+	for pos := 0; pos < len(s); {
+		r, w := utf8.DecodeRuneInString(s[pos:])
+		pos += w
+		v += escape(r, special)
+	}
+	return v
+}
+
+func encodeIso(s string, special string) string {
 	var r rune
 	var w int
 	var v string
diff --git a/properties_test.go b/properties_test.go
index 0522007..36950e3 100644
--- a/properties_test.go
+++ b/properties_test.go
@@ -83,33 +83,41 @@
 	// expansion tests
 	{"key=value\nkey2=${key}", "key", "value", "key2", "value"},
 	{"key=value\nkey2=${key}\nkey3=${key2}", "key", "value", "key2", "value", "key3", "value"},
-
-	// circular references
-	{"key=${key}", "key", "${key}"},
-	{"key1=${key2}\nkey2=${key1}", "key1", "${key2}", "key2", "${key1}"},
-
-	// malformed expressions
-	{"key=${ke", "key", "${ke"},
-	{"key=valu${ke", "key", "valu${ke"},
 }
 
 // define error test cases in the form of
 // {"input", "expected error message"}
 var errorTests = [][]string{
-	{"key\\u1 = value", "invalid unicode literal"},
-	{"key\\u12 = value", "invalid unicode literal"},
-	{"key\\u123 = value", "invalid unicode literal"},
-	{"key\\u123g = value", "invalid unicode literal"},
-	{"key\\u123", "invalid unicode literal"},
+	// unicode literals
+	{"key\\u1 = value", "Invalid unicode literal"},
+	{"key\\u12 = value", "Invalid unicode literal"},
+	{"key\\u123 = value", "Invalid unicode literal"},
+	{"key\\u123g = value", "Invalid unicode literal"},
+	{"key\\u123", "Invalid unicode literal"},
+
+	// circular references
+	{"key=${key}", "Circular reference"},
+	{"key1=${key2}\nkey2=${key1}", "Circular reference"},
+
+	// malformed expressions
+	{"key=${ke", "Malformed expression"},
+	{"key=valu${ke", "Malformed expression"},
 }
 
 // define write encoding test cases in the form of
-// {"input", "expected output after write"}
+// {"input", "expected output after write", ["UTF-8", "ISO-8859-1"]}
 var writeTests = [][]string{
-	{"key = value", "key = value\n"},
-	{"key = value \\\n   continued", "key = value continued\n"},
-	{"key⌘ = value", "key\\u2318 = value\n"},
-	{"ke\\ \\:y = value", "ke\\ \\:y = value\n"},
+	// ISO-8859-1 tests
+	{"key = value", "key = value\n", "ISO-8859-1"},
+	{"key = value \\\n   continued", "key = value continued\n", "ISO-8859-1"},
+	{"key⌘ = value", "key\\u2318 = value\n", "ISO-8859-1"},
+	{"ke\\ \\:y = value", "ke\\ \\:y = value\n", "ISO-8859-1"},
+
+	// UTF-8 tests
+	{"key = value", "key = value\n", "UTF-8"},
+	{"key = value \\\n   continued", "key = value continued\n", "UTF-8"},
+	{"key⌘ = value⌘", "key⌘ = value⌘\n", "UTF-8"},
+	{"ke\\ \\:y = value", "ke\\ \\:y = value\n", "UTF-8"},
 }
 
 // Benchmarks the decoder by creating a property file with 1000 key/value pairs.
@@ -149,11 +157,17 @@
 // Test write encoding.
 func (l *TestSuite) TestWrite(c *C) {
 	for _, test := range writeTests {
-		input, output := test[0], test[1]
+		input, output, enc := test[0], test[1], test[2]
 		p, err := parse(input)
 
 		buf := new(bytes.Buffer)
-		n, err := p.Write(buf)
+		var n int
+		switch enc {
+		case "UTF-8":
+			n, err = p.Write(buf, UTF8)
+		case "ISO-8859-1":
+			n, err = p.Write(buf, ISO_8859_1)
+		}
 		c.Assert(err, IsNil)
 		s := string(buf.Bytes())
 		c.Assert(n, Equals, len(output), Commentf("input=%q expected=%q obtained=%q", input, output, s))