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}