Statistics
| Revision:

svn-gvsig-desktop / trunk / org.gvsig.desktop / org.gvsig.desktop.plugin / org.gvsig.app / org.gvsig.app.mainplugin / src / main / java / org / gvsig / app / gui / filter / FilterDialog.java @ 43169

History | View | Annotate | Download (23.1 KB)

1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU General Public License
8
 * as published by the Free Software Foundation; either version 3
9
 * of the License, or (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License
17
 * along with this program; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
 * MA  02110-1301, USA.
20
 *
21
 * For any additional information, do not hesitate to contact us
22
 * at info AT gvsig.com, or visit our website www.gvsig.com.
23
 */
24
package org.gvsig.app.gui.filter;
25

    
26
import java.awt.event.MouseAdapter;
27
import java.awt.event.MouseEvent;
28
import java.text.NumberFormat;
29
import java.text.ParseException;
30
import java.util.ArrayList;
31
import java.util.Collection;
32
import java.util.Comparator;
33
import java.util.Date;
34
import java.util.Iterator;
35
import java.util.List;
36
import java.util.TreeSet;
37
import java.util.regex.Matcher;
38
import java.util.regex.Pattern;
39
import javax.swing.JOptionPane;
40

    
41
import javax.swing.tree.DefaultMutableTreeNode;
42
import javax.swing.tree.DefaultTreeModel;
43
import org.apache.commons.lang.StringEscapeUtils;
44
import org.apache.commons.lang3.StringUtils;
45

    
46
import org.slf4j.Logger;
47
import org.slf4j.LoggerFactory;
48

    
49
import org.gvsig.andami.PluginServices;
50
import org.gvsig.andami.ui.mdiManager.IWindow;
51
import org.gvsig.andami.ui.mdiManager.IWindowListener;
52
import org.gvsig.andami.ui.mdiManager.WindowInfo;
53
import org.gvsig.app.ApplicationLocator;
54
import org.gvsig.app.ApplicationManager;
55
import org.gvsig.fmap.dal.exception.DataException;
56
import org.gvsig.fmap.dal.feature.Feature;
57
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
58
import org.gvsig.fmap.dal.feature.FeatureQuery;
59
import org.gvsig.fmap.dal.feature.FeatureSet;
60
import org.gvsig.fmap.dal.feature.FeatureStore;
61
import org.gvsig.gui.beans.filterPanel.tableFilterQueryPanel.TableFilterQueryJPanel;
62
import org.gvsig.tools.ToolsLocator;
63
import org.gvsig.tools.dispose.DisposableIterator;
64
import org.gvsig.tools.dispose.DisposeUtils;
65
import org.gvsig.tools.i18n.I18nManager;
66
import org.gvsig.utils.DefaultCharSet;
67
import org.gvsig.utils.StringUtilities;
68
import org.gvsig.utils.exceptionHandling.ExceptionHandlingSupport;
69
import org.gvsig.utils.exceptionHandling.ExceptionListener;
70

    
71
/**
72
 * This class substitutes the old "FilterDialog" class made by
73
 * "Fernando Gonz?lez Cort?s"
74
 * The functionality is the same, but now the class is made from separately (and
75
 * reusable) components
76
 * 
77
 * @author Pablo Piqueras Bartolom? (p_queras@hotmail.com)
78
 */
79
public class FilterDialog extends TableFilterQueryJPanel implements IWindow,
80
    IWindowListener {
81

    
82
    /**
83
     * 
84
     */
85
    private static final long serialVersionUID = -149317534873551735L;
86
    private static final Logger logger = LoggerFactory.getLogger(FilterDialog.class);
87

    
88
    private FeatureStore model = null;
89
    
90
    private final List<ExpressionListener> expressionListeners = new ArrayList<>();
91
    private final ExceptionHandlingSupport exceptionHandlingSupport = new ExceptionHandlingSupport();
92
    private final NumberFormat nf = NumberFormat.getNumberInstance();
93

    
94
    private String title;
95

    
96
    private final int filterDialog_Width = 500;
97
    private final int filterDialog_Height = 362;
98
    private final int widthIncrementForAndami = 20; // This is necessary because
99
                                                    // when the panel is sent to
100
                                                    // Andami, that needs a bit
101
                                                    // more width-space to show
102
                                                    // that panel.
103

    
104
    /**
105
     * This is the default constructor
106
     * @param _title
107
     */
108
    public FilterDialog(String _title) {
109
        super();
110
        title = _title;
111
        defaultTreeModel = (DefaultTreeModel) fieldsJTree.getModel();
112
    }
113

    
114
    /**
115
     * This is the default constructor
116
     */
117
    public FilterDialog() {
118
        super();
119
        defaultTreeModel = (DefaultTreeModel) fieldsJTree.getModel();
120
    }
121

    
122
    /*
123
     * (non-Javadoc)
124
     * 
125
     * @see
126
     * org.gvsig.gui.beans.filterPanel.AbstractFilterQueryJPanel#initialize()
127
     */
128
    @Override
129
    protected void initialize() {
130
        super.initialize();
131

    
132
        super.resizeHeight(filterDialog_Height);
133
        super.resizeWidth(filterDialog_Width - widthIncrementForAndami);
134

    
135
        this.addNewListeners();
136
    }
137

    
138
    /**
139
     * Adds some listeners
140
     */
141
    private void addNewListeners() {
142
        // Listener for "btnAdd"
143
        // Adds more elements to the current set
144
        getBtnAddToCurrentSet().addActionListener(
145
                new java.awt.event.ActionListener() {
146

    
147
                    @Override
148
                    public void actionPerformed(java.awt.event.ActionEvent e) {
149
                        final String expr = getTxtExpression().getText();
150
                        if (!validateQuotes(expr) || !validateLike(expr)) {
151
                            return;
152
                        }
153

    
154
                        PluginServices.backgroundExecution(new Runnable() {
155

    
156
                            @Override
157
                            public void run() {
158
                                for (ExpressionListener l : expressionListeners) {
159
                                    try {
160
                                        l.addToSet(expr);
161
                                    } catch (DataException e) {
162
                                        logger.warn("Can't add expression '"+expr+"' to set.", e);
163
                                    }
164
                                }
165
                            }
166
                        });
167
                    }
168
                });
169

    
170
        // Listener for "btnNuevo"
171
        // Adds a new set
172
        getBtnNewSet().addActionListener(new java.awt.event.ActionListener() {
173

    
174
            @Override
175
            public void actionPerformed(java.awt.event.ActionEvent e) {
176
                final String expr = getTxtExpression().getText();
177
                if (!validateQuotes(expr) || !validateLike(expr)) {
178
                    return;
179
                }
180

    
181
                PluginServices.backgroundExecution(new Runnable() {
182

    
183
                    @Override
184
                    public void run() {
185
                        for (ExpressionListener l : expressionListeners) {
186
                            try {
187
                                l.newSet(expr);
188
                            } catch (DataException e) {
189
                                logger.warn("Can't create set with expression '"+expr+"'.", e);
190
                            }
191
                        }
192
                    }
193
                });
194
            }
195
        });
196

    
197
        // Listener for "btnFromSet"
198
        // Selects elements from the current filtered selection
199
        getBtnFromSet().addActionListener(new java.awt.event.ActionListener() {
200

    
201
            @Override
202
            public void actionPerformed(java.awt.event.ActionEvent e) {
203
                final String expr = getTxtExpression().getText();
204
                if (!validateQuotes(expr) || !validateLike(expr) ) {
205
                    return;
206
                }
207

    
208
                PluginServices.backgroundExecution(new Runnable() {
209

    
210
                    @Override
211
                    public void run() {
212
                        for (ExpressionListener l : expressionListeners) {
213
                            try {
214
                                l.fromSet(expr);
215
                            } catch (DataException e) {
216
                                logger.warn("Can't create expression '"+expr+"' from set.", e);
217
                            }
218
                        }
219
                    }
220
                });
221
            }
222
        });
223

    
224
        // Listener for "fieldsJTree"
225
        getFieldsJTree().addMouseListener(new MouseAdapter() {
226

    
227
            @Override
228
            public void mouseClicked(MouseEvent e) {
229
                int row = fieldsJTree.getRowForLocation(e.getX(), e.getY());
230
                if (row > -1) {
231
                    switch (e.getClickCount()) {
232
                        case 1:
233
                            fillValues(row);
234
                            break;
235
                        case 2:
236
                            putSymbol(jtreeRoot.getChildAt(row).toString());
237
                            break;
238
                    }
239
                }
240
            }
241
        });
242

    
243
        // Listener for "valuesJList"
244
        getValuesJList().addMouseListener(new MouseAdapter() {
245

    
246
            @Override
247
            public void mouseClicked(MouseEvent e) {
248
                if (e.getClickCount() == 2) {
249
                    Object valor = valuesListModel.getElementAt(getValuesJList()
250
                            .getSelectedIndex());
251
                    if (valor == null) {
252
                        putSymbol("null");
253
                    } else if (valor instanceof Date) {
254
                        putSymbol("date('" + valor + "')");
255
                    } else if (valor instanceof Boolean) {
256
                        putSymbol(valor.toString());
257
                    } else if (valor instanceof String) {
258
                        // putSymbol("'" + StringEscapeUtils.escapeSql((String) valor) + "'");
259
                        putSymbol("'" + ((String)valor).replace('\'', '?') + "'");
260
                    } else {
261
                        putSymbol(StringEscapeUtils.escapeSql(valor.toString()));
262
                    }
263
                }
264
            }
265
        });
266
    }
267

    
268
    private boolean validateQuotes(String expr) {
269
        if ((StringUtils.countMatches(expr, "'") % 2) != 0) {
270
            ApplicationManager application = ApplicationLocator.getManager();
271
            I18nManager i18n = ToolsLocator.getI18nManager();
272
            String msg = i18n.getTranslation("_The_number_of_quotes_is_doubtful_It_could_be_that_some_of_them_should_escape_Do_you_want_to_continue_the_operation_anyway");
273
            int resp = application.confirmDialog(
274
                    msg,
275
                    i18n.getTranslation("_Caution"),
276
                    JOptionPane.YES_NO_OPTION,
277
                    JOptionPane.QUESTION_MESSAGE,
278
                    "Doubtless number of quotes in the filter dialog"
279
            );
280
            if (resp == JOptionPane.NO_OPTION) {
281
                return false;
282
            }
283
        }
284
        return true;
285

    
286
    }
287

    
288
    private boolean validateLike(String expr) {
289
        if( (expr.toLowerCase().contains("?") || 
290
                expr.toLowerCase().contains("%")) && 
291
                !expr.toLowerCase().contains("like") ) 
292
            {
293
            ApplicationManager application = ApplicationLocator.getManager();
294
            I18nManager i18n = ToolsLocator.getI18nManager();
295
            String msg = i18n.getTranslation("_It_looks_like_youre_using_percent_o_question_and_the_like_operator_is_not_being_used_I_should_probably_keep_the_equal_operator_by_like_Do_you_want_to_continue_anyway");
296
            int resp = application.confirmDialog(
297
                    msg,
298
                    i18n.getTranslation("_Caution"),
299
                    JOptionPane.YES_NO_OPTION,
300
                    JOptionPane.QUESTION_MESSAGE,
301
                    "You may need the like operator in the filter dialog"
302
            );
303
            if (resp == JOptionPane.NO_OPTION) {
304
                return false;
305
            }
306
        }
307
        return true;
308

    
309
    }
310
    /**
311
     * Rellena la lista con los valores del campo seleccionado
312
     */
313
    private void fillValues(int row) {
314
        // int index = lstCampos.getSelectedIndex();
315

    
316
        // Index es ahora el ?ndice del campo seleccionado
317
        // Se eliminan los duplicados
318
        Collection conjunto = new TreeSet(new Comparator() {
319

    
320
            public int compare(Object o1, Object o2) {
321
                return ((Comparable) o1).compareTo(o2);
322
            }
323
        }); // Para poder ordenar
324

    
325
        valuesListModel.clear();
326
        FeatureSet fs = null;
327
        DisposableIterator iterator = null;
328
        try {
329
            String[] fieldName =
330
                new String[] { ((FeatureAttributeDescriptor) model
331
                    .getDefaultFeatureType().get(row)).getName() };
332

    
333
            FeatureQuery query = model.createFeatureQuery();
334
            query.setAttributeNames(fieldName);
335
            fs = model.getFeatureSet(query);
336
            iterator = fs.fastIterator();
337
            while (iterator.hasNext()) {
338
                Feature feature = (Feature) iterator.next();
339
                Object value = feature.get(fieldName[0]);
340
                if (value == null) {
341
                    continue;
342
                }
343

    
344
                conjunto.add(value);
345
            }
346

    
347
            Iterator it = conjunto.iterator();
348

    
349
            while (it.hasNext()) {
350
                valuesListModel.addElement(it.next());
351
            }
352
        } catch (DataException e) {
353
            throwException(e);
354
        } finally {
355
            DisposeUtils.dispose(iterator);
356
            DisposeUtils.dispose(fs);
357
        }
358
    }
359

    
360
    /**
361
     * DOCUMENT ME!
362
     * 
363
     * @param t
364
     *            DOCUMENT ME!
365
     */
366
    public void setModel(FeatureStore t) {
367
        // try {
368
        model = t;
369
        // model.start();
370
        // } catch (ReadException e1) {
371
        // NotificationManager.addError(e1.getMessage(), e1);
372
        // }
373

    
374
        jtreeRoot.removeAllChildren();
375

    
376
        try {
377
            Iterator attributes = model.getDefaultFeatureType().iterator();
378
            while (attributes.hasNext()) {
379
                FeatureAttributeDescriptor descriptor =
380
                    (FeatureAttributeDescriptor) attributes.next();
381
                Object field = descriptor.getName();
382

    
383
                if (field != null) {
384
                    jtreeRoot.add(new DefaultMutableTreeNode(field.toString()));
385
                }
386
            }
387
            // for (int i = 0; i < model.getFieldCount(); i++) {
388
            // Object field = model.getFieldName(i);
389
            //
390
            // if (field != null) {
391
            // jtreeRoot.add(new DefaultMutableTreeNode(field.toString()));
392
            // }
393
            // }
394

    
395
            defaultTreeModel.setRoot(jtreeRoot);
396
        } catch (DataException e) {
397
            throwException(e);
398
        }
399
    }
400

    
401
    /**
402
     * DOCUMENT ME!
403
     * 
404
     * @return DOCUMENT ME!
405
     * 
406
     * @throws ParseException
407
     *             DOCUMENT ME!
408
     */
409
    private String validateExpression() throws ParseException {
410
        String expression = txtExpression.getText();
411
        // HashSet variablesIndexes = new HashSet();
412
        //
413
        // StringBuffer traducida = new StringBuffer();
414

    
415
        // Se transforman los nombres de los campos en las variables xix que
416
        // analizar?n
417
        // Se quitan los Date(fecha) y se mete la fecha correspondiente
418
        expression = translateDates(expression);
419
        expression = translateNumber(expression);
420
        expression = translateWord(expression, "true", "1");
421
        expression = translateWord(expression, "false", "0");
422

    
423
        String replacement;
424
        Pattern patron = Pattern.compile("[^<>!]=");
425
        Matcher m = patron.matcher(expression);
426
        int index = 0;
427

    
428
        while (m.find(index)) {
429
            index = m.start();
430
            replacement = expression.charAt(index) + "==";
431
            m.replaceFirst(replacement);
432
            index++;
433
        }
434

    
435
        expression = expression.replaceAll("[^<>!]=", "==");
436

    
437
        logger.debug(expression);
438

    
439
        return expression;
440
    }
441

    
442
    /**
443
     * Redefinition of the 'putSymbol' method of AbstractFilterQueryJPanel
444
     * (I've made this redefinition for write the same code as the 'putSymbol'
445
     * code of the original class (FilterDialog) that was in this project
446
     * (appgvSIG) and didn't has path troubles to find 'StringUtilities').
447
     * 
448
     * Sets a symbol on the filter expression (JTextArea that stores and shows
449
     * the current filter expression)
450
     * 
451
     * @param symbol
452
     *            A symbol: character, characters, number, ...
453
     */
454
    @Override
455
    protected void putSymbol(String symbol) {
456
        int position = txtExpression.getCaretPosition();
457
        txtExpression.setText(StringUtilities.insert(txtExpression.getText(),
458
            position, symbol));
459

    
460
        if (symbol.equals(" () ")) {
461
            position = position + 2;
462
        } else {
463
            position = position + symbol.length();
464
        }
465

    
466
        txtExpression.setCaretPosition(position);
467
    }
468

    
469
    /**
470
     * DOCUMENT ME!
471
     * 
472
     * @param expresion
473
     *            DOCUMENT ME!
474
     * @param substring
475
     *            DOCUMENT ME!
476
     * @param startingPos
477
     *            DOCUMENT ME!
478
     * 
479
     * @return DOCUMENT ME!
480
     */
481
    private int getIndex(String expresion, String substring, int startingPos) {
482
        int index = startingPos;
483

    
484
        do {
485
            index = expresion.indexOf(substring, index);
486
        } while ((StringUtilities.isBetweenSymbols(expresion, index, "\""))
487
            && (index != -1));
488

    
489
        return index;
490
    }
491

    
492
    /**
493
     * DOCUMENT ME!
494
     * 
495
     * @param expresion
496
     *            DOCUMENT ME!
497
     * @param word
498
     *            DOCUMENT ME!
499
     * @param translation
500
     *            DOCUMENT ME!
501
     * 
502
     * @return DOCUMENT ME!
503
     * 
504
     * @throws ParseException
505
     *             DOCUMENT ME!
506
     */
507
    private String translateWord(String expresion, String word,
508
        String translation) throws ParseException {
509
        int booleanIndex = 0;
510
        int endIndex = 0;
511
        StringBuffer res = new StringBuffer();
512

    
513
        while ((booleanIndex = getIndex(expresion, word, booleanIndex)) != -1) {
514
            res.append(expresion.substring(endIndex, booleanIndex));
515
            endIndex = booleanIndex + word.length();
516
            booleanIndex++;
517
            res.append(translation);
518
        }
519

    
520
        if (endIndex < expresion.length()) {
521
            res.append(expresion.substring(endIndex));
522
        }
523

    
524
        return res.toString();
525
    }
526

    
527
    /**
528
     * DOCUMENT ME!
529
     * 
530
     * @param expresion
531
     *            DOCUMENT ME!
532
     * 
533
     * @return DOCUMENT ME!
534
     * 
535
     * @throws ParseException
536
     *             DOCUMENT ME!
537
     */
538
    private String translateDates(String expresion) throws ParseException {
539
        // Se obtiene el valor de la fecha
540
        String date =
541
            StringUtilities.substringDelimited(expresion, "Date(", ")", 0);
542

    
543
        if (date == null) {
544
            return expresion;
545
        }
546

    
547
        // Se comprueba que no est? entre comillas
548
        int startIndex = expresion.indexOf(date);
549

    
550
        while (startIndex != -1) {
551
            if (!StringUtilities.isBetweenSymbols(expresion, startIndex, "\"")) {
552
                // Se sustituye por el valor ordinal de la fecha
553
                expresion =
554
                    expresion.substring(0, startIndex - 5)
555
                        + expresion.substring(startIndex).replaceFirst(
556
                            date + "\\)",
557
                            new Long((filterButtonsJPanel.getDateFormat()
558
                                .parse(date)).getTime()).toString());
559
                ;
560
            } else {
561
                startIndex += date.length();
562
            }
563

    
564
            // Se obtiene el valor de la fecha
565

    
566
            /*
567
             * date = StringUtilities.substringDelimited(expresion, "Date(",
568
             * ")",
569
             * startIndex);
570
             */
571
            if (date == null) {
572
                return expresion;
573
            }
574

    
575
            startIndex = expresion.indexOf(date, startIndex);
576
        }
577

    
578
        return expresion;
579
    }
580

    
581
    /**
582
     * DOCUMENT ME!
583
     * 
584
     * @param expresion
585
     *            DOCUMENT ME!
586
     * 
587
     * @return DOCUMENT ME!
588
     * 
589
     * @throws ParseException
590
     *             DOCUMENT ME!
591
     */
592
    public String translateNumber(String expresion) throws ParseException {
593
        DefaultCharSet ss = new DefaultCharSet();
594
        ss.addInterval('0', '9');
595
        ss.addCharacter(',');
596
        ss.addCharacter('.');
597

    
598
        String number = StringUtilities.substringWithSymbols(expresion, ss, 0);
599

    
600
        if (number == null) {
601
            return expresion;
602
        }
603

    
604
        int startIndex = expresion.indexOf(number);
605

    
606
        while (startIndex != -1) {
607
            Number n = nf.parse(number);
608

    
609
            if (!StringUtilities.isBetweenSymbols(expresion, startIndex, "\"")) {
610
                // Se sustituye por el valor ordinal de la fecha
611
                expresion =
612
                    expresion.substring(0, startIndex)
613
                        + expresion.substring(startIndex).replaceFirst(number,
614
                            n.toString());
615
            } else {
616
                startIndex += n.toString().length();
617
            }
618

    
619
            number =
620
                StringUtilities.substringWithSymbols(expresion, ss, startIndex);
621

    
622
            if (number == null) {
623
                return expresion;
624
            }
625

    
626
            startIndex = expresion.indexOf(number, startIndex);
627
        }
628

    
629
        return expresion;
630
    }
631

    
632
    /**
633
     * DOCUMENT ME!
634
     * 
635
     * @param arg0
636
     * 
637
     * @return
638
     */
639
    public boolean addExpressionListener(ExpressionListener arg0) {
640
        return expressionListeners.add(arg0);
641
    }
642

    
643
    /**
644
     * DOCUMENT ME!
645
     * 
646
     * @param arg0
647
     * 
648
     * @return
649
     */
650
    public boolean removeExpressionListener(ExpressionListener arg0) {
651
        return expressionListeners.remove(arg0);
652
    }
653

    
654
    /**
655
     * @see com.iver.mdiApp.ui.MDIManager.IWindow#getWindowInfo()
656
     */
657
    public WindowInfo getWindowInfo() {
658
        WindowInfo vi = new WindowInfo(WindowInfo.ICONIFIABLE);
659

    
660
        // if (System.getProperty("os.name")co.compareTo(arg0))
661
        vi.setHeight(this.filterDialog_Height);
662
        vi.setWidth(this.filterDialog_Width);
663

    
664
        // Old instructions
665
        // vi.setWidth(480);
666
        // vi.setHeight(362);
667
        vi
668
            .setTitle(PluginServices.getText(this, "_Selection_by_attributes") + " (" + title
669
                + ")");
670
        return vi;
671
    }
672

    
673
    /**
674
     * DOCUMENT ME!
675
     * 
676
     * @param o
677
     *            DOCUMENT ME!
678
     */
679
    public void addExceptionListener(ExceptionListener o) {
680
        exceptionHandlingSupport.addExceptionListener(o);
681
    }
682

    
683
    /**
684
     * DOCUMENT ME!
685
     * 
686
     * @param o
687
     *            DOCUMENT ME!
688
     * 
689
     * @return DOCUMENT ME!
690
     */
691
    public boolean removeExceptionListener(ExceptionListener o) {
692
        return exceptionHandlingSupport.removeExceptionListener(o);
693
    }
694

    
695
    /**
696
     * DOCUMENT ME!
697
     * 
698
     * @param t
699
     *            DOCUMENT ME!
700
     */
701
    private void throwException(Throwable t) {
702
        exceptionHandlingSupport.throwException(t);
703
    }
704

    
705
    /*
706
     * (non-Javadoc)
707
     * 
708
     * @see com.iver.andami.ui.mdiManager.ViewListener#viewActivated()
709
     */
710
    public void windowActivated() {
711
    }
712

    
713
    /*
714
     * (non-Javadoc)
715
     * 
716
     * @see com.iver.andami.ui.mdiManager.ViewListener#viewClosed()
717
     */
718
    public void windowClosed() {
719
        // try {
720
        // model.stop();
721
        // } catch (ReadDriverException e) {
722
        // NotificationManager.addError(e.getMessage(), e);
723
        // }
724
    }
725

    
726
    public Object getWindowProfile() {
727
        return WindowInfo.TOOL_PROFILE;
728
    }
729
}