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}