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