JAL-3878 Add dataset and viewport arguments to ResultSupplier.
[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 AlignViewport viewport;
306
307     private final List<AlignedCodonFrame> codonFrame = new ArrayList<>();
308
309     private List<ArgumentI> args = Collections.emptyList();
310
311     private String alnTitle = "";
312
313     private boolean submitGaps = false;
314
315     private boolean preserveOrder = false;
316
317     private char gapCharacter;
318
319     private WSJobList jobs = new WSJobList();
320
321     private Map<Long, JobInput> inputs = new LinkedHashMap<>();
322
323     private WebserviceInfo wsInfo;
324
325     private Map<Long, Integer> exceptionCount = new HashMap<>();
326
327     private final int MAX_RETRY = 5;
328
329     AlignmentWorker(AlignmentView msa, List<ArgumentI> args,
330             String alnTitle, boolean submitGaps, boolean preserveOrder,
331             AlignmentI alignment, AlignViewport viewport)
332     {
333       this.msa = msa;
334       this.dataset = alignment.getDataset();
335       List<AlignedCodonFrame> cf = Objects.requireNonNullElse(
336               alignment.getCodonFrames(), Collections.emptyList());
337       this.codonFrame.addAll(cf);
338       this.args = args;
339       this.alnTitle = alnTitle;
340       this.submitGaps = submitGaps;
341       this.preserveOrder = preserveOrder;
342       this.viewport = viewport;
343       this.gapCharacter = viewport.getGapCharacter();
344
345       String panelInfo = String.format("%s using service hosted at %s%n%s",
346               service.getName(), service.getHostName(),
347               Objects.requireNonNullElse(service.getDescription(), ""));
348       wsInfo = new WebserviceInfo(service.getName(), panelInfo, false);
349     }
350
351     @Override
352     public long getUID()
353     {
354       return uid;
355     }
356
357     @Override
358     public WebServiceI getWebService()
359     {
360       return service;
361     }
362
363     @Override
364     public List<WSJob> getJobs()
365     {
366       return Collections.unmodifiableList(jobs);
367     }
368
369     @Override
370     public void startJobs() throws IOException
371     {
372       Cache.log.info(format("Starting new %s job.", service.getName()));
373       String outputHeader = String.format("%s of %s%nJob details%n",
374               submitGaps ? "Re-alignment" : "Alignment", alnTitle);
375       SequenceI[][] conmsa = msa.getVisibleContigs('-');
376       if (conmsa == null)
377       {
378         return;
379       }
380       WebServiceInfoUpdater updater = new WebServiceInfoUpdater(wsInfo);
381       updater.setOutputHeader(outputHeader);
382       int numValid = 0;
383       for (int i = 0; i < conmsa.length; i++)
384       {
385         JobInput input = JobInput.create(conmsa[i], 2, submitGaps);
386         WSJob job = new WSJob(service.getProviderName(), service.getName(),
387                 service.getHostName());
388         job.setJobNum(wsInfo.addJobPane());
389         if (conmsa.length > 1)
390         {
391           wsInfo.setProgressName(String.format("region %d", i),
392                   job.getJobNum());
393         }
394         wsInfo.setProgressText(job.getJobNum(), outputHeader);
395         job.addPropertyChangeListener(updater);
396         inputs.put(job.getUid(), input);
397         jobs.add(job);
398         if (input.isInputValid())
399         {
400           int count;
401           String jobId = null;
402           do
403           {
404             count = exceptionCount.getOrDefault(job.getUid(), MAX_RETRY);
405             try
406             {
407               jobId = service.submit(input.inputSequences, args);
408               Cache.log.debug((format("Job %s submitted", job)));
409               exceptionCount.remove(job.getUid());
410             } catch (IOException e)
411             {
412               exceptionCount.put(job.getUid(), --count);
413             }
414           } while (jobId == null && count > 0);
415           if (jobId != null)
416           {
417             job.setJobId(jobId);
418             job.setStatus(WSJobStatus.SUBMITTED);
419             numValid++;
420           }
421           else
422           {
423             job.setStatus(WSJobStatus.SERVER_ERROR);
424           }
425         }
426         else
427         {
428           job.setStatus(WSJobStatus.INVALID);
429           job.setErrorLog(
430                   MessageManager.getString("label.empty_alignment_job"));
431         }
432       }
433       if (numValid > 0)
434       {
435         // wsInfo.setThisService() should happen here
436         wsInfo.setVisible(true);
437       }
438       else
439       {
440         wsInfo.setVisible(false);
441         // TODO show notification dialog.
442         // JvOptionPane.showMessageDialog(frame,
443         // MessageManager.getString("info.invalid_msa_input_mininfo"),
444         // MessageManager.getString("info.invalid_msa_notenough"),
445         // JvOptionPane.INFORMATION_MESSAGE);
446       }
447     }
448
449     @Override
450     public boolean pollJobs()
451     {
452       boolean done = true;
453       for (WSJob job : getJobs())
454       {
455         if (!job.getStatus().isDone() && !job.getStatus().isFailed())
456         {
457           Cache.log.debug(format("Polling job %s.", job));
458           try
459           {
460             service.updateProgress(job);
461             exceptionCount.remove(job.getUid());
462           } catch (IOException e)
463           {
464             Cache.log.error(format("Polling job %s failed.", job), e);
465             wsInfo.appendProgressText(job.getJobNum(),
466                     MessageManager.formatMessage("info.server_exception",
467                             service.getName(), e.getMessage()));
468             int count = exceptionCount.getOrDefault(job.getUid(),
469                     MAX_RETRY);
470             if (--count <= 0)
471             {
472               job.setStatus(WSJobStatus.SERVER_ERROR);
473               Cache.log.warn(format(
474                       "Attempts limit exceeded. Droping job %s.", job));
475             }
476             exceptionCount.put(job.getUid(), count);
477           } catch (OutOfMemoryError e)
478           {
479             job.setStatus(WSJobStatus.BROKEN);
480             Cache.log.error(
481                     format("Out of memory when retrieving job %s", job), e);
482           }
483           Cache.log.debug(
484                   format("Job %s status is %s", job, job.getStatus()));
485         }
486         done &= job.getStatus().isDone() || job.getStatus().isFailed();
487       }
488       updateWSInfoGlobalStatus();
489       return done;
490     }
491
492     private void updateWSInfoGlobalStatus()
493     {
494       if (jobs.countRunning() > 0)
495       {
496         wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
497       }
498       else if (jobs.countQueuing() > 0
499               || jobs.countSubmitted() < jobs.size())
500       {
501         wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
502       }
503       else
504       {
505         if (jobs.countSuccessful() > 0)
506         {
507           wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
508         }
509         else if (jobs.countCancelled() > 0)
510         {
511           wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
512         }
513         else if (jobs.countFailed() > 0)
514         {
515           wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
516         }
517       }
518     }
519
520     @Override
521     public void done()
522     {
523       long progbarId = MathUtils.getUID();
524       wsInfo.setProgressBar(
525               MessageManager.getString("status.collecting_job_results"),
526               progbarId);
527       Map<Long, AlignmentI> results = new LinkedHashMap<>();
528       for (WSJob job : getJobs())
529       {
530         if (job.getStatus().isFailed())
531           continue;
532         try
533         {
534           AlignmentI alignment = supplier.getResult(job, dataset.getSequences(), viewport);
535           if (alignment != null)
536           {
537             results.put(job.getUid(), alignment);
538           }
539         } catch (Exception e)
540         {
541           if (!service.handleCollectionError(job, e))
542           {
543             Cache.log.error("Couldn't get alignment for job.", e);
544             // TODO: Increment exception count and retry.
545             job.setStatus(WSJobStatus.SERVER_ERROR);
546           }
547         }
548       }
549       updateWSInfoGlobalStatus();
550       if (results.size() > 0)
551       {
552         OutputWrapper out = prepareOutput(results);
553         wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
554                 new Alignment(out.aln), out.alorders, out.hidden));
555         wsInfo.setResultsReady();
556       }
557       else
558       {
559         wsInfo.setFinishedNoResults();
560       }
561       wsInfo.removeProgressBar(progbarId);
562     }
563
564     private class OutputWrapper
565     {
566       AlignmentI aln;
567
568       List<AlignmentOrder> alorders;
569
570       HiddenColumns hidden;
571
572       OutputWrapper(AlignmentI aln, List<AlignmentOrder> alorders,
573               HiddenColumns hidden)
574       {
575         this.aln = aln;
576         this.alorders = alorders;
577         this.hidden = hidden;
578       }
579     }
580
581     private OutputWrapper prepareOutput(Map<Long, AlignmentI> alignments)
582     {
583       List<AlignmentOrder> alorders = new ArrayList<>();
584       SequenceI[][] results = new SequenceI[jobs.size()][];
585       AlignmentOrder[] orders = new AlignmentOrder[jobs.size()];
586       for (int i = 0; i < jobs.size(); i++)
587       {
588         WSJob job = jobs.get(i);
589         AlignmentI aln = alignments.get(job.getUid());
590         if (aln != null) // equivalent of job.hasResults()
591         {
592           /* Get the alignment including any empty sequences in the original
593            * order with original ids. */
594           JobInput input = inputs.get(job.getUid());
595           char gapChar = aln.getGapCharacter();
596           List<SequenceI> emptySeqs = input.emptySequences;
597           List<SequenceI> alnSeqs = aln.getSequences();
598           // find the width of the longest sequence
599           int width = 0;
600           for (var seq : alnSeqs)
601             width = Integer.max(width, seq.getLength());
602           for (var emptySeq : emptySeqs)
603             width = Integer.max(width, emptySeq.getLength());
604           // pad shorter sequences with gaps
605           String gapSeq = String.join("",
606                   Collections.nCopies(width, Character.toString(gapChar)));
607           List<SequenceI> seqs = new ArrayList<>(
608                   alnSeqs.size() + emptySeqs.size());
609           seqs.addAll(alnSeqs);
610           seqs.addAll(emptySeqs);
611           for (var seq : seqs)
612           {
613             if (seq.getLength() < width)
614               seq.setSequence(seq.getSequenceAsString()
615                       + gapSeq.substring(seq.getLength()));
616           }
617           SequenceI[] result = seqs.toArray(new SequenceI[0]);
618           AlignmentOrder msaOrder = new AlignmentOrder(result);
619           AlignmentSorter.recoverOrder(result);
620           // temporary workaround for deuniquify
621           @SuppressWarnings({ "rawtypes", "unchecked" })
622           Hashtable names = new Hashtable(input.sequenceNames);
623           // FIXME first call to deuniquify alters original alignment
624           SeqsetUtils.deuniquify(names, result);
625           alorders.add(msaOrder);
626           results[i] = result;
627           orders[i] = msaOrder;
628         }
629         else
630         {
631           results[i] = null;
632         }
633       }
634
635       Object[] newView = msa.getUpdatedView(results, orders, gapCharacter);
636       // free references to original data
637       for (int i = 0; i < jobs.size(); i++)
638       {
639         results[i] = null;
640         orders[i] = null;
641       }
642       SequenceI[] alignment = (SequenceI[]) newView[0];
643       HiddenColumns hidden = (HiddenColumns) newView[1];
644       Alignment aln = new Alignment(alignment);
645       aln.setProperty("Alignment Program", service.getName());
646       if (dataset != null)
647         aln.setDataset(dataset);
648
649       propagateDatasetMappings(aln);
650       return new OutputWrapper(aln, alorders, hidden);
651       // displayNewFrame(aln, alorders, hidden);
652     }
653
654     /*
655      * conserves dataset references to sequence objects returned from web
656      * services. propagate codon frame data to alignment.
657      */
658     private void propagateDatasetMappings(Alignment aln)
659     {
660       if (codonFrame != null)
661       {
662         SequenceI[] alignment = aln.getSequencesArray();
663         for (SequenceI seq : alignment)
664         {
665           for (AlignedCodonFrame acf : codonFrame)
666           {
667             if (acf != null && acf.involvesSequence(seq))
668             {
669               aln.addCodonFrame(acf);
670               break;
671             }
672           }
673         }
674       }
675     }
676
677     private void displayNewFrame(AlignmentI aln,
678             List<AlignmentOrder> alorders, HiddenColumns hidden)
679     {
680       AlignFrame frame = new AlignFrame(aln, hidden,
681               AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
682       // TODO store feature renderer settings in worker object
683       // frame.getFeatureRenderer().transferSettings(featureSettings);
684       var regions = sortOrders(alorders);
685       if (alorders.size() == 1)
686       {
687         frame.addSortByOrderMenuItem(
688                 format("%s Ordering", service.getName()), alorders.get(0));
689       }
690       else
691       {
692         for (int i = 0; i < alorders.size(); i++)
693         {
694           final int j = i;
695           Iterable<String> iter = () -> regions.get(j).stream()
696                   .map(it -> Integer.toString(it)).iterator();
697           var orderName = format("%s Region %s Ordering", service.getName(),
698                   String.join(",", iter));
699           frame.addSortByOrderMenuItem(orderName, alorders.get(i));
700         }
701       }
702
703       /* TODO
704        * If alignment was requested from one half of a SplitFrame, show in a
705        * SplitFrame with the other pane similarly aligned.
706        */
707
708       Desktop.addInternalFrame(frame, alnTitle, AlignFrame.DEFAULT_WIDTH,
709               AlignFrame.DEFAULT_HEIGHT);
710     }
711
712     private List<List<Integer>> sortOrders(List<?> alorders)
713     {
714       List<List<Integer>> regions = new ArrayList<>();
715       for (int i = 0; i < alorders.size(); i++)
716       {
717         List<Integer> regs = new ArrayList<>();
718         regs.add(i);
719         int j = i + 1;
720         while (j < alorders.size())
721         {
722           if (alorders.get(i).equals(alorders.get(j)))
723           {
724             alorders.remove(j);
725             regs.add(j);
726           }
727           else
728           {
729             j++;
730           }
731         }
732         regions.add(regs);
733       }
734       return regions;
735     }
736   }
737
738   private static class JobInput
739   {
740     final List<SequenceI> inputSequences;
741
742     final List<SequenceI> emptySequences;
743
744     @SuppressWarnings("rawtypes")
745     final Map<String, ? extends Map> sequenceNames;
746
747     private JobInput(int numSequences, List<SequenceI> inputSequences,
748             List<SequenceI> emptySequences,
749             @SuppressWarnings("rawtypes") Map<String, ? extends Map> names)
750     {
751       this.inputSequences = Collections.unmodifiableList(inputSequences);
752       this.emptySequences = Collections.unmodifiableList(emptySequences);
753       this.sequenceNames = names;
754     }
755
756     boolean isInputValid()
757     {
758       return inputSequences.size() >= 2;
759     }
760
761     static JobInput create(SequenceI[] sequences, int minLength,
762             boolean submitGaps)
763     {
764       assert minLength >= 0 : MessageManager.getString(
765               "error.implementation_error_minlen_must_be_greater_zero");
766       int numSeq = 0;
767       for (SequenceI seq : sequences)
768       {
769         if (seq.getEnd() - seq.getStart() >= minLength)
770         {
771           numSeq++;
772         }
773       }
774
775       List<SequenceI> inputSequences = new ArrayList<>();
776       List<SequenceI> emptySequences = new ArrayList<>();
777       @SuppressWarnings("rawtypes")
778       Map<String, Hashtable> names = new LinkedHashMap<>();
779       for (int i = 0; i < sequences.length; i++)
780       {
781         SequenceI seq = sequences[i];
782         String newName = SeqsetUtils.unique_name(i);
783         @SuppressWarnings("rawtypes")
784         Hashtable hash = SeqsetUtils.SeqCharacterHash(seq);
785         names.put(newName, hash);
786         if (numSeq > 1 && seq.getEnd() - seq.getStart() >= minLength)
787         {
788           String seqString = seq.getSequenceAsString();
789           if (!submitGaps)
790           {
791             seqString = AlignSeq.extractGaps(
792                     jalview.util.Comparison.GapChars, seqString);
793           }
794           inputSequences.add(new Sequence(newName, seqString));
795         }
796         else
797         {
798           String seqString = "";
799           if (seq.getEnd() >= seq.getStart())  // true if gaps only
800           {
801             seqString = seq.getSequenceAsString();
802             if (!submitGaps)
803             {
804               seqString = AlignSeq.extractGaps(
805                       jalview.util.Comparison.GapChars, seqString);
806             }
807           }
808           emptySequences.add(new Sequence(newName, seqString));
809         }
810       }
811
812       return new JobInput(numSeq, inputSequences, emptySequences, names);
813     }
814   }
815
816 }