001package votorola.a.diff; // Copyright 2011-2013, Michael Allan.  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Votorola Software"), to deal in the Votorola Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Votorola Software, and to permit persons to whom the Votorola Software is furnished to do so, subject to the following conditions: The preceding copyright notice and this permission notice shall be included in all copies or substantial portions of the Votorola Software. THE VOTOROLA 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 VOTOROLA SOFTWARE OR THE USE OR OTHER DEALINGS IN THE VOTOROLA SOFTWARE.
002
003import votorola.a.count.*;
004import votorola.g.lang.*;
005
006
007/** The identifier of a difference between two draft revisions (a and b).  A difference
008  * key is unique within the context of a single vote-server.  It is expressed as a string
009  * in the following form:<pre>
010  *
011  *     a0 [. a1 [. a2]] - b0 [. b1 [. b2]] [! series]
012  *    |----------------| |----------------|
013  *     revision path (a)  revision path (b)</pre>
014  *
015  * <p>For example:</p><pre
016  *
017 *>     9712-8303.441
018  *
019  *     9059.9863.524-9360.9851.486!1</pre>
020  *
021  * <p>Each of the two draft revisions (left and right) is identified by a sequence of one
022  * to {@value votorola.a.position.DraftRevision#MAX_PATH_LENGTH} page revisions that is
023  * known as a {@linkplain votorola.a.position.DraftRevision draft revision path}.  A
024  * series identifier is appended if the {@linkplain
025  * votorola.a.PollwikiVS#revisionSeries() pollwiki revision series} is non-zero.</p>
026  *
027  * <h3 id='ord'>Normal order between draft revisions (R/C/U)</h3>
028  *
029  * <p>Which of the two draft revisions is expressed as 'a' and which as 'b' is determined
030  * by the first applicable rule from the rule lists below.  The following rule list
031  * applies only if both revisions have the same author.  In that case they are ordered
032  * numerically by revision number.  Traversing the two revision paths beginning at index
033  * zero yields a sequence of numeric pairs, one of which eventually differs:</p><ul>
034  *
035  *     <li>(R1) Where the difference is that one number of the pair is smaller, that one
036  *     is 'b'.</li>
037  *
038  *     <li>(R2) Otherwise the difference is that one number of the pair is missing
039  *     because its revision path is shorter.  That one is 'b'.</li>
040  *
041  * </ul><p>The next rule list applies only if both revisions are position drafts in the
042  * same poll.  In that case they are ordered by exclusive cast relation:</p><ul>
043  *
044  *     <li>(C1) Where the exclusive cast relation of the authors is {@linkplain
045  *     votorola.a.count.XCastRelation#CO_BASE base co-candidate} and exactly one is the
046  *     direct recipient of the other's vote (non-tight cycle), then that recipient's
047  *     revision is 'b'.</li>
048  *
049  *     <li>(C2) Where the exclusive cast relation of one author for the other is
050  *     {@linkplain votorola.a.count.XCastRelation#VOTER voter}, that author's revision is
051  *     'a'.</li>
052  *
053  *     <li>(C3) Where the exclusive cast relation of the authors is {@linkplain
054  *     votorola.a.count.XCastRelation#CO_BASE base co-candidate} (non-cyclic or tight
055  *     cyclic) or {@linkplain votorola.a.count.XCastRelation#CO_VOTER co-voter} and the
056  *     {@linkplain votorola.a.count.CountNode#dartSector() dart sector} of one is less
057  *     than the other, that author's revision is 'a'.</li>
058  *
059  * </ul><p>Otherwise the order is lexical by username:</p><ul>
060  *
061  *     <li>(U1) Where the username of one author is prior to the other in a {@linkplain
062  *     String#compareToIgnoreCase(String) caseless comparison}, that author's revision is
063  *     'a'.</li>
064  *
065  *     <li>(U2) Otherwise the username of one author must be prior to the other in a
066  *     {@linkplain String#compareTo(String) cased comparison}.  That author's revision is
067  *     'a'.</li>
068  *
069  * </ul>
070  */
071public @ThreadSafe final class DiffKey
072{
073
074    private DiffKey() {}
075
076
077
078    /** Answers whether <code>aNode</code> is dart-wise or lexically prior to
079      * <code>bNode</code> for purposes of {@linkplain DiffKey normal ordering}.
080      *
081      *     @throws IllegalArgumentException if <code>aName</code> and
082      *       <code>bName</code> are equal.
083      */
084    public static boolean isDartOrdered( final CountNode aNode, final String aName,
085                                         final CountNode bNode, final String bName )
086    {
087        final byte aS = aNode.dartSector();
088        final byte bS = bNode.dartSector();
089        return aS == bS? isLexicallyOrdered(aName,bName): aS < bS;
090    }
091
092
093
094    /** Answers whether <code>aName</code> is lexically prior to <code>bName</code> for
095      * purposes of {@linkplain DiffKey normal ordering}.
096      *
097      *     @throws IllegalArgumentException if <code>aName</code> and
098      *       <code>bName</code> are equal.
099      */
100    public static boolean isLexicallyOrdered( final String aName, final String bName )
101    {
102        int signum = aName.compareToIgnoreCase( bName );
103        if( signum == 0 )
104        {
105            signum = aName.compareTo( bName );
106            if( signum == 0 ) throw new IllegalArgumentException();
107        }
108
109        return signum < 0;
110    }
111
112
113
114    /** Constructs the reverse of the specified diff key in which the a-draft revision
115      * path and b-draft revision path are interchanged.
116      */
117    public static String newReverseKey( final String diffKey )
118    {
119        final int aEndBound = diffKey.indexOf( '-' );
120        if( aEndBound < 0 ) throw new IllegalArgumentException();
121
122        final int bStart = aEndBound + 1;
123        final String seriesSpecifier; // with its leading ! delimiter
124        int bEndBound = diffKey.indexOf( '!', bStart + 1 );
125        if( bEndBound < 0 )
126        {
127            seriesSpecifier = "";
128            bEndBound = diffKey.length();
129        }
130        else seriesSpecifier = diffKey.substring( bEndBound );
131        return diffKey.substring(bStart,bEndBound) + '-' + diffKey.substring(0,aEndBound)
132          + seriesSpecifier;
133    }
134
135
136}