[jsemver] 40/95: Implement a Parser instead of RegExp

Alexandre Viau reazem-guest at moszumanska.debian.org
Mon Feb 16 14:58:28 UTC 2015


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

reazem-guest pushed a commit to branch master
in repository jsemver.

commit 78266dc1ca42ebd3324d1cf5ce9a141728f5c095
Author: Zafar Khaja <zafarkhaja at gmail.com>
Date:   Mon Oct 14 22:46:46 2013 +0400

    Implement a Parser instead of RegExp
    
    This commit also updates the code to comply with the SemVer 2.0.0.
---
 README.md                                          |   7 +-
 .../github/zafarkhaja/semver/GrammarException.java |  35 +++
 .../github/zafarkhaja/semver/MetadataVersion.java  |  54 ++--
 .../github/zafarkhaja/semver/NormalVersion.java    |  18 --
 .../java/com/github/zafarkhaja/semver/Parser.java  |  32 +++
 .../github/zafarkhaja/semver/ParserException.java  |  39 +++
 .../semver/UnexpectedCharacterException.java       |  51 ++++
 .../java/com/github/zafarkhaja/semver/Version.java |  81 ++----
 .../github/zafarkhaja/semver/VersionParser.java    | 309 +++++++++++++++++++++
 .../zafarkhaja/semver/MetadataVersionTest.java     | 127 +++++----
 .../zafarkhaja/semver/NormalVersionTest.java       |  12 +-
 .../semver/VersionParserCharStreamTest.java        | 118 ++++++++
 .../zafarkhaja/semver/VersionParserCharTest.java   |  84 ++++++
 .../zafarkhaja/semver/VersionParserTest.java       | 115 ++++++++
 .../com/github/zafarkhaja/semver/VersionTest.java  |  13 +-
 15 files changed, 919 insertions(+), 176 deletions(-)

diff --git a/README.md b/README.md
index 36242ab..38b9cbc 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,9 @@
-Java SemVer v0.7.0 [![Build Status](https://travis-ci.org/zafarkhaja/java-semver.png)](https://travis-ci.org/zafarkhaja/java-semver)
+Java SemVer v0.7.0 (SemVer 2) [![Build Status](https://travis-ci.org/zafarkhaja/java-semver.png)](https://travis-ci.org/zafarkhaja/java-semver)
 ==================
 
 Java SemVer is a Java implementation of the Semantic Versioning Specification
 (http://semver.org/).
 
-**NOTE**: The current version of the Java SemVer corresponds to the Semantic
-Versioning 2.0.0-rc.2.
-
 
 Versioning
 ----------
@@ -132,7 +129,7 @@ Version v4 = v1.incrementPatchVersion("alpha"); // "1.2.4-alpha"
 Version v2 = v1.incrementPreReleaseVersion();   // "1.2.3-beta.1"
 ```
 **NOTE**: The discussion page https://github.com/mojombo/semver/issues/60 might
-be of good use in better understanding some of the decisions made regarding the 
+be of good use in better understanding some of the decisions made regarding the
 incrementor methods.
 
 ### Comparing Versions ###
diff --git a/src/main/java/com/github/zafarkhaja/semver/GrammarException.java b/src/main/java/com/github/zafarkhaja/semver/GrammarException.java
new file mode 100644
index 0000000..93ca594
--- /dev/null
+++ b/src/main/java/com/github/zafarkhaja/semver/GrammarException.java
@@ -0,0 +1,35 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+public class GrammarException extends ParserException {
+
+    GrammarException(String message) {
+        super(message);
+    }
+}
diff --git a/src/main/java/com/github/zafarkhaja/semver/MetadataVersion.java b/src/main/java/com/github/zafarkhaja/semver/MetadataVersion.java
index f6f9d30..83b46ab 100644
--- a/src/main/java/com/github/zafarkhaja/semver/MetadataVersion.java
+++ b/src/main/java/com/github/zafarkhaja/semver/MetadataVersion.java
@@ -24,8 +24,6 @@
 package com.github.zafarkhaja.semver;
 
 import java.util.Arrays;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  *
@@ -33,27 +31,14 @@ import java.util.regex.Pattern;
  */
 class MetadataVersion implements Comparable<MetadataVersion> {
 
-    private final String value;
+    private final String[] idents;
 
-    static final String FORMAT = "([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*)";
-    private static final Pattern PATTERN = Pattern.compile("^" + FORMAT + "$");
-
-    MetadataVersion(String value) {
-        if (value == null) {
-            throw new NullPointerException("Metadata version MUST NOT be NULL");
-        }
-        Matcher matcher = PATTERN.matcher(value);
-        if (!matcher.matches()) {
-            throw new IllegalArgumentException(
-                "Metadata version MUST consist of dot separated identifiers [0-9A-Za-z-]"
-            );
-        }
-        this.value = matcher.group(0);
+    MetadataVersion(String[] identifiers) {
+        idents = identifiers;
     }
 
     MetadataVersion increment() {
-        String[] ids  = value.split("\\.");
-
+        String[] ids  = idents;
         String lastId = ids[ids.length - 1];
         if (isInt(lastId)) {
             int intId = Integer.parseInt(lastId);
@@ -62,7 +47,7 @@ class MetadataVersion implements Comparable<MetadataVersion> {
             ids = Arrays.copyOf(ids, ids.length + 1);
             ids[ids.length - 1] = String.valueOf(1);
         }
-        return new MetadataVersion(joinIdentifiers(ids));
+        return new MetadataVersion(ids);
     }
 
     @Override
@@ -78,31 +63,32 @@ class MetadataVersion implements Comparable<MetadataVersion> {
 
     @Override
     public int hashCode() {
-        return value.hashCode();
+        return Arrays.hashCode(idents);
     }
 
     @Override
     public String toString() {
-        return value;
+        StringBuilder sb = new StringBuilder();
+        for (String id : idents) {
+            sb.append(id).append(".");
+        }
+        return sb.deleteCharAt(sb.lastIndexOf(".")).toString();
     }
 
     @Override
     public int compareTo(MetadataVersion other) {
-        String[] thisIds  = value.split("\\.");
-        String[] otherIds = other.value.split("\\.");
-
-        int result = compareIdentifierArrays(thisIds, otherIds);
+        int result = compareIdentifierArrays(other.idents);
         if (result == 0) {
-            result = thisIds.length - otherIds.length;
+            result = idents.length - other.idents.length;
         }
         return result;
     }
 
-    private int compareIdentifierArrays(String[] ids1, String[] ids2) {
+    private int compareIdentifierArrays(String[] otherIdents) {
         int result = 0;
-        int length = getLeastCommonArrayLength(ids1, ids2);
+        int length = getLeastCommonArrayLength(idents, otherIdents);
         for (int i = 0; i < length; i++) {
-            result = compareIdentifiers(ids1[i], ids2[i]);
+            result = compareIdentifiers(idents[i], otherIdents[i]);
             if (result != 0) {
                 break;
             }
@@ -130,12 +116,4 @@ class MetadataVersion implements Comparable<MetadataVersion> {
         }
         return true;
     }
-
-    private String joinIdentifiers(String[] ids) {
-        StringBuilder sb = new StringBuilder();
-        for (String id : ids) {
-            sb.append(id).append(".");
-        }
-        return sb.deleteCharAt(sb.lastIndexOf(".")).toString();
-    }
 }
diff --git a/src/main/java/com/github/zafarkhaja/semver/NormalVersion.java b/src/main/java/com/github/zafarkhaja/semver/NormalVersion.java
index 87a1510..7779b64 100644
--- a/src/main/java/com/github/zafarkhaja/semver/NormalVersion.java
+++ b/src/main/java/com/github/zafarkhaja/semver/NormalVersion.java
@@ -23,9 +23,6 @@
  */
 package com.github.zafarkhaja.semver;
 
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
 /**
  *
  * @author Zafar Khaja <zafarkhaja at gmail.com>
@@ -36,9 +33,6 @@ class NormalVersion implements Comparable<NormalVersion> {
     private final int minor;
     private final int patch;
 
-    static final String FORMAT = "(\\d+)\\.(\\d+)\\.(\\d+)";
-    private static final Pattern PATTERN = Pattern.compile("^" + FORMAT + "$");
-
     NormalVersion(int major, int minor, int patch) {
         if (major < 0 || minor < 0 || patch < 0) {
             throw new IllegalArgumentException(
@@ -50,18 +44,6 @@ class NormalVersion implements Comparable<NormalVersion> {
         this.patch = patch;
     }
 
-    static NormalVersion valueOf(String value) {
-        Matcher matcher = PATTERN.matcher(value);
-        if (!matcher.matches()) {
-            throw new IllegalArgumentException("Illegal normal version format");
-        }
-        return new NormalVersion(
-            Integer.parseInt(matcher.group(1)),
-            Integer.parseInt(matcher.group(2)),
-            Integer.parseInt(matcher.group(3))
-        );
-    }
-
     int getMajor() {
         return major;
     }
diff --git a/src/main/java/com/github/zafarkhaja/semver/Parser.java b/src/main/java/com/github/zafarkhaja/semver/Parser.java
new file mode 100644
index 0000000..32d7571
--- /dev/null
+++ b/src/main/java/com/github/zafarkhaja/semver/Parser.java
@@ -0,0 +1,32 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+public interface Parser<T> {
+    T parse(String input);
+}
diff --git a/src/main/java/com/github/zafarkhaja/semver/ParserException.java b/src/main/java/com/github/zafarkhaja/semver/ParserException.java
new file mode 100644
index 0000000..469c060
--- /dev/null
+++ b/src/main/java/com/github/zafarkhaja/semver/ParserException.java
@@ -0,0 +1,39 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+public class ParserException extends RuntimeException {
+
+    ParserException(String message) {
+        super(message);
+    }
+
+    ParserException() {
+
+    }
+}
diff --git a/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java b/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java
new file mode 100644
index 0000000..f52fda0
--- /dev/null
+++ b/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java
@@ -0,0 +1,51 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+import com.github.zafarkhaja.semver.VersionParser.CharStream.CharType;
+import java.util.Arrays;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+public class UnexpectedCharacterException extends ParserException {
+
+    private final char unexpected;
+    private final CharType[] expected;
+
+    UnexpectedCharacterException(char chr, CharType... expected) {
+        unexpected = chr;
+        this.expected = expected;
+    }
+
+    @Override
+    public String toString() {
+        String message = "Unexpected character '" + unexpected + "'";
+        if (expected.length > 0) {
+            message += ", expecting '" + Arrays.toString(expected) + "'";
+        }
+        return message;
+    }
+}
diff --git a/src/main/java/com/github/zafarkhaja/semver/Version.java b/src/main/java/com/github/zafarkhaja/semver/Version.java
index f18ad70..2970399 100644
--- a/src/main/java/com/github/zafarkhaja/semver/Version.java
+++ b/src/main/java/com/github/zafarkhaja/semver/Version.java
@@ -24,8 +24,6 @@
 package com.github.zafarkhaja.semver;
 
 import java.util.Comparator;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 /**
  *
@@ -40,64 +38,40 @@ public class Version implements Comparable<Version> {
     private static final String PRE_RELEASE_PREFIX = "-";
     private static final String BUILD_PREFIX = "+";
 
-    private static final Pattern SEMVER_PATTERN;
-
-    static {
-        StringBuilder sb = new StringBuilder();
-        sb.append("^")
-            .append("(?<normal>")
-                .append(NormalVersion.FORMAT)
-            .append(")")
-            .append("(?:")
-                .append(PRE_RELEASE_PREFIX)
-                .append("(?<preRelease>")
-                    .append(MetadataVersion.FORMAT)
-                .append(")")
-            .append(")?").append("(?:")
-                .append("\\").append(BUILD_PREFIX)
-                .append("(?<build>")
-                    .append(MetadataVersion.FORMAT)
-                .append(")")
-            .append(")?")
-        .append("$");
-
-        SEMVER_PATTERN = Pattern.compile(sb.toString());
-    }
-
     public static class Builder {
 
         private String normal;
         private String preRelease;
         private String build;
 
-        public Builder(String normal) {
-            if (normal == null) {
+        public Builder(String normalVersion) {
+            if (normalVersion == null) {
                 throw new NullPointerException(
                     "Normal version MUST NOT be NULL"
                 );
             }
-            this.normal = normal;
+            normal = normalVersion;
         }
 
-        public void setPreReleaseVersion(String preRelease) {
-            this.preRelease = preRelease;
+        public void setPreReleaseVersion(String preReleaseVersion) {
+            preRelease = preReleaseVersion;
         }
 
-        public void setBuildMetadata(String build) {
-            this.build = build;
+        public void setBuildMetadata(String buildMetadata) {
+            build = buildMetadata;
         }
 
         public Version build() {
             MetadataVersion preReleaseVersion = null;
             if (preRelease != null) {
-                preReleaseVersion = new MetadataVersion(preRelease);
+                preReleaseVersion = VersionParser.parsePreRelease(preRelease);
             }
             MetadataVersion buildMetadata = null;
             if (build != null) {
-                buildMetadata = new MetadataVersion(build);
+                buildMetadata = VersionParser.parseBuild(build);
             }
             return new Version(
-                NormalVersion.valueOf(normal),
+                VersionParser.parseVersionCore(normal),
                 preReleaseVersion,
                 buildMetadata
             );
@@ -123,7 +97,7 @@ public class Version implements Comparable<Version> {
                 result = v1.build.compareTo(v2.build);
             } else if (v1.build == null ^ v2.build == null) {
                 /**
-                 * Build versions satisfy and have a higher precedence
+                 * Build versions should have a higher precedence
                  * than the associated normal version.
                  */
                 result = (v1.build == null) ? -1 : 1;
@@ -150,16 +124,8 @@ public class Version implements Comparable<Version> {
         this.build      = build;
     }
 
-    public static Version valueOf(String value) {
-        Matcher matcher = SEMVER_PATTERN.matcher(value);
-        if (!matcher.matches()) {
-            throw new IllegalArgumentException("Illegal version format");
-        }
-
-        Builder builder = new Builder(matcher.group("normal"));
-        builder.setPreReleaseVersion(matcher.group("preRelease"));
-        builder.setBuildMetadata(matcher.group("build"));
-        return builder.build();
+    public static Version valueOf(String version) {
+        return VersionParser.parseValidSemVer(version);
     }
 
     public Version incrementMajorVersion() {
@@ -169,7 +135,7 @@ public class Version implements Comparable<Version> {
     public Version incrementMajorVersion(String preRelease) {
         return new Version(
             normal.incrementMajor(),
-            new MetadataVersion(preRelease)
+            VersionParser.parsePreRelease(preRelease)
         );
     }
 
@@ -180,7 +146,7 @@ public class Version implements Comparable<Version> {
     public Version incrementMinorVersion(String preRelease) {
         return new Version(
             normal.incrementMinor(),
-            new MetadataVersion(preRelease)
+            VersionParser.parsePreRelease(preRelease)
         );
     }
 
@@ -191,7 +157,7 @@ public class Version implements Comparable<Version> {
     public Version incrementPatchVersion(String preRelease) {
         return new Version(
             normal.incrementPatch(),
-            new MetadataVersion(preRelease)
+            VersionParser.parsePreRelease(preRelease)
         );
     }
 
@@ -210,11 +176,18 @@ public class Version implements Comparable<Version> {
     }
 
     public Version setPreReleaseVersion(String preRelease) {
-        return new Version(normal, new MetadataVersion(preRelease));
+        return new Version(
+            normal,
+            VersionParser.parsePreRelease(preRelease)
+        );
     }
 
     public Version setBuildMetadata(String build) {
-        return new Version(normal, preRelease, new MetadataVersion(build));
+        return new Version(
+            normal,
+            preRelease,
+            VersionParser.parseBuild(build)
+        );
     }
 
     public int getMajorVersion() {
@@ -308,8 +281,8 @@ public class Version implements Comparable<Version> {
             result = preRelease.compareTo(other.preRelease);
         } else if (preRelease == null ^ other.preRelease == null) {
             /**
-             * Pre-release versions satisfy but have a lower precedence
-             * than the associated normal version. (SemVer p.9)
+             * Pre-release versions have a lower precedence than
+             * the associated normal version. (SemVer p.9)
              */
             result = (preRelease == null) ? 1 : -1;
         }
diff --git a/src/main/java/com/github/zafarkhaja/semver/VersionParser.java b/src/main/java/com/github/zafarkhaja/semver/VersionParser.java
new file mode 100644
index 0000000..516cee4
--- /dev/null
+++ b/src/main/java/com/github/zafarkhaja/semver/VersionParser.java
@@ -0,0 +1,309 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+import com.github.zafarkhaja.semver.VersionParser.CharStream;
+import java.util.ArrayList;
+import java.util.List;
+import static com.github.zafarkhaja.semver.VersionParser.Char.*;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+class VersionParser implements Parser<Version> {
+
+    static class CharStream {
+
+        static interface CharType {
+            boolean isMatchedBy(char chr);
+        }
+
+        private final char[] data;
+
+        private int offset = 0;
+
+        static final char EOL = (char) -1;
+
+        CharStream(String input) {
+            data = input.toCharArray();
+        }
+
+        char consume() {
+            if (offset + 1 <= data.length) {
+                return data[offset++];
+            }
+            return EOL;
+        }
+
+        char consume(CharType... expected) {
+            char la = lookahead(1);
+            for (CharType charType : expected) {
+                if (charType.isMatchedBy(la)) {
+                    return consume();
+                }
+            }
+            throw new UnexpectedCharacterException(la, expected);
+        }
+
+        char lookahead() {
+            return lookahead(1);
+        }
+
+        char lookahead(int pos) {
+            int idx = offset + pos - 1;
+            if (idx < data.length) {
+                return data[idx];
+            }
+            return EOL;
+        }
+
+        boolean positiveLookahead(CharType... expected) {
+            char la = lookahead(1);
+            for (CharType charType : expected) {
+                if (charType.isMatchedBy(la)) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        boolean positiveLookaheadBefore(CharType before, CharType... expected) {
+            char la;
+            for (int i = 1; i <= data.length; i++) {
+                la = lookahead(i);
+                if (before.isMatchedBy(la)) {
+                    break;
+                }
+                for (CharType charType : expected) {
+                    if (charType.isMatchedBy(la)) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        char[] toArray() {
+            return data.clone();
+        }
+    }
+
+    static enum Char implements CharStream.CharType {
+
+        DIGIT {
+            @Override
+            public boolean isMatchedBy(char chr) {
+                return chr >= '0' && chr <= '9';
+            }
+        },
+        LETTER {
+            @Override
+            public boolean isMatchedBy(char chr) {
+                return (chr >= 'a' && chr <= 'z')
+                    || (chr >= 'A' && chr <= 'Z');
+            }
+        },
+        DOT {
+            @Override
+            public boolean isMatchedBy(char chr) {
+                return chr == '.';
+            }
+        },
+        HYPHEN {
+            @Override
+            public boolean isMatchedBy(char chr) {
+                return chr == '-';
+            }
+        },
+        PLUS {
+            @Override
+            public boolean isMatchedBy(char chr) {
+                return chr == '+';
+            }
+        },
+        EOL {
+            @Override
+            public boolean isMatchedBy(char chr) {
+                return chr == CharStream.EOL;
+            }
+        };
+    }
+
+    private final CharStream chars;
+
+    VersionParser(String input) {
+        chars = new CharStream(input);
+    }
+
+    @Override
+    public Version parse(String input) {
+        return parseValidSemVer();
+    }
+
+    static Version parseValidSemVer(String version) {
+        VersionParser parser = new VersionParser(version);
+        return parser.parseValidSemVer();
+    }
+
+    static NormalVersion parseVersionCore(String versionCore) {
+        VersionParser parser = new VersionParser(versionCore);
+        return parser.parseVersionCore();
+    }
+
+    static MetadataVersion parsePreRelease(String preRelease) {
+        VersionParser parser = new VersionParser(preRelease);
+        return parser.parsePreRelease();
+    }
+
+    static MetadataVersion parseBuild(String build) {
+        VersionParser parser = new VersionParser(build);
+        return parser.parseBuild();
+    }
+
+    private Version parseValidSemVer() {
+        NormalVersion normalVersion = parseVersionCore();
+        MetadataVersion preReleaseVersion = null;
+        MetadataVersion buildMetadata = null;
+        if (chars.positiveLookahead(HYPHEN)) {
+            chars.consume();
+            preReleaseVersion = parsePreRelease();
+        }
+        if (chars.positiveLookahead(PLUS)) {
+            chars.consume();
+            buildMetadata = parseBuild();
+        }
+        return new Version(
+            normalVersion,
+            preReleaseVersion,
+            buildMetadata
+        );
+    }
+
+    private NormalVersion parseVersionCore() {
+        int major = Integer.parseInt(numericIdentifier());
+        chars.consume(DOT);
+        int minor = Integer.parseInt(numericIdentifier());
+        chars.consume(DOT);
+        int patch = Integer.parseInt(numericIdentifier());
+        return new NormalVersion(major, minor, patch);
+    }
+
+    private MetadataVersion parsePreRelease() {
+        Char end = closestEndpoint(PLUS, EOL);
+        Char before = closestEndpoint(DOT, end);
+        List<String> idents = new ArrayList<String>();
+        while (!chars.positiveLookahead(end)) {
+            if (before == DOT) {
+                checkForEmptyIdentifier();
+            }
+            if (chars.positiveLookaheadBefore(before, LETTER, HYPHEN)) {
+                idents.add(alphanumericIdentifier());
+            } else {
+                idents.add(numericIdentifier());
+            }
+            if (before == DOT) {
+                chars.consume();
+            }
+            before = closestEndpoint(DOT, end);
+        }
+        return new MetadataVersion(
+            idents.toArray(new String[idents.size()])
+        );
+    }
+
+    private MetadataVersion parseBuild() {
+        Char end = EOL;
+        Char before = closestEndpoint(DOT, end);
+        List<String> idents = new ArrayList<String>();
+        while (!chars.positiveLookahead(end)) {
+            if (before == DOT) {
+                checkForEmptyIdentifier();
+            }
+            if (chars.positiveLookaheadBefore(before, LETTER, HYPHEN)) {
+                idents.add(alphanumericIdentifier());
+            } else {
+                idents.add(digits());
+            }
+            if (before == DOT) {
+                chars.consume();
+            }
+            before = closestEndpoint(DOT, end);
+        }
+        return new MetadataVersion(
+            idents.toArray(new String[idents.size()])
+        );
+    }
+
+    private String numericIdentifier() {
+        checkForLeadingZeroes();
+        StringBuilder sb = new StringBuilder();
+        sb.append(chars.consume(DIGIT));
+        while (chars.positiveLookahead(DIGIT)) {
+            sb.append(chars.consume());
+        }
+        return sb.toString();
+    }
+
+    private String alphanumericIdentifier() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(chars.consume(DIGIT, LETTER, HYPHEN));
+        while (chars.positiveLookahead(DIGIT, LETTER, HYPHEN)) {
+            sb.append(chars.consume());
+        }
+        return sb.toString();
+    }
+
+    private String digits() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(chars.consume(DIGIT));
+        while (chars.positiveLookahead(DIGIT)) {
+            sb.append(chars.consume());
+        }
+        return sb.toString();
+    }
+
+    private Char closestEndpoint(Char tryThis, Char orThis) {
+        if (chars.positiveLookaheadBefore(orThis, tryThis)) {
+            return tryThis;
+        }
+        return orThis;
+    }
+
+    private void checkForLeadingZeroes() {
+        char la1 = chars.lookahead(1);
+        char la2 = chars.lookahead(2);
+        if (la1 == '0' && DIGIT.isMatchedBy(la2)) {
+            throw new GrammarException(
+                "Numeric identifier MUST NOT contain leading zeroes"
+            );
+        }
+    }
+
+    private void checkForEmptyIdentifier() {
+        if (DOT.isMatchedBy(chars.lookahead(1))) {
+            throw new GrammarException("Identifiers MUST NOT be empty");
+        }
+    }
+}
diff --git a/src/test/java/com/github/zafarkhaja/semver/MetadataVersionTest.java b/src/test/java/com/github/zafarkhaja/semver/MetadataVersionTest.java
index 9214dc3..a679f76 100644
--- a/src/test/java/com/github/zafarkhaja/semver/MetadataVersionTest.java
+++ b/src/test/java/com/github/zafarkhaja/semver/MetadataVersionTest.java
@@ -23,10 +23,10 @@
  */
 package com.github.zafarkhaja.semver;
 
-import static org.junit.Assert.*;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
 import org.junit.runner.RunWith;
+import static org.junit.Assert.*;
 
 /**
  *
@@ -38,78 +38,87 @@ public class MetadataVersionTest {
     public static class CoreFunctionalityTest {
 
         @Test
-        public void mustConsistOfDotSeparatedIdentifiersOfAlphaNumericsAndHyphen() {
-            String[] invalidVersions = {
-                null,
-                "",
-                "123!",
-                "1a:2b:3c",
-                "123,abc,123",
-            };
-            for (String ver : invalidVersions) {
-                try {
-                    MetadataVersion v = new MetadataVersion(ver);
-                } catch (Exception e) {
-                    continue;
-                }
-                fail("Metadata version MUST consist of dot separated identifiers [0-9A-Za-z-]");
-            }
-        }
-
-        @Test
         public void mustCompareEachIdentifierSeparately() {
-            MetadataVersion v1 = new MetadataVersion("beta.2.abc");
-            MetadataVersion v2 = new MetadataVersion("beta.1.edf");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"beta", "2", "abc"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"beta", "1", "edf"}
+            );
             assertTrue(0 < v1.compareTo(v2));
         }
 
         @Test
         public void shouldCompareIdentifiersCountIfCommonIdentifiersAreEqual() {
-            MetadataVersion v1 = new MetadataVersion("beta.abc");
-            MetadataVersion v2 = new MetadataVersion("beta.abc.def");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"beta", "abc"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"beta", "abc", "def"}
+            );
             assertTrue(0 > v1.compareTo(v2));
         }
 
         @Test
         public void shouldComapareDigitsOnlyIdentifiersNumerically() {
-            MetadataVersion v1 = new MetadataVersion("alpha.123");
-            MetadataVersion v2 = new MetadataVersion("alpha.321");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"alpha", "321"}
+            );
             assertTrue(0 > v1.compareTo(v2));
         }
 
         @Test
         public void shouldCompareMixedIdentifiersLexicallyInAsciiSortOrder() {
-            MetadataVersion v1 = new MetadataVersion("beta.abc");
-            MetadataVersion v2 = new MetadataVersion("beta.111");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"beta", "abc"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"beta", "111"}
+            );
             assertTrue(0 < v1.compareTo(v2));
         }
 
         @Test
         public void shouldOverrideEqualsMethod() {
-            MetadataVersion v1 = new MetadataVersion("alpha.123");
-            MetadataVersion v2 = new MetadataVersion("alpha.123");
-            MetadataVersion v3 = new MetadataVersion("alpha.321");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v3 = new MetadataVersion(
+                new String[] {"alpha", "321"}
+            );
             assertTrue(v1.equals(v2));
             assertFalse(v1.equals(v3));
         }
 
         @Test
         public void shouldProvideIncrementMethod() {
-            MetadataVersion v1 = new MetadataVersion("alpha.1");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "1"}
+            );
             MetadataVersion v2 = v1.increment();
             assertEquals("alpha.2", v2.toString());
         }
 
         @Test
         public void shouldAppendOneAsLastIdentifierIfLastOneIsAlphaNumericWhenIncrementing() {
-            MetadataVersion v1 = new MetadataVersion("alpha");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha"}
+            );
             MetadataVersion v2 = v1.increment();
             assertEquals("alpha.1", v2.toString());
         }
 
         @Test
         public void shouldBeImmutable() {
-            MetadataVersion v1 = new MetadataVersion("alpha.1");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "1"}
+            );
             MetadataVersion v2 = v1.increment();
             assertNotSame(v1, v2);
         }
@@ -119,23 +128,35 @@ public class MetadataVersionTest {
 
         @Test
         public void shouldBeReflexive() {
-            MetadataVersion v = new MetadataVersion("alpha.123");
+            MetadataVersion v = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
             assertTrue(v.equals(v));
         }
 
         @Test
         public void shouldBeSymmetric() {
-            MetadataVersion v1 = new MetadataVersion("alpha.123");
-            MetadataVersion v2 = new MetadataVersion("alpha.123");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
             assertTrue(v1.equals(v2));
             assertTrue(v2.equals(v1));
         }
 
         @Test
         public void shouldBeTransitive() {
-            MetadataVersion v1 = new MetadataVersion("alpha.123");
-            MetadataVersion v2 = new MetadataVersion("alpha.123");
-            MetadataVersion v3 = new MetadataVersion("alpha.123");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v3 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
             assertTrue(v1.equals(v2));
             assertTrue(v2.equals(v3));
             assertTrue(v1.equals(v3));
@@ -143,8 +164,12 @@ public class MetadataVersionTest {
 
         @Test
         public void shouldBeConsistent() {
-            MetadataVersion v1 = new MetadataVersion("alpha.123");
-            MetadataVersion v2 = new MetadataVersion("alpha.123");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
             assertTrue(v1.equals(v2));
             assertTrue(v1.equals(v2));
             assertTrue(v1.equals(v2));
@@ -152,13 +177,17 @@ public class MetadataVersionTest {
 
         @Test
         public void shouldReturnFalseIfOtherVersionIsOfDifferentType() {
-            MetadataVersion v = new MetadataVersion("alpha.123");
+            MetadataVersion v = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
             assertFalse(v.equals(new String("alpha.123")));
         }
 
         @Test
         public void shouldReturnFalseIfOtherVersionIsNull() {
-            MetadataVersion v1 = new MetadataVersion("alpha.123");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
             MetadataVersion v2 = null;
             assertFalse(v1.equals(v2));
         }
@@ -168,8 +197,12 @@ public class MetadataVersionTest {
 
         @Test
         public void shouldReturnSameHashCodeIfVersionsAreEqual() {
-            MetadataVersion v1 = new MetadataVersion("alpha.123");
-            MetadataVersion v2 = new MetadataVersion("alpha.123");
+            MetadataVersion v1 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
+            MetadataVersion v2 = new MetadataVersion(
+                new String[] {"alpha", "123"}
+            );
             assertTrue(v1.equals(v2));
             assertEquals(v1.hashCode(), v2.hashCode());
         }
@@ -180,7 +213,7 @@ public class MetadataVersionTest {
         @Test
         public void shouldReturnStringRepresentation() {
             String value = "beta.abc.def";
-            MetadataVersion v = new MetadataVersion(value);
+            MetadataVersion v = new MetadataVersion(value.split("\\."));
             assertEquals(value, v.toString());
         }
     }
diff --git a/src/test/java/com/github/zafarkhaja/semver/NormalVersionTest.java b/src/test/java/com/github/zafarkhaja/semver/NormalVersionTest.java
index 587e312..1fe2cdf 100644
--- a/src/test/java/com/github/zafarkhaja/semver/NormalVersionTest.java
+++ b/src/test/java/com/github/zafarkhaja/semver/NormalVersionTest.java
@@ -23,10 +23,10 @@
  */
 package com.github.zafarkhaja.semver;
 
-import static org.junit.Assert.*;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
 import org.junit.runner.RunWith;
+import static org.junit.Assert.*;
 
 /**
  *
@@ -56,7 +56,7 @@ public class NormalVersionTest {
             int[][] invalidVersions = {{-1, 2, 3}, {1, -2, 3}, {1, 2, -3}};
             for (int[] versionParts : invalidVersions) {
                 try {
-                    NormalVersion v = new NormalVersion(
+                    new NormalVersion(
                         versionParts[0],
                         versionParts[1],
                         versionParts[2]
@@ -116,14 +116,6 @@ public class NormalVersionTest {
         }
 
         @Test
-        public void shouldHaveStaticFactoryMethod() {
-            NormalVersion v = NormalVersion.valueOf("1.2.3");
-            assertEquals(1, v.getMajor());
-            assertEquals(2, v.getMinor());
-            assertEquals(3, v.getPatch());
-        }
-        
-        @Test
         public void shoudBeImmutable() {
             NormalVersion version = new NormalVersion(1, 2, 3);
             NormalVersion incementedMajor = version.incrementMajor();
diff --git a/src/test/java/com/github/zafarkhaja/semver/VersionParserCharStreamTest.java b/src/test/java/com/github/zafarkhaja/semver/VersionParserCharStreamTest.java
new file mode 100644
index 0000000..1c3bf77
--- /dev/null
+++ b/src/test/java/com/github/zafarkhaja/semver/VersionParserCharStreamTest.java
@@ -0,0 +1,118 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+import com.github.zafarkhaja.semver.VersionParser.Char;
+import com.github.zafarkhaja.semver.VersionParser.CharStream;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+public class VersionParserCharStreamTest {
+
+    @Test
+    public void shouldBeBackedByCharArray() {
+        String input = "abc";
+        CharStream chars = new CharStream(input);
+        assertArrayEquals(input.toCharArray(), chars.toArray());
+    }
+
+    @Test
+    public void shouldNotReturnRealCharArray() {
+        CharStream chars = new CharStream("abc");
+        char[] charArray = chars.toArray();
+        charArray[0] = 'z';
+        assertEquals('z', charArray[0]);
+        assertEquals('a', chars.lookahead());
+    }
+
+    @Test
+    public void shouldConsumeCharactersOneByOne() {
+        CharStream chars = new CharStream("abc");
+        assertEquals('a', chars.consume());
+        assertEquals('b', chars.consume());
+        assertEquals('c', chars.consume());
+    }
+
+    @Test
+    public void shouldReturnEolWhenNothingLeftToConsume() {
+        CharStream chars = new CharStream("abc");
+        assertEquals('a', chars.consume());
+        assertEquals('b', chars.consume());
+        assertEquals('c', chars.consume());
+        assertEquals(CharStream.EOL, chars.consume());
+    }
+
+    @Test
+    public void shouldRaiseErrorWhenUnexpectedCharTypeConsumed() {
+        CharStream chars = new CharStream("abc");
+        try {
+            chars.consume(Char.DIGIT);
+        } catch (UnexpectedCharacterException e) {
+            return;
+        }
+        fail("Should raise error when unexpected character type is consumed");
+    }
+
+    @Test
+    public void shouldLookaheadWithoutConsuming() {
+        CharStream chars = new CharStream("abc");
+        assertEquals('a', chars.lookahead());
+        assertEquals('a', chars.lookahead());
+    }
+
+    @Test
+    public void shouldLookaheadArbitraryNumberOfCharacters() {
+        CharStream chars = new CharStream("abc");
+        assertEquals('a', chars.lookahead(1));
+        assertEquals('b', chars.lookahead(2));
+        assertEquals('c', chars.lookahead(3));
+    }
+
+    @Test
+    public void shouldReturnEolWhenNothingLeftToLookahead() {
+        CharStream chars = new CharStream("abc");
+        assertEquals('a', chars.consume());
+        assertEquals('b', chars.consume());
+        assertEquals('c', chars.consume());
+        assertEquals(CharStream.EOL, chars.lookahead());
+    }
+
+    @Test
+    public void shouldCheckIfLookaheadIsOfExpectedTypes() {
+        CharStream chars = new CharStream("abc");
+        assertTrue(chars.positiveLookahead(Char.LETTER));
+        assertFalse(chars.positiveLookahead(Char.DIGIT, Char.PLUS));
+    }
+
+    @Test
+    public void shouldCheckIfCharOfExpectedTypesExistBeforeGivenType() {
+        CharStream chars = new CharStream("1.0.0");
+        assertTrue(chars.positiveLookaheadBefore(Char.EOL, Char.DOT));
+        assertFalse(chars.positiveLookaheadBefore(Char.EOL, Char.LETTER));
+    }
+}
diff --git a/src/test/java/com/github/zafarkhaja/semver/VersionParserCharTest.java b/src/test/java/com/github/zafarkhaja/semver/VersionParserCharTest.java
new file mode 100644
index 0000000..3d4741c
--- /dev/null
+++ b/src/test/java/com/github/zafarkhaja/semver/VersionParserCharTest.java
@@ -0,0 +1,84 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+import com.github.zafarkhaja.semver.VersionParser.Char;
+import com.github.zafarkhaja.semver.VersionParser.CharStream;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+public class VersionParserCharTest {
+
+    @Test
+    public void shouldBeMatchedByDigit() {
+        assertTrue(Char.DIGIT.isMatchedBy('0'));
+        assertTrue(Char.DIGIT.isMatchedBy('9'));
+        assertFalse(Char.DIGIT.isMatchedBy('a'));
+        assertFalse(Char.DIGIT.isMatchedBy('A'));
+    }
+
+    @Test
+    public void shouldBeMatchedByLetter() {
+        assertTrue(Char.LETTER.isMatchedBy('a'));
+        assertTrue(Char.LETTER.isMatchedBy('A'));
+        assertFalse(Char.LETTER.isMatchedBy('0'));
+        assertFalse(Char.LETTER.isMatchedBy('9'));
+    }
+
+    @Test
+    public void shouldBeMatchedByDot() {
+        assertTrue(Char.DOT.isMatchedBy('.'));
+        assertFalse(Char.DOT.isMatchedBy('-'));
+        assertFalse(Char.DOT.isMatchedBy('0'));
+        assertFalse(Char.DOT.isMatchedBy('9'));
+    }
+
+    @Test
+    public void shouldBeMatchedByHyphen() {
+        assertTrue(Char.HYPHEN.isMatchedBy('-'));
+        assertFalse(Char.HYPHEN.isMatchedBy('+'));
+        assertFalse(Char.HYPHEN.isMatchedBy('a'));
+        assertFalse(Char.HYPHEN.isMatchedBy('0'));
+    }
+
+    @Test
+    public void shouldBeMatchedByPlus() {
+        assertTrue(Char.PLUS.isMatchedBy('+'));
+        assertFalse(Char.PLUS.isMatchedBy('-'));
+        assertFalse(Char.PLUS.isMatchedBy('a'));
+        assertFalse(Char.PLUS.isMatchedBy('0'));
+    }
+
+    @Test
+    public void shouldBeMatchedByEol() {
+        assertTrue(Char.EOL.isMatchedBy(CharStream.EOL));
+        assertFalse(Char.EOL.isMatchedBy('-'));
+        assertFalse(Char.EOL.isMatchedBy('a'));
+        assertFalse(Char.EOL.isMatchedBy('0'));
+    }
+}
diff --git a/src/test/java/com/github/zafarkhaja/semver/VersionParserTest.java b/src/test/java/com/github/zafarkhaja/semver/VersionParserTest.java
new file mode 100644
index 0000000..6831c7a
--- /dev/null
+++ b/src/test/java/com/github/zafarkhaja/semver/VersionParserTest.java
@@ -0,0 +1,115 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2013 Zafar Khaja <zafarkhaja at gmail.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package com.github.zafarkhaja.semver;
+
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Zafar Khaja <zafarkhaja at gmail.com>
+ */
+public class VersionParserTest {
+
+    @Test
+    public void shouldParseNormalVersion() {
+        NormalVersion version = VersionParser.parseVersionCore("1.0.0");
+        assertEquals(new NormalVersion(1, 0, 0), version);
+    }
+
+    @Test
+    public void shouldRaiseErrorIfNumericIdentifierHasLeadingZeroes() {
+        try {
+            VersionParser.parseVersionCore("01.1.0");
+        } catch (GrammarException e) {
+            return;
+        }
+        fail("Numeric identifier MUST NOT contain leading zeroes");
+    }
+
+    @Test
+    public void shouldParsePreReleaseVersion() {
+        MetadataVersion preRelease = VersionParser.parsePreRelease("beta-1.1");
+        assertEquals(new MetadataVersion(new String[] {"beta-1", "1"}), preRelease);
+    }
+
+    @Test
+    public void shouldNotAllowDigitsInPreReleaseVersion() {
+        try {
+            VersionParser.parsePreRelease("alpha.01");
+        } catch (ParserException e) {
+            return;
+        }
+        fail("Should not allow digits in pre-release version");
+    }
+
+    @Test
+    public void shouldRaiseErrorForEmptyPreReleaseIdentifier() {
+        try {
+            VersionParser.parsePreRelease("beta-1..1");
+        } catch (GrammarException e) {
+            return;
+        }
+        fail("Identifiers MUST NOT be empty");
+    }
+
+    @Test
+    public void shouldParseBuildMetadata() {
+        MetadataVersion build = VersionParser.parseBuild("build.1");
+        assertEquals(new MetadataVersion(new String[] {"build", "1"}), build);
+    }
+
+    @Test
+    public void shouldAllowDigitsInBuildMetadata() {
+        try {
+            VersionParser.parseBuild("build.01");
+        } catch (ParserException e) {
+            fail("Should allow digits in build metadata");
+        }
+    }
+
+    @Test
+    public void shouldRaiseErrorForEmptyBuildIdentifier() {
+        try {
+            VersionParser.parseBuild(".build.01");
+        } catch (GrammarException e) {
+            return;
+        }
+        fail("Identifiers MUST NOT be empty");
+    }
+
+    @Test
+    public void shouldParseValidSemVer() {
+        VersionParser parser = new VersionParser("1.0.0-rc.2+build.05");
+        Version version = parser.parse(null);
+        assertEquals(
+            new Version(
+                new NormalVersion(1, 0, 0),
+                new MetadataVersion(new String[] {"rc", "2"}),
+                new MetadataVersion(new String[] {"build", "05"})
+            ),
+            version
+        );
+    }
+}
diff --git a/src/test/java/com/github/zafarkhaja/semver/VersionTest.java b/src/test/java/com/github/zafarkhaja/semver/VersionTest.java
index 9533b51..af65488 100644
--- a/src/test/java/com/github/zafarkhaja/semver/VersionTest.java
+++ b/src/test/java/com/github/zafarkhaja/semver/VersionTest.java
@@ -23,10 +23,10 @@
  */
 package com.github.zafarkhaja.semver;
 
-import static org.junit.Assert.*;
 import org.junit.Test;
 import org.junit.experimental.runners.Enclosed;
 import org.junit.runner.RunWith;
+import static org.junit.Assert.*;
 
 /**
  *
@@ -118,10 +118,15 @@ public class VersionTest {
             String[] versions = {
                 "1.0.0-alpha",
                 "1.0.0-alpha.1",
+                "1.0.0-alpha.beta",
+                "1.0.0-beta",
                 "1.0.0-beta.2",
                 "1.0.0-beta.11",
                 "1.0.0-rc.1",
                 "1.0.0",
+                "2.0.0",
+                "2.1.0",
+                "2.1.1"
             };
             for (int i = 1; i < versions.length; i++) {
                 Version v1 = Version.valueOf(versions[i-1]);
@@ -235,7 +240,7 @@ public class VersionTest {
         public void shouldThrowExceptionWhenIncrementingPreReleaseIfItsNull() {
             Version v1 = Version.valueOf("1.0.0");
             try {
-                Version v2 = v1.incrementPreReleaseVersion();
+                v1.incrementPreReleaseVersion();
             } catch (NullPointerException e) {
                 return;
             }
@@ -260,7 +265,7 @@ public class VersionTest {
         public void shouldThrowExceptionWhenIncrementingBuildIfItsNull() {
             Version v1 = Version.valueOf("1.0.0");
             try {
-                Version v2 = v1.incrementBuildMetadata();
+                v1.incrementBuildMetadata();
             } catch (NullPointerException e) {
                 return;
             }
@@ -377,7 +382,7 @@ public class VersionTest {
         @Test
         public void shouldThrowNullPointerExceptionIfNormalVersionIsNull() {
             try {
-                Version.Builder builder = new Version.Builder(null);
+                new Version.Builder(null);
             } catch (NullPointerException e) {
                 return;
             }

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



More information about the pkg-java-commits mailing list