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