JAL-3878 Add getName() to Operation interface.
[jalview.git] / src / jalview / ws2 / operations / AlignmentOperation.java
1 package jalview.ws2.operations;
2
3 import static java.lang.String.format;
4
5 import java.awt.event.MouseAdapter;
6 import java.awt.event.MouseEvent;
7 import java.io.IOException;
8 import java.util.ArrayList;
9 import java.util.Collections;
10 import java.util.HashMap;
11 import java.util.Hashtable;
12 import java.util.LinkedHashMap;
13 import java.util.List;
14 import java.util.Map;
15 import java.util.Objects;
16 import java.util.concurrent.CompletionStage;
17
18 import javax.swing.JMenu;
19 import javax.swing.JMenuItem;
20 import javax.swing.ToolTipManager;
21
22 import jalview.analysis.AlignSeq;
23 import jalview.analysis.AlignmentSorter;
24 import jalview.analysis.SeqsetUtils;
25 import jalview.bin.Cache;
26 import jalview.datamodel.AlignedCodonFrame;
27 import jalview.datamodel.Alignment;
28 import jalview.datamodel.AlignmentI;
29 import jalview.datamodel.AlignmentOrder;
30 import jalview.datamodel.AlignmentView;
31 import jalview.datamodel.HiddenColumns;
32 import jalview.datamodel.SequenceI;
33 import jalview.datamodel.Sequence;
34 import jalview.gui.AlignFrame;
35 import jalview.gui.AlignViewport;
36 import jalview.gui.Desktop;
37 import jalview.gui.JvSwingUtils;
38 import jalview.gui.WebserviceInfo;
39 import jalview.gui.WsJobParameters;
40 import jalview.util.MathUtils;
41 import jalview.util.MessageManager;
42 import jalview.ws.params.ArgumentI;
43 import jalview.ws.params.WsParamSetI;
44 import jalview.ws2.MenuEntryProviderI;
45 import jalview.ws2.ResultSupplier;
46 import jalview.ws2.WSJob;
47 import jalview.ws2.WSJobStatus;
48 import jalview.ws2.WebServiceExecutor;
49 import jalview.ws2.WebServiceI;
50 import jalview.ws2.WebServiceInfoUpdater;
51 import jalview.ws2.WebServiceWorkerI;
52 import jalview.ws2.utils.WSJobList;
53
54 /**
55  *
56  * @author mmwarowny
57  *
58  */
59 public class AlignmentOperation implements Operation
60 {
61   final WebServiceI service;
62
63   final ResultSupplier<AlignmentI> supplier;
64
65   public AlignmentOperation(WebServiceI service,
66           ResultSupplier<AlignmentI> supplier)
67   {
68     this.service = service;
69     this.supplier = supplier;
70   }
71
72   @Override
73   public String getName()
74   {
75     return service.getName();
76   }
77
78   @Override
79   public String getTypeName()
80   {
81     return "Multiple Sequence Alignment";
82   }
83
84   @Override
85   public int getMinSequences()
86   {
87     return 2;
88   }
89
90   @Override
91   public int getMaxSequences()
92   {
93     return Integer.MAX_VALUE;
94   }
95
96   @Override
97   public boolean isProteinOperation()
98   {
99     return true;
100   }
101
102   @Override
103   public boolean isNucleotideOperation()
104   {
105     return true;
106   }
107
108   @Override
109   public boolean canSubmitGaps()
110   {
111     // hack copied from original jabaws code, don't blame me
112     return service.getName().contains("lustal");
113   }
114
115   @Override
116   public MenuEntryProviderI getMenuBuilder()
117   {
118     return this::buildMenu;
119   }
120
121   protected void buildMenu(JMenu parent, AlignFrame frame)
122   {
123     if (canSubmitGaps())
124     {
125       var alignSubmenu = new JMenu(service.getName());
126       buildMenu(alignSubmenu, frame, false);
127       parent.add(alignSubmenu);
128       var realignSubmenu = new JMenu(MessageManager.formatMessage(
129               "label.realign_with_params", service.getName()));
130       realignSubmenu.setToolTipText(MessageManager
131               .getString("label.align_sequences_to_existing_alignment"));
132       buildMenu(realignSubmenu, frame, true);
133       parent.add(realignSubmenu);
134     }
135     else
136     {
137       buildMenu(parent, frame, false);
138     }
139   }
140
141   protected void buildMenu(JMenu parent, AlignFrame frame,
142           boolean submitGaps)
143   {
144     final String action = submitGaps ? "Align" : "Realign";
145     final var calcName = service.getName();
146
147     final AlignmentView msa = frame.gatherSequencesForAlignment();
148     final AlignViewport viewport = frame.getViewport();
149     final AlignmentI alignment = frame.getViewport().getAlignment();
150     String title = frame.getTitle();
151     WebServiceExecutor executor = frame.getViewport().getWSExecutor();
152     {
153       var item = new JMenuItem(MessageManager.formatMessage(
154               "label.calcname_with_default_settings", calcName));
155       item.setToolTipText(MessageManager
156               .formatMessage("label.action_with_default_settings", action));
157       item.addActionListener((event) -> {
158         if (msa != null)
159         {
160           WebServiceWorkerI worker = new AlignmentWorker(msa,
161                   Collections.emptyList(), title, submitGaps, true,
162                   alignment, viewport);
163           executor.submit(worker);
164         }
165       });
166       parent.add(item);
167     }
168
169     if (service.hasParameters())
170     {
171       var item = new JMenuItem(
172               MessageManager.getString("label.edit_settings_and_run"));
173       item.setToolTipText(MessageManager.getString(
174               "label.view_and_change_parameters_before_alignment"));
175       item.addActionListener((event) -> {
176         if (msa != null)
177         {
178           openEditParamsDialog(service, null, null)
179                   .thenAcceptAsync((arguments) -> {
180                     if (arguments != null)
181                     {
182                       WebServiceWorkerI worker = new AlignmentWorker(msa,
183                               arguments, title, submitGaps, true, alignment,
184                               viewport);
185                       executor.submit(worker);
186                     }
187                   });
188         }
189       });
190       parent.add(item);
191     }
192
193     var presets = service.getParamStore().getPresets();
194     if (presets != null && presets.size() > 0)
195     {
196       final var presetList = new JMenu(MessageManager
197               .formatMessage("label.run_with_preset_params", calcName));
198       final var showToolTipFor = ToolTipManager.sharedInstance()
199               .getDismissDelay();
200       for (final var preset : presets)
201       {
202         var item = new JMenuItem(preset.getName());
203         final int QUICK_TOOLTIP = 1500;
204         item.addMouseListener(new MouseAdapter()
205         {
206           @Override
207           public void mouseEntered(MouseEvent e)
208           {
209             ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
210           }
211
212           @Override
213           public void mouseExited(MouseEvent e)
214           {
215             ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
216           }
217         });
218         String tooltip = JvSwingUtils.wrapTooltip(true,
219                 format("<strong>%s</strong><br/>%s",
220                         MessageManager.getString(
221                                 preset.isModifiable() ? "label.user_preset"
222                                         : "label.service_preset"),
223                         preset.getDescription()));
224         item.setToolTipText(tooltip);
225         item.addActionListener((event) -> {
226           if (msa != null)
227           {
228             WebServiceWorkerI worker = new AlignmentWorker(msa,
229                     preset.getArguments(), title, submitGaps, true,
230                     alignment, viewport);
231             executor.submit(worker);
232           }
233         });
234         presetList.add(item);
235       }
236       parent.add(presetList);
237     }
238   }
239
240   private CompletionStage<List<ArgumentI>> openEditParamsDialog(
241           WebServiceI service, WsParamSetI preset,
242           List<ArgumentI> arguments)
243   {
244     WsJobParameters jobParams;
245     if (preset == null && arguments != null && arguments.size() > 0)
246       jobParams = new WsJobParameters(service.getParamStore(), preset,
247               arguments);
248     else
249       jobParams = new WsJobParameters(service.getParamStore(), preset,
250               null);
251     var stage = jobParams.showRunDialog();
252     return stage.thenApply((startJob) -> {
253       if (startJob)
254       {
255         if (jobParams.getPreset() == null)
256         {
257           return jobParams.getJobParams();
258         }
259         else
260         {
261           return jobParams.getPreset().getArguments();
262         }
263       }
264       else
265       {
266         return null;
267       }
268     });
269   }
270
271   /**
272    * Implementation of the web service worker performing multiple sequence
273    * alignment.
274    *
275    * @author mmwarowny
276    *
277    */
278   private class AlignmentWorker implements WebServiceWorkerI
279   {
280
281     private long uid = MathUtils.getUID();
282
283     private final AlignmentView msa;
284
285     private final AlignmentI dataset;
286
287     private final List<AlignedCodonFrame> codonFrame = new ArrayList<>();
288
289     private List<ArgumentI> args = Collections.emptyList();
290
291     private String alnTitle = "";
292
293     private boolean submitGaps = false;
294
295     private boolean preserveOrder = false;
296
297     private char gapCharacter;
298
299     private WSJobList jobs = new WSJobList();
300
301     private Map<Long, JobInput> inputs = new LinkedHashMap<>();
302
303     private WebserviceInfo wsInfo;
304
305     private Map<Long, Integer> exceptionCount = new HashMap<>();
306
307     private final int MAX_RETRY = 5;
308
309     AlignmentWorker(AlignmentView msa, List<ArgumentI> args,
310             String alnTitle, boolean submitGaps, boolean preserveOrder,
311             AlignmentI alignment, AlignViewport viewport)
312     {
313       this.msa = msa;
314       this.dataset = alignment.getDataset();
315       List<AlignedCodonFrame> cf = Objects.requireNonNullElse(
316               alignment.getCodonFrames(), Collections.emptyList());
317       this.codonFrame.addAll(cf);
318       this.args = args;
319       this.alnTitle = alnTitle;
320       this.submitGaps = submitGaps;
321       this.preserveOrder = preserveOrder;
322       this.gapCharacter = viewport.getGapCharacter();
323
324       String panelInfo = String.format("%s using service hosted at %s%n%s",
325               service.getName(), service.getHostName(),
326               Objects.requireNonNullElse(service.getDescription(), ""));
327       wsInfo = new WebserviceInfo(service.getName(), panelInfo, false);
328     }
329
330     @Override
331     public long getUID()
332     {
333       return uid;
334     }
335
336     @Override
337     public WebServiceI getWebService()
338     {
339       return service;
340     }
341
342     @Override
343     public List<WSJob> getJobs()
344     {
345       return Collections.unmodifiableList(jobs);
346     }
347
348     @Override
349     public void startJobs() throws IOException
350     {
351       String outputHeader = String.format("%s of %s%nJob details%n",
352               submitGaps ? "Re-alignment" : "Alignment", alnTitle);
353       SequenceI[][] conmsa = msa.getVisibleContigs('-');
354       if (conmsa == null)
355       {
356         return;
357       }
358       WebServiceInfoUpdater updater = new WebServiceInfoUpdater(wsInfo);
359       updater.setOutputHeader(outputHeader);
360       int numValid = 0;
361       for (int i = 0; i < conmsa.length; i++)
362       {
363         JobInput input = JobInput.create(conmsa[i], 2, submitGaps);
364         WSJob job = new WSJob(service.getProviderName(), service.getName(),
365                 service.getHostName());
366         job.setJobNum(wsInfo.addJobPane());
367         if (conmsa.length > 0)
368         {
369           wsInfo.setProgressName(String.format("region %d", i),
370                   job.getJobNum());
371         }
372         wsInfo.setProgressText(job.getJobNum(), outputHeader);
373         job.addPropertyChangeListener(updater);
374         inputs.put(job.getUid(), input);
375         jobs.add(job);
376         if (input.isInputValid())
377         {
378           int count;
379           String jobId = null;
380           do
381           {
382             count = exceptionCount.getOrDefault(job.getUid(), MAX_RETRY);
383             try
384             {
385               jobId = service.submit(input.inputSequences, args);
386               Cache.log.debug((format("Job %s submitted", job)));
387               exceptionCount.remove(job.getUid());
388             } catch (IOException e)
389             {
390               exceptionCount.put(job.getUid(), --count);
391             }
392           } while (jobId == null && count > 0);
393           if (jobId != null)
394           {
395             job.setJobId(jobId);
396             job.setStatus(WSJobStatus.SUBMITTED);
397             numValid++;
398           }
399           else
400           {
401             job.setStatus(WSJobStatus.SERVER_ERROR);
402           }
403         }
404         else
405         {
406           job.setStatus(WSJobStatus.INVALID);
407           job.setErrorLog(
408                   MessageManager.getString("label.empty_alignment_job"));
409         }
410       }
411       if (numValid > 0)
412       {
413         // wsInfo.setThisService() should happen here
414         wsInfo.setVisible(true);
415       }
416       else
417       {
418         wsInfo.setVisible(false);
419         // TODO show notification dialog.
420         // JvOptionPane.showMessageDialog(frame,
421         // MessageManager.getString("info.invalid_msa_input_mininfo"),
422         // MessageManager.getString("info.invalid_msa_notenough"),
423         // JvOptionPane.INFORMATION_MESSAGE);
424       }
425     }
426
427     @Override
428     public boolean pollJobs()
429     {
430       boolean done = true;
431       for (WSJob job : getJobs())
432       {
433         if (!job.getStatus().isDone())
434         {
435           Cache.log.debug(format("Polling job %s.", job));
436           try
437           {
438             service.updateProgress(job);
439             exceptionCount.remove(job.getUid());
440           } catch (IOException e)
441           {
442             Cache.log.error(format("Polling job %s failed.", job), e);
443             wsInfo.appendProgressText(job.getJobNum(),
444                     MessageManager.formatMessage("info.server_exception",
445                             service.getName(), e.getMessage()));
446             int count = exceptionCount.getOrDefault(job.getUid(),
447                     MAX_RETRY);
448             if (--count <= 0)
449             {
450               job.setStatus(WSJobStatus.SERVER_ERROR);
451               Cache.log.warn(format(
452                       "Attempts limit exceeded. Droping job %s.", job));
453             }
454             exceptionCount.put(job.getUid(), count);
455           } catch (OutOfMemoryError e)
456           {
457             job.setStatus(WSJobStatus.BROKEN);
458             Cache.log.error(
459                     format("Out of memory when retrieving job %s", job), e);
460           }
461           Cache.log.debug(
462                   format("Job %s status is %s", job, job.getStatus()));
463         }
464         done &= job.getStatus().isDone();
465       }
466       updateWSInfoGlobalStatus();
467       return done;
468     }
469
470     private void updateWSInfoGlobalStatus()
471     {
472       if (jobs.countRunning() > 0)
473       {
474         wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
475       }
476       else if (jobs.countQueuing() > 0
477               || jobs.countSubmitted() < jobs.size())
478       {
479         wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
480       }
481       else
482       {
483         if (jobs.countSuccessful() > 0)
484         {
485           wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
486         }
487         else if (jobs.countCancelled() > 0)
488         {
489           wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
490         }
491         else if (jobs.countFailed() > 0)
492         {
493           wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
494         }
495       }
496     }
497
498     @Override
499     public void done()
500     {
501       long progbarId = MathUtils.getUID();
502       wsInfo.setProgressBar(
503               MessageManager.getString("status.collecting_job_results"),
504               progbarId);
505       Map<Long, AlignmentI> results = new LinkedHashMap<>();
506       for (WSJob job : getJobs())
507       {
508         try
509         {
510           AlignmentI alignment = supplier.getResult(job);
511           if (alignment != null)
512           {
513             results.put(job.getUid(), alignment);
514           }
515         } catch (Exception e)
516         {
517           if (!service.handleCollectionError(job, e))
518           {
519             Cache.log.error("Couldn't get alignment for job.", e);
520             // TODO: Increment exception count and retry.
521             job.setStatus(WSJobStatus.SERVER_ERROR);
522           }
523         }
524       }
525       updateWSInfoGlobalStatus();
526       if (results.size() > 0)
527       {
528         OutputWrapper out = prepareOutput(results);
529         wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
530                 new Alignment(out.aln), out.alorders, out.hidden));
531         wsInfo.setResultsReady();
532       }
533       else
534       {
535         wsInfo.setFinishedNoResults();
536       }
537       wsInfo.removeProgressBar(progbarId);
538     }
539
540     private class OutputWrapper
541     {
542       AlignmentI aln;
543
544       List<AlignmentOrder> alorders;
545
546       HiddenColumns hidden;
547
548       OutputWrapper(AlignmentI aln, List<AlignmentOrder> alorders,
549               HiddenColumns hidden)
550       {
551         this.aln = aln;
552         this.alorders = alorders;
553         this.hidden = hidden;
554       }
555     }
556
557     private OutputWrapper prepareOutput(Map<Long, AlignmentI> alignments)
558     {
559       List<AlignmentOrder> alorders = new ArrayList<>();
560       SequenceI[][] results = new SequenceI[jobs.size()][];
561       AlignmentOrder[] orders = new AlignmentOrder[jobs.size()];
562       for (int i = 0; i < jobs.size(); i++)
563       {
564         WSJob job = jobs.get(i);
565         AlignmentI aln = alignments.get(job.getUid());
566         if (aln != null) // equivalent of job.hasResults()
567         {
568           /* Get the alignment including any empty sequences in the original
569            * order with original ids. */
570           JobInput input = inputs.get(job.getUid());
571           char gapChar = aln.getGapCharacter();
572           List<SequenceI> emptySeqs = input.emptySequences;
573           List<SequenceI> alnSeqs = aln.getSequences();
574           // find the width of the longest sequence
575           int width = 0;
576           for (var seq : alnSeqs)
577             width = Integer.max(width, seq.getLength());
578           for (var emptySeq : emptySeqs)
579             width = Integer.max(width, emptySeq.getLength());
580           // pad shorter sequences with gaps
581           String gapSeq = String.join("",
582                   Collections.nCopies(width, Character.toString(gapChar)));
583           List<SequenceI> seqs = new ArrayList<>(
584                   alnSeqs.size() + emptySeqs.size());
585           seqs.addAll(alnSeqs);
586           seqs.addAll(emptySeqs);
587           for (var seq : seqs)
588           {
589             if (seq.getLength() < width)
590               seq.setSequence(seq.getSequenceAsString()
591                       + gapSeq.substring(seq.getLength()));
592           }
593           SequenceI[] result = seqs.toArray(new SequenceI[0]);
594           AlignmentOrder msaOrder = new AlignmentOrder(result);
595           AlignmentSorter.recoverOrder(result);
596           // temporary workaround for deuniquify
597           @SuppressWarnings({ "rawtypes", "unchecked" })
598           Hashtable names = new Hashtable(input.sequenceNames);
599           // FIXME first call to deuniquify alters original alignment
600           SeqsetUtils.deuniquify(names, result);
601           alorders.add(msaOrder);
602           results[i] = result;
603           orders[i] = msaOrder;
604         }
605         else
606         {
607           results[i] = null;
608         }
609       }
610
611       Object[] newView = msa.getUpdatedView(results, orders, gapCharacter);
612       // free references to original data
613       for (int i = 0; i < jobs.size(); i++)
614       {
615         results[i] = null;
616         orders[i] = null;
617       }
618       SequenceI[] alignment = (SequenceI[]) newView[0];
619       HiddenColumns hidden = (HiddenColumns) newView[1];
620       Alignment aln = new Alignment(alignment);
621       aln.setProperty("Alignment Program", service.getName());
622       if (dataset != null)
623         aln.setDataset(dataset);
624
625       propagateDatasetMappings(aln);
626       return new OutputWrapper(aln, alorders, hidden);
627       // displayNewFrame(aln, alorders, hidden);
628     }
629
630     /*
631      * conserves dataset references to sequence objects returned from web
632      * services. propagate codon frame data to alignment.
633      */
634     private void propagateDatasetMappings(Alignment aln)
635     {
636       if (codonFrame != null)
637       {
638         SequenceI[] alignment = aln.getSequencesArray();
639         for (SequenceI seq : alignment)
640         {
641           for (AlignedCodonFrame acf : codonFrame)
642           {
643             if (acf != null && acf.involvesSequence(seq))
644             {
645               aln.addCodonFrame(acf);
646               break;
647             }
648           }
649         }
650       }
651     }
652
653     private void displayNewFrame(AlignmentI aln,
654             List<AlignmentOrder> alorders, HiddenColumns hidden)
655     {
656       AlignFrame frame = new AlignFrame(aln, hidden,
657               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
658       // TODO store feature renderer settings in worker object
659       // frame.getFeatureRenderer().transferSettings(featureSettings);
660       var regions = sortOrders(alorders);
661       if (alorders.size() == 1)
662       {
663         frame.addSortByOrderMenuItem(
664                 format("%s Ordering", service.getName()), alorders.get(0));
665       }
666       else
667       {
668         for (int i = 0; i < alorders.size(); i++)
669         {
670           final int j = i;
671           Iterable<String> iter = () -> regions.get(j).stream()
672                   .map(it -> Integer.toString(it)).iterator();
673           var orderName = format("%s Region %s Ordering", service.getName(),
674                   String.join(",", iter));
675           frame.addSortByOrderMenuItem(orderName, alorders.get(i));
676         }
677       }
678
679       /* TODO
680        * If alignment was requested from one half of a SplitFrame, show in a
681        * SplitFrame with the other pane similarly aligned.
682        */
683
684       Desktop.addInternalFrame(frame, alnTitle, AlignFrame.DEFAULT_WIDTH,
685               AlignFrame.DEFAULT_HEIGHT);
686     }
687
688     private List<List<Integer>> sortOrders(List<?> alorders)
689     {
690       List<List<Integer>> regions = new ArrayList<>();
691       for (int i = 0; i < alorders.size(); i++)
692       {
693         List<Integer> regs = new ArrayList<>();
694         regs.add(i);
695         int j = i + 1;
696         while (j < alorders.size())
697         {
698           if (alorders.get(i).equals(alorders.get(j)))
699           {
700             alorders.remove(j);
701             regs.add(j);
702           }
703           else
704           {
705             j++;
706           }
707         }
708         regions.add(regs);
709       }
710       return regions;
711     }
712   }
713
714   private static class JobInput
715   {
716     final List<SequenceI> inputSequences;
717
718     final List<SequenceI> emptySequences;
719
720     @SuppressWarnings("rawtypes")
721     final Map<String, ? extends Map> sequenceNames;
722
723     private JobInput(int numSequences, List<SequenceI> inputSequences,
724             List<SequenceI> emptySequences,
725             @SuppressWarnings("rawtypes") Map<String, ? extends Map> names)
726     {
727       this.inputSequences = Collections.unmodifiableList(inputSequences);
728       this.emptySequences = Collections.unmodifiableList(emptySequences);
729       this.sequenceNames = names;
730     }
731
732     boolean isInputValid()
733     {
734       return inputSequences.size() >= 2;
735     }
736
737     static JobInput create(SequenceI[] sequences, int minLength,
738             boolean submitGaps)
739     {
740       assert minLength >= 0 : MessageManager.getString(
741               "error.implementation_error_minlen_must_be_greater_zero");
742       int numSeq = 0;
743       for (SequenceI seq : sequences)
744       {
745         if (seq.getEnd() - seq.getStart() >= minLength)
746         {
747           numSeq++;
748         }
749       }
750
751       List<SequenceI> inputSequences = new ArrayList<>();
752       List<SequenceI> emptySequences = new ArrayList<>();
753       @SuppressWarnings("rawtypes")
754       Map<String, Hashtable> names = new LinkedHashMap<>();
755       for (int i = 0; i < sequences.length; i++)
756       {
757         SequenceI seq = sequences[i];
758         String newName = SeqsetUtils.unique_name(i);
759         @SuppressWarnings("rawtypes")
760         Hashtable hash = SeqsetUtils.SeqCharacterHash(seq);
761         names.put(newName, hash);
762         if (numSeq > 1 && seq.getEnd() - seq.getStart() >= minLength)
763         {
764           String seqString = seq.getSequenceAsString();
765           if (!submitGaps)
766           {
767             seqString = AlignSeq.extractGaps(
768                     jalview.util.Comparison.GapChars, seqString);
769           }
770           inputSequences.add(new Sequence(newName, seqString));
771         }
772         else
773         {
774           String seqString = null;
775           if (seq.getEnd() >= seq.getStart()) // is it ever false?
776           {
777             seqString = seq.getSequenceAsString();
778             if (!submitGaps)
779             {
780               seqString = AlignSeq.extractGaps(
781                       jalview.util.Comparison.GapChars, seqString);
782             }
783           }
784           emptySequences.add(new Sequence(newName, seqString));
785         }
786       }
787
788       return new JobInput(numSeq, inputSequences, emptySequences, names);
789     }
790   }
791
792 }