upstream version 1.2.2
[debian/freetts] / demo / JSAPI / Player / PlayerModelImpl.java
1 /**
2  * Copyright 2001 Sun Microsystems, Inc.
3  * 
4  * See the file "license.terms" for information on usage and
5  * redistribution of this file, and for a DISCLAIMER OF ALL 
6  * WARRANTIES.
7  */
8 import java.beans.PropertyVetoException;
9 import java.io.BufferedReader;
10 import java.io.File;
11 import java.io.FileInputStream;
12 import java.io.FileNotFoundException;
13 import java.io.FileReader;
14 import java.io.InputStream;
15 import java.io.InputStreamReader;
16 import java.io.IOException;
17 import java.net.MalformedURLException;
18 import java.net.URL;
19 import java.util.Enumeration;
20 import java.util.Locale;
21 import java.util.HashSet;
22 import java.util.Set;
23 import java.util.Iterator;
24
25 import javax.speech.AudioException;
26 import javax.speech.Central;
27 import javax.speech.Engine;
28 import javax.speech.EngineException;
29 import javax.speech.EngineList;
30 import javax.speech.synthesis.Synthesizer;
31 import javax.speech.synthesis.SynthesizerModeDesc;
32 import javax.speech.synthesis.SynthesizerProperties;
33 import javax.speech.synthesis.Voice;
34
35 import javax.swing.DefaultComboBoxModel;
36 import javax.swing.DefaultListModel;
37 import javax.swing.JFrame;
38 import javax.swing.ListModel;
39
40 import com.sun.speech.engine.synthesis.SynthesizerMonitor;
41
42 /**
43  * Implements the text-to-speech data model of the Player application, using
44  * JSAPI. It should work with any JSAPI implementation.
45  */
46 public class PlayerModelImpl implements PlayerModel {
47
48     private Synthesizer synthesizer;
49     private Monitor monitor;
50     private boolean monitorVisible = false;
51     private boolean paused = false;
52     private boolean stopped = false;
53     private boolean playingFile = false;
54     private DefaultListModel playList;
55     private DefaultComboBoxModel synthesizerList;
56     private DefaultComboBoxModel voiceList;
57     private float volume = -1;
58     private static boolean debug = false;
59     private Set loadedSynthesizers;
60
61     
62     /**
63      * Constructs a default PlayerModelImpl.
64      */
65     public PlayerModelImpl() {
66         playList = new DefaultListModel();
67         synthesizerList = new DefaultComboBoxModel();
68         voiceList = new DefaultComboBoxModel();
69         loadedSynthesizers = new HashSet();
70     }
71     
72
73     /**
74      * Creates a FreeTTS synthesizer.
75      */
76     public void createSynthesizers() {
77         try {
78             EngineList list = Central.availableSynthesizers(null); 
79             Enumeration e = list.elements();
80
81             while (e.hasMoreElements()) {
82                 MySynthesizerModeDesc myModeDesc =
83                     new MySynthesizerModeDesc
84                     ((SynthesizerModeDesc) e.nextElement(), this);
85                 debugPrint(myModeDesc.getEngineName() + " " +
86                            myModeDesc.getLocale() + " " +
87                            myModeDesc.getModeName() + " " +
88                            myModeDesc.getRunning());
89                 synthesizerList.addElement(myModeDesc);
90             }
91             
92             if (synthesizerList.getSize() > 0) {
93                 setSynthesizer(0);
94             } else {
95                 System.err.println(noSynthesizerMessage());
96             }
97             if (synthesizer == null) {
98                 System.err.println("PlayerModelImpl: Can't find synthesizer");
99                 System.exit(1);
100             }
101         } catch (Exception e) {
102             e.printStackTrace();
103         }
104     }
105
106     /**
107      * Returns a "no synthesizer" message, and asks 
108      * the user to check if the "speech.properties" file is
109      * at <code>user.home</code> or <code>java.home/lib</code>.
110      *
111      * @return a no synthesizer message
112      */
113     static private String noSynthesizerMessage() {
114         String message =
115             "No synthesizer created.  This may be the result of any\n" +
116             "number of problems.  It's typically due to a missing\n" +
117             "\"speech.properties\" file that should be at either of\n" +
118             "these locations: \n\n";
119         message += "user.home    : " + System.getProperty("user.home") + "\n";
120         message += "java.home/lib: " + System.getProperty("java.home") +
121             File.separator + "lib\n\n" +
122             "Another cause of this problem might be corrupt or missing\n" +
123             "voice jar files in the freetts lib directory.  This problem\n" +
124             "also sometimes arises when the freetts.jar file is corrupt\n" +
125             "or missing.  Sorry about that.  Please check for these\n" +
126             "various conditions and then try again.\n";
127         return message;
128     }
129             
130     /**
131      * Performs TTS on the given Playable.
132      *
133      * @param playable the Playable object to play
134      */
135     public void play(Playable playable) {
136         if (playable != null) {
137             if (playable.getType() == PlayableType.TEXT) {
138                 play(playable.getText());
139             } else if (playable.getType() == PlayableType.JSML) {
140                 playJSML(playable.getText());
141             } else if (playable.getType() == PlayableType.TEXT_FILE ||
142                        playable.getType() == PlayableType.JSML_FILE) {
143                 playFile(playable.getFile(), playable.getType());
144             } else if (playable.getType() == PlayableType.URL) {
145                 try {
146                     playURL(new URL(playable.getName()));
147                 } catch (MalformedURLException mue) {
148                     mue.printStackTrace();
149                 }
150             }
151         }
152     }
153
154
155     /**
156      * Performs TTS on the object at the given index of the play list.
157      *
158      * @param index index of the object in the playlist to play
159      */
160     public void play(int index) {
161         if (0 <= index && index < playList.getSize()) {
162             Playable playable = (Playable) playList.getElementAt(index);
163             if (playable != null) {
164                 play(playable);
165             }
166         }
167     }
168
169
170     /**
171      * Performs text-to-speech on the given text.
172      *
173      * @param text the text to perform TTS
174      */
175     private void play(String text) {
176         synthesizer.speakPlainText(text, null);
177     }
178     
179
180     /**
181      * Performs text-to-speech on the given JSML text.
182      *
183      * @param text the text to perform TTS
184      */
185     private void playJSML(String jsmlText) {
186         try {
187             synthesizer.speak(jsmlText, null);
188         } catch (Exception e) {
189             e.printStackTrace();
190         }
191     }
192
193
194     /**
195      * Plays the text in the given File.
196      *
197      * @param file the File to play
198      * @param type the file type
199      */
200     private void playFile(File file, PlayableType type) {
201         try {
202             FileInputStream fileStream = new FileInputStream(file);
203             playInputStream(fileStream, type);
204         } catch (FileNotFoundException fnfe) {
205             fnfe.printStackTrace();
206         }
207     }
208         
209
210     /**
211      * Plays the text in the given File.
212      *
213      * @param file the File to play
214      * @param type the file type
215      */
216     private void playInputStream(InputStream inStream, PlayableType type) {
217         playingFile = true;
218         if (inStream != null) {
219             try {
220                 BufferedReader reader = new BufferedReader
221                     (new InputStreamReader(inStream));
222                 String line = "";
223                 if (type == PlayableType.TEXT_FILE) {
224                     while (!isStopped() && 
225                            (line = reader.readLine()) != null) {
226                         if (line.length() > 0) {
227                             play(line);
228                         }
229                     }
230                 } else if (type == PlayableType.JSML_FILE) {
231                     String fileText = "";
232                     while ((line = reader.readLine()) != null) {
233                         fileText += line;
234                     }
235                     if (fileText != null && fileText.length() > 0) {
236                         playJSML(fileText);
237                     }
238                 }
239                 stopped = false;
240             } catch (IOException ioe) {
241                 ioe.printStackTrace();
242             }
243         }
244         playingFile = false;
245     }
246
247
248     /**
249      * Plays the contents of the given URL.
250      *
251      * @param url the URL to play
252      */
253     private void playURL(URL url) {
254         try {
255             synthesizer.speak(url, null);
256         } catch (Exception e) {
257             e.printStackTrace();
258         }
259     }
260         
261
262     /**
263      * Returns true if the player is paused.
264      *
265      * @return true if the player is paused, false otherwise
266      */
267     public synchronized boolean isPaused() {
268         return paused;
269     }
270     
271
272     /**
273      * Pauses the player.
274      */
275     public synchronized void pause() {
276         paused = true;
277         synthesizer.pause();
278     }
279         
280
281     /**
282      * Resumes the player.
283      */
284     public synchronized void resume() {
285         paused = false;
286         try {
287             synthesizer.resume();
288         } catch (AudioException ae) {
289             ae.printStackTrace();
290         }       
291     }
292             
293
294     /**
295      * Stops the player if it is playing.
296      */
297     public synchronized void stop() {
298         if (playingFile) {
299             stopped = true;
300         }
301         synthesizer.cancelAll();
302     }
303
304
305     /**
306      * Cancels the currently playing item.
307      */
308     public void cancel() {
309         synthesizer.cancel();
310     }
311
312     /**
313      * Close this playable
314      */
315     public void close() {
316         for (Iterator i = loadedSynthesizers.iterator(); i.hasNext();) {
317             Synthesizer synth = (Synthesizer) i.next();
318             try {
319                 synth.deallocate();
320             } catch (EngineException ee) {
321                 System.out.println("Trouble closing the synthesizer: " + ee);
322             }
323         }
324     }
325
326
327     /**
328      * Returns true if the Player is currently being stopped.
329      *
330      * @return true if the Player is currently being stopped; false otherwise
331      */    
332     private synchronized boolean isStopped() {
333         return stopped;
334     }
335     
336
337     /**
338      * Sets whether the Monitor is visible
339      *
340      * @param visible true to set the Monitor as visible
341      */
342     public void setMonitorVisible(boolean visible) {
343         monitorVisible = visible;
344         if (monitor != null) {
345             monitor.setVisible(monitorVisible);
346         }
347     }
348     
349
350     /**
351      * Tells whether the monitor is visible.
352      *
353      * @return true if the monitor is visible, false otherwise
354      */
355     public boolean isMonitorVisible() {
356         return monitorVisible;
357     }
358             
359
360     /**
361      * Returns the monitor of the synthesizer at the given index.
362      *
363      * @param index the position of the synthesizer in the synthesizer list
364      *
365      * @return the monitor of the specified synthesizer
366      */
367     public Monitor getMonitor(int index) {
368         MySynthesizerModeDesc myModeDesc = (MySynthesizerModeDesc)
369             synthesizerList.getElementAt(index);
370         Monitor monitor = null;
371
372         if (myModeDesc != null) {
373             monitor = myModeDesc.getMonitor();
374         }
375         return monitor;
376     }
377
378
379     /**
380      * Returns the monitor of the current synthesizer.
381      *
382      * @return the monitor of the current synthesizer
383      */
384     public Monitor getMonitor() {
385         return monitor;
386     }
387
388
389     /**
390      * Sets the current monitor.
391      *
392      * @param monitor the current monitor
393      */
394     public void setMonitor(Monitor monitor) {
395         this.monitor = monitor;
396     }
397
398
399     /**
400      * Sets the Synthesizer at the given index to use
401      *
402      * @param index index of the synthesizer in the list
403      */
404     public void setSynthesizer(int index) {
405         MySynthesizerModeDesc myModeDesc = (MySynthesizerModeDesc)
406             synthesizerList.getElementAt(index);
407         if (myModeDesc != null) {
408             if (isMonitorVisible()) {
409                 if (monitor != null) {
410                     monitor.setVisible(false);
411                 }
412             }
413             synthesizer = myModeDesc.getSynthesizer();
414             if (synthesizer == null) {
415                 synthesizer = myModeDesc.createSynthesizer();
416                 if (synthesizer == null) {
417                     debugPrint("still null");
418                 } else {
419                     debugPrint("created");
420                 }
421             } else {
422                 debugPrint("not null");
423             }
424             monitor = myModeDesc.getMonitor();
425             if (myModeDesc.isSynthesizerLoaded()) {
426                 setVoiceList(myModeDesc);
427             } else {
428                 myModeDesc.loadSynthesizer();
429             }
430
431             loadedSynthesizers.add(synthesizer);
432             synthesizerList.setSelectedItem(myModeDesc);
433         }
434     }
435     
436
437     /**
438      * Sets the Voice at the given to use.
439      *
440      * @param index the index of the voice
441      */
442     public void setVoice(int index) {
443         try {
444             Voice voice = (Voice) voiceList.getElementAt(index);
445             if (voice != null) {
446                 float oldVolume = getVolume();
447                 float oldSpeakingRate = getSpeakingRate();
448                 synthesizer.waitEngineState(Synthesizer.QUEUE_EMPTY);
449                 synthesizer.getSynthesizerProperties().setVoice(voice);
450                 setVolume(oldVolume);
451                 setSpeakingRate(oldSpeakingRate);
452                 voiceList.setSelectedItem(voice);
453             }
454         } catch (PropertyVetoException pve) {
455             pve.printStackTrace();
456         } catch (InterruptedException ie) {
457             ie.printStackTrace();
458         }
459     }
460     
461
462     /**
463      * Returns the volume, in the range of 0 to 10.
464      *
465      * @return the volume, or -1 if unknown, or an error occurred
466      */
467     public float getVolume() {
468         try {
469             float adjustedVolume =
470                 synthesizer.getSynthesizerProperties().getVolume();
471             if (adjustedVolume < 0.5) {
472                 volume = 0;
473             } else {
474                 volume = (float) ((adjustedVolume - 0.5) * 20);
475             }
476         } catch (Exception e) {
477             e.printStackTrace();
478         } 
479         return volume;
480     }
481     
482
483     /**
484      * Sets the volume, in the range of 0 to 10.
485      *
486      * @param volume the new volume
487      *
488      * @return true if new volume is set; false otherwise
489      */
490     public boolean setVolume(float volume) {
491         try {
492             float adjustedVolume = (float) (volume/20 + 0.5);
493             if (synthesizer != null) {
494                 synthesizer.getSynthesizerProperties().setVolume
495                     (adjustedVolume);
496                 this.volume = volume;
497                 return true;
498             } else {
499                 this.volume = volume;
500                 return false;
501             }
502         } catch (PropertyVetoException pve) {
503             try {
504                 synthesizer.getSynthesizerProperties().setVolume(this.volume);
505             } catch (PropertyVetoException pe) {
506                 pe.printStackTrace();
507             }
508             return false;
509         }
510     }
511                 
512
513     /**
514      * Returns the speaking rate.
515      *
516      * @return the speaking rate, or -1 if unknown or an error occurred
517      */
518     public float getSpeakingRate() {
519         if (synthesizer != null) {
520             return synthesizer.getSynthesizerProperties().getSpeakingRate();
521         } else {
522             return -1;
523         }
524     }
525         
526
527     /**
528      * Sets the speaking rate in terms of words per minute.
529      *
530      * @param wordsPerMin the new speaking rate
531      *
532      * @return the speaking rate, or -1 if unknown or an error occurred
533      */
534     public boolean setSpeakingRate(float wordsPerMin) {
535         float oldSpeed = getSpeakingRate();
536         SynthesizerProperties properties =
537             synthesizer.getSynthesizerProperties();
538         try {
539             properties.setSpeakingRate(wordsPerMin);
540             return true;
541         } catch (PropertyVetoException pve) {
542             try {
543                 properties.setSpeakingRate(oldSpeed);
544             } catch (PropertyVetoException pe) {
545                 pe.printStackTrace();
546             }
547             return false;
548         }
549     }   
550
551
552     /**
553      * Returns the baseline pitch for the current synthesis voice.
554      *
555      * @return the baseline pitch for the current synthesis voice
556      */
557     public float getPitch() {
558         return synthesizer.getSynthesizerProperties().getPitch();
559     }   
560
561
562     /**
563      * Sets the baseline pitch for the current synthesis voice.
564      *
565      * @param pitch the baseline pitch
566      *
567      * @return true if new pitch is set; false otherwise
568      */
569     public boolean setPitch(float pitch) {
570         float oldPitch = getPitch();
571         try {
572             synthesizer.getSynthesizerProperties().setPitch(pitch);
573             return true;
574         } catch (PropertyVetoException pve) {
575             try {
576                 synthesizer.getSynthesizerProperties().setPitch(oldPitch);
577             } catch (PropertyVetoException pe) {
578                 pe.printStackTrace();
579             }
580             return false;
581         }
582     }
583     
584
585     /**
586      * Returns the pitch range for the current synthesis voice.
587      *
588      * @return the pitch range for the current synthesis voice
589      */
590     public float getRange() {
591         return synthesizer.getSynthesizerProperties().getPitchRange();
592     }
593     
594
595     /**
596      * Sets the pitch range for the current synthesis voice.
597      *
598      * @param range the pitch range
599      *
600      * @return true if new range is set; false otherwise
601      */
602     public boolean setRange(float range) {
603         float oldRange = getRange();
604         try {
605             synthesizer.getSynthesizerProperties().setPitchRange(range);
606             return true;
607         } catch (PropertyVetoException pve) {
608             try {
609                 synthesizer.getSynthesizerProperties().setPitchRange(oldRange);
610             } catch (PropertyVetoException pe) {
611                 pe.printStackTrace();
612             }
613             return false;
614         }
615     }
616          
617
618     /**
619      * Sets the list of voices using the given Synthesizer mode description.
620      *
621      * @param modeDesc the synthesizer mode description
622      */
623     public void setVoiceList(SynthesizerModeDesc modeDesc) {
624         Voice[] voices = modeDesc.getVoices();
625         voiceList.removeAllElements();
626         for (int i = 0; i < voices.length; i++) {
627             voiceList.addElement(new MyVoice(voices[i].getName(),
628                                              voices[i].getGender(),
629                                              voices[i].getAge(),
630                                              voices[i].getStyle()));
631         }
632     }
633
634
635     /**
636      * Returns the play list.
637      *
638      * @return the play list
639      */
640     public ListModel getPlayList() {
641         return playList;
642     }
643
644
645     /**
646      * Returns the list of voices.
647      *
648      * @return the list of voices
649      */
650     public ListModel getVoiceList() {
651         return voiceList;
652     }
653
654
655     /**
656      * Returns the list synthesizers
657      *
658      * @return the synthesizer list
659      */
660     public ListModel getSynthesizerList() {
661         return synthesizerList;
662     }
663
664     
665     /**
666      * Returns the Playable object at the given index of the play list.
667      *
668      * @return the Playable object
669      */
670     public Object getPlayableAt(int index) {
671         return null;
672     }
673     
674
675     /**
676      * Adds the given Playable object to the end of the play list.
677      *
678      * @param playable the Playable object to add
679      */
680     public void addPlayable(Playable playable) {
681         playList.addElement(playable);
682     }
683
684
685     /**
686      * Removes the playable at the given position from the list
687      *
688      * @param index the index of the Playable to remove
689      */
690     public void removePlayableAt(int index) {
691         if (index < playList.getSize()) {
692             playList.removeElementAt(index);
693         }
694     }
695
696
697     /**
698      * Prints debug statements.
699      *
700      * @param statement debug statements
701      */
702     public static void debugPrint(String statement) {
703         if (debug) {
704             System.out.println(statement);
705         }
706     }
707 }
708
709
710 /**
711  * A Voice that implements the <code>toString()</code> method so that
712  * it returns the name of the person who owns this Voice.
713  */
714 class MyVoice extends Voice {
715
716
717     /**
718      * Constructor provided with voice name, gender, age and style.
719      *
720      * @param name the name of the person who owns this Voice
721      * @param gender the gender of the person
722      * @param age the age of the person
723      * @param style the style of the person
724      */
725     public MyVoice(String name, int gender, int age, String style) {
726         super(name, gender, age, style);
727     }
728
729
730     /**
731      * Returns the name of the person who owns this Voice.
732      *
733      * @param String the name of the person
734      */
735     public String toString() {
736         return getName();
737     }
738 }
739
740
741 /**
742  * A SynthesizerModeDesc that implements the <code>toString()</code>
743  * method so that it returns the name of the synthesizer.
744  */
745 class MySynthesizerModeDesc extends SynthesizerModeDesc {
746
747     private PlayerModel playerModel = null;
748     private Synthesizer synthesizer = null;
749     private Monitor monitor = null;
750     private boolean synthesizerLoaded = false;
751     
752
753     /**
754      * Constructs a MySynthesizerModeDesc with the attributes from
755      * the given SynthesizerModeDesc.
756      *
757      * @param modeDesc the SynthesizerModeDesc to get attributes from
758      */
759     public MySynthesizerModeDesc(SynthesizerModeDesc modeDesc,
760                                  PlayerModel playerModel) {
761         super(modeDesc.getEngineName(), modeDesc.getModeName(),
762               modeDesc.getLocale(), modeDesc.getRunning(), 
763               modeDesc.getVoices());
764         this.playerModel = playerModel;
765     }
766     
767     
768     /**
769      * Returns true if the synthesizer is already loaded.
770      *
771      * @return true if the synthesizer is already loaded
772      */
773     public synchronized boolean isSynthesizerLoaded() {
774         if (synthesizer == null) {
775             return false;
776         }
777         return ((synthesizer.getEngineState() & Engine.ALLOCATED) != 0);
778     }
779     
780     
781     /**
782      * Returns a Synthesizer that fits the description of this
783      * MySynthesizerModeDesc. If the synthesize was never loaded,
784      * it is loaded in a separate thread.
785      *
786      * @return a Synthesizer
787      */
788     public synchronized Synthesizer getSynthesizer() {
789         debugPrint("MyModeDesc.getSynthesizer(): " + getEngineName());
790         return synthesizer;
791     }
792
793
794     /**
795      * Creates the Synthesizer and its Monitor.
796      *
797      * @return the created Synthesizer
798      */
799     public Synthesizer createSynthesizer() {
800         try {
801             debugPrint("Creating " + getEngineName() + "...");
802             synthesizer = Central.createSynthesizer(this);
803             
804             if (synthesizer == null) {
805                 System.out.println("Central created null synthesizer");
806             } else {
807                 synthesizer.allocate();
808                 synthesizer.resume();
809                 monitor = new Monitor(synthesizer, getEngineName());
810                 debugPrint("...created monitor");
811             }
812         } catch (Exception e) {
813             e.printStackTrace();
814         } 
815         return synthesizer;
816     }
817
818     
819
820     /**
821      * Allocates the synthesizer if it has never been allocated. This
822      * method should be called after method <code>createSynthesizer()</code>.
823      * It spawns a new thread to allocate the synthesizer.
824      */
825     public Synthesizer loadSynthesizer() {
826         try {
827             if (!synthesizerLoaded) {
828                 debugPrint("Loading " + getEngineName() + "...");
829                 synthesizerLoaded = true;
830                 SynthesizerLoader loader = new SynthesizerLoader
831                     (synthesizer, this);
832                 loader.start();
833             }
834         } catch (Exception e) {
835             e.printStackTrace();
836         }
837         return synthesizer;
838     }
839
840
841     /**
842      * Returns the Monitor of this Synthesizer.
843      *
844      * @return the Monitor
845      */
846     public synchronized Monitor getMonitor() {
847         if (monitor == null) {
848             createSynthesizer();
849         }
850         return monitor;
851     }
852
853     
854     /**
855      * Returns the PlayerModel.
856      *
857      * @return the PlayerModel
858      */
859     public PlayerModel getPlayerModel() {
860         return playerModel;
861     }
862
863     
864     /**
865      * Returns the name of the Synthesizer.
866      *
867      * @return the name of the Synthesizer
868      */
869     public String toString() {
870         return getEngineName();
871     }
872
873     
874     /**
875      * Prints debug statements.
876      *
877      * @param statement debug statements
878      */
879     private void debugPrint(String statement) {
880         PlayerModelImpl.debugPrint(statement);
881     }
882 }
883
884
885 /**
886  * A Thread that loads the Synthesizer.
887  */
888 class SynthesizerLoader extends Thread {
889     private Synthesizer synthesizer;
890     private MySynthesizerModeDesc modeDesc;
891     private PlayerModel playerModel;
892    
893     
894     /**
895      * Constructs a SynthesizerLoaded which loads the given Synthesizer.
896      *
897      * @param synthesizer the Synthesizer to load
898      * @param modeDesc the MySynthesizerModeDesc from which we can retrieve
899      *    the PlayerModel
900      */
901     public SynthesizerLoader(Synthesizer synthesizer,
902                              MySynthesizerModeDesc modeDesc) {
903         this.synthesizer = synthesizer;
904         this.modeDesc = modeDesc;
905         this.playerModel = modeDesc.getPlayerModel();
906     }
907     
908
909     /**
910      * Implements the <code>run()</code> method of the Thread class.
911      */
912     public void run() {
913         try {
914             System.out.println("allocating...");
915             synthesizer.allocate();
916             System.out.println("...allocated");
917             synthesizer.resume();
918             System.out.println("...resume");
919             playerModel.setVoiceList(modeDesc);
920         } catch (Exception e) {
921             e.printStackTrace();
922         }
923     }
924 }