001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.bio.program.blast2html;
023
024import java.io.PrintWriter;
025import java.util.Arrays;
026import java.util.List;
027import java.util.Properties;
028
029/**
030 * Renders HTML version of blast-like output.<p>
031 *
032 * Makes an assumption that the gap character is '-',
033 * should parameterize.
034 *
035 *
036 * Primary author -
037 *                 Colin Hardman      (CAT)
038 * Other authors  -
039 *                 Tim Dilks          (CAT)
040 *                 Simon Brocklehurst (CAT)
041 *                 Stuart Johnston    (CAT)
042 *                 Lawerence Bower    (CAT)
043 *                 Derek Crockford    (CAT)
044 *                 Neil Benn          (CAT)
045 *
046 * Copyright 2001 Cambridge Antibody Technology Group plc.
047 *
048 * This code released to the biojava project, May 2001
049 * under the LGPL license.
050 *
051 * @author Cambridge Antibody Technology Group plc
052 * @author Greg Cox
053 * @version 1.0
054 */
055public class HTMLRenderer  {
056
057    private StringBuffer align = new StringBuffer();
058    private StringBuffer alignMarkUp1 = new StringBuffer();
059    private StringBuffer alignMarkUp2 = new StringBuffer();
060
061    private PrintWriter out = null;
062    private String      oStyleDef;
063    private StringBuffer pcDataBuffer = new StringBuffer( 80 );
064
065    private boolean tAlternateSummary = false;
066    private int     iSummaryCount =0;
067
068    private boolean tNeedComma = false;
069
070    /**
071     * The width, in characters, of the sequence alignments
072     */
073    private int iAlignLen = 50;
074
075    private List             oURLGeneratorList = null;
076    private DatabaseURLGenerator oFirstURLGenerator = null;
077
078    private AlignmentMarker oAlignmentMarker;
079
080    private static String oJavaScriptDef
081        = "<script language=\"JavaScript\">\nfunction setStatus(msgStr) { \n status=msgStr;\n  document.retVal = true;\n}\n</script>";
082
083    private char[] padding = new char[100];
084
085    {
086        Arrays.fill( padding,
087                     0,
088                     100,
089                     ' ' );
090    }
091
092    private Properties oOptions = null;
093
094    /**
095     * Flag to detect if HTML output was empty.
096     */
097    private boolean wasEmpty = true;
098
099    /**
100     * Creates an HTMLRenderer, that outputs the HTML to the specified
101     * PrintWriter. <p>
102     *
103     * The style definition is expected to defines the following styles
104     * <UL>
105     *  <LI> footer
106     *  <LI> alignment
107     *  <LI> dbRetrieve
108     *  <LI> titleLevel1
109     *  <LI> titleLevel1Sub
110     *  <LI> titleLevel2
111     *  <LI> titleLevel3
112     *  <LI> titleLevel3Sub
113     *  <LI> titleLevel4
114     *  <LI> summaryBodyLineOdd
115     *  <LI> summaryBodyLineEven
116     * </UL
117     *
118     * The Javascript function setStatus overrides status text for some links.
119     *
120     * The URLGeneratorFactory provides a list of objects that can convert a
121     * database ID into a URL for fetching the entry. Obviously this requires
122     * the ID to be parsed correctly.<B>
123     * These URL's are used in two places, in the summary and detail sections.
124     * The first URLGenerator is used for the summary, and all for the detail.
125     *
126     *
127     * @param poPrintWriter - the output stream to write the HTML to.
128     * @param poStyleDef - definition of styles, if null uses hard coded.
129     * @param piAlignmentWidth width in characters of the alignment regions
130     *                         of output
131     * @param poFactory <code>URLGeneratorFactory</code> provides an array of
132     *                  <code>DatabaseURLGenerator</code>s, null for no links
133     * @param poAlignmentMarker - for configurable markup of alignments,
134     *                            for no markup use null.
135     * @param poOptions properties of
136     */
137    public HTMLRenderer( PrintWriter poPrintWriter,
138                         String      poStyleDef,
139                         int         piAlignmentWidth,
140                         URLGeneratorFactory poFactory,
141                         AlignmentMarker poAlignmentMarker,
142                         Properties  poOptions ) {
143
144        oOptions = poOptions;
145
146        if ( poFactory != null ) {
147
148            oURLGeneratorList = poFactory.getDatabaseURLGenerators();
149            if ( !oURLGeneratorList.isEmpty() ) {
150                oFirstURLGenerator
151                    = (DatabaseURLGenerator) oURLGeneratorList.get( 0 );
152            }
153        }
154
155        if ( piAlignmentWidth <= 0 ) {
156            throw new IllegalArgumentException
157                ( "Alignment length must be > 0, not " + piAlignmentWidth );
158        }
159
160        iAlignLen = piAlignmentWidth;
161        oStyleDef = poStyleDef ;
162
163        if ( poPrintWriter == null ) {
164            throw new IllegalArgumentException
165                ( "PrintWriter cannot be null" );
166        }
167
168        out = poPrintWriter;
169        oAlignmentMarker = poAlignmentMarker;
170    }
171
172    /**
173     * Set the <CODE>PrintWriter</CODE> to output the HTML
174     * to.
175     *
176     * @param poPrintWriter a <code>PrintWriter</code>
177     */
178    public void setPrintWriter( PrintWriter poPrintWriter ) {
179
180        out = poPrintWriter;
181
182        // initialize other stuff
183        tAlternateSummary = false;
184        iSummaryCount =0;
185        tNeedComma = false;
186        wasEmpty = true;
187
188        align.setLength( 0 );
189        alignMarkUp1.setLength( 0 );
190        alignMarkUp2.setLength( 0 );
191        pcDataBuffer.setLength( 0 );
192    }
193
194    /**
195     * Returns true if  no HitSummary and no Hit elements were
196     * encountered
197     *
198     * @return <code>boolean</code> - true if no summary of Hit elements
199     *          where encountered.
200     */
201    public boolean wasEmpty() {
202        return wasEmpty;
203    }
204
205    /**
206     * Writes out the header section of the report.
207     *
208     * @param oProgram a <code>String</code> - eg 'ncbi-blastp'
209     * @param oVersion a <code>String</code> - eg '2.2.1'
210     * @param oQuery a <code>String</code>   - eg 'my query id'
211     * @param oDatabase a <code>String</code>- eg 'embl'
212     */
213    void writeTitleAndHeader( String oProgram, String oVersion,
214                              String oQuery,   String oDatabase ) {
215
216        out.print( "<TITLE>" );
217        out.print( oProgram );
218        out.print( " version " );
219        out.print( oVersion );
220        out.print( " Search Results Query ID : " );
221        out.print( oQuery );
222        out.println( "</TITLE>" );
223
224        out.println( "<table width=\"100%\" border=\"0\" cellpadding=\"3\" cellspacing=\"0\">" );
225        out.println( "  <tr align=\"left\" valign=\"top\">" );
226        out.println( "    <td class=\"titleLevel1\" rowspan=\"3\"><a name=\"#AnchorTop\"></a>Sequence Similarity Report</td>" );
227        out.println( "    <td align=\"right\" class=\"titleLevel1Sub\"> Query Id:</td>" );
228        out.print( "    <td class=\"titleLevel1Sub\">" );
229        out.print( oQuery  );
230        out.println( "&nbsp;</td>" );
231        out.println( "  </tr><tr align=\"left\" valign=\"top\" class=\"level1titleContainer\"> " );
232        out.println( "    <td class=\"titleLevel1Sub\" align=\"right\">Search&nbsp;Program:</td>" );
233        out.print( "    <td class=\"titleLevel1Sub\">" );
234        out.print( oProgram );
235        out.print( " v.&nbsp;" );
236        out.print( oVersion );
237        out.println( "</td>" );
238        out.println( "  </tr><tr align=\"left\" valign=\"top\" class=\"level1titleContainer\">" );
239        out.println( "    <td class=\"titleLevel1Sub\" align=\"right\">Database:</td>" );
240        out.print( "    <td class=\"titleLevel1Sub\">" );
241        out.print( oDatabase );
242        out.println( "</td>" );
243        out.println( "  </tr>" );
244        out.println( "</table>" );
245    }
246
247
248    /**
249     * Returns the appropriate style and javascript definitions for this
250     * renderer.
251     *
252     * @return a <code>String</code> value
253     */
254    public String getHeaderDefinitions() {
255
256        StringBuffer sb = new StringBuffer();
257
258        sb.append("<STYLE TYPE=\"text/css\">\n");
259        sb.append("<!--\n");
260        sb.append( oStyleDef );
261        if ( oAlignmentMarker != null ) {
262            sb.append( oAlignmentMarker.getAlignmentStyles() );
263        }
264        sb.append( "-->\n</STYLE>\n" );
265        sb.append( oJavaScriptDef );
266        return sb.substring(0);
267    }
268
269
270    /**
271     * Called when first summary item is reached.
272     *
273     * @param oHitSummary a <code>HitSummary</code> - the first Summary item.
274     */
275    void startSummaryTable( HitSummary oHitSummary ) {
276
277        wasEmpty = false;
278
279        out.println( "<br>" );
280        out.println( "<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">" );
281        out.println( "<tr valign=\"middle\"> " );
282        out.println( "<td colspan=\"4\" class=\"titleLevel2\" align=\"left\">Overview of Results</td>" );
283        out.println( "</tr>" );
284        out.println( "</table>" );
285
286        out.println( "<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">" );
287
288        out.println( "<td class=\"titleLevel3\" >" );
289        out.println( "<table  width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">" );
290        out.println( "<tr> " );
291
292        out.println( "<td class=\"titleLevel3\" width=\"40%\" align=\"left\" >Hit Id</td>" );
293        out.println( "<td class=\"titleLevel3\" width=\"60%\" align=\"left\">Hit Description</td>" );
294        out.println( "</tr>" );
295
296        out.println( "</table>" );
297        out.println( "</td> " );
298
299        if ( oHitSummary.readingFrame != null ) {
300            out.println( "<td width=\"7%\" class=\"titleLevel3\" align=\"center\">Frame</td>" );
301        }
302
303        out.println( "<td width=\"7%\" class=\"titleLevel3\" align=\"center\">Score</td>" );
304
305        if ( oHitSummary.expectValue != null ) {
306            out.println( "<td width=\"7%\" class=\"titleLevel3\" align=\"center\">E value</td>" );
307        }
308        if ( oHitSummary.smallestSumProbability != null ) {
309            out.println( "<td width=\"7%\" class=\"titleLevel3\" align=\"center\">P(N)</td>" );
310        }
311        if ( oHitSummary.numberOfHSPs != null ) {
312            out.println( "<td width=\"7%\" class=\"titleLevel3\" align=\"left\">HSPs</td>" );
313        }
314        if ( oHitSummary.numberOfContributingHSPs != null ) {
315            out.println( "<td width=\"7%\" class=\"titleLevel3\" align=\"center\">HSPs</td>" );
316        }
317
318        out.println( "</tr>" );
319        // start a new summary table
320        iSummaryCount = 10;
321    }
322
323
324    /**
325     * Called when a hit summary is reached ( except first ).
326     *
327     * @param oHitSummary a <code>HitSummary</code> value
328     */
329    void writeCurrentSummary( HitSummary oHitSummary ) {
330
331        if ( iSummaryCount == 10 ) { // lets break up that big table
332            iSummaryCount = 0;
333
334            out.println( "</table>" );
335            out.println
336        ("<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">");
337        }
338
339        out.println( "<tr valign=\"middle\" align=\"left\" > " );
340
341        out.print( "<td class=\"" );
342        if ( tAlternateSummary ) {
343            out.print( "summaryBodyLineEven" );
344        } else {
345            out.print( "summaryBodyLineOdd" );
346        }
347        out.print( "\">" );
348
349        out.println
350            ("<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">");
351        out.println( "<tr> " );
352
353        out.print( "<td width=\"40%\" class=\"" );
354
355        if ( tAlternateSummary ) {
356            out.print( "summaryBodyLineEven" );
357        } else {
358            out.print( "summaryBodyLineOdd" );
359        }
360        out.print( "\" >" );
361
362        if ( oFirstURLGenerator!= null ) {
363
364            out.print( "<a href=\"" );
365            out.print( oFirstURLGenerator.toURL( oHitSummary.oHitId.id,
366                                                 oOptions ));
367            out.print( "\" onMouseOver=\"setStatus('Retrieve hit from database');return document.retVal\" onMouseOut=\"setStatus(' ');return document.retVal\" class=\"dbRetrieve\">" );
368            out.print( oHitSummary.oHitId.id );
369            out.print( "</a></td>" );
370        } else {
371            out.print( oHitSummary.oHitId.id );
372            out.println( "</td>" );
373        }
374
375        out.print( "<td width=\"60%\" class=\"" );
376        if ( tAlternateSummary ) {
377            out.print( "summaryBodyLineEven" );
378        } else {
379            out.print( "summaryBodyLineOdd" );
380        }
381        out.print( "\"><a href=\"#Anchor" );
382        out.print( oHitSummary.oHitId.id );
383        out.print( "\" onMouseOver=\"setStatus('Jump to detailed results');return document.retVal\" onMouseOut=\"setStatus(' ');return document.retVal\">" );
384
385        this.writeContentChars( oHitSummary.oDesc.hitDescription );
386
387        out.println( "</a></td>" );
388        out.println( "</tr>" );
389
390        out.println( "</table>" );
391        out.println( "</td> " );
392
393        // reading frame
394        this.writeSummaryColumn( oHitSummary.readingFrame, "center" );
395
396        // score
397        out.print( "<td width=\"7%\" align=\"center\" class=\"" );
398        if ( tAlternateSummary ) {
399            out.print( "summaryBodyLineEven" );
400        } else {
401            out.print( "summaryBodyLineOdd" );
402        }
403        out.print( "\"><a href=\"#Anchor" );
404        out.print( oHitSummary.oHitId.id );
405        out.print( "\" onMouseOver=\"setStatus('Jump to detailed results');return document.retVal\" onMouseOut=\"setStatus(' ');return document.retVal\">" );
406        out.print(  oHitSummary.score );
407        out.println( "</a></td>" );
408
409        // Expect Value
410        this.writeSummaryColumn( oHitSummary.expectValue, "center" );
411        // Smallest sum prob
412        this.writeSummaryColumn( oHitSummary.smallestSumProbability, "center" );
413        // numberOfHSPs
414        this.writeSummaryColumn( oHitSummary.numberOfHSPs, "center");
415        // numberOfContributingHSPs
416        this.writeSummaryColumn( oHitSummary.numberOfContributingHSPs, "center" );
417        out.println( "</tr>" );
418
419        tAlternateSummary = !tAlternateSummary;
420        iSummaryCount ++;
421    }
422
423    /**
424     * Outputs a summary column item.
425     *
426     */
427    void writeSummaryColumn( String poValue, String poAlign ) {
428
429        if ( poValue != null ) {
430
431            out.print( "<td width=\"7%\" align=\"" );
432            out.print( poAlign );
433            out.print( "\" class=\"" );
434            if ( tAlternateSummary ) {
435                out.print( "summaryBodyLineEven" );
436            } else {
437                out.print( "summaryBodyLineOdd" );
438            }
439            out.print( "\">" );
440            out.print( poValue );
441            out.println( "</td>" );
442        }
443    }
444
445    /**
446     * Called when summary table is complete.
447     *
448     */
449    void endSummaryTable() {
450
451        out.println( "</table>" );
452        out.println( "<br>" );
453
454    }
455
456
457    /**
458     * Start the Detail table.
459     *
460     */
461    void startDetailTable() {
462
463        wasEmpty = false;
464
465        out.println( "<br>" );
466        out.println( "<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\">" );
467        out.println( "<tr valign=\"middle\"> " );
468        out.println( "<td class=\"titleLevel2\" align=\"left\">Detailed Analysis of Results</td>" );
469        out.println( "</tr>" );
470        out.println( "</table>" );
471    }
472
473    /**
474     * Write the current detail.
475     *
476     */
477    void writeCurrentDetail( DetailHit oDetailHit ) {
478
479        out.println
480            ( "<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"5\">" );
481        out.println( "<tr align=\"left\" valign=\"top\"> " );
482        out.print( "<td class=\"titleLevel3\"> <a name=\"Anchor" );
483        out.print( oDetailHit.oHitId.id );
484        out.print( "\"></a>Hit Id : " );
485        out.print( oDetailHit.oHitId.id );
486        out.println( "<br><span class=\"titleLevel3Sub\">" );
487        this.writeContentChars( oDetailHit.oDesc.hitDescription );
488        out.println( "</span></td>" );
489        out.println( "<td class=\"titleLevel3\" align=\"right\" color=\"#000000\" ><a href=\"#AnchorTop\" onMouseOver=\"setStatus('Jump to top of  document');return document.retVal\" onMouseOut=\"setStatus(' ');return document.retVal\">Top</a></td>" );
490        out.println( "</tr>" );
491        out.println( "<tr align=\"left\" valign=\"top\"> " );
492        out.println( "<td colspan=\"2\"><span class=\"alignment\">" );
493        out.print( "<p>Sequence length of hit = " );
494        out.print( oDetailHit.sequenceLength);
495        out.println( "</span></td>" );
496        out.println( "<br>" );
497
498        if ( oURLGeneratorList != null && !oURLGeneratorList.isEmpty() ) {
499            out.println( "<tr align=\"left\" valign=\"top\"> " );
500
501            out.print( "<td colspan=\"" );
502            out.print( oURLGeneratorList.size() + "" );
503            out.println( "\"> " );
504
505            for ( int i =0 ; i < oURLGeneratorList.size() ; i++ ) {
506
507                DatabaseURLGenerator oURLGenerator = ( DatabaseURLGenerator )
508                    oURLGeneratorList.get( i );
509                out.print( oURLGenerator.toLink( oDetailHit.oHitId.id,
510                                                 oOptions ));
511            }
512            out.println( "</p></td>" );
513            out.println( "</tr>" );
514        }
515
516        out.print( "</table>" );
517        //      out.println( "<br>" );
518     }
519
520
521
522    /**
523     * Utility for writing out each HSP info item, such as
524     * score ot number of identities.
525     *
526     */
527    void writeHSPInfo( String poName, String poValue ) {
528
529        if ( poValue != null ) {
530            if ( tNeedComma ) {
531            out.print( ", " );
532            }
533            out.print( poName );
534            out.print( "&nbsp;=&nbsp;" );
535            out.print( poValue );
536            tNeedComma = true;
537        }
538
539    }
540
541    /**
542     * Writes out the current HSP.
543     *
544     */
545    void writeCurrentHSP( HSPSummary oHSPSummary, BlastLikeAlignment oAlignment ) {
546         tNeedComma = false;
547
548         out.println
549             ( "<table width=\"100%\" border=\"0\" cellspacing=\"0\" cellpadding=\"5\">" );
550         out.println( "<tr align=\"left\" valign=\"top\"> " );
551         out.println
552             ( "<td class=\"titleLevel4\">High-scoring segment pair (HSP) group</td>" );
553         out.println( "</tr>" );
554         out.println( "</table>" );
555         out.println( "<div align=\"right\"><br>" );
556         out.println
557             ( "<table width=\"95%\" border=\"0\" cellspacing=\"0\" cellpadding=\"5\">" );
558         out.println( "<tr align=\"left\" valign=\"top\" class=\"level4title\"> " );
559         out.println( "<td class=\"titleLevel4\"> " );
560         out.println( "<p>HSP Information<br>" );
561
562         this.writeHSPInfo( "Score", oHSPSummary.score );
563         this.writeHSPInfo( "E", oHSPSummary.expectValue );
564         this.writeHSPInfo( "P", oHSPSummary.pValue );
565
566         if ( oHSPSummary.numberOfIdentities != null ||
567              oHSPSummary.percentageIdentity != null ) {
568
569             out.print( ", Identities&nbsp;=&nbsp;" );
570             if ( oHSPSummary.numberOfIdentities != null ) {
571                 out.print( oHSPSummary.numberOfIdentities );
572
573                 if ( oHSPSummary.alignmentSize != null ) {
574                     out.print( "/" );
575                     out.print( oHSPSummary.alignmentSize );
576                 }
577
578                 if ( oHSPSummary.percentageIdentity != null ) {
579                     out.print( "&nbsp;(" );
580                     out.print( oHSPSummary.percentageIdentity );
581                     out.print( "%)" );
582                 }
583             }
584         }
585
586         if ( oHSPSummary.numberOfPositives != null ||
587              oHSPSummary.percentagePositives != null ) {
588
589             out.print( ", Positives&nbsp;=&nbsp;" );
590             if ( oHSPSummary.numberOfPositives != null ) {
591                 out.print( oHSPSummary.numberOfPositives );
592
593                 if ( oHSPSummary.alignmentSize != null ) {
594                     out.print( "/" );
595                     out.print( oHSPSummary.alignmentSize );
596                 }
597
598                 if ( oHSPSummary.percentagePositives != null ) {
599                     out.print( "&nbsp;(" );
600                     out.print( oHSPSummary.percentagePositives );
601                     out.print( "%)" );
602                 }
603             }
604         }
605
606         this.writeHSPInfo( "Length", oHSPSummary.alignmentSize );
607         this.writeHSPInfo( "Query Frame",
608                            this.toSign( oHSPSummary.queryFrame ) );
609         this.writeHSPInfo( "Hit Frame",
610                            this.toSign( oHSPSummary.hitFrame ) );
611
612         if ( oHSPSummary.queryFrame == null ) {
613             this.writeHSPInfo( "Query Strand",
614                                this.toSign( oHSPSummary.queryStrand ) );
615         }
616         if ( oHSPSummary.hitFrame == null ) {
617             this.writeHSPInfo( "Hit Strand",
618                                this.toSign( oHSPSummary.hitStrand ));
619         }
620
621         this.writeHSPInfo( "P(N)", oHSPSummary.sumPValues );
622         this.writeHSPInfo( "No.&nbsp;of&nbsp;gaps", oHSPSummary.numberOfGaps );
623
624         out.println( "</p>" );
625         out.println( "</td>" );
626         out.println( "</tr>" );
627         out.println( "</table>" );
628         out.println( "</div>" );
629
630         out.println( "<div align=\"right\">" );
631         out.println( "<table width=\"95%\" border=\"0\" cellspacing=\"0\" cellpadding=\"5\">" );
632         out.println( "<tr> " );
633         out.println( "<td> </td>" );
634         out.println( "</tr>" );
635
636         this.drawCurrentAlignment( oAlignment );
637
638         out.println( "</table>" );
639         out.println( "</div>" );
640     }
641
642    /**
643     * Draws one block of the alignment.
644     */
645    String drawSubAlignment( String piQueryStart,
646                             String piQueryStop,
647                             String piHitStart,
648                             String piHitStop,
649                             String poQuery,
650                             String poConsensus,
651                             String poHit ) {
652
653        align.setLength( 0 );
654
655        int iMax = 4;
656
657        if ( piQueryStart.length() > iMax ) {
658            iMax = piQueryStart.length();
659        }
660        if ( piHitStart.length() > iMax ) {
661            iMax = piHitStart.length();
662        }
663        iMax = iMax+2;
664
665        String[] oFormattedSeq = new String[]{ poQuery, poHit, poConsensus };
666        if ( oAlignmentMarker != null ) {
667            oAlignmentMarker.alignment2HTML( oFormattedSeq );
668        }
669
670        String oConsensusPad = "       ";
671        oConsensusPad = oConsensusPad.concat( this.padTo( "", iMax) );
672
673        align.append( "<tr align=\"left\" valign=\"top\"> " );
674        align.append( "\n");
675        align.append( "<td>" );
676        align.append( "\n");
677        align.append( "<pre><span class=\"alignment\">Query: " );
678        align.append( this.padTo( piQueryStart, iMax) );
679
680        align.append( oFormattedSeq[0] );
681        align.append( "  ");
682        align.append( this.padTo( piQueryStop, iMax) );
683
684        align.append( "\n");
685
686        align.append( oConsensusPad );
687
688        align.append( oFormattedSeq[2] );
689        align.append( "\n");
690        align.append( "Hit  : " );
691        align.append( this.padTo( piHitStart, iMax ) );
692
693        align.append( oFormattedSeq[1] );
694        align.append( "  ");
695        align.append( this.padTo( piHitStop, iMax) );
696        align.append( "</span></PRE> " );
697        align.append( "\n");
698        align.append( "" );
699        align.append( "\n");
700        align.append( "</td>" );
701        align.append( "\n");
702        align.append( "</tr>" );
703        align.append( "\n");
704
705        return align.substring(0);
706    }
707
708
709    /**
710     * Draws a full alignment block.
711     */
712    void drawCurrentAlignment( BlastLikeAlignment oAlignment ) {
713
714        int i = 0;
715
716        int iCurrentQueryStart = Integer.parseInt
717            ( oAlignment.oQuerySeq.startPosition );
718        int iCurrentHitStart = Integer.parseInt
719            ( oAlignment.oHitSeq.startPosition );
720
721        int iQueryLen = oAlignment.oQuerySeq.seq.length();
722        int iHitLen = oAlignment.oHitSeq.seq.length();
723
724        int iNumberOfQueryGaps = 0;
725        int iNumberOfHitGaps = 0;
726        int index = -1;
727        while ( ( index = oAlignment.oQuerySeq.seq.indexOf( '-', index+1 ) ) != -1 ) {
728            iNumberOfQueryGaps++;
729        }
730        index = -1;
731        while ( ( index = oAlignment.oHitSeq.seq.indexOf( '-', index+1 ) ) != -1 ) {
732            iNumberOfHitGaps++;
733        }
734
735        int iQStop = Integer.parseInt( oAlignment.oQuerySeq.stopPosition );
736        int iHStop = Integer.parseInt( oAlignment.oHitSeq.stopPosition );
737
738
739        int queryDirection = 1;
740        int hitDirection   = 1;
741
742        if ( iQStop < iCurrentQueryStart ) {
743            queryDirection = -1;
744        }
745        if ( iHStop < iCurrentHitStart ) {
746            hitDirection = -1;
747        }
748
749        int iQueryMultiplier = (( iQStop  - iCurrentQueryStart ) + queryDirection)/
750            ( iQueryLen - iNumberOfQueryGaps );
751
752        int iHitMultiplier   = (( iHStop - iCurrentHitStart ) + hitDirection )/
753            ( iHitLen - iNumberOfHitGaps );
754
755        int iCurrentQueryEnd = 0;
756        int iCurrentHitEnd   = 0;
757
758
759        //
760        //
761        // Substring  ( i*iAlignLen, (i+1)*iAlignLen )
762        //
763        // Increment the end number by ( (iAlign-numberofgaps)* multiplier )
764        //
765        // The end check should be the current end number
766        //
767
768        while( ((i+1)*iAlignLen) < iQueryLen ) {
769
770            String oCurrentQueryString =  oAlignment.oQuerySeq.seq.substring
771                ( i*iAlignLen, (i+1)*iAlignLen );
772            String oCurrentHitString   =  oAlignment.oHitSeq.seq.substring
773                ( i*iAlignLen, (i+1)*iAlignLen );
774
775            iNumberOfQueryGaps = this.countNumberOfGaps( oCurrentQueryString );
776            iNumberOfHitGaps   = this.countNumberOfGaps( oCurrentHitString );
777
778            iCurrentQueryEnd = iCurrentQueryStart +
779                ( ( iAlignLen - iNumberOfQueryGaps ) * iQueryMultiplier );
780            iCurrentHitEnd =   iCurrentHitStart   +
781                (( iAlignLen - iNumberOfHitGaps   ) * iHitMultiplier );
782
783            out.println( drawSubAlignment
784                         ( iCurrentQueryStart + "",
785                           (iCurrentQueryEnd - queryDirection) + "",
786                           iCurrentHitStart + "",
787                           (iCurrentHitEnd - hitDirection) + "",
788                           oCurrentQueryString,
789                           oAlignment.oConsensus.substring
790                           ( i*iAlignLen, (i+1)*iAlignLen ) ,
791                           oCurrentHitString )
792                         );
793            i++;
794            iCurrentQueryStart += ( ( iAlignLen - iNumberOfQueryGaps )
795                                    * iQueryMultiplier );
796            iCurrentHitStart   += ( ( iAlignLen - iNumberOfHitGaps   )
797                                    * iHitMultiplier );
798
799        } // end while
800
801        if ( iQStop != iCurrentQueryEnd ) {
802
803            iCurrentQueryEnd = iQStop;
804            iCurrentHitEnd = iHStop;
805
806            out.println( drawSubAlignment( iCurrentQueryStart + "",
807                                           iCurrentQueryEnd + "",
808                                           iCurrentHitStart + "",
809                                           iCurrentHitEnd + "",
810                                           oAlignment.oQuerySeq.seq.substring
811                                           ( i*iAlignLen ) ,
812                                           oAlignment.oConsensus.substring
813                                           ( i*iAlignLen ) ,
814                                           oAlignment.oHitSeq.seq.substring
815                                           ( i*iAlignLen ) )
816                         );
817        }
818
819    }
820
821    /**
822     * Renderers end of detail table
823     *
824     */
825    void endDetailTable() {
826        out.println
827            ( "<table width=\"100%\" border=\"0\" cellpadding=\"0\" cellspacing=\"0\">" );
828        out.println( "<tr> " );
829        out.println( "<td class=\"footer\" height=\"21\"> " );
830        out.println
831    ("<div align=\"center\"> <a href=\"http://www.biojava.org/\" class=\"footer\"> </a> " );
832        out.println
833     ( "Produced using <a href=\"http://www.biojava.org/\" class=\"footer\">biojava</a> " );
834        out.println( "- www.biojava.org</div>" );
835        out.println( "</td>" );
836        out.println( "</tr>" );
837        out.println( "</table>" );
838    }
839
840
841    /**
842     * Convert from 'plus' and 'minus' to '+' and '-'
843     *
844     * @param poString <code>String</code> to convert.
845     * @return new <code>String</code>
846     */
847    private String toSign( String poString ) {
848
849        String oSign = null;
850
851        if ( poString != null ) {
852            if (  poString.startsWith( "plus" ) ) {
853                oSign = "+";
854                if ( poString.length() > 4 ) {
855                    oSign = oSign.concat( poString.substring( 4 ));
856                }
857
858            } else if ( poString.startsWith( "minus" )) {
859                oSign = "-";
860                if ( poString.length() > 5 ) {
861                    oSign = oSign.concat( poString.substring( 5 ));
862                }
863            }
864        }
865        return oSign;
866    }
867
868    // ************************************************************ //
869    // *                 Escape HTML chars                        * //
870    // *                                                          * //
871    // *   Probably replace this with something in a apache or    * //
872    // *   java lib.                                              * //
873    // *                                                          * //
874    // *                                                          * //
875    // ************************************************************ //
876
877    private void writeContentChars( String poLine ) {
878
879        poLine = this.replace( poLine, '&', "&amp;");
880        poLine = this.replaceGtLtAndQuote( poLine );
881
882        out.print( poLine );
883    }
884
885    private String replaceGtLtAndQuote( String poInputString ) {
886
887        pcDataBuffer.setLength( 0 );
888        pcDataBuffer.append( poInputString );
889        int iLength = ( pcDataBuffer.length() );
890
891        for (int i = iLength; --i >= 0; ) {
892            if (  (pcDataBuffer.charAt(i) == '<') ) {
893                pcDataBuffer.deleteCharAt(i);
894                // then insert escape char to the LHS
895                pcDataBuffer.insert(i, "&lt;" );
896            } else if (  (pcDataBuffer.charAt(i) == '>') ) {
897                pcDataBuffer.deleteCharAt(i);
898                // then insert escape char to the LHS
899                pcDataBuffer.insert(i, "&gt;" );
900            } else if (  (pcDataBuffer.charAt(i) == '\\') ) {
901                pcDataBuffer.deleteCharAt(i);
902                // then insert escape char to the LHS
903                pcDataBuffer.insert(i, "&quot;" );
904            } // end if
905        } // end for
906
907        return pcDataBuffer.substring(0);
908    }
909
910    private String replace( String poInputString,
911                            char pcCharToReplace,
912                            String poReplacementString ) {
913
914        pcDataBuffer.setLength( 0 );
915        pcDataBuffer.append( poInputString );
916        int iLength = ( pcDataBuffer.length() );
917
918        for (int i = iLength; --i >= 0; ) {
919            if (  pcDataBuffer.charAt(i) == pcCharToReplace ) {
920                pcDataBuffer.deleteCharAt(i);
921                // then insert escape char to the LHS
922                pcDataBuffer.insert(i, poReplacementString );
923            } // end if
924        } // end for
925
926        return pcDataBuffer.substring(0);
927    }
928
929
930    /**
931     * Makes assumption about the gap character.
932     *
933     */
934    int countNumberOfGaps( String poString ) {
935
936        int index = -1;
937        int iNumberOfGaps = 0;
938        while ( ( index = poString.indexOf( '-', index+1 ) ) != -1 ) {
939            iNumberOfGaps++;
940        }
941        return iNumberOfGaps;
942    }
943
944    /**
945     * Ensures the given string is the correct length.
946     *
947     */
948    String padTo( String poString, int iNumberOfChars ) {
949
950        int iLen = iNumberOfChars - poString.length();
951
952        if ( iLen > padding.length ) {
953            padding = new char[ iLen ];
954            Arrays.fill( padding,
955                         0,
956                         iLen,
957                         ' ' );
958        }
959        return poString.concat( String.copyValueOf( padding, 0, iLen ) );
960    }
961
962
963} // end class
964