001    // This file is part of the Attempto Java Packages.
002    // Copyright 2008-2009, Attempto Group, University of Zurich (see http://attempto.ifi.uzh.ch).
003    //
004    // The Attempto Java Packages is free software: you can redistribute it and/or modify it under the
005    // terms of the GNU Lesser General Public License as published by the Free Software Foundation,
006    // either version 3 of the License, or (at your option) any later version.
007    //
008    // The Attempto Java Packages is distributed in the hope that it will be useful, but WITHOUT ANY
009    // WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
010    // PURPOSE. See the GNU Lesser General Public License for more details.
011    //
012    // You should have received a copy of the GNU Lesser General Public License along with the Attempto
013    // Java Packages. If not, see http://www.gnu.org/licenses/.
014    
015    package ch.uzh.ifi.attempto.preditor;
016    
017    import java.util.ArrayList;
018    import java.util.Arrays;
019    import java.util.List;
020    
021    import nextapp.echo2.app.Alignment;
022    import nextapp.echo2.app.ApplicationInstance;
023    import nextapp.echo2.app.Border;
024    import nextapp.echo2.app.Button;
025    import nextapp.echo2.app.Color;
026    import nextapp.echo2.app.Column;
027    import nextapp.echo2.app.Extent;
028    import nextapp.echo2.app.Font;
029    import nextapp.echo2.app.Insets;
030    import nextapp.echo2.app.Row;
031    import nextapp.echo2.app.SplitPane;
032    import nextapp.echo2.app.TextArea;
033    import nextapp.echo2.app.event.ActionEvent;
034    import nextapp.echo2.app.event.ActionListener;
035    import nextapp.echo2.app.event.WindowPaneEvent;
036    import nextapp.echo2.app.event.WindowPaneListener;
037    import ch.uzh.ifi.attempto.chartparser.ChartParser;
038    import ch.uzh.ifi.attempto.chartparser.Grammar;
039    import ch.uzh.ifi.attempto.echocomp.GeneralButton;
040    import ch.uzh.ifi.attempto.echocomp.Label;
041    import ch.uzh.ifi.attempto.echocomp.Logger;
042    import ch.uzh.ifi.attempto.echocomp.Style;
043    import ch.uzh.ifi.attempto.echocomp.TextField;
044    import ch.uzh.ifi.attempto.echocomp.WindowPane;
045    import ch.uzh.ifi.attempto.preditor.text.ContextChecker;
046    import ch.uzh.ifi.attempto.preditor.text.TextContainer;
047    import ch.uzh.ifi.attempto.preditor.text.TextElement;
048    import echopointng.KeyStrokeListener;
049    
050    /**
051     * This class represents a predictive editor window. The predictive editor enables easy creation of texts that
052     * comply with a certain grammar. The users can create such a text word-by-word by clicking on one of different
053     * menu items. The menu items are structured into menu blocks each of which has a name that is displayed above
054     * the menu block.
055     * 
056     * @author Tobias Kuhn
057     */
058    public class PreditorWindow extends WindowPane implements ActionListener, WindowPaneListener {
059            
060            private static final long serialVersionUID = -7815494421993305554L;
061            
062            private List<MenuBlockContent> menuBlockContents = new ArrayList<MenuBlockContent>();
063            private List<MenuBlock> menuBlocksTop = new ArrayList<MenuBlock>();
064            private List<MenuBlock> menuBlocksBottom = new ArrayList<MenuBlock>();
065            private List<SplitPane> menuSplitPanesTop = new ArrayList<SplitPane>();
066            private List<SplitPane> menuSplitPanesBottom = new ArrayList<SplitPane>();
067            
068            private TextArea textArea = new TextArea();
069            private TextField textField;
070            private SplitPane menuBlockPane = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT, new Extent(0));
071            private SplitPane doubleColumnMenuPane =  new SplitPane(SplitPane.ORIENTATION_VERTICAL_TOP_BOTTOM, new Extent(258));
072            private GeneralButton deleteButton = new GeneralButton("< Delete", 70, this);
073            private GeneralButton clearButton = new GeneralButton("Clear", 70, this);
074            
075            private KeyStrokeListener keyStrokeListener = new KeyStrokeListener();
076            
077            // BUG: It would be nice to retrieve the text elements from the chart so that this text container
078            // would not be needed.
079            private TextContainer newTextContainer = new TextContainer();
080            
081            private MenuCreator menuCreator;
082            
083            private ChartParser parser;
084            
085            private String filter = "";
086            
087            private Button okButton = new GeneralButton("OK", 70, this);
088            private Button cancelButton = new GeneralButton("Cancel", 70, this);
089            
090            private ArrayList<ActionListener> actionListeners = new ArrayList<ActionListener>();
091            
092            private Logger logger;
093            
094            /**
095             * Creates a new predictive editor window for the given grammar using the given menu creator.
096             * 
097             * @param title The title of the window.
098             * @param grammar The grammar to be used.
099             * @param menuCreator The menu creator to be used.
100             */
101            public PreditorWindow(String title, Grammar grammar, MenuCreator menuCreator) {
102                    this.parser = new ChartParser(grammar);
103                    this.menuCreator = menuCreator;
104                    
105                    addWindowPaneListener(this);
106                    setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
107                    setModal(true);
108                    setTitle(title);
109                    setTitleFont(new Font(Style.fontTypeface, Font.ITALIC, new Extent(13)));
110                    setWidth(new Extent(753));
111                    setHeight(new Extent(523));
112                    setResizable(false);
113                    setTitleBackground(Style.windowTitleBackground);
114                    setStyleName("Default");
115                    
116                    Row buttonBar = new Row();
117                    buttonBar.setAlignment(new Alignment(Alignment.RIGHT, Alignment.CENTER));
118                    buttonBar.setInsets(new Insets(10, 17, 10, 10));
119                    buttonBar.setCellSpacing(new Extent(5));
120                    buttonBar.add(okButton);
121                    buttonBar.add(cancelButton);
122                    
123                    SplitPane splitPane = new SplitPane(SplitPane.ORIENTATION_VERTICAL_BOTTOM_TOP, new Extent(47));
124                    splitPane.add(buttonBar);
125                    add(splitPane);
126                    
127                    SplitPane editorPane = new SplitPane(SplitPane.ORIENTATION_VERTICAL_TOP_BOTTOM, new Extent(168));
128                    
129                    Column textColumn = new Column();
130                    textColumn.setInsets(new Insets(10, 10, 0, 0));
131                    textColumn.setCellSpacing(new Extent(10));
132                    
133                    Column textAreaColumn = new Column();
134                    
135                    textArea.setWidth(new Extent(703));
136                    textArea.setHeight(new Extent(64));
137                    textArea.setEnabled(false);
138                    textArea.setFont(new Font(Style.fontTypeface, Font.PLAIN, new Extent(12)));
139                    textArea.setBorder(new Border(1, new Color(180, 180, 180), Border.STYLE_SOLID));
140                    textArea.setInsets(new Insets(3, 3));
141                    textArea.setBackground(new Color(255, 255, 255));
142                    textArea.setFocusTraversalParticipant(false);
143                    textAreaColumn.add(textArea);
144                    
145                    Row textAreaButtonBar = new Row();
146                    textAreaButtonBar.setAlignment(new Alignment(Alignment.RIGHT, Alignment.CENTER));
147                    textAreaButtonBar.setInsets(new Insets(0, 5, 10, 0));
148                    textAreaButtonBar.setCellSpacing(new Extent(5));
149                    clearButton.setVisible(false);
150                    clearButton.setFocusTraversalParticipant(false);
151                    deleteButton.setFocusTraversalParticipant(false);
152                    textAreaButtonBar.add(clearButton);
153                    textAreaButtonBar.add(deleteButton);
154                    textAreaColumn.add(textAreaButtonBar);
155                    
156                    textColumn.add(textAreaColumn);
157                    
158                    Column textFieldColumn = new Column();
159                    textFieldColumn.setCellSpacing(new Extent(1));
160                    Label textFieldLabel = new Label("text", Font.ITALIC, 11);
161                    textFieldColumn.add(textFieldLabel);
162                    
163                    textField = new TextField(this);
164                    textField.setWidth(new Extent(708));
165                    textField.setFocusTraversalParticipant(true);
166                    textField.setFocusTraversalIndex(0);
167                    textField.setDisabledBackground(Style.lightDisabled);
168                    Row textFieldRow = new Row();
169                    textFieldRow.add(textField);
170                    TextField dummyTextField = new TextField();
171                    dummyTextField.setWidth(new Extent(1));
172                    dummyTextField.setBorder(new Border(0, null, 0));
173                    dummyTextField.setBackground(Color.WHITE);
174                    textFieldRow.add(dummyTextField);
175                    textFieldColumn.add(textFieldRow);
176                    
177                    keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_TAB, "Tab");
178                    keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_ESCAPE, "Esc");
179                    keyStrokeListener.addKeyCombination(KeyStrokeListener.VK_BACK_SPACE | KeyStrokeListener.CONTROL_MASK, "Ctrl-Backspace");
180                    keyStrokeListener.addActionListener(this);
181                    textFieldColumn.add(keyStrokeListener);
182                    
183                    textColumn.add(textFieldColumn);
184                    editorPane.add(textColumn);
185                    
186                    menuBlockPane.setSeparatorWidth(new Extent(10));
187                    menuBlockPane.setSeparatorColor(Color.WHITE);
188                    menuBlockPane.add(new Label());
189                    
190                    SplitPane parentSplitPane = doubleColumnMenuPane;
191                    for (int i=0; i<10; i++) {
192                            MenuBlock menuBlock = new MenuBlock(this, this);
193                            menuBlocksTop.add(menuBlock);
194                            SplitPane menuSplitPane = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT);
195                            menuSplitPane.setSeparatorWidth(new Extent(10));
196                            menuSplitPane.setSeparatorColor(Color.WHITE);
197                            menuSplitPane.setVisible(false);
198                            menuSplitPane.add(menuBlock);
199                            menuSplitPanesTop.add(menuSplitPane);
200                            parentSplitPane.add(menuSplitPane);
201                            parentSplitPane = menuSplitPane;
202                    }
203                    
204                    parentSplitPane = doubleColumnMenuPane;
205                    for (int i=0; i<10; i++) {
206                            MenuBlock menuBlock = new MenuBlock(this, this);
207                            menuBlocksBottom.add(menuBlock);
208                            SplitPane menuSplitPane = new SplitPane(SplitPane.ORIENTATION_HORIZONTAL_LEFT_RIGHT);
209                            menuSplitPane.setSeparatorWidth(new Extent(10));
210                            menuSplitPane.setSeparatorColor(Color.WHITE);
211                            menuSplitPane.setVisible(false);
212                            menuSplitPane.add(menuBlock);
213                            menuSplitPanesBottom.add(menuSplitPane);
214                            parentSplitPane.add(menuSplitPane);
215                            parentSplitPane = menuSplitPane;
216                    }
217                    
218                    doubleColumnMenuPane.setSeparatorHeight(new Extent(12));
219                    doubleColumnMenuPane.setSeparatorColor(Color.WHITE);
220                    menuBlockPane.add(doubleColumnMenuPane);
221                    editorPane.add(menuBlockPane);
222                    splitPane.add(editorPane);
223                    
224                    update();
225            }
226            
227            /**
228             * Shows or hides the "clear" button.
229             * 
230             * @param visible true to show the "clear" button; false to hide it.
231             */
232            public void setClearButtonVisible(boolean visible) {
233                    clearButton.setVisible(visible);
234            }
235            
236            /**
237             * Sets the context checker.
238             * 
239             * @param contextChecker The context checker.
240             */
241            public void setContextChecker(ContextChecker contextChecker) {
242                    newTextContainer.setContextChecker(contextChecker);
243            }
244            
245            /**
246             * Returns the (partial) text that has been entered .
247             * 
248             * @return The (partial) text in the form of a text container.
249             */
250            public TextContainer getTextContainer() {
251                    return newTextContainer;
252            }
253            
254            /**
255             * Returns a list of text elements that contain one of the given texts and that are
256             * possible next tokens.
257             * 
258             * @param text The content of the text elements to search for.
259             * @return The list of text elements.
260             */
261            public ArrayList<TextElement> getPossibleNextTokens(String... text) {
262                    ArrayList<TextElement> l = new ArrayList<TextElement>();
263                    for (MenuBlockContent m : menuBlockContents) {
264                            for (String s : text) {
265                                    TextElement e = m.getEntry(s);
266                                    if (e != null) l.add(e);
267                            }
268                    }
269                    return l;
270            }
271            
272            /**
273             * Adds the text element to the end of the text.
274             * 
275             * @param te The text element to be added.
276             */
277            public void addTextElement(TextElement te) {
278                    textElementSelected(te);
279                    textField.setText("");
280                    update();
281            }
282            
283            /**
284             * Reads the text and adds it to the end of the current text as far as possible.
285             * 
286             * @param text The text to be added.
287             */
288            public void addText(String text) {
289                    handleTextInput(tokenize(text));
290                    update();
291            }
292            
293            private void textElementSelected(TextElement te) {
294                    ArrayList<TextElement> l = new ArrayList<TextElement>();
295                    l.add(te);
296                    textElementSelected(l);
297            }
298            
299            private void textElementSelected(List<TextElement> textElements) {
300                    if (textElements.isEmpty()) return;
301                    TextElement te = textElements.get(0);
302                    for (int i = 1 ; i < textElements.size() ; i++) {
303                            te.include(textElements.get(i));
304                    }
305                    
306                    newTextContainer.addElement(te);
307                    parser.addToken(te.getCategories());
308                    
309                    log("words added: " + te);
310            }
311            
312            private String getStartString() {
313                    String startString = "";
314                    ArrayList<String> blockStartStrings = new ArrayList<String>();
315                    
316                    for (MenuBlockContent mc : menuBlockContents) {
317                            String s = mc.getStartString();
318                            if (s != null) {
319                                    blockStartStrings.add(s);
320                            }
321                    }
322                    
323                    if (blockStartStrings.isEmpty()) return null;
324                    
325                    String first = blockStartStrings.get(0);
326                    blockStartStrings.remove(0);
327                    
328                    if (blockStartStrings.isEmpty()) return first;
329                    
330                    for (int i = 0; i < first.length(); i++) {
331                            char c = first.charAt(i);
332                            boolean stop = false;
333                            for (String s : blockStartStrings) {
334                                    if (s.length() <= i || s.charAt(i) != c) stop = true;
335                            }
336                            if (stop) break;
337                            startString += c;
338                    }
339                    
340                    return startString;
341            }
342            
343            private void update() {
344                    updateMenuBlockContents();
345                    setFilter(textField.getText());
346                    int mbCount = menuBlockContents.size();
347                    if (mbCount < 5) {
348                            int width = ( 720 / ( mbCount > 3 ? mbCount : 3 ) ) - 10;
349                            for (int i=0; i < menuBlocksTop.size(); i++) {
350                                    if (menuBlockContents.size() > i) {
351                                            menuBlocksTop.get(i).setContent(menuBlockContents.get(i), width, 16);
352                                            menuSplitPanesTop.get(i).setSeparatorPosition(new Extent(width));
353                                            menuSplitPanesTop.get(i).setVisible(true);
354                                    } else {
355                                            menuSplitPanesTop.get(i).setVisible(false);
356                                    }
357                            }
358                            for (int i=0; i < menuBlocksBottom.size(); i++) {
359                                    menuSplitPanesBottom.get(i).setVisible(false);
360                            }
361                            doubleColumnMenuPane.setSeparatorPosition(new Extent(258));
362                    } else {
363                            int firstRowCount = (mbCount + 1) / 2;
364                            int width = ( 720 / firstRowCount ) - 10;
365                            for (int i=0; i < menuBlocksTop.size(); i++) {
366                                    if (i < firstRowCount) {
367                                            menuBlocksTop.get(i).setContent(menuBlockContents.get(i), width, 7);
368                                            menuSplitPanesTop.get(i).setSeparatorPosition(new Extent(width));
369                                            menuSplitPanesTop.get(i).setVisible(true);
370                                    } else {
371                                            menuSplitPanesTop.get(i).setVisible(false);
372                                    }
373                            }
374                            for (int i=0; i < menuBlocksBottom.size(); i++) {
375                                    if (firstRowCount + i < mbCount) {
376                                            menuBlocksBottom.get(i).setContent(menuBlockContents.get(firstRowCount + i), width, 7);
377                                            menuSplitPanesBottom.get(i).setSeparatorPosition(new Extent(width));
378                                            menuSplitPanesBottom.get(i).setVisible(true);
379                                    } else {
380                                            menuSplitPanesBottom.get(i).setVisible(false);
381                                    }
382                            }
383                            doubleColumnMenuPane.setSeparatorPosition(new Extent(123));
384                    }
385                    textField.setEnabled(menuBlockContents.size() > 0 || !textField.getText().equals(""));
386                    ApplicationInstance.getActive().setFocusedComponent(textField);
387                    clearButton.setEnabled(textArea.getText().length() > 0);
388                    deleteButton.setEnabled(textArea.getText().length() > 0);
389            }
390            
391            private void updateMenuBlockContents() {
392                    textArea.setText(newTextContainer.getText());
393                    menuBlockContents.clear();
394                    List<MenuBlockContent> newMenuBlockContents = menuCreator.createMenu(parser);
395                    for (MenuBlockContent c : newMenuBlockContents) {
396                            if (!c.isEmpty()) {
397                                    menuBlockContents.add(c);
398                            }
399                    }
400            }
401            
402            private void setFilter(String filter) {
403                    if (filter == null) filter = "";
404                    filter = filter.replaceFirst("^\\s*", "").replaceFirst("\\s*$", "");
405                    
406                    for (MenuBlockContent c : menuBlockContents) {
407                            c.setFilter(filter);
408                    }
409                    this.filter = filter;
410            }
411            
412            private void handleTextInput() {
413                    handleTextInput(tokenize(textField.getText()));
414            }
415            
416            private void handleTextInput(ArrayList<String> textList) {
417                    
418                    ArrayList<TextElement> recognizedElements = null;
419                    int recognizedElementLength = 0;
420                    String text = "";
421                    
422                    for (int pos = 0 ; pos < textList.size() ; pos++) {
423                            if (text.length() > 0) text += " ";
424                            text += textList.get(pos);
425                            
426                            setFilter(text);
427                            ArrayList<TextElement> potentialElements = getPossibleNextTokens(text);
428                            if (potentialElements.isEmpty()) {
429                                    potentialElements = null;
430                            }
431                            
432                            // Counting how many different menu entries are available under the current filter.
433                            // elCount=2 means two or more entries.
434                            int elCount = 0;
435                            String firstElementText = null;
436                            for (MenuBlockContent m : menuBlockContents) {
437                                    for (MenuEntry e : m.getEntries()) {
438                                            String thisElementText = e.getTextElement().getText();
439                                            if (firstElementText == null) {
440                                                    firstElementText = thisElementText;
441                                                    elCount = 1;
442                                            } else if (!firstElementText.equals(thisElementText)) {
443                                                    elCount = 2;
444                                                    break;
445                                            }
446                                    }
447                                    if (elCount == 2) break;
448                            }
449                            
450                            boolean atLastPosition = pos == textList.size()-1;
451                            
452                            if (potentialElements != null && !atLastPosition) {
453                                    recognizedElements = potentialElements;
454                                    recognizedElementLength = pos + 1;
455                                    if (elCount == 1) break;
456                            }
457                            if (elCount == 0) {
458                                    break;
459                            } else if (atLastPosition) {
460                                    recognizedElements = null;
461                                    break;
462                            }
463                    }
464                    
465                    setFilter(null);
466                    
467                    if (recognizedElements != null) {
468                            textElementSelected(recognizedElements);
469                            updateMenuBlockContents();
470                            for (int i = 0; i < recognizedElementLength; i++) {
471                                    textList.remove(0);
472                            }
473                            handleTextInput(textList);
474                    } else {
475                            text = "";
476                            for (String textPart : textList) {
477                                    if (text.length() > 0) text += " ";
478                                    text += textPart;
479                            }
480                            textField.setText(text);
481                    }
482            }
483            
484            private ArrayList<String> tokenize(String text) {
485                    text = text.replaceAll("\\.\\s", " . ");
486                    text = text.replaceAll("\\.$", " .");
487                    text = text.replaceAll("\\?\\s", " ? ");
488                    text = text.replaceAll("\\?$", " ?");
489                    text = text.replaceAll("\\!\\s", " ! ");
490                    text = text.replaceAll("\\!$", " !");
491                    
492                    ArrayList<String> tokens = new ArrayList<String>(Arrays.asList(text.split(" ")));
493                    
494                    while (tokens.contains("")) {
495                            tokens.remove("");
496                    }
497                    
498                    if (text.endsWith(" ")) {
499                            tokens.add("");
500                    }
501                    
502                    return tokens;
503            }
504            
505            /**
506             * Adds a new action-listener.
507             * 
508             * @param actionListener The new action-listener.
509             */
510            public void addActionListener(ActionListener actionListener) {
511                    actionListeners.add(actionListener);
512            }
513            
514            /**
515             * Removes the action-listener.
516             * 
517             * @param actionListener The action-listener to be removed.
518             */
519            public void removeActionListener(ActionListener actionListener) {
520                    actionListeners.remove(actionListener);
521            }
522            
523            /**
524             * Removes all action-listeners.
525             */
526            public void removeAllActionListeners() {
527                    actionListeners.clear();
528            }
529            
530            private void notifyActionListeners(ActionEvent event) {
531                    for (ActionListener al : actionListeners) {
532                            al.actionPerformed(event);
533                    }
534            }
535            
536            public void actionPerformed(ActionEvent e) {
537                    if (e.getSource() == cancelButton) {
538                            log("pressed: cancel");
539                            notifyActionListeners(new ActionEvent(this, "Cancel"));
540                            return;
541                    } else if (e.getSource() == okButton) {
542                            log("pressed: ok");
543                            handleTextInput();
544                            update();
545                            notifyActionListeners(new ActionEvent(this, "OK"));
546                            return;
547                    } else if (e.getSource() == deleteButton) {
548                            log("pressed: < delete");
549                            if (newTextContainer.getTextElementsCount() > 0) {
550                                    newTextContainer.removeLastElement();
551                                    parser.removeToken();
552                                    textField.setText("");
553                            }
554                    } else if (e.getSource() == clearButton) {
555                            log("pressed: clear");
556                            newTextContainer.removeAllElements();
557                            parser.removeAllTokens();
558                            textField.setText("");
559                    } else if (e.getSource() instanceof MenuEntry) {
560                            TextElement te = ((MenuEntry) e.getSource()).getTextElement();
561                            log("pressed: menu-entry " + te.getText());
562                            textElementSelected(te);
563                            textField.setText("");
564                    } else if (e.getSource() == textField) {
565                            log("pressed: enter-key");
566                            if (textField.getText().equals("") && filter.equals("")) {
567                                    notifyActionListeners(new ActionEvent(this, "OK"));
568                                    return;
569                            } else {
570                                    handleTextInput();
571                                    ArrayList<TextElement> te = getPossibleNextTokens(textField.getText());
572                                    if (!te.isEmpty()) {
573                                            textElementSelected(te);
574                                            textField.setText("");
575                                    }
576                            }
577                    } else if ("Tab".equals(e.getActionCommand())) {
578                            log("pressed: tab-key");
579                            handleTextInput();
580                    } else if ("Esc".equals(e.getActionCommand())) {
581                            log("pressed: escape key");
582                            notifyActionListeners(new ActionEvent(this, "Cancel"));
583                            return;
584                    } else if ("Ctrl-Backspace".equals(e.getActionCommand())) {
585                            log("pressed: ctrl-backspace");
586                            if (newTextContainer.getTextElementsCount() > 0) {
587                                    newTextContainer.removeLastElement();
588                                    parser.removeToken();
589                                    textField.setText("");
590                            }
591                    }
592                    
593                    update();
594                    
595                    if ("Tab".equals(e.getActionCommand())) {
596                            String s = getStartString();
597                            if (s != null) textField.setText(s);
598                    }
599            }
600            
601            public void windowPaneClosing(WindowPaneEvent e) {
602                    log("pressed: close window");
603                    notifyActionListeners(new ActionEvent(this, "Cancel"));
604            }
605            
606            /**
607             * Sets the logger.
608             * 
609             * @param logger The logger object or null.
610             */
611            public void setLogger(Logger logger) {
612                    this.logger = logger;
613            }
614            
615            private void log(String text) {
616                    if (logger != null) {
617                            logger.log("pred", text);
618                    }
619            }
620            
621            public String toString() {
622                    return "sentence: " + newTextContainer.getText();
623            }
624            
625    }