Clover icon

Coverage Report

  1. Project Clover database Thu Dec 4 2025 16:11:35 GMT
  2. Package jalview.ws.jws1

File MsaWSThread.java

 

Coverage histogram

../../../img/srcFileCovDistChart0.png
60% of files have more coverage

Code metrics

128
228
18
2
721
556
98
0.43
12.67
9
5.44

Classes

Class Line # Actions
MsaWSThread 43 145 57
0.00%
MsaWSThread.MsaWSJob 53 83 41
0.00%
 

Contributing tests

No tests hitting this source file were found.

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.ws.jws1;
22   
23    import jalview.analysis.AlignSeq;
24    import jalview.bin.Console;
25    import jalview.datamodel.Alignment;
26    import jalview.datamodel.AlignmentI;
27    import jalview.datamodel.AlignmentOrder;
28    import jalview.datamodel.AlignmentView;
29    import jalview.datamodel.HiddenColumns;
30    import jalview.datamodel.SequenceI;
31    import jalview.gui.AlignFrame;
32    import jalview.gui.Desktop;
33    import jalview.gui.WebserviceInfo;
34    import jalview.util.MessageManager;
35    import jalview.ws.AWsJob;
36    import jalview.ws.JobStateSummary;
37    import jalview.ws.WSClientI;
38   
39    import java.util.Vector;
40   
41    import vamsas.objects.simple.MsaResult;
42   
 
43    class MsaWSThread extends JWS1Thread implements WSClientI
44    {
45    boolean submitGaps = false; // pass sequences including gaps to alignment
46   
47    // service
48   
49    boolean preserveOrder = true; // and always store and recover sequence
50   
51    // order
52   
 
53    class MsaWSJob extends WSJob
54    {
55    // hold special input for this
56    vamsas.objects.simple.SequenceSet seqs = new vamsas.objects.simple.SequenceSet();
57   
58    /**
59    * MsaWSJob
60    *
61    * @param jobNum
62    * int
63    * @param jobId
64    * String
65    */
 
66  0 toggle public MsaWSJob(int jobNum, SequenceI[] inSeqs)
67    {
68  0 this.jobnum = jobNum;
69  0 if (!prepareInput(inSeqs, 2))
70    {
71  0 submitted = true;
72  0 subjobComplete = true;
73  0 result = new MsaResult();
74  0 result.setFinished(true);
75  0 result.setStatus(MessageManager.getString("label.job_never_ran"));
76    }
77   
78    }
79   
80    Vector emptySeqs = new Vector();
81   
82    /**
83    * prepare input sequences for MsaWS service
84    *
85    * @param seqs
86    * jalview sequences to be prepared
87    * @param minlen
88    * minimum number of residues required for this MsaWS service
89    * @return true if seqs contains sequences to be submitted to service.
90    */
 
91  0 toggle private boolean prepareInput(SequenceI[] seqs, int minlen)
92    {
93  0 int nseqs = 0;
94  0 if (minlen < 0)
95    {
96  0 throw new Error(MessageManager.getString(
97    "error.implementation_error_minlen_must_be_greater_zero"));
98    }
99  0 for (int i = 0; i < seqs.length; i++)
100    {
101  0 if (seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
102    {
103  0 nseqs++;
104    }
105    }
106  0 boolean valid = nseqs > 1; // need at least two seqs
107  0 vamsas.objects.simple.Sequence[] seqarray = (valid)
108    ? new vamsas.objects.simple.Sequence[nseqs]
109    : null;
110  0 for (int i = 0, n = 0; i < seqs.length; i++)
111    {
112   
113  0 String newname = jalview.analysis.SeqsetUtils.unique_name(i); // same
114    // for
115    // any
116    // subjob
117  0 SeqNames.put(newname,
118    jalview.analysis.SeqsetUtils.SeqCharacterHash(seqs[i]));
119  0 if (valid && seqs[i].getEnd() - seqs[i].getStart() > minlen - 1)
120    {
121  0 seqarray[n] = new vamsas.objects.simple.Sequence();
122  0 seqarray[n].setId(newname);
123  0 seqarray[n++].setSeq((submitGaps) ? seqs[i].getSequenceAsString()
124    : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
125    seqs[i].getSequenceAsString()));
126    }
127    else
128    {
129  0 String empty = null;
130  0 if (seqs[i].getEnd() >= seqs[i].getStart())
131    {
132  0 empty = (submitGaps) ? seqs[i].getSequenceAsString()
133    : AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
134    seqs[i].getSequenceAsString());
135    }
136  0 emptySeqs.add(new String[] { newname, empty });
137    }
138    }
139  0 this.seqs = new vamsas.objects.simple.SequenceSet();
140  0 this.seqs.setSeqs(seqarray);
141  0 return valid;
142    }
143   
144    /**
145    *
146    * @return true if getAlignment will return a valid alignment result.
147    */
 
148  0 toggle @Override
149    public boolean hasResults()
150    {
151  0 if (subjobComplete && result != null && result.isFinished()
152    && ((MsaResult) result).getMsa() != null
153    && ((MsaResult) result).getMsa().getSeqs() != null)
154    {
155  0 return true;
156    }
157  0 return false;
158    }
159   
 
160  0 toggle public Object[] getAlignment()
161    {
162   
163  0 if (result != null && result.isFinished())
164    {
165  0 SequenceI[] alseqs = null;
166  0 char alseq_gapchar = '-';
167  0 int alseq_l = 0;
168  0 if (((MsaResult) result).getMsa() != null)
169    {
170  0 alseqs = getVamsasAlignment(((MsaResult) result).getMsa());
171  0 alseq_gapchar = ((MsaResult) result).getMsa().getGapchar()
172    .charAt(0);
173  0 alseq_l = alseqs.length;
174    }
175  0 if (emptySeqs.size() > 0)
176    {
177  0 SequenceI[] t_alseqs = new SequenceI[alseq_l + emptySeqs.size()];
178    // get width
179  0 int i, w = 0;
180  0 if (alseq_l > 0)
181    {
182  0 for (i = 0, w = alseqs[0].getLength(); i < alseq_l; i++)
183    {
184  0 if (w < alseqs[i].getLength())
185    {
186  0 w = alseqs[i].getLength();
187    }
188  0 t_alseqs[i] = alseqs[i];
189  0 alseqs[i] = null;
190    }
191    }
192    // check that aligned width is at least as wide as emptySeqs width.
193  0 int ow = w, nw = w;
194  0 for (i = 0, w = emptySeqs.size(); i < w; i++)
195    {
196  0 String[] es = (String[]) emptySeqs.get(i);
197  0 if (es != null && es[1] != null)
198    {
199  0 int sw = es[1].length();
200  0 if (nw < sw)
201    {
202  0 nw = sw;
203    }
204    }
205    }
206    // make a gapped string.
207  0 StringBuffer insbuff = new StringBuffer(w);
208  0 for (i = 0; i < nw; i++)
209    {
210  0 insbuff.append(alseq_gapchar);
211    }
212  0 if (ow < nw)
213    {
214  0 for (i = 0; i < alseq_l; i++)
215    {
216  0 int sw = t_alseqs[i].getLength();
217  0 if (nw > sw)
218    {
219    // pad at end
220  0 alseqs[i].setSequence(t_alseqs[i].getSequenceAsString()
221    + insbuff.substring(0, sw - nw));
222    }
223    }
224    }
225  0 for (i = 0, w = emptySeqs.size(); i < w; i++)
226    {
227  0 String[] es = (String[]) emptySeqs.get(i);
228  0 if (es[1] == null)
229    {
230  0 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(es[0],
231    insbuff.toString(), 1, 0);
232    }
233    else
234    {
235  0 if (es[1].length() < nw)
236    {
237  0 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
238    es[0],
239    es[1] + insbuff.substring(0, nw - es[1].length()),
240    1, 1 + es[1].length());
241    }
242    else
243    {
244  0 t_alseqs[i + alseq_l] = new jalview.datamodel.Sequence(
245    es[0], es[1]);
246    }
247    }
248    }
249  0 alseqs = t_alseqs;
250    }
251  0 AlignmentOrder msaorder = new AlignmentOrder(alseqs);
252    // always recover the order - makes parseResult()'s life easier.
253  0 jalview.analysis.AlignmentSorter.recoverOrder(alseqs);
254    // account for any missing sequences
255  0 jalview.analysis.SeqsetUtils.deuniquify(SeqNames, alseqs);
256  0 return new Object[] { alseqs, msaorder };
257    }
258  0 return null;
259    }
260   
261    /**
262    * mark subjob as cancelled and set result object appropriatly
263    */
 
264  0 toggle void cancel()
265    {
266  0 cancelled = true;
267  0 subjobComplete = true;
268  0 result = null;
269    }
270   
271    /**
272    *
273    * @return boolean true if job can be submitted.
274    */
 
275  0 toggle @Override
276    public boolean hasValidInput()
277    {
278  0 if (seqs.getSeqs() != null)
279    {
280  0 return true;
281    }
282  0 return false;
283    }
284    }
285   
286    String alTitle; // name which will be used to form new alignment window.
287   
288    AlignmentI dataset; // dataset to which the new alignment will be
289   
290    // associated.
291   
292    ext.vamsas.MuscleWS server = null;
293   
294    /**
295    * set basic options for this (group) of Msa jobs
296    *
297    * @param subgaps
298    * boolean
299    * @param presorder
300    * boolean
301    */
 
302  0 toggle MsaWSThread(ext.vamsas.MuscleWS server, String wsUrl,
303    WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
304    AlignmentView alview, String wsname, boolean subgaps,
305    boolean presorder)
306    {
307  0 super(alFrame, wsinfo, alview, wsname, wsUrl);
308  0 this.server = server;
309  0 this.submitGaps = subgaps;
310  0 this.preserveOrder = presorder;
311    }
312   
313    /**
314    * create one or more Msa jobs to align visible seuqences in _msa
315    *
316    * @param title
317    * String
318    * @param _msa
319    * AlignmentView
320    * @param subgaps
321    * boolean
322    * @param presorder
323    * boolean
324    * @param seqset
325    * Alignment
326    */
 
327  0 toggle MsaWSThread(ext.vamsas.MuscleWS server, String wsUrl,
328    WebserviceInfo wsinfo, jalview.gui.AlignFrame alFrame,
329    String wsname, String title, AlignmentView _msa, boolean subgaps,
330    boolean presorder, AlignmentI seqset)
331    {
332  0 this(server, wsUrl, wsinfo, alFrame, _msa, wsname, subgaps, presorder);
333  0 OutputHeader = wsInfo.getProgressText();
334  0 alTitle = title;
335  0 dataset = seqset;
336   
337  0 SequenceI[][] conmsa = _msa.getVisibleContigs('-');
338  0 if (conmsa != null)
339    {
340  0 int njobs = conmsa.length;
341  0 jobs = new MsaWSJob[njobs];
342  0 for (int j = 0; j < njobs; j++)
343    {
344  0 if (j != 0)
345    {
346  0 jobs[j] = new MsaWSJob(wsinfo.addJobPane(), conmsa[j]);
347    }
348    else
349    {
350  0 jobs[j] = new MsaWSJob(0, conmsa[j]);
351    }
352  0 if (njobs > 0)
353    {
354  0 wsinfo.setProgressName("region " + jobs[j].getJobnum(),
355    jobs[j].getJobnum());
356    }
357  0 wsinfo.setProgressText(jobs[j].getJobnum(), OutputHeader);
358    }
359    }
360    }
361   
 
362  0 toggle @Override
363    public boolean isCancellable()
364    {
365  0 return true;
366    }
367   
 
368  0 toggle @Override
369    public void cancelJob()
370    {
371  0 if (!jobComplete && jobs != null)
372    {
373  0 boolean cancelled = true;
374  0 for (int job = 0; job < jobs.length; job++)
375    {
376  0 if (jobs[job].isSubmitted() && !jobs[job].isSubjobComplete())
377    {
378  0 String cancelledMessage = "";
379  0 try
380    {
381  0 vamsas.objects.simple.WsJobId cancelledJob = server
382    .cancel(jobs[job].getJobId());
383  0 if (cancelledJob.getStatus() == 2)
384    {
385    // CANCELLED_JOB
386  0 cancelledMessage = "Job cancelled.";
387  0 ((MsaWSJob) jobs[job]).cancel();
388  0 wsInfo.setStatus(jobs[job].getJobnum(),
389    WebserviceInfo.STATE_CANCELLED_OK);
390    }
391  0 else if (cancelledJob.getStatus() == 3)
392    {
393    // VALID UNSTOPPABLE JOB
394  0 cancelledMessage += "Server cannot cancel this job. just close the window.\n";
395  0 cancelled = false;
396    // wsInfo.setStatus(jobs[job].jobnum,
397    // WebserviceInfo.STATE_RUNNING);
398    }
399   
400  0 if (cancelledJob.getJobId() != null)
401    {
402  0 cancelledMessage += ("[" + cancelledJob.getJobId() + "]");
403    }
404   
405  0 cancelledMessage += "\n";
406    } catch (Exception exc)
407    {
408  0 cancelledMessage += ("\nProblems cancelling the job : Exception received...\n"
409    + exc + "\n");
410  0 Console.warn(
411    "Exception whilst cancelling " + jobs[job].getJobId(),
412    exc);
413    }
414  0 wsInfo.setProgressText(jobs[job].getJobnum(),
415    OutputHeader + cancelledMessage + "\n");
416    }
417    }
418  0 if (cancelled)
419    {
420  0 wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
421  0 jobComplete = true;
422    }
423  0 this.interrupt(); // kick thread to update job states.
424    }
425    else
426    {
427  0 if (!jobComplete)
428    {
429  0 wsInfo.setProgressText(OutputHeader
430    + "Server cannot cancel this job because it has not been submitted properly. just close the window.\n");
431    }
432    }
433    }
434   
 
435  0 toggle @Override
436    public void pollJob(AWsJob job) throws Exception
437    {
438  0 ((MsaWSJob) job).result = server.getResult(((MsaWSJob) job).getJobId());
439    }
440   
 
441  0 toggle @Override
442    public void StartJob(AWsJob job)
443    {
444  0 if (!(job instanceof MsaWSJob))
445    {
446  0 throw new Error(MessageManager.formatMessage(
447    "error.implementation_error_msawbjob_called", new String[]
448    { job.getClass().toString() }));
449    }
450  0 MsaWSJob j = (MsaWSJob) job;
451  0 if (j.isSubmitted())
452    {
453  0 if (Console.isDebugEnabled())
454    {
455  0 Console.debug(
456    "Tried to submit an already submitted job " + j.getJobId());
457    }
458  0 return;
459    }
460  0 if (j.seqs.getSeqs() == null)
461    {
462    // special case - selection consisted entirely of empty sequences...
463  0 j.setSubmitted(true);
464  0 j.result = new MsaResult();
465  0 j.result.setFinished(true);
466  0 j.result.setStatus(
467    MessageManager.getString("label.empty_alignment_job"));
468  0 ((MsaResult) j.result).setMsa(null);
469    }
470  0 try
471    {
472  0 vamsas.objects.simple.WsJobId jobsubmit = server.align(j.seqs);
473   
474  0 if ((jobsubmit != null) && (jobsubmit.getStatus() == 1))
475    {
476  0 j.setJobId(jobsubmit.getJobId());
477  0 j.setSubmitted(true);
478  0 j.setSubjobComplete(false);
479    // jalview.bin.Console.outPrintln(WsURL + " Job Id '" + jobId + "'");
480    }
481    else
482    {
483  0 if (jobsubmit == null)
484    {
485  0 throw new Exception(MessageManager.formatMessage(
486    "exception.web_service_returned_null_try_later",
487    new String[]
488    { WsUrl }));
489    }
490   
491  0 throw new Exception(jobsubmit.getJobId());
492    }
493    } catch (Exception e)
494    {
495    // TODO: JBPNote catch timeout or other fault types explicitly
496    // For unexpected errors
497  0 jalview.bin.Console.errPrintln(WebServiceName
498    + "Client: Failed to submit the sequences for alignment (probably a server side problem)\n"
499    + "When contacting Server:" + WsUrl + "\n" + e.toString()
500    + "\n");
501  0 j.setAllowedServerExceptions(0);
502  0 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_SERVERERROR);
503  0 wsInfo.setStatus(j.getJobnum(),
504    WebserviceInfo.STATE_STOPPED_SERVERERROR);
505  0 wsInfo.appendProgressText(j.getJobnum(), MessageManager
506    .getString("info.failed_to_submit_sequences_for_alignment"));
507   
508    // e.printStackTrace(); // TODO: JBPNote DEBUG
509    }
510    }
511   
 
512  0 toggle private jalview.datamodel.Sequence[] getVamsasAlignment(
513    vamsas.objects.simple.Alignment valign)
514    {
515    // TODO: refactor to helper class for vamsas.objects.simple objects
516  0 vamsas.objects.simple.Sequence[] seqs = valign.getSeqs().getSeqs();
517  0 jalview.datamodel.Sequence[] msa = new jalview.datamodel.Sequence[seqs.length];
518   
519  0 for (int i = 0, j = seqs.length; i < j; i++)
520    {
521  0 msa[i] = new jalview.datamodel.Sequence(seqs[i].getId(),
522    seqs[i].getSeq());
523    }
524   
525  0 return msa;
526    }
527   
 
528  0 toggle @Override
529    public void parseResult()
530    {
531  0 int results = 0; // number of result sets received
532  0 JobStateSummary finalState = new JobStateSummary();
533  0 try
534    {
535  0 for (int j = 0; j < jobs.length; j++)
536    {
537  0 finalState.updateJobPanelState(wsInfo, OutputHeader, jobs[j]);
538  0 if (jobs[j].isSubmitted() && jobs[j].isSubjobComplete()
539    && jobs[j].hasResults())
540    {
541  0 results++;
542    // if (Cache.isDebugEnabled())
543    // {
544    // jalview.bin.Console.outPrintln("Job lob for job
545    // "+jobs[j].getJobId()+":"+jobs[j].getJobnum());
546    // jalview.bin.Console.outPrintln(jobs[j].getStatus());
547    // }
548   
549  0 vamsas.objects.simple.Alignment valign = ((MsaResult) ((MsaWSJob) jobs[j]).result)
550    .getMsa();
551  0 if (valign != null)
552    {
553  0 wsInfo.appendProgressText(jobs[j].getJobnum(), MessageManager
554    .getString("info.alignment_object_method_notes"));
555  0 String[] lines = valign.getMethod();
556  0 for (int line = 0; line < lines.length; line++)
557    {
558  0 wsInfo.appendProgressText(jobs[j].getJobnum(),
559    lines[line] + "\n");
560    }
561    // JBPNote The returned files from a webservice could be
562    // hidden behind icons in the monitor window that,
563    // when clicked, pop up their corresponding data
564   
565    }
566    }
567    }
568    } catch (Exception ex)
569    {
570   
571  0 Console.error(
572    "Unexpected exception when processing results for " + alTitle,
573    ex);
574  0 wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
575    }
576  0 if (results > 0)
577    {
578  0 wsInfo.showResultsNewFrame
579    .addActionListener(new java.awt.event.ActionListener()
580    {
 
581  0 toggle @Override
582    public void actionPerformed(java.awt.event.ActionEvent evt)
583    {
584  0 displayResults(true);
585    }
586    });
587  0 wsInfo.mergeResults
588    .addActionListener(new java.awt.event.ActionListener()
589    {
 
590  0 toggle @Override
591    public void actionPerformed(java.awt.event.ActionEvent evt)
592    {
593  0 displayResults(false);
594    }
595    });
596  0 wsInfo.setResultsReady();
597    }
598    else
599    {
600  0 wsInfo.setFinishedNoResults();
601    }
602    }
603   
 
604  0 toggle void displayResults(boolean newFrame)
605    {
606    // view input or result data for each block
607  0 Vector alorders = new Vector();
608  0 SequenceI[][] results = new SequenceI[jobs.length][];
609  0 AlignmentOrder[] orders = new AlignmentOrder[jobs.length];
610  0 for (int j = 0; j < jobs.length; j++)
611    {
612  0 if (jobs[j].hasResults())
613    {
614  0 Object[] res = ((MsaWSJob) jobs[j]).getAlignment();
615  0 alorders.add(res[1]);
616  0 results[j] = (SequenceI[]) res[0];
617  0 orders[j] = (AlignmentOrder) res[1];
618   
619    // SequenceI[] alignment = input.getUpdated
620    }
621    else
622    {
623  0 results[j] = null;
624    }
625    }
626  0 Object[] newview = input.getUpdatedView(results, orders, getGapChar());
627    // trash references to original result data
628  0 for (int j = 0; j < jobs.length; j++)
629    {
630  0 results[j] = null;
631  0 orders[j] = null;
632    }
633  0 SequenceI[] alignment = (SequenceI[]) newview[0];
634  0 HiddenColumns hidden = (HiddenColumns) newview[1];
635  0 Alignment al = new Alignment(alignment);
636    // TODO: add 'provenance' property to alignment from the method notes
637    // accompanying each subjob
638  0 if (dataset != null)
639    {
640  0 al.setDataset(dataset);
641    }
642   
643  0 propagateDatasetMappings(al);
644    // JBNote- TODO: warn user if a block is input rather than aligned data ?
645   
646  0 if (newFrame)
647    {
648  0 AlignFrame af = new AlignFrame(al, hidden, AlignFrame.DEFAULT_WIDTH,
649    AlignFrame.DEFAULT_HEIGHT);
650   
651    // initialise with same renderer settings as in parent alignframe.
652  0 af.getFeatureRenderer().transferSettings(this.featureSettings);
653    // update orders
654  0 if (alorders.size() > 0)
655    {
656  0 if (alorders.size() == 1)
657    {
658  0 af.addSortByOrderMenuItem(WebServiceName + " Ordering",
659    (AlignmentOrder) alorders.get(0));
660    }
661    else
662    {
663    // construct a non-redundant ordering set
664  0 Vector names = new Vector();
665  0 for (int i = 0, l = alorders.size(); i < l; i++)
666    {
667  0 String orderName = new String(" Region " + i);
668  0 int j = i + 1;
669   
670  0 while (j < l)
671    {
672  0 if (((AlignmentOrder) alorders.get(i))
673    .equals((alorders.get(j))))
674    {
675  0 alorders.remove(j);
676  0 l--;
677  0 orderName += "," + j;
678    }
679    else
680    {
681  0 j++;
682    }
683    }
684   
685  0 if (i == 0 && j == 1)
686    {
687  0 names.add(new String(""));
688    }
689    else
690    {
691  0 names.add(orderName);
692    }
693    }
694  0 for (int i = 0, l = alorders.size(); i < l; i++)
695    {
696  0 af.addSortByOrderMenuItem(
697    WebServiceName + ((String) names.get(i)) + " Ordering",
698    (AlignmentOrder) alorders.get(i));
699    }
700    }
701    }
702   
703  0 Desktop.addInternalFrame(af, alTitle, AlignFrame.DEFAULT_WIDTH,
704    AlignFrame.DEFAULT_HEIGHT);
705   
706    }
707    else
708    {
709  0 jalview.bin.Console.outPrintln("MERGE WITH OLD FRAME");
710    // TODO: modify alignment in original frame, replacing old for new
711    // alignment using the commands.EditCommand model to ensure the update can
712    // be undone
713    }
714    }
715   
 
716  0 toggle @Override
717    public boolean canMergeResults()
718    {
719  0 return false;
720    }
721    }