001package votorola.a.position; // Copyright 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 java.io.*; 004import java.util.*; 005import votorola.a.*; 006import votorola.a.count.*; 007import votorola.g.*; 008import votorola.g.lang.*; 009 010 011/** A component pipe revision constructed nominally for the latest revision, not for a 012 * specific {@linkplain DraftRevision draft revision path}. 013 */ 014public final @ThreadSafe class ComponentPipeRevisionL extends ComponentPipeRevision 015{ 016 017 018 /** Partially constructs a ComponentPipeRevisionL for {@linkplain 019 * #init(PollwikiVS,String,CountSource) init}, or {@linkplain 020 * #init1(PollwikiVS,String,CountSource) init1} and {@linkplain 021 * #init2(List,PollwikiVS,List,boolean) init2} to finish. 022 * 023 * @see #pageID() 024 * @param _pageName will be normalized for the {@linkplain #pageName() pageName}. 025 * @see #rev() 026 * @see #revLatest() 027 * @see #nominee() 028 * @see #person() 029 * @see #pollName() 030 */ 031 ComponentPipeRevisionL( final PollwikiVS wiki, int _pageID, String _pageName, int _rev, 032 int _revLatest, String _nomineeName, String _personName, final String pollName ) 033 throws MalformedContent 034 { 035 super( wiki, _pageID, _pageName, _rev, _revLatest, _nomineeName, _personName, pollName ); 036 wildVariantName = wiki.positionPageName( nominee().username(), pollName ); 037 } 038 039 040 041 /** {@inheritDoc} Call either this method, or {@linkplain 042 * #init1(PollwikiVS,String,CountSource) init1} and {@linkplain 043 * #init2(List,PollwikiVS,List,boolean) init2}, not both. 044 */ 045 public @ThreadRestricted("constructor") void init( final PollwikiVS wiki, 046 final String contextPersonName, final CountSource countSource ) throws IOException 047 { 048 init1( wiki, contextPersonName, countSource ); 049 init2( Collections.singletonList(ComponentPipeRevisionL.this), wiki, 050 ThrowableX.ENLIST_NONE, /*toContinue*/false ); 051 // init2() meets API contract regarding duplicate call to init() 052 if( candidateVariant != null ) candidateVariant.init( wiki, contextPersonName, countSource ); 053 wildVariant.init( wiki, contextPersonName, countSource ); 054 } 055 056 057 058 /** Continues the construction of this ComponentPipeRevisionL by setting the 059 * {@linkplain #candidateVariantName() candidate variant name} if it is not already 060 * set and the proposed pairing appears to require it. This method may be called 061 * multiple times. Call either {@linkplain #init(PollwikiVS,String,CountSource) 062 * init}, or this method and {@linkplain #init2(List,PollwikiVS,List,boolean) init2}, 063 * not both. 064 * 065 * @param contextPersonName the name of a person from whose viewpoint this 066 * revision will eventually be viewed. 067 * @param countSource for possible use during this call. 068 */ 069 public @ThreadRestricted("constructor") void init1( final PollwikiVS wiki, 070 final String contextPersonName, final CountSource countSource ) throws IOException 071 { 072 if( candidateVariantName != null ) return; // already set 073 074 if( nominee().username().equals( contextPersonName )) // from viewpoint of wild variant 075 { 076 final Count count = countSource.count( pollName() ); 077 if( count != null ) try 078 { 079 final CountNode thisNode = count.countTablePV().get( person().email() ); 080 if( thisNode != null ) 081 { 082 final String candidateName = thisNode.candidateName(); 083 if( candidateName != null ) 084 { 085 // will diff the wild variant against this pipe's candidate: 086 candidateVariantName = wiki.positionPageName( candidateName, pollName() ); 087 } 088 } 089 } 090 // Failing these, the wild variant will get a latest pairing that effectively 091 // self-diffs. The situation will correct itself whenever the candidate 092 // becomes known. 093 catch( final java.sql.SQLException|javax.xml.stream.XMLStreamException x ) 094 { 095 throw new IOException( x ); 096 } 097 } 098 } 099 100 101 102 /** Finishes the construction of the specifed component pipe revisions and sets 103 * {@linkplain #isFullyConstructed() isFullyConstructed} true for each. Call either 104 * {@linkplain #init(PollwikiVS,String,CountSource) init}, or {@linkplain 105 * #init1(PollwikiVS,String,CountSource) init1} and this method, not both. 106 * 107 * @param components the list of partially constructed component pipe revisions. 108 * @param userXList a pre-constructed list to which any user actionable 109 * exceptions are to be appended, or null if none was pre-constructed. Provide 110 * {@linkplain ThrowableX#ENLIST_NONE ENLIST_NONE} to instead force the 111 * immediate throwing of such exceptions. 112 * @param toContinue whether to continue in the event of a missing wild variant 113 * and to leave the incompletely constructed component pipe revision as is 114 * (true), or to throw a NoSuchPage exception (false). 115 * 116 * @return the same userXList if there was one; otherwise null if no 117 * exceptions were listed; otherwise a newly constructed list. 118 * 119 * @throws IllegalStateException if called a second time for a given component 120 * pipe revision. 121 * @throws MediaWiki.NoSuchPage if toContinue is false and a wild variant is 122 * missing. 123 */ 124 public static @ThreadRestricted("constructor") List<Throwable> init2( 125 final List<ComponentPipeRevisionL> components, final PollwikiVS wiki, 126 List<Throwable> userXList, final boolean toContinue ) throws IOException 127 { 128 // cf. DraftPair.newDraftPair(DiffKeyParse,..) "Initialize any component pipes..." 129 130 final ArrayList<String> variantNames; 131 { 132 final int cN = components.size(); 133 if( cN == 0 ) return userXList; // nothing to do 134 135 variantNames = new ArrayList<>( cN ); 136 } 137 138 for( final ComponentPipeRevisionL component: components ) 139 { 140 variantNames.add( component.wildVariantName ); 141 final String candidateVariantName = component.candidateVariantName; 142 if( candidateVariantName != null ) variantNames.add( candidateVariantName ); 143 } 144 final LinkedList<CoreRevision> variants = new LinkedList<>(); 145 try 146 { 147 userXList = CoreRevision.U.partialFromPageNames( variantNames, variants, 148 /*componentPipeClass*/null/*even where a page name designates candidate not 149 nominee, still it should not be a component because components are not 150 intended to directly interconnect*/, wiki, userXList, toContinue ); 151 } 152 catch( final MediaWiki.NoSuchPage x ) { throw newMissingVariant( x, components, wiki ); } 153 154 for( final ComponentPipeRevisionL component: components ) 155 { 156 // response order unpredictable, search for corresponding variant(s): 157 for( final CoreRevision variant: variants ) 158 { 159 final String variantName = variant.pageName(); 160 if( variantName.equals( component.candidateVariantName )) 161 { 162 component.candidateVariant = variant; 163 } 164 if( variantName.equals( component.wildVariantName )) component.init2( variant ); 165 // keep searching variants, either candidate or wild may still need setting 166 } 167 } 168 return userXList; 169 } 170 171 172 173 private @ThreadRestricted("constructor") void init2( CoreRevision _wildVariant ) 174 { 175 if( isFullyConstructed ) throw new IllegalStateException(); 176 // per API contract of all init methods 177 178 wildVariant = _wildVariant; 179 // if( wildVariant == null ) throw new NullPointerException(); // fail fast 180 /// done here: 181 if( !wildVariant.pageName().equals( wildVariantName )) 182 { 183 throw new IllegalArgumentException( "expecting variant '" + wildVariantName 184 + "', recieved: " + wildVariant.pageName() ); 185 } 186 187 isFullyConstructed = true; 188 } 189 190 191 192 // ------------------------------------------------------------------------------------ 193 194 195 /** The core of the position this pipe is voting for as resolved from the candidate 196 * variant name, or null if no candidate variant name was set during construction. 197 */ 198 public CoreRevision candidateVariant() { return candidateVariant; } 199 200 201 private CoreRevision candidateVariant; // final when fully constructed 202 203 204 205 /** The page name of the candidate's position core including the namespace, or null if 206 * this information is unknown or not required. It is required only if this 207 * component pipe revision will be viewed from the context of its own wild variant, 208 * among other possible views. 209 * 210 * @see #init1(PollwikiVS,String,CountSource) 211 */ 212 public String candidateVariantName() { return candidateVariantName; } 213 214 215 private String candidateVariantName; // final when fully constructed 216 217 218 219 /** The wild variant indicated by this component pipe. 220 */ 221 public CoreRevision wildVariant() { return wildVariant; } 222 223 224 private CoreRevision wildVariant; // final when fully constructed, never null then 225 226 227 228 /** The page name of the wild variant including the namespace. 229 */ 230 public String wildVariantName() { return wildVariantName; } 231 232 233 private final String wildVariantName; 234 235 236 237 // - C o r e - R e v i s i o n -------------------------------------------------------- 238 239 240 public List<Integer> addDraftRevisionPath( final List<Integer> path ) 241 { 242 path.add( rev() ); 243 return wildVariant.addDraftRevisionPath( path ); 244 } 245 246 247 248 public CoreRevision contextView( final String contextPersonName ) 249 { 250 return nominee().username().equals(contextPersonName)? 251 new ComponentPipeRevisionWV(ComponentPipeRevisionL.this): ComponentPipeRevisionL.this; 252 } 253 254 255 256 public DraftRevision draft() { return wildVariant.draft(); } 257 258 259 260 /** @see #init1(PollwikiVS,String,CountSource) 261 * @see #init2(List,PollwikiVS,List,boolean) 262 */ 263 public boolean isFullyConstructed() { return isFullyConstructed; } 264 265 266 private boolean isFullyConstructed; 267 268 269 270//// P r i v a t e /////////////////////////////////////////////////////////////////////// 271 272 273 /** Constructs an appropriate exception to throw when the position draft of a 274 * component pipe's nominee or candidate is missing. 275 * 276 * @param x the original exception that signaled the missing draft. 277 * @param components a list of component pipes including the broken one. 278 */ 279 private static IOException newMissingVariant( final MediaWiki.NoSuchPage x, 280 final List<ComponentPipeRevisionL> components, final PollwikiVS wiki ) 281 { 282 final String variantName = x.pageName(); // find corresponding component: 283 for( final ComponentPipeRevisionL co: components ) 284 { 285 if( variantName.equals( co.wildVariantName )) 286 { 287 return new MalformedContent( "nominee has no draft", x, wiki, co.rev() ); 288 } 289 290 if( variantName.equals( co.candidateVariantName )) 291 { 292 return new MalformedContent( "candidate has no draft", x, wiki, co.rev() ); 293 } 294 } 295 296 assert false; 297 return x; 298 } 299 300 301}