001    /*
002     * Jpkg - Java library and tools for operating system package creation.
003     *
004     * Copyright (c) 2007 Three Rings Design, Inc.
005     * All rights reserved.
006     *
007     * Redistribution and use in source and binary forms, with or without
008     * modification, are permitted provided that the following conditions
009     * are met:
010     * 1. Redistributions of source code must retain the above copyright
011     *    notice, this list of conditions and the following disclaimer.
012     * 2. Redistributions in binary form must reproduce the above copyright
013     *    notice, this list of conditions and the following disclaimer in the
014     *    documentation and/or other materials provided with the distribution.
015     * 3. Neither the name of the copyright owner nor the names of contributors
016     *    may be used to endorse or promote products derived from this software
017     *    without specific prior written permission.
018     *
019     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
021     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
023     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
024     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
025     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
026     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
027     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
029     * POSSIBILITY OF SUCH DAMAGE.
030     */
031    package com.threerings.jpkg.debian;
032    
033    import java.util.regex.Pattern;
034    
035    /**
036     * Holds and parses the Debian package version.
037     * @see <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version">Debian Policy Manual</a>
038     */
039    public class PackageVersion
040        implements ControlFileData
041    {
042        /**
043         * Construct a fully populated PackageVersion object with the "upstream_version" field set
044         * to the supplied value.
045         * The supplied string may only contain alphanumeric characters, ".", "+", and "~".
046         * The Debian version and epoch fields of the version field will not be set, meaning they
047         * will have the default values assigned to them by the dpkg tools.
048         * If the supplied string does not match these rules a ControlDataInvalidException will be
049         * thrown.
050         * @throws ControlDataInvalidException
051         */
052        public PackageVersion (String upstream_version)
053            throws ControlDataInvalidException
054        {
055            this(upstream_version, DEFAULT_DEBIAN_VERSION, DEFAULT_EPOCH);
056        }
057    
058        /**
059         * Construct a fully populated PackageVersion object with the "upstream_version",
060         * "debian_version", and "epoch" set.
061         * The upstream version may only contain alphanumeric characters, ".", "+", "-", ":", and "~".
062         * The Debian version may only contain alphanumeric characters, "+", ".", and "~".
063         * It is conventional to start the Debian version at 1.
064         * Epoch must be 0 or greater. 0 is a safe default and indicates no epoch.
065         * If the supplied string does not match these rules a ControlDataInvalidException will be
066         * thrown.
067         * @throws ControlDataInvalidException
068         */
069        public PackageVersion (String upstream_version, String debian_version, int epoch)
070            throws ControlDataInvalidException
071        {
072            _upstreamVersion = validateUpstreamVersion(upstream_version);
073            _debianVersion = validateDebianVersion(debian_version);
074            _epoch = validateEpoch(epoch);
075        }
076    
077        // from ControlFileData
078        public String getField ()
079        {
080            return "Version";
081        }
082    
083        // from ControlFileData
084        public String getFieldValue ()
085        {
086            final StringBuilder builder = new StringBuilder();
087            if (_epoch != DEFAULT_EPOCH) {
088                builder.append(_epoch).append(':');
089            }
090            builder.append(_upstreamVersion);
091            if (!_debianVersion.equals(DEFAULT_DEBIAN_VERSION)) {
092                builder.append('-').append(_debianVersion);
093            }
094            return builder.toString();
095        }
096    
097        /**
098         * Validate the supplied package upstream version.
099         */
100        private String validateUpstreamVersion (String version)
101            throws ControlDataInvalidException
102        {
103            if (!UPSTREAM_PATTERN.matcher(version).matches()) {
104                throw new ControlDataInvalidException(
105                    "Upstream version must match the pattern. version=[" + version + "] pattern=[" + UPSTREAM_PATTERN.pattern() + "]");
106            }
107    
108            return version;
109        }
110    
111        /**
112         * Validate the supplied package Debian version.
113         */
114        private String validateDebianVersion (String version)
115            throws ControlDataInvalidException
116        {
117            if (!DEBIAN_PATTERN.matcher(version).matches()) {
118                throw new ControlDataInvalidException(
119                    "Debian version must match the pattern. version=[" + version + "] pattern=[" + DEBIAN_PATTERN.pattern() + "]");
120            }
121    
122            return version;
123        }
124    
125        /**
126         * Validate the supplied package Epoch.
127         */
128        private int validateEpoch (int epoch)
129            throws ControlDataInvalidException
130        {
131            if (!(epoch >= 0)) {
132                throw new ControlDataInvalidException(
133                    "Epoch must be equal to or greater than 0. epoch=[" + epoch + "]");
134            }
135    
136            return epoch;
137        }
138    
139        /**
140         * The regex against which the package upstream version must match.
141         * From: <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version">Debian Policy Manual</a>
142         * "The upstream_version may contain only alphanumerics and the characters . + - : ~
143         * (full stop, plus, hyphen, colon, tilde) and should start with a digit." If the Debian version
144         * is set, the upstream version may not contain a "-" and if the epoch is set, it may not contain
145         * a ":". We will simply never allow it to contain either of these characters for clarity.
146         */
147        private static final Pattern UPSTREAM_PATTERN = Pattern.compile("[\\p{Digit}][\\p{Alnum}.+~]*");
148    
149        /**
150         * The regex against which the package upstream version must match.
151         * From: <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s-f-Version">Debian Policy Manual</a>
152         * "This part of the version number specifies the version of the Debian package based on the
153         * upstream version. It may contain only alphanumerics and the characters + . ~
154         * (plus, full stop, tilde) and is compared in the same way as the upstream_version is."
155         */
156        private static final Pattern DEBIAN_PATTERN = Pattern.compile("[\\p{Alnum}+.~]+");
157    
158        /** The Debian project recommended default value for the Debian version. */
159        private static final String DEFAULT_DEBIAN_VERSION = "1";
160    
161        /** The default value for the epoch, indicating no epoch. */
162        private static final int DEFAULT_EPOCH = 0;
163    
164        /** The package upstream version. */
165        private final String _upstreamVersion;
166    
167        /** The package Debian version. */
168        private final String _debianVersion;
169    
170        /** The package epoch. */
171        private final int _epoch;
172    }