[joda-convert] 01/05: Imported Upstream version 1.2

Tony Mancill tmancill at moszumanska.debian.org
Sat Sep 5 21:38:38 UTC 2015


This is an automated email from the git hooks/post-receive script.

tmancill pushed a commit to branch master
in repository joda-convert.

commit 12d0f776fbeb133eac4c69e9be675260951b4bde
Author: tony mancill <tmancill at debian.org>
Date:   Sat Sep 5 14:37:05 2015 -0700

    Imported Upstream version 1.2
---
 LICENSE.txt                                        | 202 +++++++++
 NOTICE.txt                                         |   5 +
 RELEASE-NOTES.txt                                  |  21 +
 checkstyle.xml                                     |  79 ++++
 pom.xml                                            | 322 +++++++++++++
 src/changes/changes.xml                            |  50 +++
 src/conf/MANIFEST.MF                               |  17 +
 src/main/assembly/dist.xml                         |  29 ++
 src/main/java/org/joda/convert/FromString.java     |  39 ++
 .../java/org/joda/convert/FromStringConverter.java |  36 ++
 .../java/org/joda/convert/JDKStringConverter.java  | 413 +++++++++++++++++
 .../convert/MethodConstructorStringConverter.java  |  80 ++++
 .../org/joda/convert/MethodsStringConverter.java   |  81 ++++
 .../joda/convert/ReflectionStringConverter.java    |  80 ++++
 src/main/java/org/joda/convert/StringConvert.java  | 488 ++++++++++++++++++++
 .../java/org/joda/convert/StringConverter.java     |  28 ++
 src/main/java/org/joda/convert/ToString.java       |  37 ++
 .../java/org/joda/convert/ToStringConverter.java   |  35 ++
 src/site/resources/css/site.css                    | 129 ++++++
 src/site/site.xml                                  |  55 +++
 src/site/xdoc/index.xml                            |  99 ++++
 src/site/xdoc/privacy.xml                          |  49 ++
 src/site/xdoc/userguide.xml                        | 108 +++++
 ...tanceFromStringConstructorInvalidParameter.java |  45 ++
 ...FromStringConstructorInvalidParameterCount.java |  45 ++
 .../joda/convert/DistanceFromStringException.java  |  47 ++
 .../DistanceFromStringInvalidParameter.java        |  45 ++
 .../DistanceFromStringInvalidParameterCount.java   |  45 ++
 .../DistanceFromStringInvalidReturnType.java       |  45 ++
 .../joda/convert/DistanceFromStringNoToString.java |  45 ++
 .../DistanceMethodAndConstructorAnnotations.java   |  52 +++
 .../joda/convert/DistanceMethodConstructor.java    |  46 ++
 .../DistanceMethodConstructorCharSequence.java     |  46 ++
 .../org/joda/convert/DistanceMethodMethod.java     |  46 ++
 .../convert/DistanceMethodMethodCharSequence.java  |  46 ++
 .../org/joda/convert/DistanceNoAnnotations.java    |  48 ++
 .../convert/DistanceNoAnnotationsCharSequence.java |  48 ++
 .../joda/convert/DistanceToStringException.java    |  48 ++
 .../convert/DistanceToStringInvalidParameters.java |  46 ++
 .../convert/DistanceToStringInvalidReturnType.java |  46 ++
 .../joda/convert/DistanceToStringNoFromString.java |  45 ++
 .../DistanceTwoFromStringMethodAnnotations.java    |  57 +++
 .../convert/DistanceTwoToStringAnnotations.java    |  47 ++
 .../joda/convert/MockDistanceStringConverter.java  |  45 ++
 .../joda/convert/MockIntegerStringConverter.java   |  45 ++
 .../org/joda/convert/SubMethodConstructor.java     |  28 ++
 .../java/org/joda/convert/SubMethodMethod.java     |  33 ++
 .../java/org/joda/convert/SubNoAnnotations.java    |  27 ++
 .../java/org/joda/convert/SuperFactorySub.java     |  27 ++
 .../java/org/joda/convert/SuperFactorySuper.java   |  47 ++
 .../org/joda/convert/TestJDKStringConverters.java  | 391 ++++++++++++++++
 .../java/org/joda/convert/TestStringConvert.java   | 497 +++++++++++++++++++++
 52 files changed, 4560 insertions(+)

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..75b5248
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   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.
diff --git a/NOTICE.txt b/NOTICE.txt
new file mode 100644
index 0000000..ac95834
--- /dev/null
+++ b/NOTICE.txt
@@ -0,0 +1,5 @@
+=============================================================================
+= NOTICE file corresponding to section 4d of the Apache License Version 2.0 =
+=============================================================================
+This product includes software developed by
+Joda.org (http://www.joda.org/).
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
new file mode 100644
index 0000000..0d18d7c
--- /dev/null
+++ b/RELEASE-NOTES.txt
@@ -0,0 +1,21 @@
+Joda-Convert version 1.1
+------------------------
+
+Joda-Convert is a small library providing conversion to and from String.
+
+This is the second release of Joda-Convert.
+
+JDK 1.5 or later is required.
+
+Joda-Convert is licensed under the business-friendly Apache License Version 2.
+This is the same license as all of Apache, plus other open source projects such as Spring.
+The intent is to make the code available to the Java community with the minimum
+of restrictions. If the license causes you problems please contact the mailing list.
+
+
+Feedback
+--------
+All feedback is welcomed via the joda-convert-interest mailing list.
+
+The Joda team
+http://joda-convert.sourceforge.net
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..999d5ae
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+
+<!DOCTYPE module PUBLIC
+    "-//Puppy Crawl//DTD Check Configuration 1.1//EN"
+    "http://www.puppycrawl.com/dtds/configuration_1_1.dtd">
+
+<!-- customization of default Checkstyle behavior -->
+<module name="Checker">
+  <!--property name="basedir" value="."/-->
+  <property name="localeLanguage" value="en"/>
+  <!--module name="PackageHtml"/-->
+  <module name="TreeWalker">
+    <module name="MemberName">
+      <property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
+    </module>
+    <module name="LocalVariableName">
+      <property name="format" value="^ex[0-9]*$"/>
+      <property name="tokens" value="PARAMETER_DEF"/>
+    </module>
+    <module name="AvoidStarImport"/>
+    <module name="RedundantImport"/>
+    <module name="UnusedImports"/>
+    
+    <module name="TabCharacter"/>
+    <module name="NeedBraces"/>
+    <!--module name="TypecastParenPad"/-->
+    <module name="WhitespaceAfter"/>
+    <module name="WhitespaceAround"/>
+    <module name="ModifierOrder"/>
+    <module name="RedundantModifier"/>
+    
+    <module name="EmptyBlock"/>
+    <module name="LeftCurly"/>
+    <module name="NeedBraces"/>
+    <module name="RightCurly"/>
+    <!--module name="AvoidNestedBlocks">
+      <property name="allowInSwitchCase" value="true"/>
+    </module-->
+    
+    <!--module name="ArrayTrailingComma"/-->
+    <!--module name="CovariantEquals"/-->
+    <module name="DoubleCheckedLocking"/>
+    <module name="EmptyStatement"/>
+    <module name="EqualsHashCode"/>
+    <!--module name="HiddenField">
+      <property name="ignoreConstructorParameter" value="true"/>
+      <property name="ignoreSetter" value="true"/>
+    </module-->
+    <module name="IllegalInstantiation">
+      <property name="classes" value="java.lang.Boolean"/>
+    </module>
+    <!--module name="SuperClone"/-->
+    <!--module name="ExplicitInitialization"/-->
+              
+    <module name="GenericIllegalRegexp">
+      <property name="format" value="System\.out\.println"/>
+    </module>
+    <module name="GenericIllegalRegexp">
+      <property name="format" value="System\.err\.println"/>
+    </module>
+    <module name="TodoComment"/>
+    <module name="UpperEll"/>
+    <module name="ArrayTypeStyle"/>
+    <module name="Indentation"/>
+          
+    <module name="RedundantThrows">
+      <property name="allowUnchecked" value="true"/>
+    </module>
+    <module name="LineLength">
+      <property name="max" value="120"/>
+    </module>
+    <module name="JavadocVariable"/>
+    <module name="JavadocMethod">
+      <property name="allowUndeclaredRTE" value="true"/>
+    </module>
+ </module>
+</module>
+                        
+
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..c068041
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,322 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project
+    xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.joda</groupId>
+  <artifactId>joda-convert</artifactId>
+  <packaging>jar</packaging>
+  <name>Joda convert</name>
+  <version>1.2</version>
+  <description>Library to convert Objects to and from String</description>
+  <url>http://joda-convert.sourceforge.net</url>
+  <issueManagement>
+  	<system>Sourceforge</system>
+    <url>https://sourceforge.net/tracker/?group_id=344317&atid=1440693</url>
+  </issueManagement>
+  <inceptionYear>2010</inceptionYear>
+  <mailingLists>
+    <mailingList>
+      <name>Joda Convert Interest list</name>
+      <subscribe>https://lists.sourceforge.net/lists/listinfo/joda-convert-interest</subscribe>
+      <unsubscribe>https://lists.sourceforge.net/lists/listinfo/joda-convert-interest</unsubscribe>
+      <archive>http://sourceforge.net/mailarchive/forum.php?forum_name=joda-convert-interest</archive>
+    </mailingList>
+  </mailingLists>
+  <developers>
+    <developer>
+      <id>scolebourne</id>
+      <name>Stephen Colebourne</name>
+      <email></email>
+      <roles>
+        <role>Project Lead</role>
+      </roles>
+      <timezone>0</timezone>
+    </developer>
+  </developers>
+  <licenses>
+    <license>
+      <name>Apache 2</name>
+      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
+      <distribution>repo</distribution>
+    </license>
+  </licenses>
+  <scm>
+    <connection>scm:git:git at github.com:JodaOrg/joda-convert.git</connection>
+    <developerConnection>scm:git:git at github.com:JodaOrg/joda-convert.git</developerConnection>
+    <url>https://github.com/JodaOrg/joda-convert</url>
+  </scm>
+  <organization>
+    <name>Joda.org</name>
+    <url>http://www.joda.org</url>
+  </organization>
+  <build>
+    <resources>
+      <resource>
+        <targetPath>META-INF</targetPath>
+        <directory>.</directory>
+        <includes>
+          <include>LICENSE.txt</include>
+          <include>NOTICE.txt</include>
+        </includes>
+      </resource>
+    </resources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-clean-plugin</artifactId>
+        <version>2.4.1</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.1</version>
+        <configuration>
+          <verbose>true</verbose>
+          <fork>true</fork>
+          <compilerVersion>1.5</compilerVersion>
+          <source>1.5</source>
+          <target>1.5</target>
+          <debug>true</debug>
+          <debuglevel>lines,source</debuglevel>
+          <optimize>true</optimize>
+          <showDeprecation>false</showDeprecation>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>2.5</version>
+        <configuration>
+          <includes>
+            <include>**/Test*.java</include>
+          </includes>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <version>2.3.1</version>
+        <configuration>
+          <archive>
+            <manifestFile>src/conf/MANIFEST.MF</manifestFile>
+          </archive>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.7</version>
+        <configuration>
+          <linksource>true</linksource>
+          <links>
+            <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+          </links>
+          <encoding>UTF-8</encoding>
+        </configuration>
+        <executions>
+          <execution>
+            <id>attach-javadocs</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <version>2.1.2</version>
+        <executions>
+          <execution>
+            <id>attach-sources</id>
+            <phase>package</phase>
+            <goals>
+              <goal>jar-no-fork</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>2.1.1</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-repository-plugin</artifactId>
+        <version>2.3.1</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-assembly-plugin</artifactId>
+        <configuration>
+          <descriptors>
+            <descriptor>src/main/assembly/dist.xml</descriptor>
+          </descriptors>
+          <tarLongFileMode>gnu</tarLongFileMode>
+        </configuration>
+        <executions>
+          <execution>
+            <id>make-assembly</id>
+            <phase>deploy</phase>
+            <goals>
+              <goal>single</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.5</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <reporting>
+  	<plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-project-info-reports-plugin</artifactId>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>index</report>
+              <report>dependencies</report>
+              <report>project-team</report>
+              <report>mailing-list</report>
+              <report>issue-tracking</report>
+              <report>license</report>
+              <report>scm</report>
+              <report>summary</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.3</version>
+        <configuration>
+          <configLocation>${basedir}/checkstyle.xml</configLocation>
+          <enableRulesSummary>false</enableRulesSummary>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <version>2.7</version>
+        <configuration>
+          <linksource>true</linksource>
+          <links>
+            <link>http://download.oracle.com/javase/1.5.0/docs/api/</link>
+          </links>
+          <encoding>UTF-8</encoding>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-report-plugin</artifactId>
+        <version>2.5</version>
+        <configuration>
+           <showSuccess>true</showSuccess>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jxr-plugin</artifactId>
+        <version>2.2</version>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-changes-plugin</artifactId>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>changes-report</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+  	</plugins>
+  </reporting>
+  <distributionManagement>
+    <repository>
+      <id>sonatype-joda-staging</id>
+      <name>Sonatype OSS staging repository</name>
+      <url>http://oss.sonatype.org/service/local/staging/deploy/maven2/</url>
+      <layout>default</layout>
+    </repository>
+    <snapshotRepository>
+      <uniqueVersion>false</uniqueVersion>
+      <id>sonatype-joda-snapshot</id>
+      <name>Sonatype OSS snapshot repository</name>
+      <url>http://oss.sonatype.org/content/repositories/joda-snapshots</url>
+      <layout>default</layout>
+    </snapshotRepository>
+    <site>
+      <id>sf-web-joda-convert</id>
+      <name>Sourceforge Site</name>
+      <url>scpexe://shell.sourceforge.net/home/project-web/joda-convert/htdocs</url>
+    </site>
+    <downloadUrl>http://oss.sonatype.org/content/repositories/joda-releases</downloadUrl>
+  </distributionManagement>
+  <profiles>
+    <profile>
+      <id>repo-sign-artifacts</id>
+      <activation>
+        <property>
+          <name>oss.repo</name>
+          <value>true</value>
+        </property>
+      </activation>
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-toolchains-plugin</artifactId>
+            <version>1.0</version>
+            <executions>
+              <execution>
+                <phase>validate</phase>
+                <goals>
+                  <goal>toolchain</goal>
+                </goals>
+              </execution>
+            </executions>
+            <configuration>
+              <toolchains>
+                <jdk>
+                  <version>1.5</version>
+                  <vendor>sun</vendor>
+                </jdk>
+              </toolchains>
+            </configuration>
+          </plugin>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-gpg-plugin</artifactId>
+            <version>1.1</version>
+            <executions>
+              <execution>
+                <id>sign-artifacts</id>
+                <phase>verify</phase>
+                <goals>
+                  <goal>sign</goal>
+                </goals>
+              </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+  <properties>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+  </properties>
+</project>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
new file mode 100644
index 0000000..0ddc2ac
--- /dev/null
+++ b/src/changes/changes.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<document>
+  <properties>
+    <title>Java convert - Changes</title>
+    <author>Stephen Colebourne</author>
+  </properties>
+
+  <body>
+    <!-- types are add, fix, remove, update -->
+    <release version="1.2" date="2011-10-27">
+      <action dev="scolebourne" type="add" >
+        Add support for CharSequence based fromString methods.
+      </action>
+      <action dev="scolebourne" type="add" >
+        Add support for JSR-310 by reflection, avoiding a dependency.
+      </action>
+      <action dev="scolebourne" type="add" >
+        Allow registration of conversions by method name.
+      </action>
+      <action dev="scolebourne" type="add" >
+        Allow toString conversion to specify the desired type.
+      </action>
+    </release>
+    <release version="1.1.1" date="2011-10-21">
+      <action dev="scolebourne" type="fix" >
+        Allow conversion of primitive types.
+      </action>
+    </release>
+    <release version="1.1.1" date="2011-10-24">
+      <action dev="scolebourne" type="fix" >
+        Allow conversion of primitive types
+      </action>
+    </release>
+    <release version="1.1" date="2010-12-04">
+      <action dev="scolebourne" type="fix" >
+        Enable superclass factories.
+      </action>
+    </release>
+    <release version="1.0" date="2010-09-05">
+      <action dev="scolebourne" type="fix" >
+        Lots of tests and fixes.
+      </action>
+    </release>
+    <release version="0.5" date="unreleased">
+      <action dev="scolebourne" type="fix" >
+        Initial checkin.
+      </action>
+    </release>
+  </body>
+</document>
diff --git a/src/conf/MANIFEST.MF b/src/conf/MANIFEST.MF
new file mode 100644
index 0000000..7da94d7
--- /dev/null
+++ b/src/conf/MANIFEST.MF
@@ -0,0 +1,17 @@
+Package: org.joda.convert
+Extension-Name: joda-convert
+Specification-Title: Joda-Convert
+Specification-Vendor: Joda.org
+Specification-Version: 1.2
+Implementation-Vendor: Joda.org
+Implementation-Title: org.joda.convert
+Implementation-Version: 1.2
+Implementation-Vendor-Id: org.joda
+Bundle-ManifestVersion: 2
+Bundle-Vendor: Joda.org
+Bundle-Name: Joda-Convert
+Bundle-SymbolicName: joda-convert
+Bundle-Version: 1.2
+Export-Package: org.joda.time.convert;version=1.2
+Bundle-License: Apache 2.0
+Bundle-DocURL: http://joda-convert.sourceforge.net/
diff --git a/src/main/assembly/dist.xml b/src/main/assembly/dist.xml
new file mode 100644
index 0000000..d1a282c
--- /dev/null
+++ b/src/main/assembly/dist.xml
@@ -0,0 +1,29 @@
+<assembly>
+    <id>dist</id>
+    <formats>
+        <format>tar.gz</format>
+        <format>zip</format>
+    </formats>
+    <baseDirectory>${artifactId}-${version}</baseDirectory>
+    <fileSets>
+        <fileSet>
+            <includes>
+                <include>checkstyle.xml</include>
+                <include>LICENSE.txt</include>
+                <include>NOTICE.txt</include>
+                <include>pom.xml</include>
+                <include>RELEASE-NOTES.txt</include>
+            </includes>
+        </fileSet>
+        <fileSet>
+            <directory>src</directory>
+        </fileSet>
+        <fileSet>
+            <directory>target</directory>
+            <outputDirectory></outputDirectory>
+            <includes>
+                <include>*.jar</include>
+            </includes>
+        </fileSet>
+    </fileSets>
+</assembly>
diff --git a/src/main/java/org/joda/convert/FromString.java b/src/main/java/org/joda/convert/FromString.java
new file mode 100644
index 0000000..4403300
--- /dev/null
+++ b/src/main/java/org/joda/convert/FromString.java
@@ -0,0 +1,39 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to mark a method or constructor as being suitable for converting
+ * an object from a {@code String}.
+ * <p>
+ * When applying to a method, this annotation should be applied once per class.
+ * The method must be static and have one {@code String} parameter with a
+ * return type of the type that the method is implemented on.
+ * For example, {@link Integer#parseInt(String)}.
+ * <p>
+ * When applying to a constructor, this annotation should be applied to the constructor
+ * that takes one {@code String} parameter.
+ */
+ at Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface FromString {
+
+}
diff --git a/src/main/java/org/joda/convert/FromStringConverter.java b/src/main/java/org/joda/convert/FromStringConverter.java
new file mode 100644
index 0000000..e5bace9
--- /dev/null
+++ b/src/main/java/org/joda/convert/FromStringConverter.java
@@ -0,0 +1,36 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Interface defining conversion from a {@code String}.
+ * <p>
+ * FromStringConverter is an interface and must be implemented with care.
+ * Implementations must be immutable and thread-safe.
+ * 
+ * @param <T>  the type of the converter
+ */
+public interface FromStringConverter<T> {
+
+    /**
+     * Converts the specified object from a {@code String}.
+     * @param cls  the class to convert to, not null
+     * @param str  the string to convert, not null
+     * @return the converted object, may be null but generally not
+     */
+    T convertFromString(Class<? extends T> cls, String str);
+
+}
diff --git a/src/main/java/org/joda/convert/JDKStringConverter.java b/src/main/java/org/joda/convert/JDKStringConverter.java
new file mode 100644
index 0000000..c90448d
--- /dev/null
+++ b/src/main/java/org/joda/convert/JDKStringConverter.java
@@ -0,0 +1,413 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Currency;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Conversion between JDK classes and a {@code String}.
+ */
+enum JDKStringConverter implements StringConverter<Object> {
+
+    /**
+     * String converter.
+     */
+    STRING(String.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return str;
+        }
+    },
+    /**
+     * CharSequence converter.
+     */
+    CHAR_SEQUENCE(CharSequence.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return str;
+        }
+    },
+    /**
+     * StringBuffer converter.
+     */
+    STRING_BUFFER(StringBuffer.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new StringBuffer(str);
+        }
+    },
+    /**
+     * StringBuilder converter.
+     */
+    STRING_BUILDER(StringBuilder.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new StringBuilder(str);
+        }
+    },
+    /**
+     * Long converter.
+     */
+    LONG(Long.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new Long(str);
+        }
+    },
+
+    /**
+     * Integer converter.
+     */
+    INTEGER(Integer.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new Integer(str);
+        }
+    },
+
+    /**
+     * Short converter.
+     */
+    SHORT (Short.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new Short(str);
+        }
+    },
+
+    /**
+     * Byte converter.
+     */
+    BYTE(Byte.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new Byte(str);
+        }
+    },
+    /**
+     * Character converter.
+     */
+    CHARACTER(Character.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            if (str.length() != 1) {
+                throw new IllegalArgumentException("Character value must be a string length 1");
+            }
+            return new Character(str.charAt(0));
+        }
+    },
+    /**
+     * Boolean converter.
+     */
+    BOOLEAN(Boolean.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            if ("true".equalsIgnoreCase(str)) {
+                return Boolean.TRUE;
+            }
+            if ("false".equalsIgnoreCase(str)) {
+                return Boolean.FALSE;
+            }
+            throw new IllegalArgumentException("Boolean value must be 'true' or 'false', case insensitive");
+        }
+    },
+    /**
+     * Double converter.
+     */
+    DOUBLE(Double.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new Double(str);
+        }
+    },
+    /**
+     * Float converter.
+     */
+    FLOAT(Float.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new Float(str);
+        }
+    },
+    /**
+     * BigInteger converter.
+     */
+    BIG_INTEGER(BigInteger.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new BigInteger(str);
+        }
+    },
+    /**
+     * BigDecimal converter.
+     */
+    BIG_DECIMAL(BigDecimal.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new BigDecimal(str);
+        }
+    },
+    /**
+     * AtomicLong converter.
+     */
+    ATOMIC_LONG(AtomicLong.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            long val = Long.parseLong(str);
+            return new AtomicLong(val);
+        }
+    },
+    /**
+     * AtomicLong converter.
+     */
+    ATOMIC_INTEGER(AtomicInteger.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            int val = Integer.parseInt(str);
+            return new AtomicInteger(val);
+        }
+    },
+    /**
+     * AtomicBoolean converter.
+     */
+    ATOMIC_BOOLEAN(AtomicBoolean.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            if ("true".equalsIgnoreCase(str)) {
+                return new AtomicBoolean(true);
+            }
+            if ("false".equalsIgnoreCase(str)) {
+                return new AtomicBoolean(false);
+            }
+            throw new IllegalArgumentException("Boolean value must be 'true' or 'false', case insensitive");
+        }
+    },
+    /**
+     * Locale converter.
+     */
+    LOCALE(Locale.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            String[] split = str.split("_", 3);
+            switch (split.length) {
+                case 1:
+                    return new Locale(split[0]);
+                case 2:
+                    return new Locale(split[0], split[1]);
+                case 3:
+                    return new Locale(split[0], split[1], split[2]);
+            }
+            throw new IllegalArgumentException("Unable to parse Locale: " + str);
+        }
+    },
+    /**
+     * Class converter.
+     */
+    CLASS(Class.class) {
+        @Override
+        public String convertToString(Object object) {
+            return ((Class<?>) object).getName();
+        }
+        public Object convertFromString(Class<?> cls, String str) {
+            try {
+                return getClass().getClassLoader().loadClass(str);
+            } catch (ClassNotFoundException ex) {
+                throw new RuntimeException("Unable to create class: " + str, ex);
+            }
+        }
+    },
+    /**
+     * Package converter.
+     */
+    PACKAGE(Package.class) {
+        @Override
+        public String convertToString(Object object) {
+            return ((Package) object).getName();
+        }
+        public Object convertFromString(Class<?> cls, String str) {
+            return Package.getPackage(str);
+        }
+    },
+    /**
+     * Currency converter.
+     */
+    CURRENCY(Currency.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return Currency.getInstance(str);
+        }
+    },
+    /**
+     * TimeZone converter.
+     */
+    TIME_ZONE(TimeZone.class) {
+        @Override
+        public String convertToString(Object object) {
+            return ((TimeZone) object).getID();
+        }
+        public Object convertFromString(Class<?> cls, String str) {
+            return TimeZone.getTimeZone(str);
+        }
+    },
+    /**
+     * UUID converter.
+     */
+    UUID(UUID.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return java.util.UUID.fromString(str);
+        }
+    },
+    /**
+     * URL converter.
+     */
+    URL(URL.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            try {
+                return new URL(str);
+            } catch (MalformedURLException ex) {
+                throw new RuntimeException(ex.getMessage(), ex);
+            }
+        }
+    },
+    /**
+     * URI converter.
+     */
+    URI(URI.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return java.net.URI.create(str);
+        }
+    },
+    /**
+     * InetAddress converter.
+     */
+    INET_ADDRESS(InetAddress.class) {
+        @Override
+        public String convertToString(Object object) {
+            return ((InetAddress) object).getHostAddress();
+        }
+        public Object convertFromString(Class<?> cls, String str) {
+            try {
+                return InetAddress.getByName(str);
+            } catch (UnknownHostException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    },
+    /**
+     * File converter.
+     */
+    FILE(File.class) {
+        public Object convertFromString(Class<?> cls, String str) {
+            return new File(str);
+        }
+    },
+    /**
+     * Date converter.
+     */
+    DATE(Date.class) {
+        @Override
+        public String convertToString(Object object) {
+            SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+            String str = f.format(object);
+            return str.substring(0, 26) + ":" + str.substring(26);
+        }
+        public Object convertFromString(Class<?> cls, String str) {
+            if (str.length() != 29) {
+                throw new IllegalArgumentException("Unable to parse date: " + str);
+            }
+            str = str.substring(0, 26) + str.substring(27);
+            SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+            try {
+                return f.parseObject(str);
+            } catch (ParseException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    },
+    /**
+     * Calendar converter.
+     */
+    CALENDAR(Calendar.class) {
+        @Override
+        public String convertToString(Object object) {
+            if (object instanceof GregorianCalendar == false) {
+                throw new RuntimeException("Unable to convert calendar as it is not a GregorianCalendar");
+            }
+            GregorianCalendar cal = (GregorianCalendar) object;
+            SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+            f.setCalendar(cal);
+            String str = f.format(cal.getTime());
+            return str.substring(0, 26) + ":" + str.substring(26) + "[" + cal.getTimeZone().getID() + "]";
+        }
+        public Object convertFromString(Class<?> cls, String str) {
+            if (str.length() < 31 || str.charAt(26) != ':'
+                    || str.charAt(29) != '[' || str.charAt(str.length() - 1) != ']') {
+                throw new IllegalArgumentException("Unable to parse date: " + str);
+            }
+            TimeZone zone = TimeZone.getTimeZone(str.substring(30, str.length() - 1));
+            str = str.substring(0, 26) + str.substring(27, 29);
+            SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+            GregorianCalendar cal = new GregorianCalendar(zone);
+            cal.setTimeInMillis(0);
+            f.setCalendar(cal);
+            try {
+                f.parseObject(str);
+                return f.getCalendar();
+            } catch (ParseException ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    },
+    /**
+     * Enum converter.
+     */
+    ENUM(Enum.class) {
+        @SuppressWarnings("rawtypes")
+        public String convertToString(Object object) {
+            return ((Enum) object).name();  // avoid toString() as that can be overridden
+        }
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        public Object convertFromString(Class cls, String str) {
+            return Enum.valueOf(cls, str);
+        }
+    },
+    ;
+
+    /** The type. */
+    private Class<?> type;
+
+    /**
+     * Creates an enum.
+     * @param type  the type, not null
+     */
+    private JDKStringConverter(Class<?> type) {
+        this.type = type;
+    }
+
+    /**
+     * Gets the type of the converter.
+     * @return the type, not null
+     */
+    Class<?> getType() {
+        return type;
+    }
+
+    //-----------------------------------------------------------------------
+    public String convertToString(Object object) {
+        return object.toString();
+    }
+
+}
diff --git a/src/main/java/org/joda/convert/MethodConstructorStringConverter.java b/src/main/java/org/joda/convert/MethodConstructorStringConverter.java
new file mode 100644
index 0000000..3b31467
--- /dev/null
+++ b/src/main/java/org/joda/convert/MethodConstructorStringConverter.java
@@ -0,0 +1,80 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+/**
+ * Conversion to and from a string using a toString method and a fromString constructor.
+ * <p>
+ * The toString method must meet the following signature:<br />
+ * {@code String anyName()} on Class T.
+ * <p>
+ * The fromString constructor must take a single {@code String} parameter.
+ * <p>
+ * MethodConstructorStringConverter is thread-safe and immutable.
+ * 
+ * @param <T>  the type of the converter
+ */
+final class MethodConstructorStringConverter<T> extends ReflectionStringConverter<T> {
+
+    /** Conversion from a string. */
+    private final Constructor<T> fromString;
+
+    /**
+     * Creates an instance using a method and a constructor.
+     * @param cls  the class this converts for, not null
+     * @param toString  the toString method, not null
+     * @param fromString  the fromString method, not null
+     * @throws RuntimeException (or subclass) if the method signatures are invalid
+     */
+    MethodConstructorStringConverter(Class<T> cls, Method toString, Constructor<T> fromString) {
+        super(cls, toString);
+        if (cls.isInterface() || Modifier.isAbstract(cls.getModifiers()) || cls.isLocalClass() || cls.isMemberClass()) {
+            throw new IllegalArgumentException("FromString constructor must be on an instantiable class");
+        }
+        if (fromString.getDeclaringClass() != cls) {
+            throw new IllegalStateException("FromString constructor must be defined on specified class");
+        }
+        this.fromString = fromString;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Converts the {@code String} to an object.
+     * @param cls  the class to convert to, not null
+     * @param str  the string to convert, not null
+     * @return the converted object, may be null but generally not
+     */
+    public T convertFromString(Class<? extends T> cls, String str) {
+        try {
+            return fromString.newInstance(str);
+        } catch (IllegalAccessException ex) {
+            throw new IllegalStateException("Constructor is not accessible");
+        } catch (InstantiationException ex) {
+            throw new IllegalStateException("Constructor is not valid");
+        } catch (InvocationTargetException ex) {
+            if (ex.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) ex.getCause();
+            }
+            throw new RuntimeException(ex.getMessage(), ex.getCause());
+        }
+    }
+
+}
diff --git a/src/main/java/org/joda/convert/MethodsStringConverter.java b/src/main/java/org/joda/convert/MethodsStringConverter.java
new file mode 100644
index 0000000..330b5d9
--- /dev/null
+++ b/src/main/java/org/joda/convert/MethodsStringConverter.java
@@ -0,0 +1,81 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Conversion to and from a string using two methods.
+ * <p>
+ * The toString method must meet the following signature:<br />
+ * {@code String anyName()} on Class T.
+ * <p>
+ * The fromString method must meet the following signature:<br />
+ * {@code static T anyName(String)} on any class.
+ * <p>
+ * MethodsStringConverter is thread-safe and immutable.
+ * 
+ * @param <T>  the type of the converter
+ */
+final class MethodsStringConverter<T> extends ReflectionStringConverter<T> {
+
+    /** Conversion from a string. */
+    private final Method fromString;
+
+    /**
+     * Creates an instance using two methods.
+     * @param cls  the class this converts for, not null
+     * @param toString  the toString method, not null
+     * @param fromString  the fromString method, not null
+     * @throws RuntimeException (or subclass) if the method signatures are invalid
+     */
+    MethodsStringConverter(Class<T> cls, Method toString, Method fromString) {
+        super(cls, toString);
+        if (fromString.getParameterTypes().length != 1) {
+            throw new IllegalStateException("FromString method must have one parameter");
+        }
+        Class<?> param = fromString.getParameterTypes()[0];
+        if (param != String.class && param != CharSequence.class) {
+            throw new IllegalStateException("FromString method must take a String or CharSequence");
+        }
+        if (fromString.getReturnType().isAssignableFrom(cls) == false) {
+            throw new IllegalStateException("FromString method must return specified class or a superclass");
+        }
+        this.fromString = fromString;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Converts the {@code String} to an object.
+     * @param cls  the class to convert to, not null
+     * @param str  the string to convert, not null
+     * @return the converted object, may be null but generally not
+     */
+    public T convertFromString(Class<? extends T> cls, String str) {
+        try {
+            return cls.cast(fromString.invoke(null, str));
+        } catch (IllegalAccessException ex) {
+            throw new IllegalStateException("Method is not accessible");
+        } catch (InvocationTargetException ex) {
+            if (ex.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) ex.getCause();
+            }
+            throw new RuntimeException(ex.getMessage(), ex.getCause());
+        }
+    }
+
+}
diff --git a/src/main/java/org/joda/convert/ReflectionStringConverter.java b/src/main/java/org/joda/convert/ReflectionStringConverter.java
new file mode 100644
index 0000000..8c6b8cd
--- /dev/null
+++ b/src/main/java/org/joda/convert/ReflectionStringConverter.java
@@ -0,0 +1,80 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Conversion to and from a string using reflection.
+ * <p>
+ * The toString method must meet the following signature:<br />
+ * {@code String anyName()} on Class T.
+ * <p>
+ * ReflectionStringConverter is abstract, but all known implementations are thread-safe and immutable.
+ * 
+ * @param <T>  the type of the converter
+ */
+abstract class ReflectionStringConverter<T> implements StringConverter<T> {
+
+    /** The converted class. */
+    final Class<T> cls;
+    /** Conversion to a string. */
+    final Method toString;
+
+    /**
+     * Creates an instance using two methods.
+     * @param cls  the class this converts for, not null
+     * @param toString  the toString method, not null
+     * @throws RuntimeException (or subclass) if the method signatures are invalid
+     */
+    ReflectionStringConverter(Class<T> cls, Method toString) {
+        if (toString.getParameterTypes().length != 0) {
+            throw new IllegalStateException("ToString method must have no parameters");
+        }
+        if (toString.getReturnType() != String.class) {
+            throw new IllegalStateException("ToString method must return a String");
+        }
+        this.cls = cls;
+        this.toString = toString;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Converts the object to a {@code String}.
+     * @param object  the object to convert, not null
+     * @return the converted string, may be null but generally not
+     */
+    public String convertToString(T object) {
+        try {
+            return (String) toString.invoke(object);
+        } catch (IllegalAccessException ex) {
+            throw new IllegalStateException("Method is not accessible");
+        } catch (InvocationTargetException ex) {
+            if (ex.getCause() instanceof RuntimeException) {
+                throw (RuntimeException) ex.getCause();
+            }
+            throw new RuntimeException(ex.getMessage(), ex.getCause());
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    @Override
+    public String toString() {
+        return "RefectionStringConverter[" + cls.getSimpleName() + "]";
+    }
+
+}
diff --git a/src/main/java/org/joda/convert/StringConvert.java b/src/main/java/org/joda/convert/StringConvert.java
new file mode 100644
index 0000000..50197e8
--- /dev/null
+++ b/src/main/java/org/joda/convert/StringConvert.java
@@ -0,0 +1,488 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Manager for conversion to and from a {@code String}, acting as the main client interface.
+ * <p>
+ * Support is provided for conversions based on the {@link StringConverter} interface
+ * or the {@link ToString} and {@link FromString} annotations.
+ * <p>
+ * StringConvert is thread-safe with concurrent caches.
+ */
+public final class StringConvert {
+
+    /**
+     * An immutable global instance.
+     * <p>
+     * This instance cannot be added to using {@link #register}, however annotated classes
+     * are picked up. To register your own converters, simply create an instance of this class.
+     */
+    public static final StringConvert INSTANCE = new StringConvert();
+
+    /**
+     * The cache of converters.
+     */
+    private final ConcurrentMap<Class<?>, StringConverter<?>> registered = new ConcurrentHashMap<Class<?>, StringConverter<?>>();
+
+    /**
+     * Creates a new conversion manager including the JDK converters.
+     */
+    public StringConvert() {
+        this(true);
+    }
+
+    /**
+     * Creates a new conversion manager.
+     * 
+     * @param includeJdkConverters  true to include the JDK converters
+     */
+    public StringConvert(boolean includeJdkConverters) {
+        if (includeJdkConverters) {
+            for (JDKStringConverter conv : JDKStringConverter.values()) {
+                registered.put(conv.getType(), conv);
+            }
+            registered.put(Boolean.TYPE, JDKStringConverter.BOOLEAN);
+            registered.put(Byte.TYPE, JDKStringConverter.BYTE);
+            registered.put(Short.TYPE, JDKStringConverter.SHORT);
+            registered.put(Integer.TYPE, JDKStringConverter.INTEGER);
+            registered.put(Long.TYPE, JDKStringConverter.LONG);
+            registered.put(Float.TYPE, JDKStringConverter.FLOAT);
+            registered.put(Double.TYPE, JDKStringConverter.DOUBLE);
+            registered.put(Character.TYPE, JDKStringConverter.CHARACTER);
+            // JSR-310 classes
+            tryRegister("javax.time.Instant", "parse");
+            tryRegister("javax.time.Duration", "parse");
+            tryRegister("javax.time.calendar.LocalDate", "parse");
+            tryRegister("javax.time.calendar.LocalTime", "parse");
+            tryRegister("javax.time.calendar.LocalDateTime", "parse");
+            tryRegister("javax.time.calendar.OffsetDate", "parse");
+            tryRegister("javax.time.calendar.OffsetTime", "parse");
+            tryRegister("javax.time.calendar.OffsetDateTime", "parse");
+            tryRegister("javax.time.calendar.ZonedDateTime", "parse");
+            tryRegister("javax.time.calendar.Year", "parse");
+            tryRegister("javax.time.calendar.YearMonth", "parse");
+            tryRegister("javax.time.calendar.MonthDay", "parse");
+            tryRegister("javax.time.calendar.Period", "parse");
+            tryRegister("javax.time.calendar.ZoneOffset", "of");
+            tryRegister("javax.time.calendar.ZoneId", "of");
+            tryRegister("javax.time.calendar.TimeZone", "of");
+        }
+    }
+
+    /**
+     * Tries to register a class using the standard toString/parse pattern.
+     * 
+     * @param className  the class name, not null
+     */
+    private void tryRegister(String className, String fromStringMethodName) {
+        try {
+            Class<?> cls = getClass().getClassLoader().loadClass(className);
+            registerMethods(cls, "toString", fromStringMethodName);
+        } catch (Exception ex) {
+            // ignore
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Converts the specified object to a {@code String}.
+     * <p>
+     * This uses {@link #findConverter} to provide the converter.
+     * 
+     * @param <T>  the type to convert from
+     * @param object  the object to convert, null returns null
+     * @return the converted string, may be null
+     * @throws RuntimeException (or subclass) if unable to convert
+     */
+    @SuppressWarnings("unchecked")
+    public <T> String convertToString(T object) {
+        if (object == null) {
+            return null;
+        }
+        Class<T> cls = (Class<T>) object.getClass();
+        StringConverter<T> conv = findConverter(cls);
+        return conv.convertToString(object);
+    }
+
+    /**
+     * Converts the specified object to a {@code String}.
+     * <p>
+     * This uses {@link #findConverter} to provide the converter.
+     * The class can be provided to select a more specific converter.
+     * 
+     * @param <T>  the type to convert from
+     * @param cls  the class to convert from, not null
+     * @param object  the object to convert, null returns null
+     * @return the converted string, may be null
+     * @throws RuntimeException (or subclass) if unable to convert
+     */
+    public <T> String convertToString(Class<T> cls, T object) {
+        if (object == null) {
+            return null;
+        }
+        StringConverter<T> conv = findConverter(cls);
+        return conv.convertToString(object);
+    }
+
+    /**
+     * Converts the specified object from a {@code String}.
+     * <p>
+     * This uses {@link #findConverter} to provide the converter.
+     * 
+     * @param <T>  the type to convert to
+     * @param cls  the class to convert to, not null
+     * @param str  the string to convert, null returns null
+     * @return the converted object, may be null
+     * @throws RuntimeException (or subclass) if unable to convert
+     */
+    public <T> T convertFromString(Class<T> cls, String str) {
+        if (str == null) {
+            return null;
+        }
+        StringConverter<T> conv = findConverter(cls);
+        return conv.convertFromString(cls, str);
+    }
+
+    /**
+     * Finds a suitable converter for the type.
+     * <p>
+     * This returns an instance of {@code StringConverter} for the specified class.
+     * This could be useful in other frameworks.
+     * <p>
+     * The search algorithm first searches the registered converters.
+     * It then searches for {@code ToString} and {@code FromString} annotations on the specified class.
+     * Both searches consider superclasses, but not interfaces.
+     * 
+     * @param <T>  the type of the converter
+     * @param cls  the class to find a converter for, not null
+     * @return the converter, not null
+     * @throws RuntimeException (or subclass) if no converter found
+     */
+    @SuppressWarnings("unchecked")
+    public <T> StringConverter<T> findConverter(final Class<T> cls) {
+        if (cls == null) {
+            throw new IllegalArgumentException("Class must not be null");
+        }
+        StringConverter<T> conv = (StringConverter<T>) registered.get(cls);
+        if (conv == null) {
+            if (cls == Object.class) {
+                throw new IllegalStateException("No registered converter found: " + cls);
+            }
+            Class<?> loopCls = cls.getSuperclass();
+            while (loopCls != null && conv == null) {
+                conv = (StringConverter<T>) registered.get(loopCls);
+                loopCls = loopCls.getSuperclass();
+            }
+            if (conv == null) {
+                conv = findAnnotationConverter(cls);
+                if (conv == null) {
+                    throw new IllegalStateException("No registered converter found: " + cls);
+                }
+            }
+            registered.putIfAbsent(cls, conv);
+        }
+        return conv;
+    }
+
+    /**
+     * Finds the conversion method.
+     * 
+     * @param <T>  the type of the converter
+     * @param cls  the class to find a method for, not null
+     * @return the method to call, null means use {@code toString}
+     */
+    private <T> StringConverter<T> findAnnotationConverter(final Class<T> cls) {
+        Method toString = findToStringMethod(cls);
+        if (toString == null) {
+            return null;
+        }
+        Constructor<T> con = findFromStringConstructor(cls);
+        Method fromString = findFromStringMethod(cls, con == null);
+        if (con == null && fromString == null) {
+            throw new IllegalStateException("Class annotated with @ToString but not with @FromString");
+        }
+        if (con != null && fromString != null) {
+            throw new IllegalStateException("Both method and constructor are annotated with @FromString");
+        }
+        if (con != null) {
+            return new MethodConstructorStringConverter<T>(cls, toString, con);
+        } else {
+            return new MethodsStringConverter<T>(cls, toString, fromString);
+        }
+    }
+
+    /**
+     * Finds the conversion method.
+     * 
+     * @param cls  the class to find a method for, not null
+     * @return the method to call, null means use {@code toString}
+     */
+    private Method findToStringMethod(Class<?> cls) {
+        Method matched = null;
+        Class<?> loopCls = cls;
+        while (loopCls != null && matched == null) {
+            Method[] methods = loopCls.getDeclaredMethods();
+            for (Method method : methods) {
+                ToString toString = method.getAnnotation(ToString.class);
+                if (toString != null) {
+                    if (matched != null) {
+                        throw new IllegalStateException("Two methods are annotated with @ToString");
+                    }
+                    matched = method;
+                }
+            }
+            loopCls = loopCls.getSuperclass();
+        }
+        return matched;
+    }
+
+    /**
+     * Finds the conversion method.
+     * 
+     * @param <T>  the type of the converter
+     * @param cls  the class to find a method for, not null
+     * @return the method to call, null means use {@code toString}
+     */
+    private <T> Constructor<T> findFromStringConstructor(Class<T> cls) {
+        Constructor<T> con;
+        try {
+            con = cls.getDeclaredConstructor(String.class);
+        } catch (NoSuchMethodException ex) {
+            try {
+                con = cls.getDeclaredConstructor(CharSequence.class);
+            } catch (NoSuchMethodException ex2) {
+                return null;
+            }
+        }
+        FromString fromString = con.getAnnotation(FromString.class);
+        return fromString != null ? con : null;
+    }
+
+    /**
+     * Finds the conversion method.
+     * 
+     * @param cls  the class to find a method for, not null
+     * @return the method to call, null means use {@code toString}
+     */
+    private Method findFromStringMethod(Class<?> cls, boolean searchSuperclasses) {
+        Method matched = null;
+        Class<?> loopCls = cls;
+        while (loopCls != null && matched == null) {
+            Method[] methods = loopCls.getDeclaredMethods();
+            for (Method method : methods) {
+                FromString fromString = method.getAnnotation(FromString.class);
+                if (fromString != null) {
+                    if (matched != null) {
+                        throw new IllegalStateException("Two methods are annotated with @ToString");
+                    }
+                    matched = method;
+                }
+            }
+            if (searchSuperclasses == false) {
+                break;
+            }
+            loopCls = loopCls.getSuperclass();
+        }
+        return matched;
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Registers a converter for a specific type.
+     * <p>
+     * The converter will be used for subclasses unless overidden.
+     * <p>
+     * No new converters may be registered for the global singleton.
+     * 
+     * @param <T>  the type of the converter
+     * @param cls  the class to register a converter for, not null
+     * @param converter  the String converter, not null
+     * @throws IllegalArgumentException if unable to register
+     * @throws IllegalStateException if class already registered
+     */
+    public <T> void register(final Class<T> cls, StringConverter<T> converter) {
+        if (cls == null ) {
+            throw new IllegalArgumentException("Class must not be null");
+        }
+        if (converter == null) {
+            throw new IllegalArgumentException("StringConverter must not be null");
+        }
+        if (this == INSTANCE) {
+            throw new IllegalStateException("Global singleton cannot be extended");
+        }
+        StringConverter<?> old = registered.putIfAbsent(cls, converter);
+        if (old != null) {
+            throw new IllegalStateException("Converter already registered for class: " + cls);
+        }
+    }
+
+    /**
+     * Registers a converter for a specific type by method names.
+     * <p>
+     * This method allows the converter to be used when the target class cannot have annotations added.
+     * The two method names must obey the same rules as defined by the annotations
+     * {@link ToString} and {@link FromString}.
+     * The converter will be used for subclasses unless overidden.
+     * <p>
+     * No new converters may be registered for the global singleton.
+     * <p>
+     * For example, {@code convert.registerMethods(Distance.class, "toString", "parse");}
+     * 
+     * @param <T>  the type of the converter
+     * @param cls  the class to register a converter for, not null
+     * @param toStringMethodName  the name of the method converting to a string, not null
+     * @param fromStringMethodName  the name of the method converting from a string, not null
+     * @throws IllegalArgumentException if unable to register
+     * @throws IllegalStateException if class already registered
+     */
+    public <T> void registerMethods(final Class<T> cls, String toStringMethodName, String fromStringMethodName) {
+        if (cls == null ) {
+            throw new IllegalArgumentException("Class must not be null");
+        }
+        if (toStringMethodName == null || fromStringMethodName == null) {
+            throw new IllegalArgumentException("Method names must not be null");
+        }
+        if (this == INSTANCE) {
+            throw new IllegalStateException("Global singleton cannot be extended");
+        }
+        Method toString = findToStringMethod(cls, toStringMethodName);
+        Method fromString = findFromStringMethod(cls, fromStringMethodName);
+        MethodsStringConverter<T> converter = new MethodsStringConverter<T>(cls, toString, fromString);
+        StringConverter<?> old = registered.putIfAbsent(cls, converter);
+        if (old != null) {
+            throw new IllegalStateException("Converter already registered for class: " + cls);
+        }
+    }
+
+    /**
+     * Registers a converter for a specific type by method and constructor.
+     * <p>
+     * This method allows the converter to be used when the target class cannot have annotations added.
+     * The two method name and constructor must obey the same rules as defined by the annotations
+     * {@link ToString} and {@link FromString}.
+     * The converter will be used for subclasses unless overidden.
+     * <p>
+     * No new converters may be registered for the global singleton.
+     * <p>
+     * For example, {@code convert.registerMethodConstructor(Distance.class, "toString");}
+     * 
+     * @param <T>  the type of the converter
+     * @param cls  the class to register a converter for, not null
+     * @param toStringMethodName  the name of the method converting to a string, not null
+     * @throws IllegalArgumentException if unable to register
+     * @throws IllegalStateException if class already registered
+     */
+    public <T> void registerMethodConstructor(final Class<T> cls, String toStringMethodName) {
+        if (cls == null ) {
+            throw new IllegalArgumentException("Class must not be null");
+        }
+        if (toStringMethodName == null) {
+            throw new IllegalArgumentException("Method name must not be null");
+        }
+        if (this == INSTANCE) {
+            throw new IllegalStateException("Global singleton cannot be extended");
+        }
+        Method toString = findToStringMethod(cls, toStringMethodName);
+        Constructor<T> fromString = findFromStringConstructorByType(cls);
+        MethodConstructorStringConverter<T> converter = new MethodConstructorStringConverter<T>(cls, toString, fromString);
+        StringConverter<?> old = registered.putIfAbsent(cls, converter);
+        if (old != null) {
+            throw new IllegalStateException("Converter already registered for class: " + cls);
+        }
+    }
+
+    /**
+     * Finds the conversion method.
+     * 
+     * @param cls  the class to find a method for, not null
+     * @param methodName  the name of the method to find, not null
+     * @return the method to call, null means use {@code toString}
+     */
+    private Method findToStringMethod(Class<?> cls, String methodName) {
+        Method m;
+        try {
+            m = cls.getMethod(methodName);
+        } catch (NoSuchMethodException ex) {
+          throw new IllegalArgumentException(ex);
+        }
+        if (Modifier.isStatic(m.getModifiers())) {
+          throw new IllegalArgumentException("Method must not be static: " + methodName);
+        }
+        return m;
+    }
+
+    /**
+     * Finds the conversion method.
+     * 
+     * @param cls  the class to find a method for, not null
+     * @param methodName  the name of the method to find, not null
+     * @return the method to call, null means use {@code toString}
+     */
+    private Method findFromStringMethod(Class<?> cls, String methodName) {
+        Method m;
+        try {
+            m = cls.getMethod(methodName, String.class);
+        } catch (NoSuchMethodException ex) {
+            try {
+                m = cls.getMethod(methodName, CharSequence.class);
+            } catch (NoSuchMethodException ex2) {
+                throw new IllegalArgumentException("Method not found", ex2);
+            }
+        }
+        if (Modifier.isStatic(m.getModifiers()) == false) {
+          throw new IllegalArgumentException("Method must be static: " + methodName);
+        }
+        return m;
+    }
+
+    /**
+     * Finds the conversion method.
+     * 
+     * @param <T>  the type of the converter
+     * @param cls  the class to find a method for, not null
+     * @return the method to call, null means use {@code toString}
+     */
+    private <T> Constructor<T> findFromStringConstructorByType(Class<T> cls) {
+        try {
+            return cls.getDeclaredConstructor(String.class);
+        } catch (NoSuchMethodException ex) {
+            try {
+                return cls.getDeclaredConstructor(CharSequence.class);
+            } catch (NoSuchMethodException ex2) {
+              throw new IllegalArgumentException("Constructor not found", ex2);
+            }
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    /**
+     * Returns a simple string representation of the object.
+     * 
+     * @return the string representation, never null
+     */
+    @Override
+    public String toString() {
+        return getClass().getSimpleName();
+    }
+
+}
diff --git a/src/main/java/org/joda/convert/StringConverter.java b/src/main/java/org/joda/convert/StringConverter.java
new file mode 100644
index 0000000..e90f51b
--- /dev/null
+++ b/src/main/java/org/joda/convert/StringConverter.java
@@ -0,0 +1,28 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Interface defining conversion to and from a {@code String}.
+ * <p>
+ * StringConverter is an interface and must be implemented with care.
+ * Implementations must be immutable and thread-safe.
+ * 
+ * @param <T>  the type of the converter
+ */
+public interface StringConverter<T> extends ToStringConverter<T>, FromStringConverter<T> {
+
+}
diff --git a/src/main/java/org/joda/convert/ToString.java b/src/main/java/org/joda/convert/ToString.java
new file mode 100644
index 0000000..da248e1
--- /dev/null
+++ b/src/main/java/org/joda/convert/ToString.java
@@ -0,0 +1,37 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to mark a method as being suitable for converting an
+ * object to a standard format {@code String}.
+ * <p>
+ * This annotation should be applied to one method on a class.
+ * The method must not be static. It must take no parameters and return a {@code String}.
+ * The string format must be able to be parsed by the matching @FromString on
+ * the same class. The format should be human readable and an industry standard
+ * where possible, for example ISO-8601 for dates and times.
+ */
+ at Target(ElementType.METHOD)
+ at Retention(RetentionPolicy.RUNTIME)
+public @interface ToString {
+
+}
diff --git a/src/main/java/org/joda/convert/ToStringConverter.java b/src/main/java/org/joda/convert/ToStringConverter.java
new file mode 100644
index 0000000..39b32af
--- /dev/null
+++ b/src/main/java/org/joda/convert/ToStringConverter.java
@@ -0,0 +1,35 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Interface defining conversion to a {@code String}.
+ * <p>
+ * ToStringConverter is an interface and must be implemented with care.
+ * Implementations must be immutable and thread-safe.
+ * 
+ * @param <T>  the type of the converter
+ */
+public interface ToStringConverter<T> {
+
+    /**
+     * Converts the specified object to a {@code String}.
+     * @param object  the object to convert, not null
+     * @return the converted string, may be null but generally not
+     */
+    String convertToString(T object);
+
+}
diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css
new file mode 100644
index 0000000..2ed8bb6
--- /dev/null
+++ b/src/site/resources/css/site.css
@@ -0,0 +1,129 @@
+body, td, select, input, li{
+  font-family: Helvetica, Arial, sans-serif;
+  font-size: 13px;
+  background-color: #fff;
+}
+a {
+  text-decoration: none;
+}
+a:link {
+  color:#009;
+}
+a:visited  {
+  color:#009;
+}
+a:active, a:hover {
+  text-decoration: underline;
+}
+a.externalLink, a.externalLink:link, a.externalLink:visited, a.externalLink:active, a.externalLink:hover {
+  background: url(../images/external.png) right center no-repeat;
+  padding-right: 15px;
+}
+a.newWindow, a.newWindow:link, a.newWindow:visited, a.newWindow:active, a.newWindow:hover {
+  background: url(../images/newwindow.png) right center no-repeat;
+  padding-right: 18px;
+}
+h2 {
+  font-family: Verdana, Helvetica, Arial, sans-serif;
+  padding: 4px 4px 4px 6px;
+  border: 1px solid #999;
+  color: #006;
+  background-color: #eef;
+  font-weight:bold;
+  font-size: 16px;
+  margin-top: 4px;
+  margin-bottom: 6px;
+}
+h3 {
+  padding: 4px 4px 4px 6px;
+  border: 1px solid #aaa;
+  color: #006;
+  background-color: #eee;
+  font-weight: normal;
+  font-size: 14px;
+  margin-top: 4px;
+  margin-bottom: 6px;
+}
+p, ul {
+  font-size: 13px;
+  margin-top: 4px;
+  margin-bottom: 6px;
+}
+#banner {
+  background-color: #eef;
+  border-bottom: 1px solid #aaa;
+  padding: 8px;
+}
+#bannerLeft, #bannerRight {
+  font-size: 30px;
+  color:black;
+  background-color:white;
+  border: 1px solid #999;
+  padding: 0px 5px;
+}
+#banner a:hover {
+  text-decoration:none;
+}
+#breadcrumbs {
+  padding-top: 1px;
+  padding-bottom: 2px;
+  border-bottom: 1px solid #aaa;
+  background-color: #ddf;
+}
+#leftColumn {
+  margin: 8px 0 8px 4px;
+  border: 1px solid #999;
+  background-color: #eef;
+}
+#navcolumn {
+  padding: 6px 4px 0 6px;
+}
+#navcolumn h5 {
+  font-size: 12px;
+  border-bottom: 1px solid #aaaaaa;
+  padding-top: 2px;
+  font-weight: normal;
+}
+#navcolumn li {
+  font-size: 12px;
+  padding-left: 12px;
+  background-color: #eef;
+}
+#navcolumn a:active, #navcolumn a:hover {
+  text-decoration: none;
+}
+#lastPublished {
+  font-size: 10px;
+}
+table.bodyTable th {
+  color: white;
+  background-color: #bbb;
+  text-align: left;
+  font-weight: bold;
+  font-size: 13px;
+}
+
+table.bodyTable th, table.bodyTable td {
+  font-size: 13px;
+}
+
+table.bodyTable tr.a {
+  background-color: #ddd;
+}
+
+table.bodyTable tr.b {
+  background-color: #eee;
+}
+
+.source {
+  border: 1px solid #999;
+  padding: 8px;
+  margin: 6px;
+}
+#footer {
+  background-color: #eef;
+  border-top: 1px solid #999;
+}
+body {
+  padding-bottom: 0px;
+}
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..921885f
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<project name="Joda-Convert">
+  <publishDate position="navigation-bottom" format="yyyy-MM-dd"/>
+  <bannerLeft>
+    <name>Joda.org</name>
+    <!--src>http://joda-convert.sourceforge.net/images/joda.png</src-->
+    <href>http://joda.sourceforge.net/</href>
+  </bannerLeft>
+  <bannerRight>
+    <name>Joda-Convert</name>
+    <!--src>http://joda-convert.sourceforge.net/images/jodaconvert.png</src-->
+    <href>http://joda-convert.sourceforge.net/</href>
+  </bannerRight>
+
+  <body>
+    <menu name="Joda Convert">
+      <item name="Overview" href="index.html"/>
+      <item name="User guide" href="userguide.html"/>
+      <!--item name="FAQ" href="faq.html"/-->
+      <item name="Javadoc (Release)" href="api-release/index.html"/>
+      <item name="License" href="license.html"/>
+      <item name="Download" href="http://sourceforge.net/projects/joda-convert/files/joda-convert/"/>
+    </menu>
+
+    <menu name="Development">
+      <item name="GitHub (Source code)" href="https://github.com/JodaOrg/joda-convert"/>
+      <item name="Sourceforge" href="http://sourceforge.net/projects/joda-convert/"/>
+      <!--item name="Test results" href="/junit-report.html"/-->
+      <!--item name="Test coverage" href="/cobertura/index.html"/-->
+      <item name="Mailing lists" href="mail-lists.html"/>
+      <!--item name="Tasks" href="tasks.html"/-->
+      <!--item name="Subversion" href="http://joda-convert.svn.sourceforge.net/viewvc/joda-convert/trunk/"/-->
+      <item name="Javadoc (Development)" href="apidocs/index.html"/>
+    </menu>
+
+    <menu name="Joda">
+      <item name="Joda home" href="http://joda.sourceforge.net"/>
+      <item name="Beans" href="http://joda-beans.sourceforge.net/index.html"/>
+      <item name="Money" href="http://joda-money.sourceforge.net/index.html"/>
+      <item name="Primitives" href="http://joda-primitives.sourceforge.net/index.html"/>
+      <item name="Time" href="http://joda-time.sourceforge.net/index.html"/>
+    </menu>
+
+    <menu ref="reports"/>
+
+    <menu name="Website">
+      <item name="Privacy" href="privacy.html"/>
+    </menu>
+  </body>
+
+  <poweredBy>
+    <logo name="Sourceforge" href="http://sourceforge.net/projects/joda-convert/"
+        img="http://sflogo.sourceforge.net/sflogo.php?group_id=344317&type=13" width="120" height="30" />
+  </poweredBy>
+</project>
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
new file mode 100644
index 0000000..db94440
--- /dev/null
+++ b/src/site/xdoc/index.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0"?>
+
+<document>
+
+ <properties>
+  <title>Joda Convert</title>
+  <author>Stephen Colebourne</author>
+ </properties>
+
+ <body>
+
+<section name="Joda Convert">
+
+<p>
+Joda-Convert provides a small set of classes to aid conversion between Objects and Strings.
+It is not intended to tackle the wider problem of Object to Object transformation.
+</p>
+<div style="border:1px solid black; padding: 0px 6px; margin: 4px;"><pre>
+// conversion to String
+String str = StringConvert.INSTANCE.convertToString(foo);
+
+// conversion from String
+Foo bar = StringConvert.INSTANCE.convertFromString(Foo.class, str);
+</pre></div>
+<p>
+Joda-Convert supports two mechanisms of extending the list of supported conversions.
+The first is to write your own converter implementing an interface.
+The second is to use annotations.
+</p>
+<p>
+The ability of Joda-Convert to use annotations to define the conversion methods is a key difference from other projects.
+For example, most value classes, like <code>Currency</code> or <code>TimeZone</code>, already have methods
+to convert to and from a standard format String.
+Consider a <code>Distance</code> class:
+</p>
+<div style="border:1px solid black; padding: 0px 6px; margin: 4px;"><pre>
+public class Distance {
+
+  @FromString
+  public static Distance parse(String str) { ... }
+
+  @ToString
+  public String getStandardOutput() { ... }
+
+}
+</pre></div>
+<p>
+As shown, the two methods may have any name. They must simply fulfil the required method signatures for conversion.
+The <code>FromString</code> annotation may also be applied to a constructor.
+</p>
+<p>
+When Joda-Convert is asked to convert between an object and a String, if there is no registered converter
+then the annotations are checked. If they are found, then the methods are called by reflection.
+</p>
+<p>
+Joda-Convert is licensed under the business-friendly <a href="license.html">Apache 2.0 licence</a>.
+</p>
+
+</section>
+
+<section name="Documentation">
+<p>
+Various documentation is available:
+<ul>
+<li>The helpful <a href="userguide.html">user guide</a></li>
+<li>The javadoc for the <a href="api-release/index.html">current release</a></li>
+<li>The javadoc for the <a href="apidocs/index.html">latest source code</a></li>
+<li>The change notes <a href="changes-report.html">for the releases</a></li>
+<!--li>A <a href="faq.html">FAQ</a> list</li-->
+<!--li>Information on <a href="installation.html">downloading and installing</a> Joda-Convert</li-->
+<li>The <a href="https://github.com/JodaOrg/joda-convert">GitHub</a> source repository</li>
+</ul>
+</p>
+</section>
+
+<section name="Releases">
+<p>
+<a href="http://sourceforge.net/projects/joda-convert/files/joda-convert/1.2/">Release 1.2</a>
+is the current latest release.
+This release is considered stable and worthy of the 1.x tag.
+It depends on JDK 1.5 or later.
+</p>
+</section>
+
+<section name="Contact">
+<p>
+If you have any questions, or want to volunteer to help, just email Stephen Colebourne
+via scolebourne.at.users.sourceforge.net.
+</p>
+<p>
+<br />
+<br />
+<br />
+<br />
+<br />
+</p>
+</section>
+</body>
+</document>
diff --git a/src/site/xdoc/privacy.xml b/src/site/xdoc/privacy.xml
new file mode 100644
index 0000000..4cb646f
--- /dev/null
+++ b/src/site/xdoc/privacy.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+
+<document>
+ <properties>
+  <title>Privacy</title>
+ </properties>
+<body>
+
+<!-- ========================================================================= -->
+
+<section name="Privacy">
+<p>
+Information about your use of this website is collected using cookies.
+The collected information consists of the following:
+<br />
+   1. The IP address from which you access the website;<br />
+   2. The type of browser and operating system you use to access our site;<br />
+   3. The date and time you access our site;<br />
+   4. The pages you visit; and<br />
+   5. The addresses of pages from where you followed a link to our site.<br />
+</p>
+<p>
+Part of this information is gathered using a tracking cookie set by the 
+<a href="http://www.google.com/analytics/">Google Analytics</a> service
+and handled by Google as described in their 
+<a href="http://www.google.com/privacy.html">privacy policy</a>. See your browser documentation for
+instructions on how to disable the cookie if you prefer not to share this data with Google.
+</p>
+<p>
+We use the gathered information to help us make our site more useful to visitors and to better
+understand how and when our site is used. We do not track or collect personally identifiable information
+or associate gathered data with any personally identifying information from other sources.
+</p>
+<p>
+By using this website, you consent to the collection of this data in the manner and for the purpose described above.
+<br />
+</p>
+<!-- <p>
+This website also makes use of targetted adverts supplied by
+<a href="https://www.google.com/adsense">Google Adsense</a>.
+To achieve this targetting, Google utilises cookies which collect the same information described above.
+The cookies identfy your usage of many different internet websites, not just this one, and may be used
+in conjunction with other advertising networks.
+For more details on the Adsense program, and to find out your options to control how the data
+is used please <a href="http://www.google.com/privacy_ads.html">click here</a>.
+</p>-->
+</section>
+</body>
+</document>
diff --git a/src/site/xdoc/userguide.xml b/src/site/xdoc/userguide.xml
new file mode 100644
index 0000000..1f19d57
--- /dev/null
+++ b/src/site/xdoc/userguide.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0"?>
+
+<document>
+
+ <properties>
+  <title>Joda Convert</title>
+  <author>Stephen Colebourne</author>
+ </properties>
+
+ <body>
+
+<section name="User guide">
+
+<p>
+Joda-Convert is intended for one simple task -
+Converting objects to and from strings.
+This is a common problem, particularly when communicating over textual protocols like XML or JSON.
+</p>
+
+<subsection name="Basic usage">
+<p>
+Using Joda-Convert is easy at the simplest level.
+The main access is via the class <code>StringConvert</code>.
+</p>
+<p>
+The easiest way to use the conversion is via the global constant:
+</p>
+<div style="border:1px solid black; padding: 0px 6px; margin: 4px;"><pre>
+// conversion to a String
+TimeZone zone = ...
+String str = StringConvert.INSTANCE.convertToString(zone);
+
+// conversion from a String
+TimeZone zone = StringConvert.INSTANCE.convertFromString(TimeZone.class, str);
+</pre></div>
+<p>
+In both cases, if the input is <code>null</code> then the output will also be <code>null</code>.
+</p>
+<p>
+The global constant is quick and easy to use, but is shared between all users in the <code>ClassLoader</code>.
+It also cannot be extended.
+</p>
+<p>
+The alternative approach is to instantiate your own instance of <code>StringConvert</code>.
+This would normally be stored in your own static variable, or made available as needed by dependency injection.
+This may be updated by registering your own converters.
+</p>
+</subsection>
+
+<subsection name="Converters">
+<p>
+Each instance of <code>StringConvert</code>, including the global singleton, includes a standard set of JDK-based converters.
+These cover all the standard JDK types for which conversion to and from a string is sensible.
+The set also includes JSR-310 types, but these are optional and loaded by reflection. The system will run without any dependency.
+</p>
+<p>
+Each <code>StringConvert</code> instance, other than the global singleton, may have additional converters registered manually.
+Each converter implements the <code>StringConverter</code> interface, which is self explanatory.
+</p>
+<p>
+Converters may also be manually added by method name.
+This is equivalent to using annotations, but suitable when you don't own the code to add them.
+See <code>StringConvert.registerMethods</code> and <code>StringConvert.registerMethodConstructor</code>.
+</p>
+<p>
+A converter can only be registered if one is not already registered for that type.
+</p>
+</subsection>
+
+<subsection name="Annotation based conversion">
+<p>
+If there is no registered converter for a type, then a search by annotation is performed.
+This will search for the <code>ToString</code> and <code>FromString</code> annotation on the type.
+These annotations will indicate which method should be called to perform the conversion.
+</p>
+<div style="border:1px solid black; padding: 0px 6px; margin: 4px;"><pre>
+public class Distance {
+
+  @FromString
+  public static Distance parse(String str) { ... }
+
+  @ToString
+  public String getStandardOutput() { ... }
+
+}
+</pre></div>
+<p>
+To be valid, the class must contain one <code>ToString</code> annotation and one <code>FromString</code> annotation.
+The <code>ToString</code> annotation must be an instance method taking no parameters and returning a String.
+The <code>FromString</code> annotation must be either a static method or a constructor taking a String parameter and
+returning the correct type.
+If the annotations are not found on the target class, then superclasses are searched.
+</p>
+<p>
+The concept is that other open source libraries, as well as your application code, will implement these two annotations.
+For open source projects, a key point is that adding the annotations is a compile-time only event.
+The Joda-Convert jar file is not needed by your users unless they want to.
+If they don't want to use Joda-Convert then the annotations are effectively ignored.
+</p>
+<p>
+Joda-Time v2.0 and Joda-Money will both contain the annotations.
+However, in both cases, the dependency is compile-time only, and not at runtime.
+</p>
+</subsection>
+
+</section>
+</body>
+</document>
diff --git a/src/test/java/org/joda/convert/DistanceFromStringConstructorInvalidParameter.java b/src/test/java/org/joda/convert/DistanceFromStringConstructorInvalidParameter.java
new file mode 100644
index 0000000..c250fbc
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceFromStringConstructorInvalidParameter.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceFromStringConstructorInvalidParameter {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public DistanceFromStringConstructorInvalidParameter(Object amount) {
+        this.amount = 0;
+    }
+
+    public DistanceFromStringConstructorInvalidParameter(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceFromStringConstructorInvalidParameterCount.java b/src/test/java/org/joda/convert/DistanceFromStringConstructorInvalidParameterCount.java
new file mode 100644
index 0000000..91a8730
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceFromStringConstructorInvalidParameterCount.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceFromStringConstructorInvalidParameterCount {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public DistanceFromStringConstructorInvalidParameterCount(String amount, int value) {
+        this.amount = 0;
+    }
+
+    public DistanceFromStringConstructorInvalidParameterCount(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceFromStringException.java b/src/test/java/org/joda/convert/DistanceFromStringException.java
new file mode 100644
index 0000000..b8dd645
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceFromStringException.java
@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.text.ParseException;
+
+/**
+ * Example class with annotated methods.
+ */
+public class DistanceFromStringException {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static DistanceFromStringException parse(String amount) throws ParseException {
+        throw new ParseException("Test", 2);
+    }
+
+    public DistanceFromStringException(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceFromStringInvalidParameter.java b/src/test/java/org/joda/convert/DistanceFromStringInvalidParameter.java
new file mode 100644
index 0000000..c1dee91
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceFromStringInvalidParameter.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceFromStringInvalidParameter {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static DistanceFromStringInvalidParameter parse(Object amount) {
+        return null;
+    }
+
+    public DistanceFromStringInvalidParameter(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceFromStringInvalidParameterCount.java b/src/test/java/org/joda/convert/DistanceFromStringInvalidParameterCount.java
new file mode 100644
index 0000000..352cac0
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceFromStringInvalidParameterCount.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceFromStringInvalidParameterCount {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static DistanceFromStringInvalidParameterCount parse(String amount, int value) {
+        return null;
+    }
+
+    public DistanceFromStringInvalidParameterCount(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceFromStringInvalidReturnType.java b/src/test/java/org/joda/convert/DistanceFromStringInvalidReturnType.java
new file mode 100644
index 0000000..d0a5a55
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceFromStringInvalidReturnType.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceFromStringInvalidReturnType {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static Integer parse(String amount) {
+        return null;
+    }
+
+    public DistanceFromStringInvalidReturnType(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceFromStringNoToString.java b/src/test/java/org/joda/convert/DistanceFromStringNoToString.java
new file mode 100644
index 0000000..1749fcf
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceFromStringNoToString.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceFromStringNoToString {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceFromStringNoToString(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public DistanceFromStringNoToString(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceMethodAndConstructorAnnotations.java b/src/test/java/org/joda/convert/DistanceMethodAndConstructorAnnotations.java
new file mode 100644
index 0000000..d1529f0
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceMethodAndConstructorAnnotations.java
@@ -0,0 +1,52 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceMethodAndConstructorAnnotations {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static DistanceMethodAndConstructorAnnotations parse(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        return new DistanceMethodAndConstructorAnnotations(Integer.parseInt(amount));
+    }
+
+    public DistanceMethodAndConstructorAnnotations(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public DistanceMethodAndConstructorAnnotations(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceMethodConstructor.java b/src/test/java/org/joda/convert/DistanceMethodConstructor.java
new file mode 100644
index 0000000..d559dab
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceMethodConstructor.java
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with annotated constructor and method.
+ */
+public class DistanceMethodConstructor {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceMethodConstructor(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public DistanceMethodConstructor(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceMethodConstructorCharSequence.java b/src/test/java/org/joda/convert/DistanceMethodConstructorCharSequence.java
new file mode 100644
index 0000000..a7ad0ad
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceMethodConstructorCharSequence.java
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with annotated constructor and method.
+ */
+public class DistanceMethodConstructorCharSequence {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceMethodConstructorCharSequence(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public DistanceMethodConstructorCharSequence(CharSequence amount) {
+        String amt = amount.toString().substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amt);
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceMethodMethod.java b/src/test/java/org/joda/convert/DistanceMethodMethod.java
new file mode 100644
index 0000000..e8ba0fc
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceMethodMethod.java
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with annotated methods.
+ */
+public class DistanceMethodMethod {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static DistanceMethodMethod parse(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        return new DistanceMethodMethod(Integer.parseInt(amount));
+    }
+
+    public DistanceMethodMethod(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceMethodMethodCharSequence.java b/src/test/java/org/joda/convert/DistanceMethodMethodCharSequence.java
new file mode 100644
index 0000000..b0b2e65
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceMethodMethodCharSequence.java
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with annotated methods.
+ */
+public class DistanceMethodMethodCharSequence {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static DistanceMethodMethodCharSequence parse(CharSequence amount) {
+        String amt = amount.toString().substring(0, amount.length() - 1);
+        return new DistanceMethodMethodCharSequence(Integer.parseInt(amt));
+    }
+
+    public DistanceMethodMethodCharSequence(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceNoAnnotations.java b/src/test/java/org/joda/convert/DistanceNoAnnotations.java
new file mode 100644
index 0000000..58b3b59
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceNoAnnotations.java
@@ -0,0 +1,48 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceNoAnnotations {
+
+    /** Amount. */
+    final int amount;
+
+    public static DistanceNoAnnotations parse(String amount) {
+        return new DistanceNoAnnotations(amount);
+    }
+
+    public DistanceNoAnnotations(int amount) {
+        this.amount = amount;
+    }
+
+    public DistanceNoAnnotations(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceNoAnnotationsCharSequence.java b/src/test/java/org/joda/convert/DistanceNoAnnotationsCharSequence.java
new file mode 100644
index 0000000..a9413bc
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceNoAnnotationsCharSequence.java
@@ -0,0 +1,48 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceNoAnnotationsCharSequence {
+
+    /** Amount. */
+    final int amount;
+
+    public static DistanceNoAnnotationsCharSequence parse(CharSequence amount) {
+        return new DistanceNoAnnotationsCharSequence(amount);
+    }
+
+    public DistanceNoAnnotationsCharSequence(int amount) {
+        this.amount = amount;
+    }
+
+    public DistanceNoAnnotationsCharSequence(CharSequence amount) {
+        String amt = amount.toString().substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amt);
+    }
+
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceToStringException.java b/src/test/java/org/joda/convert/DistanceToStringException.java
new file mode 100644
index 0000000..e29c7ab
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceToStringException.java
@@ -0,0 +1,48 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import java.text.ParseException;
+
+/**
+ * Example class with annotated methods.
+ */
+public class DistanceToStringException {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static DistanceToStringException parse(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        return new DistanceToStringException(Integer.parseInt(amount));
+    }
+
+    public DistanceToStringException(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() throws ParseException {
+        throw new ParseException("Test", 2);
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceToStringInvalidParameters.java b/src/test/java/org/joda/convert/DistanceToStringInvalidParameters.java
new file mode 100644
index 0000000..91d36ee
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceToStringInvalidParameters.java
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceToStringInvalidParameters {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceToStringInvalidParameters(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public DistanceToStringInvalidParameters(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    @ToString
+    public Object print(int num) {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceToStringInvalidReturnType.java b/src/test/java/org/joda/convert/DistanceToStringInvalidReturnType.java
new file mode 100644
index 0000000..7cabbab
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceToStringInvalidReturnType.java
@@ -0,0 +1,46 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceToStringInvalidReturnType {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceToStringInvalidReturnType(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public DistanceToStringInvalidReturnType(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    @ToString
+    public Object print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceToStringNoFromString.java b/src/test/java/org/joda/convert/DistanceToStringNoFromString.java
new file mode 100644
index 0000000..f87b7d0
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceToStringNoFromString.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceToStringNoFromString {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceToStringNoFromString(int amount) {
+        this.amount = amount;
+    }
+
+    public DistanceToStringNoFromString(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceTwoFromStringMethodAnnotations.java b/src/test/java/org/joda/convert/DistanceTwoFromStringMethodAnnotations.java
new file mode 100644
index 0000000..4a11f27
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceTwoFromStringMethodAnnotations.java
@@ -0,0 +1,57 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceTwoFromStringMethodAnnotations {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceTwoFromStringMethodAnnotations(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public static DistanceTwoFromStringMethodAnnotations parse(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        return new DistanceTwoFromStringMethodAnnotations(Integer.parseInt(amount));
+    }
+
+    @FromString
+    public static DistanceTwoFromStringMethodAnnotations parse2(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        return new DistanceTwoFromStringMethodAnnotations(Integer.parseInt(amount));
+    }
+
+    public DistanceTwoFromStringMethodAnnotations(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/DistanceTwoToStringAnnotations.java b/src/test/java/org/joda/convert/DistanceTwoToStringAnnotations.java
new file mode 100644
index 0000000..1f3a465
--- /dev/null
+++ b/src/test/java/org/joda/convert/DistanceTwoToStringAnnotations.java
@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with no annotations.
+ */
+public class DistanceTwoToStringAnnotations {
+
+    /** Amount. */
+    final int amount;
+
+    public DistanceTwoToStringAnnotations(int amount) {
+        this.amount = amount;
+    }
+
+    @FromString
+    public DistanceTwoToStringAnnotations(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        this.amount = Integer.parseInt(amount);
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @ToString
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/MockDistanceStringConverter.java b/src/test/java/org/joda/convert/MockDistanceStringConverter.java
new file mode 100644
index 0000000..1e75ea3
--- /dev/null
+++ b/src/test/java/org/joda/convert/MockDistanceStringConverter.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Conversion between an {@code DistanceMethodMethod} and a {@code String}.
+ */
+public enum MockDistanceStringConverter implements StringConverter<DistanceMethodMethod> {
+
+    /** Singleton instance. */
+    INSTANCE;
+
+    /**
+     * Converts the {@code DistanceMethodMethod} to a {@code String}.
+     * @param object  the object to convert, not null
+     * @return the converted string, may be null but generally not
+     */
+    public String convertToString(DistanceMethodMethod object) {
+        return object.print();
+    }
+
+    /**
+     * Converts the {@code String} to an {@code DistanceMethodMethod}.
+     * @param cls  the class to convert to, not null
+     * @param str  the string to convert, not null
+     * @return the converted integer, may be null but generally not
+     */
+    public DistanceMethodMethod convertFromString(Class<? extends DistanceMethodMethod> cls, String str) {
+        return DistanceMethodMethod.parse(str);
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/MockIntegerStringConverter.java b/src/test/java/org/joda/convert/MockIntegerStringConverter.java
new file mode 100644
index 0000000..79de32f
--- /dev/null
+++ b/src/test/java/org/joda/convert/MockIntegerStringConverter.java
@@ -0,0 +1,45 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Conversion between an {@code Integer} and a {@code String}.
+ */
+public enum MockIntegerStringConverter implements StringConverter<Integer> {
+
+    /** Singleton instance. */
+    INSTANCE;
+
+    /**
+     * Converts the {@code Integer} to a {@code String}.
+     * @param object  the object to convert, not null
+     * @return the converted string, may be null but generally not
+     */
+    public String convertToString(Integer object) {
+        return object.toString();
+    }
+
+    /**
+     * Converts the {@code String} to an {@code Integer}.
+     * @param cls  the class to convert to, not null
+     * @param str  the string to convert, not null
+     * @return the converted integer, may be null but generally not
+     */
+    public Integer convertFromString(Class<? extends Integer> cls, String str) {
+        return new Integer(str);
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/SubMethodConstructor.java b/src/test/java/org/joda/convert/SubMethodConstructor.java
new file mode 100644
index 0000000..ce6df54
--- /dev/null
+++ b/src/test/java/org/joda/convert/SubMethodConstructor.java
@@ -0,0 +1,28 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with annotated methods.
+ */
+public class SubMethodConstructor extends DistanceMethodConstructor {
+
+    @FromString
+    public SubMethodConstructor(String amount) {
+        super(amount);
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/SubMethodMethod.java b/src/test/java/org/joda/convert/SubMethodMethod.java
new file mode 100644
index 0000000..d3bae48
--- /dev/null
+++ b/src/test/java/org/joda/convert/SubMethodMethod.java
@@ -0,0 +1,33 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with annotated methods.
+ */
+public class SubMethodMethod extends DistanceMethodMethod {
+
+    @FromString
+    public static SubMethodMethod parse(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        return new SubMethodMethod(Integer.parseInt(amount));
+    }
+
+    public SubMethodMethod(int amount) {
+        super(amount);
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/SubNoAnnotations.java b/src/test/java/org/joda/convert/SubNoAnnotations.java
new file mode 100644
index 0000000..af58042
--- /dev/null
+++ b/src/test/java/org/joda/convert/SubNoAnnotations.java
@@ -0,0 +1,27 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class with annotated methods.
+ */
+public class SubNoAnnotations extends DistanceMethodMethod {
+
+    public SubNoAnnotations(int amount) {
+        super(amount);
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/SuperFactorySub.java b/src/test/java/org/joda/convert/SuperFactorySub.java
new file mode 100644
index 0000000..6525382
--- /dev/null
+++ b/src/test/java/org/joda/convert/SuperFactorySub.java
@@ -0,0 +1,27 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class which matches the case where a superclass acts as the sole factory.
+ */
+public class SuperFactorySub extends SuperFactorySuper {
+
+    public SuperFactorySub(int amount) {
+        super(amount);
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/SuperFactorySuper.java b/src/test/java/org/joda/convert/SuperFactorySuper.java
new file mode 100644
index 0000000..3e9be17
--- /dev/null
+++ b/src/test/java/org/joda/convert/SuperFactorySuper.java
@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+/**
+ * Example class which matches the case where a superclass acts as the sole factory.
+ */
+public class SuperFactorySuper {
+
+    /** Amount. */
+    final int amount;
+
+    @FromString
+    public static SuperFactorySuper parse(String amount) {
+        amount = amount.substring(0, amount.length() - 1);
+        int i = Integer.parseInt(amount);
+        return i > 10 ? new SuperFactorySuper(i) : new SuperFactorySub(i);
+    }
+
+    public SuperFactorySuper(int amount) {
+        this.amount = amount;
+    }
+
+    @ToString
+    public String print() {
+        return amount + "m";
+    }
+
+    @Override
+    public String toString() {
+        return "Distance[" + amount + "m]";
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/TestJDKStringConverters.java b/src/test/java/org/joda/convert/TestJDKStringConverters.java
new file mode 100644
index 0000000..540bd0d
--- /dev/null
+++ b/src/test/java/org/joda/convert/TestJDKStringConverters.java
@@ -0,0 +1,391 @@
+/*
+ *  Copyright 2010 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.net.InetAddress;
+import java.net.URI;
+import java.net.URL;
+import java.util.Calendar;
+import java.util.Currency;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.junit.Test;
+
+/**
+ * Test JDKStringConverters.
+ */
+public class TestJDKStringConverters {
+
+    @Test
+    public void test_String() {
+        JDKStringConverter test = JDKStringConverter.STRING;
+        doTest(test, String.class, "Hello", "Hello");
+    }
+
+    @Test
+    public void test_StringBuffer() {
+        JDKStringConverter test = JDKStringConverter.STRING_BUFFER;
+        Object obj = new StringBuffer("Hello");
+        assertEquals(StringBuffer.class, test.getType());
+        assertEquals("Hello", test.convertToString(obj));
+        StringBuffer back = (StringBuffer) test.convertFromString(StringBuffer.class, "Hello");
+        assertEquals("Hello", back.toString());
+    }
+
+    @Test
+    public void test_StringBuilder() {
+        JDKStringConverter test = JDKStringConverter.STRING_BUILDER;
+        Object obj = new StringBuilder("Hello");
+        assertEquals(StringBuilder.class, test.getType());
+        assertEquals("Hello", test.convertToString(obj));
+        StringBuilder back = (StringBuilder) test.convertFromString(StringBuilder.class, "Hello");
+        assertEquals("Hello", back.toString());
+    }
+
+    @Test
+    public void test_CharSequence() {
+        JDKStringConverter test = JDKStringConverter.CHAR_SEQUENCE;
+        doTest(test, CharSequence.class, "Hello", "Hello");
+        doTest(test, CharSequence.class, new StringBuffer("Hello"), "Hello", "Hello");
+        doTest(test, CharSequence.class, new StringBuilder("Hello"), "Hello", "Hello");
+    }
+
+    @Test
+    public void test_Long() {
+        JDKStringConverter test = JDKStringConverter.LONG;
+        doTest(test, Long.class, Long.valueOf(12L), "12");
+    }
+
+    @Test
+    public void test_Int() {
+        JDKStringConverter test = JDKStringConverter.INTEGER;
+        doTest(test, Integer.class, Integer.valueOf(12), "12");
+    }
+
+    @Test
+    public void test_Short() {
+        JDKStringConverter test = JDKStringConverter.SHORT;
+        doTest(test, Short.class, Short.valueOf((byte) 12), "12");
+    }
+
+    @Test
+    public void test_Character() {
+        JDKStringConverter test = JDKStringConverter.CHARACTER;
+        doTest(test, Character.class, Character.valueOf('a'), "a");
+        doTest(test, Character.class, Character.valueOf('z'), "z");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_Character_fail() {
+        JDKStringConverter.CHARACTER.convertFromString(Character.class, "RUBBISH");
+    }
+
+    @Test
+    public void test_Byte() {
+        JDKStringConverter test = JDKStringConverter.BYTE;
+        doTest(test, Byte.class, Byte.valueOf((byte) 12), "12");
+    }
+
+    @Test
+    public void test_Boolean() {
+        JDKStringConverter test = JDKStringConverter.BOOLEAN;
+        doTest(test, Boolean.class, Boolean.TRUE, "true");
+        doTest(test, Boolean.class, Boolean.FALSE, "false");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_Boolean_fail() {
+        JDKStringConverter.BOOLEAN.convertFromString(Boolean.class, "RUBBISH");
+    }
+
+    @Test
+    public void test_Double() {
+        JDKStringConverter test = JDKStringConverter.DOUBLE;
+        doTest(test, Double.class, Double.valueOf(12.4d), "12.4");
+    }
+
+    @Test
+    public void test_Float() {
+        JDKStringConverter test = JDKStringConverter.FLOAT;
+        doTest(test, Float.class, Float.valueOf(12.2f), "12.2");
+    }
+
+    @Test
+    public void test_BigInteger() {
+        JDKStringConverter test = JDKStringConverter.BIG_INTEGER;
+        doTest(test, BigInteger.class, BigInteger.valueOf(12L), "12");
+    }
+
+    @Test
+    public void test_BigDecimal() {
+        JDKStringConverter test = JDKStringConverter.BIG_DECIMAL;
+        doTest(test, BigDecimal.class, BigDecimal.valueOf(12.23d), "12.23");
+    }
+
+    @Test
+    public void test_AtomicLong() {
+        JDKStringConverter test = JDKStringConverter.ATOMIC_LONG;
+        AtomicLong obj = new AtomicLong(12);
+        assertEquals(AtomicLong.class, test.getType());
+        assertEquals("12", test.convertToString(obj));
+        AtomicLong back = (AtomicLong) test.convertFromString(AtomicLong.class, "12");
+        assertEquals(12, back.get());
+    }
+
+    @Test
+    public void test_AtomicInteger() {
+        JDKStringConverter test = JDKStringConverter.ATOMIC_INTEGER;
+        AtomicInteger obj = new AtomicInteger(12);
+        assertEquals(AtomicInteger.class, test.getType());
+        assertEquals("12", test.convertToString(obj));
+        AtomicInteger back = (AtomicInteger) test.convertFromString(AtomicInteger.class, "12");
+        assertEquals(12, back.get());
+    }
+
+    @Test
+    public void test_AtomicBoolean_true() {
+        JDKStringConverter test = JDKStringConverter.ATOMIC_BOOLEAN;
+        AtomicBoolean obj = new AtomicBoolean(true);
+        assertEquals(AtomicBoolean.class, test.getType());
+        assertEquals("true", test.convertToString(obj));
+        AtomicBoolean back = (AtomicBoolean) test.convertFromString(AtomicBoolean.class, "true");
+        assertEquals(true, back.get());
+    }
+
+    @Test
+    public void test_AtomicBoolean_false() {
+        JDKStringConverter test = JDKStringConverter.ATOMIC_BOOLEAN;
+        AtomicBoolean obj = new AtomicBoolean(false);
+        assertEquals(AtomicBoolean.class, test.getType());
+        assertEquals("false", test.convertToString(obj));
+        AtomicBoolean back = (AtomicBoolean) test.convertFromString(AtomicBoolean.class, "false");
+        assertEquals(false, back.get());
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_AtomicBoolean_fail() {
+        JDKStringConverter.ATOMIC_BOOLEAN.convertFromString(AtomicBoolean.class, "RUBBISH");
+    }
+
+    @Test
+    public void test_Locale() {
+        JDKStringConverter test = JDKStringConverter.LOCALE;
+        doTest(test, Locale.class, new Locale("en"), "en");
+        doTest(test, Locale.class, new Locale("en", "GB"), "en_GB");
+        doTest(test, Locale.class, new Locale("en", "GB", "VARIANT_B"), "en_GB_VARIANT_B");
+    }
+
+    @Test
+    public void test_Class() {
+        JDKStringConverter test = JDKStringConverter.CLASS;
+        doTest(test, Class.class, Locale.class, "java.util.Locale");
+        doTest(test, Class.class, FromString.class, "org.joda.convert.FromString");
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_Class_fail() {
+        JDKStringConverter.CLASS.convertFromString(Class.class, "RUBBISH");
+    }
+
+    @Test
+    public void test_Package() {
+        JDKStringConverter test = JDKStringConverter.PACKAGE;
+        doTest(test, Package.class, Locale.class.getPackage(), "java.util");
+        doTest(test, Package.class, FromString.class.getPackage(), "org.joda.convert");
+    }
+
+    @Test
+    public void test_Currency() {
+        JDKStringConverter test = JDKStringConverter.CURRENCY;
+        doTest(test, Currency.class, Currency.getInstance("GBP"), "GBP");
+        doTest(test, Currency.class, Currency.getInstance("USD"), "USD");
+    }
+
+    @Test
+    public void test_TimeZone() {
+        JDKStringConverter test = JDKStringConverter.TIME_ZONE;
+        doTest(test, TimeZone.class, TimeZone.getTimeZone("Europe/London"), "Europe/London");
+        doTest(test, TimeZone.class, TimeZone.getTimeZone("America/New_York"), "America/New_York");
+    }
+
+    @Test
+    public void test_UUID() {
+        JDKStringConverter test = JDKStringConverter.UUID;
+        UUID uuid = UUID.randomUUID();
+        doTest(test, UUID.class, uuid, uuid.toString());
+    }
+
+    @Test
+    public void test_URL() throws Exception {
+        JDKStringConverter test = JDKStringConverter.URL;
+        doTest(test, URL.class, new URL("http://localhost:8080/my/test"), "http://localhost:8080/my/test");
+        doTest(test, URL.class, new URL(null, "ftp:world"), "ftp:world");
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_URL_invalidFormat() {
+        JDKStringConverter.URL.convertFromString(URL.class, "RUBBISH:RUBBISH");
+    }
+
+    @Test
+    public void test_URI() {
+        JDKStringConverter test = JDKStringConverter.URI;
+        doTest(test, URI.class, URI.create("http://localhost:8080/my/test"), "http://localhost:8080/my/test");
+        doTest(test, URI.class, URI.create("/my/test"), "/my/test");
+        doTest(test, URI.class, URI.create("/my/../test"), "/my/../test");
+        doTest(test, URI.class, URI.create("urn:hello"), "urn:hello");
+    }
+
+    @Test
+    public void test_InetAddress() throws Exception {
+        JDKStringConverter test = JDKStringConverter.INET_ADDRESS;
+        doTest(test, InetAddress.class, InetAddress.getByName("1.2.3.4"), "1.2.3.4");
+        doTest(test, InetAddress.class, InetAddress.getByName("2001:0db8:85a3:0000:0000:8a2e:0370:7334"), "2001:db8:85a3:0:0:8a2e:370:7334");
+    }
+
+//    @Test(expected=RuntimeException.class)
+//    public void test_InetAddress_invalidFormat() {
+//        JDKStringConverter.INET_ADDRESS.convertFromString(InetAddress.class, "RUBBISH");
+//    }
+
+    @Test
+    public void test_File() {
+        JDKStringConverter test = JDKStringConverter.FILE;
+        File file = new File("/path/to/file");
+        doTest(test, File.class, file, file.toString());
+    }
+
+    @SuppressWarnings("deprecation")
+    @Test
+    public void test_Date() {
+        TimeZone zone = TimeZone.getDefault();
+        try {
+            TimeZone.setDefault(TimeZone.getTimeZone("Europe/Paris"));
+            JDKStringConverter test = JDKStringConverter.DATE;
+            doTest(test, Date.class, new Date(2010 - 1900, 9 - 1, 3, 12, 34, 5), "2010-09-03T12:34:05.000+02:00");
+            doTest(test, Date.class, new Date(2011 - 1900, 1 - 1, 4, 12, 34, 5), "2011-01-04T12:34:05.000+01:00");
+        } finally {
+            TimeZone.setDefault(zone);
+        }
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_Date_invalidLength() {
+        JDKStringConverter.DATE.convertFromString(Date.class, "2010-09-03");
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_Date_invalidFormat() {
+        JDKStringConverter.DATE.convertFromString(Date.class, "2010-09-03XXX:34:05.000+02:00");
+    }
+
+    @Test
+    public void test_Calendar() {
+        JDKStringConverter test = JDKStringConverter.CALENDAR;
+        GregorianCalendar cal = new GregorianCalendar(TimeZone.getTimeZone("Europe/Paris"));
+        cal.set(2010, 9 - 1, 3, 12, 34, 5);
+        cal.set(Calendar.MILLISECOND, 0);
+        doTest(test, Calendar.class, cal, "2010-09-03T12:34:05.000+02:00[Europe/Paris]");
+        
+        GregorianCalendar cal2 = new GregorianCalendar(TimeZone.getTimeZone("Europe/Paris"));
+        cal2.set(2011, 1 - 1, 4, 12, 34, 5);
+        cal2.set(Calendar.MILLISECOND, 0);
+        doTest(test, Calendar.class, cal2, "2011-01-04T12:34:05.000+01:00[Europe/Paris]");
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_Calendar_invalidLength() {
+        JDKStringConverter.CALENDAR.convertFromString(GregorianCalendar.class, "2010-09-03");
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_Calendar_invalidFormat() {
+        JDKStringConverter.CALENDAR.convertFromString(GregorianCalendar.class, "2010-09-03XXX:34:05.000+02:00[Europe/London]");
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_Calendar_notGregorian() {
+        JDKStringConverter.CALENDAR.convertToString(new Calendar() {
+            private static final long serialVersionUID = 1L;
+            @Override
+            public void roll(int field, boolean up) {
+            }
+            @Override
+            public int getMinimum(int field) {
+                return 0;
+            }
+            @Override
+            public int getMaximum(int field) {
+                return 0;
+            }
+            @Override
+            public int getLeastMaximum(int field) {
+                return 0;
+            }
+            @Override
+            public int getGreatestMinimum(int field) {
+                return 0;
+            }
+            @Override
+            protected void computeTime() {
+            }
+            @Override
+            protected void computeFields() {
+            }
+            @Override
+            public void add(int field, int amount) {
+            }
+        });
+    }
+
+    @Test
+    public void test_Enum() {
+        JDKStringConverter test = JDKStringConverter.ENUM;
+        assertEquals(Enum.class, test.getType());
+        assertEquals("CEILING", test.convertToString(RoundingMode.CEILING));
+        assertEquals(RoundingMode.CEILING, test.convertFromString(RoundingMode.class, "CEILING"));
+    }
+
+    @Test(expected=RuntimeException.class)
+    public void test_Enum_invalidConstant() {
+        JDKStringConverter.ENUM.convertFromString(RoundingMode.class, "RUBBISH");
+    }
+
+    //-----------------------------------------------------------------------
+    public void doTest(JDKStringConverter test, Class<?> cls, Object obj, String str) {
+        doTest(test, cls, obj, str, obj);
+    }
+
+    public void doTest(JDKStringConverter test, Class<?> cls, Object obj, String str, Object objFromStr) {
+        assertEquals(cls, test.getType());
+        assertEquals(str, test.convertToString(obj));
+        assertEquals(objFromStr, test.convertFromString(cls, str));
+    }
+
+}
diff --git a/src/test/java/org/joda/convert/TestStringConvert.java b/src/test/java/org/joda/convert/TestStringConvert.java
new file mode 100644
index 0000000..a893b5b
--- /dev/null
+++ b/src/test/java/org/joda/convert/TestStringConvert.java
@@ -0,0 +1,497 @@
+/*
+ *  Copyright 2010-2011 Stephen Colebourne
+ *
+ *  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 org.joda.convert;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.fail;
+
+import java.math.RoundingMode;
+import java.text.ParseException;
+
+import org.junit.Test;
+
+/**
+ * Test StringConvert.
+ */
+public class TestStringConvert {
+
+    @Test
+    public void test_constructor() {
+        StringConvert test = new StringConvert();
+        StringConverter<?> conv = test.findConverter(Integer.class);
+        assertEquals(true, conv instanceof JDKStringConverter);
+    }
+
+    @Test
+    public void test_constructor_true() {
+        StringConvert test = new StringConvert(true);
+        StringConverter<?> conv = test.findConverter(Integer.class);
+        assertEquals(true, conv instanceof JDKStringConverter);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_constructor_false() {
+        StringConvert test = new StringConvert(false);
+        StringConverter<?> conv = test.findConverter(Integer.class);
+        assertEquals(null, conv);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_convertToString() {
+        Integer i = 6;
+        assertEquals("6", StringConvert.INSTANCE.convertToString(i));
+    }
+
+    @Test
+    public void test_convertToString_primitive() {
+        int i = 6;
+        assertEquals("6", StringConvert.INSTANCE.convertToString(i));
+    }
+
+    @Test
+    public void test_convertToString_inherit() {
+        assertEquals("CEILING", StringConvert.INSTANCE.convertToString(RoundingMode.CEILING));
+    }
+
+    @Test
+    public void test_convertToString_null() {
+        assertEquals(null, StringConvert.INSTANCE.convertToString(null));
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_convertToString_withType() {
+        Integer i = 6;
+        assertEquals("6", StringConvert.INSTANCE.convertToString(Integer.class, i));
+    }
+
+    @Test
+    public void test_convertToString_withType_primitive1() {
+        int i = 6;
+        assertEquals("6", StringConvert.INSTANCE.convertToString(Integer.class, i));
+    }
+
+    @Test
+    public void test_convertToString_withType_primitive2() {
+        int i = 6;
+        assertEquals("6", StringConvert.INSTANCE.convertToString(Integer.TYPE, i));
+    }
+
+    @Test
+    public void test_convertToString_withType_inherit1() {
+        assertEquals("CEILING", StringConvert.INSTANCE.convertToString(RoundingMode.class, RoundingMode.CEILING));
+    }
+
+    @Test
+    public void test_convertToString_withType_inherit2() {
+        assertEquals("CEILING", StringConvert.INSTANCE.convertToString(Enum.class, RoundingMode.CEILING));
+    }
+
+    @Test
+    public void test_convertToString_withType_null() {
+        assertEquals(null, StringConvert.INSTANCE.convertToString(Integer.class, null));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void test_convertToString_withType_nullClass() {
+        assertEquals(null, StringConvert.INSTANCE.convertToString(null, "6"));
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_convertFromString() {
+        assertEquals(Integer.valueOf(6), StringConvert.INSTANCE.convertFromString(Integer.class, "6"));
+    }
+
+    @Test
+    public void test_convertFromString_primitiveInt() {
+      assertEquals(Integer.valueOf(6), StringConvert.INSTANCE.convertFromString(Integer.TYPE, "6"));
+    }
+
+    @Test
+    public void test_convertFromString_primitiveBoolean() {
+      assertEquals(Boolean.TRUE, StringConvert.INSTANCE.convertFromString(Boolean.TYPE, "true"));
+    }
+
+    @Test
+    public void test_convertFromString_inherit() {
+        assertEquals(RoundingMode.CEILING, StringConvert.INSTANCE.convertFromString(RoundingMode.class, "CEILING"));
+    }
+
+    @Test
+    public void test_convertFromString_null() {
+        assertEquals(null, StringConvert.INSTANCE.convertFromString(Integer.class, null));
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void test_convertFromString_nullClass() {
+        assertEquals(null, StringConvert.INSTANCE.convertFromString(null, "6"));
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(expected=IllegalArgumentException.class)
+    public void test_findConverter_null() {
+        StringConvert.INSTANCE.findConverter(null);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_findConverter_Object() {
+        StringConvert.INSTANCE.findConverter(Object.class);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_convert_annotationMethodMethod() {
+        StringConvert test = new StringConvert();
+        DistanceMethodMethod d = new DistanceMethodMethod(25);
+        assertEquals("25m", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceMethodMethod.class, "25m").amount);
+        StringConverter<DistanceMethodMethod> conv = test.findConverter(DistanceMethodMethod.class);
+        assertEquals(true, conv instanceof MethodsStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceMethodMethod.class));
+        assertEquals(true, conv.toString().startsWith("RefectionStringConverter"));
+    }
+
+    @Test
+    public void test_convert_annotationMethodMethodCharSequence() {
+        StringConvert test = new StringConvert();
+        DistanceMethodMethodCharSequence d = new DistanceMethodMethodCharSequence(25);
+        assertEquals("25m", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceMethodMethodCharSequence.class, "25m").amount);
+        StringConverter<DistanceMethodMethodCharSequence> conv = test.findConverter(DistanceMethodMethodCharSequence.class);
+        assertEquals(true, conv instanceof MethodsStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceMethodMethodCharSequence.class));
+        assertEquals(true, conv.toString().startsWith("RefectionStringConverter"));
+    }
+
+    @Test
+    public void test_convert_annotationMethodConstructor() {
+        StringConvert test = new StringConvert();
+        DistanceMethodConstructor d = new DistanceMethodConstructor(25);
+        assertEquals("25m", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceMethodConstructor.class, "25m").amount);
+        StringConverter<DistanceMethodConstructor> conv = test.findConverter(DistanceMethodConstructor.class);
+        assertEquals(true, conv instanceof MethodConstructorStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceMethodConstructor.class));
+        assertEquals(true, conv.toString().startsWith("RefectionStringConverter"));
+    }
+
+    @Test
+    public void test_convert_annotationMethodConstructorCharSequence() {
+        StringConvert test = new StringConvert();
+        DistanceMethodConstructorCharSequence d = new DistanceMethodConstructorCharSequence(25);
+        assertEquals("25m", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceMethodConstructorCharSequence.class, "25m").amount);
+        StringConverter<DistanceMethodConstructorCharSequence> conv = test.findConverter(DistanceMethodConstructorCharSequence.class);
+        assertEquals(true, conv instanceof MethodConstructorStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceMethodConstructorCharSequence.class));
+        assertEquals(true, conv.toString().startsWith("RefectionStringConverter"));
+    }
+
+    @Test
+    public void test_convert_annotationSubMethodMethod() {
+        StringConvert test = new StringConvert();
+        SubMethodMethod d = new SubMethodMethod(25);
+        assertEquals("25m", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(SubMethodMethod.class, "25m").amount);
+        StringConverter<SubMethodMethod> conv = test.findConverter(SubMethodMethod.class);
+        assertEquals(true, conv instanceof MethodsStringConverter<?>);
+        assertSame(conv, test.findConverter(SubMethodMethod.class));
+    }
+
+    @Test
+    public void test_convert_annotationSubMethodConstructor() {
+        StringConvert test = new StringConvert();
+        SubMethodConstructor d = new SubMethodConstructor("25m");
+        assertEquals("25m", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(SubMethodConstructor.class, "25m").amount);
+        StringConverter<SubMethodConstructor> conv = test.findConverter(SubMethodConstructor.class);
+        assertEquals(true, conv instanceof MethodConstructorStringConverter<?>);
+        assertSame(conv, test.findConverter(SubMethodConstructor.class));
+    }
+
+    @Test
+    public void test_convert_annotationSuperFactorySuper() {
+        StringConvert test = new StringConvert();
+        SuperFactorySuper d = new SuperFactorySuper(25);
+        assertEquals("25m", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(SuperFactorySuper.class, "25m").amount);
+        StringConverter<SuperFactorySuper> conv = test.findConverter(SuperFactorySuper.class);
+        assertEquals(true, conv instanceof MethodsStringConverter<?>);
+        assertSame(conv, test.findConverter(SuperFactorySuper.class));
+    }
+
+    @Test
+    public void test_convert_annotationSuperFactorySubViaSuper() {
+        StringConvert test = new StringConvert();
+        SuperFactorySub d = new SuperFactorySub(8);
+        assertEquals("8m", test.convertToString(d));
+        SuperFactorySuper fromStr = test.convertFromString(SuperFactorySuper.class, "8m");
+        assertEquals(d.amount, fromStr.amount);
+        assertEquals(true, fromStr instanceof SuperFactorySub);
+        StringConverter<SuperFactorySuper> conv = test.findConverter(SuperFactorySuper.class);
+        assertEquals(true, conv instanceof MethodsStringConverter<?>);
+        assertSame(conv, test.findConverter(SuperFactorySuper.class));
+    }
+
+    @Test
+    public void test_convert_annotationSuperFactorySubViaSub1() {
+        StringConvert test = new StringConvert();
+        SuperFactorySub d = new SuperFactorySub(25);
+        assertEquals("25m", test.convertToString(d));
+    }
+
+    // TODO problem is fwks, that just request a converter baed on the type of the object
+    @Test(expected = ClassCastException.class)
+    public void test_convert_annotationSuperFactorySubViaSub2() {
+        StringConvert test = new StringConvert();
+        test.convertFromString(SuperFactorySub.class, "25m");
+    }
+
+    @Test
+    public void test_convert_annotationToStringInvokeException() {
+        StringConvert test = new StringConvert();
+        DistanceToStringException d = new DistanceToStringException(25);
+        StringConverter<DistanceToStringException> conv = test.findConverter(DistanceToStringException.class);
+        try {
+            conv.convertToString(d);
+            fail();
+        } catch (RuntimeException ex) {
+            assertEquals(ParseException.class, ex.getCause().getClass());
+        }
+    }
+
+    @Test
+    public void test_convert_annotationFromStringInvokeException() {
+        StringConvert test = new StringConvert();
+        StringConverter<DistanceFromStringException> conv = test.findConverter(DistanceFromStringException.class);
+        try {
+            conv.convertFromString(DistanceFromStringException.class, "25m");
+            fail();
+        } catch (RuntimeException ex) {
+            assertEquals(ParseException.class, ex.getCause().getClass());
+        }
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotationNoMethods() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceNoAnnotations.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedMethodAndConstructor() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceMethodAndConstructorAnnotations.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedTwoToString() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceTwoToStringAnnotations.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedToStringInvalidReturnType() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceToStringInvalidReturnType.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedToStringInvalidParameters() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceToStringInvalidParameters.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedFromStringInvalidReturnType() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceFromStringInvalidReturnType.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedFromStringInvalidParameter() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceFromStringInvalidParameter.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedFromStringInvalidParameterCount() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceFromStringInvalidParameterCount.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedFromStringConstructorInvalidParameter() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceFromStringConstructorInvalidParameter.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedFromStringConstructorInvalidParameterCount() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceFromStringConstructorInvalidParameterCount.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedToStringNoFromString() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceToStringNoFromString.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedFromStringNoToString() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceFromStringNoToString.class);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_convert_annotatedTwoFromStringMethod() {
+        StringConvert test = new StringConvert();
+        test.findConverter(DistanceTwoFromStringMethodAnnotations.class);
+    }
+
+    //-----------------------------------------------------------------------
+    @Test(expected=IllegalArgumentException.class)
+    public void test_register_classNotNull() {
+        StringConvert.INSTANCE.register(null, MockIntegerStringConverter.INSTANCE);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_register_converterNotNull() {
+        StringConvert.INSTANCE.register(Integer.class, null);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_register_notOnShared() {
+        StringConvert.INSTANCE.register(Integer.class, MockIntegerStringConverter.INSTANCE);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_register_classAlreadyRegistered() {
+        new StringConvert().register(Integer.class, MockIntegerStringConverter.INSTANCE);
+    }
+
+    public void test_register_distance() {
+        StringConvert test = new StringConvert();
+        test.register(DistanceMethodMethod.class, MockDistanceStringConverter.INSTANCE);
+        assertSame(MockDistanceStringConverter.INSTANCE, test.findConverter(DistanceMethodMethod.class));
+    }
+
+    //-------------------------------------------------------------------------
+    @Test
+    public void test_registerMethods() {
+        StringConvert test = new StringConvert();
+        test.registerMethods(DistanceNoAnnotations.class, "toString", "parse");
+        DistanceNoAnnotations d = new DistanceNoAnnotations(25);
+        assertEquals("Distance[25m]", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceNoAnnotations.class, "25m").amount);
+        StringConverter<DistanceNoAnnotations> conv = test.findConverter(DistanceNoAnnotations.class);
+        assertEquals(true, conv instanceof MethodsStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceNoAnnotations.class));
+    }
+
+    @Test
+    public void test_registerMethodsCharSequence() {
+        StringConvert test = new StringConvert();
+        test.registerMethods(DistanceNoAnnotationsCharSequence.class, "toString", "parse");
+        DistanceNoAnnotationsCharSequence d = new DistanceNoAnnotationsCharSequence(25);
+        assertEquals("Distance[25m]", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceNoAnnotationsCharSequence.class, "25m").amount);
+        StringConverter<DistanceNoAnnotationsCharSequence> conv = test.findConverter(DistanceNoAnnotationsCharSequence.class);
+        assertEquals(true, conv instanceof MethodsStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceNoAnnotationsCharSequence.class));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_registerMethods_nullClass() {
+        StringConvert test = new StringConvert();
+        test.registerMethods(null, "toString", "parse");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_registerMethods_nullToString() {
+        StringConvert test = new StringConvert();
+        test.registerMethods(DistanceNoAnnotations.class, null, "parse");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_registerMethods_nullFromString() {
+        StringConvert test = new StringConvert();
+        test.registerMethods(DistanceNoAnnotations.class, "toString", null);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_registerMethods_classAlreadyRegistered() {
+      StringConvert test = new StringConvert();
+      test.registerMethods(DistanceNoAnnotations.class, "toString", "parse");
+      test.registerMethods(DistanceNoAnnotations.class, "toString", "parse");
+    }
+
+    //-------------------------------------------------------------------------
+    @Test
+    public void test_registerMethodConstructorCharSequence() {
+        StringConvert test = new StringConvert();
+        test.registerMethodConstructor(DistanceNoAnnotationsCharSequence.class, "toString");
+        DistanceNoAnnotationsCharSequence d = new DistanceNoAnnotationsCharSequence(25);
+        assertEquals("Distance[25m]", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceNoAnnotationsCharSequence.class, "25m").amount);
+        StringConverter<DistanceNoAnnotationsCharSequence> conv = test.findConverter(DistanceNoAnnotationsCharSequence.class);
+        assertEquals(true, conv instanceof MethodConstructorStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceNoAnnotationsCharSequence.class));
+    }
+
+    @Test
+    public void test_registerMethodConstructor() {
+        StringConvert test = new StringConvert();
+        test.registerMethodConstructor(DistanceNoAnnotations.class, "toString");
+        DistanceNoAnnotations d = new DistanceNoAnnotations(25);
+        assertEquals("Distance[25m]", test.convertToString(d));
+        assertEquals(d.amount, test.convertFromString(DistanceNoAnnotations.class, "25m").amount);
+        StringConverter<DistanceNoAnnotations> conv = test.findConverter(DistanceNoAnnotations.class);
+        assertEquals(true, conv instanceof MethodConstructorStringConverter<?>);
+        assertSame(conv, test.findConverter(DistanceNoAnnotations.class));
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_registerMethodConstructor_nullClass() {
+        StringConvert test = new StringConvert();
+        test.registerMethodConstructor(null, "toString");
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void test_registerMethodConstructor_nullToString() {
+        StringConvert test = new StringConvert();
+        test.registerMethodConstructor(DistanceNoAnnotations.class, null);
+    }
+
+    @Test(expected=IllegalStateException.class)
+    public void test_registerMethodConstructor_classAlreadyRegistered() {
+      StringConvert test = new StringConvert();
+      test.registerMethodConstructor(DistanceNoAnnotations.class, "toString");
+      test.registerMethodConstructor(DistanceNoAnnotations.class, "toString");
+    }
+
+    //-----------------------------------------------------------------------
+    @Test
+    public void test_convert_toString() {
+        assertEquals("StringConvert", new StringConvert().toString());
+    }
+
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/joda-convert.git



More information about the pkg-java-commits mailing list