net.tourbook.statistic.StatContainer.java Source code

Java tutorial

Introduction

Here is the source code for net.tourbook.statistic.StatContainer.java

Source

/*******************************************************************************
 * Copyright (C) 2005, 2010  Wolfgang Schramm and Contributors
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 *******************************************************************************/
package net.tourbook.statistic;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import net.tourbook.Messages;
import net.tourbook.application.TourbookPlugin;
import net.tourbook.data.TourPerson;
import net.tourbook.database.TourDatabase;
import net.tourbook.preferences.ITourbookPreferences;
import net.tourbook.ui.SQLFilter;
import net.tourbook.ui.TourTypeFilter;
import net.tourbook.ui.UI;
import net.tourbook.util.StringToArrayConverter;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.IViewSite;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.part.PageBook;

public class StatContainer extends Composite {

    private final boolean _isOSX = net.tourbook.util.UI.IS_OSX;
    private final boolean _isLinux = net.tourbook.util.UI.IS_LINUX;

    private static final String MEMENTO_SELECTED_STATISTIC = "statistic.container.selected_statistic"; //$NON-NLS-1$
    private static final String MEMENTO_SELECTED_YEAR = "statistic.container.selected-year"; //$NON-NLS-1$
    private static final String MEMENTO_NUMBER_OF_YEARS = "statistic.container.number_of_years"; //$NON-NLS-1$

    private static ArrayList<TourbookStatistic> _statisticExtensionPoints;

    private final Calendar _calendar = GregorianCalendar.getInstance();

    private int _selectedYear = -1;

    private TourbookStatistic _activeStatistic;
    private TourPerson _activePerson;
    private TourTypeFilter _activeTourTypeFilter;

    /**
     * contains all years which have tours for the selected tour type and person
     */
    private ArrayList<Integer> _availableYears;

    /**
     * contains the statistics in the same sort order as the statistic combo box
     */
    private ArrayList<TourbookStatistic> _allStatisticProvider;

    private ActionSynchChartScale _actionSynchChartScale;
    private boolean _isSynchScaleEnabled;

    private final IPostSelectionProvider _postSelectionProvider;

    /*
     * UI controls
     */
    private Composite _statContainer;
    private Combo _cboYear;
    private Combo _cboStatistics;
    private Combo _cboNumberOfYears;
    private PageBook _pageBookStatistic;

    private ToolBarManager _tbm;
    private ToolBar _toolBar;

    private final IViewSite _viewSite;

    StatContainer(final Composite parent, final IViewSite viewSite, final IPostSelectionProvider selectionProvider,
            final int style) {

        super(parent, style);

        _viewSite = viewSite;
        _postSelectionProvider = selectionProvider;

        createActions();

        createUI(this);
        updateUI();

        addDisposeListener(new DisposeListener() {
            @Override
            public void widgetDisposed(final DisposeEvent e) {

                // dispose all statistic resources
                for (final TourbookStatistic statistic : getComboStatistics()) {
                    statistic.dispose();
                }

                _activeStatistic = null;
            }
        });
    }

    /**
     * this method is synchronized to conform to FindBugs
     *
     * @return Returns statistics from the extension registry in the sort order of the registry
     */
    public static synchronized ArrayList<TourbookStatistic> getStatisticExtensionPoints() {

        if (_statisticExtensionPoints != null) {
            return _statisticExtensionPoints;
        }

        _statisticExtensionPoints = new ArrayList<TourbookStatistic>();

        final IExtensionPoint extPoint = Platform.getExtensionRegistry().getExtensionPoint(TourbookPlugin.PLUGIN_ID,
                TourbookPlugin.EXT_POINT_STATISTIC_YEAR);

        if (extPoint != null) {

            for (final IExtension extension : extPoint.getExtensions()) {

                for (final IConfigurationElement configElement : extension.getConfigurationElements()) {

                    if (configElement.getName().equalsIgnoreCase("statistic")) { //$NON-NLS-1$

                        Object object;
                        try {
                            object = configElement.createExecutableExtension("class"); //$NON-NLS-1$
                            if (object instanceof TourbookStatistic) {

                                final TourbookStatistic statisticItem = (TourbookStatistic) object;

                                statisticItem.visibleName = configElement.getAttribute("name"); //$NON-NLS-1$
                                statisticItem.statisticId = configElement.getAttribute("id"); //$NON-NLS-1$

                                _statisticExtensionPoints.add(statisticItem);
                            }
                        } catch (final CoreException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return _statisticExtensionPoints;
    }

    /**
     * @return Returns statistic providers with the custom sort order
     */
    public static ArrayList<TourbookStatistic> getStatisticProviders() {

        final ArrayList<TourbookStatistic> availableStatistics = getStatisticExtensionPoints();
        final ArrayList<TourbookStatistic> visibleStatistics = new ArrayList<TourbookStatistic>();

        final String[] prefStoreStatisticIds = StringToArrayConverter.//
                convertStringToArray(TourbookPlugin.getDefault().getPreferenceStore()
                        .getString(ITourbookPreferences.STATISTICS_STATISTIC_PROVIDER_IDS));

        // get all statistics which are saved in the pref store
        for (final String statisticId : prefStoreStatisticIds) {

            // get statistic item from the id
            for (final TourbookStatistic tourbookStatistic : availableStatistics) {
                if (statisticId.equals(tourbookStatistic.statisticId)) {
                    visibleStatistics.add(tourbookStatistic);
                    break;
                }
            }
        }

        // get statistics which are available but not saved in the prefstore
        for (final TourbookStatistic availableStatistic : availableStatistics) {

            if (visibleStatistics.contains(availableStatistic) == false) {
                visibleStatistics.add(availableStatistic);
            }
        }

        return visibleStatistics;
    }

    void actionSynchScale(final boolean isEnabled) {

        _isSynchScaleEnabled = isEnabled;
        _activeStatistic.setSynchScale(_isSynchScaleEnabled);

        if (_activeStatistic instanceof IYearStatistic) {
            ((IYearStatistic) _activeStatistic).refreshStatistic(_activePerson, _activeTourTypeFilter,
                    _selectedYear, getNumberOfYears(), false);
        }
    }

    void activateActions(final IWorkbenchPartSite partSite) {

        if (_activeStatistic == null) {
            return;
        }

        _activeStatistic.activateActions(partSite);
    }

    private void createActions() {

        _actionSynchChartScale = new ActionSynchChartScale(this);
    }

    private void createUI(final StatContainer parent) {

        GridLayoutFactory.fillDefaults().applyTo(parent);

        /*
         * container: statistic combo
         */
        _statContainer = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, true).applyTo(_statContainer);
        GridLayoutFactory.fillDefaults().spacing(0, 0).applyTo(_statContainer);
        _statContainer.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_BLUE));
        {
            createUI10Toolbar(_statContainer);

            // pagebook: statistics
            _pageBookStatistic = new PageBook(_statContainer, SWT.NONE);
            _pageBookStatistic.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
        }
    }

    private void createUI10Toolbar(final Composite parent) {

        final PixelConverter pc = new PixelConverter(this);

        final Composite container = new Composite(parent, SWT.NONE);
        GridDataFactory.fillDefaults().grab(true, false).applyTo(container);
        GridLayoutFactory.fillDefaults().numColumns(5).applyTo(container);
        {
            /*
             * combo: year
             */
            _cboYear = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
            GridDataFactory.fillDefaults()//
                    .hint(pc.convertWidthInCharsToPixels(_isOSX ? 12 : _isLinux ? 12 : 5), SWT.DEFAULT)
                    .applyTo(_cboYear);
            _cboYear.setToolTipText(Messages.Tour_Book_Combo_year_tooltip);
            _cboYear.setVisibleItemCount(50);
            _cboYear.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    onSelectYear();
                }
            });

            /*
             * number of years
             */
            final Label label = new Label(container, SWT.NONE);
            GridDataFactory.fillDefaults()//
                    .align(SWT.FILL, SWT.CENTER).indent(10, 0).applyTo(label);
            label.setText(Messages.tour_statistic_label_years);

            // combo: year numbers
            _cboNumberOfYears = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
            GridDataFactory.fillDefaults()//
                    .indent(2, 0).hint(pc.convertWidthInCharsToPixels(_isOSX ? 8 : _isLinux ? 8 : 4), SWT.DEFAULT)
                    .applyTo(_cboNumberOfYears);
            _cboNumberOfYears.setToolTipText(Messages.tour_statistic_number_of_years);
            _cboNumberOfYears.setVisibleItemCount(20);
            _cboNumberOfYears.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    onSelectYear();
                }
            });

            // combo: statistics
            _cboStatistics = new Combo(container, SWT.DROP_DOWN | SWT.READ_ONLY);
            GridDataFactory.fillDefaults()//
                    .indent(15, 0).applyTo(_cboStatistics);
            _cboStatistics.setToolTipText(Messages.Tour_Book_Combo_statistic_tooltip);
            _cboStatistics.setVisibleItemCount(50);
            _cboStatistics.addSelectionListener(new SelectionAdapter() {
                @Override
                public void widgetSelected(final SelectionEvent e) {
                    onSelectStatistic();
                }
            });

            /*
             * toolbar
             */
            _toolBar = new ToolBar(container, SWT.FLAT | SWT.WRAP | SWT.RIGHT);
            GridDataFactory.fillDefaults()//
                    .align(SWT.FILL, SWT.END).grab(true, false).applyTo(_toolBar);

            _tbm = new ToolBarManager(_toolBar);
        }
    }

    void deactivateActions(final IWorkbenchPartSite partSite) {
        if (_activeStatistic != null) {
            _activeStatistic.deactivateActions(partSite);
        }
    }

    /**
     * @param defaultYear
     * @return Returns the index for the active year or <code>-1</code> when there are no years
     *         available
     */
    private int getActiveYearComboboxIndex(final int defaultYear) {

        int selectedYearIndex = -1;

        if (_availableYears == null) {
            return selectedYearIndex;
        }

        /*
         * try to get the year index for the default year
         */
        if (defaultYear != -1) {

            int yearIndex = 0;
            for (final Integer year : _availableYears) {

                if (year == defaultYear) {

                    _selectedYear = defaultYear;

                    return yearIndex;
                }
                yearIndex++;
            }
        }

        /*
         * try to get year index of the selected year
         */
        int yearIndex = 0;
        for (final Integer year : _availableYears) {
            if (year == _selectedYear) {
                selectedYearIndex = yearIndex;
                break;
            }
            yearIndex++;
        }

        return selectedYearIndex;
    }

    /**
     * @return Returns all statistic plugins which are displayed in the statistic combo box
     */
    private ArrayList<TourbookStatistic> getComboStatistics() {

        if (_allStatisticProvider == null) {
            _allStatisticProvider = getStatisticProviders();
        }

        return _allStatisticProvider;
    }

    /**
     * @return Returns number of years which are selected in the combobox
     */
    private int getNumberOfYears() {

        int numberOfYears = 1;
        final int selectedIndex = _cboNumberOfYears.getSelectionIndex();

        if (selectedIndex != -1) {
            numberOfYears = selectedIndex + 1;
        }

        return numberOfYears;
    }

    /**
     * @return Returns the visible statistic {@link TourbookStatistic} or <code>null</code> when a
     *         statistic is not available
     */
    TourbookStatistic getSelectedStatistic() {
        return _activeStatistic;
    }

    /**
     * @return Returns the selected statistic in the combo box, it creates the statistic when this
     *         is not done, returns <code>null</code> when a statistic is not available
     */
    private TourbookStatistic getStatistic() {

        // get selected statistic
        final int selectedIndex = _cboStatistics.getSelectionIndex();
        if (selectedIndex == -1) {
            return null;
        }

        final TourbookStatistic statistic = getComboStatistics().get(selectedIndex);

        // get statistic control
        Composite statControlContainer = statistic.getControl();
        if (statControlContainer != null) {
            return statistic;
        }

        // create statistic ui
        statControlContainer = new Composite(_pageBookStatistic, SWT.NONE);
        final GridLayout gl = new GridLayout();
        gl.marginHeight = 0;
        gl.marginWidth = 0;
        statControlContainer.setLayout(gl);
        statControlContainer.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        statistic.createControl(statControlContainer, _viewSite, _postSelectionProvider);
        statistic.setContainer(statControlContainer);

        final Composite statControl = statistic.getControl();
        statControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));

        return statistic;
    }

    private void onSelectStatistic() {

        _activeStatistic = getStatistic();
        if (_activeStatistic == null) {
            return;
        }

        refreshStatistic(_activePerson, _activeTourTypeFilter, _selectedYear, false);

        if (_activeStatistic.canSelectTour()) {
            //         selectTour(fSelectedTourId);
        }

        //      // reselect data
        //      switch (fLastSelectionType) {
        //      case SELECTION_TYPE_MONTH:
        //         selectMonth(fSelectedMonth);
        //         break;
        //
        //      case SELECTION_TYPE_DAY:
        //         selectDay(fSelectedDate);
        //         break;
        //
        //      case SELECTION_TYPE_TOUR:
        //         if (selectTour(fSelectedTourId)) {
        ////                  fTourChartViewer.showTourChart(true);
        //         } else {
        //            // a tour was not selected, hide the tour chart
        ////                  fTourChartViewer.showTourChart(-1);
        //         }
        //         break;
        //      }
    }

    private void onSelectYear() {

        final int selectedItem = _cboYear.getSelectionIndex();

        if (selectedItem != -1) {

            _selectedYear = Integer.parseInt(_cboYear.getItem(selectedItem));

            refreshStatistic(_activePerson, _activeTourTypeFilter, _selectedYear, false);
        }
    }

    /**
     * update all statistics which have been created because person or tour type can be changed
     *
     * @param person
     */
    void refreshStatistic(final TourPerson person, final TourTypeFilter tourTypeFilter) {

        _activeStatistic = getStatistic();
        if (_activeStatistic == null) {
            return;
        }

        _activePerson = person;
        _activeTourTypeFilter = tourTypeFilter;

        refreshYearCombobox();
        selectYear(-1);

        // tell all existing statistics the data have changed
        for (final TourbookStatistic statistic : getComboStatistics()) {

            if (statistic.getControl() != null) {
                if (statistic instanceof IYearStatistic) {

                    statistic.setSynchScale(_isSynchScaleEnabled);
                    statistic.setDataDirty();
                }
            }
        }

        // refresh current statistic
        ((IYearStatistic) _activeStatistic).refreshStatistic(_activePerson, _activeTourTypeFilter, _selectedYear,
                getNumberOfYears(), true);

        //      resetSelection();
    }

    private void refreshStatistic(final TourPerson person, final TourTypeFilter activeTourTypeFilter,
            final int selectedYear, final boolean refreshData) {

        _activePerson = person;
        _activeTourTypeFilter = activeTourTypeFilter;

        // keep current year
        if (selectedYear == -1) {
            return;
        }
        _selectedYear = selectedYear;

        _activeStatistic = getStatistic();
        if (_activeStatistic == null || _activeStatistic.getControl().isDisposed()) {
            return;
        }

        selectYear(-1);

        _activeStatistic.setSynchScale(_isSynchScaleEnabled);

        if (_activeStatistic instanceof IYearStatistic) {
            ((IYearStatistic) _activeStatistic).refreshStatistic(_activePerson, _activeTourTypeFilter, selectedYear,
                    getNumberOfYears(), refreshData);
        }

        updateUIToolbar();

        // display selected statistic
        _pageBookStatistic.showPage(_activeStatistic.getControl());
    }

    void refreshStatisticProvider() {

        // get selected stat
        final TourbookStatistic selectedStatistic = getStatistic();

        _allStatisticProvider = getStatisticProviders();

        _cboStatistics.removeAll();
        int indexCounter = 0;
        int selectedIndex = 0;

        // fill combobox with statistic names
        for (final TourbookStatistic statistic : getComboStatistics()) {

            _cboStatistics.add(statistic.visibleName);

            if (selectedStatistic.statisticId.equals(statistic.statisticId)) {
                selectedIndex = indexCounter;
            }

            indexCounter++;
        }

        // reselect stat
        _cboStatistics.select(selectedIndex);
        onSelectStatistic();
    }

    //   private void resetSelection() {
    //
    //      if (fActiveStatistic == null) {
    //         return;
    //      }
    //
    //      // reset selection
    //      fSelectedDate = -1;
    //      fSelectedMonth = -1;
    //      fSelectedTourId = -1;
    //
    //      fActiveStatistic.resetSelection();
    //   }

    /**
     * create the year list for all tours and fill the year combobox with the available years
     */
    private void refreshYearCombobox() {

        final SQLFilter sqlFilter = new SQLFilter();

        final String sqlString = //
                "SELECT" //                                     //$NON-NLS-1$
                        + " startYear " //                           //$NON-NLS-1$
                        + " FROM " + TourDatabase.TABLE_TOUR_DATA //      //$NON-NLS-1$
                        + " WHERE 1=1 " + sqlFilter.getWhereClause() //      //$NON-NLS-1$
                        + " GROUP BY STARTYEAR ORDER BY STARTYEAR"; //      //$NON-NLS-1$

        _availableYears = new ArrayList<Integer>();

        try {
            final Connection conn = TourDatabase.getInstance().getConnection();
            final PreparedStatement statement = conn.prepareStatement(sqlString);
            sqlFilter.setParameters(statement, 1);

            final ResultSet result = statement.executeQuery();

            while (result.next()) {
                _availableYears.add(result.getInt(1));
            }

            conn.close();

        } catch (final SQLException e) {
            UI.showSQLException(e);
        }

        _cboYear.removeAll();

        /*
         * add all years of the tours and the current year
         */
        _calendar.setTime(new Date());
        final int thisYear = _calendar.get(Calendar.YEAR);

        boolean isThisYearSet = false;

        for (final Integer year : _availableYears) {

            if (year.intValue() == thisYear) {
                isThisYearSet = true;
            }

            _cboYear.add(year.toString());
        }

        // add currenty year if not set
        if (isThisYearSet == false) {
            _availableYears.add(thisYear);
            _cboYear.add(Integer.toString(thisYear));
        }
    }

    /**
     * Restore selected statistic
     *
     * @param viewState
     * @param activeTourTypeFilter
     * @param activePerson
     */
    void restoreStatistics(final IDialogSettings viewState, final TourPerson activePerson,
            final TourTypeFilter activeTourTypeFilter) {

        _activePerson = activePerson;
        _activeTourTypeFilter = activeTourTypeFilter;

        // select statistic
        int prevStatIndex = 0;
        final String mementoStatisticId = viewState.get(MEMENTO_SELECTED_STATISTIC);
        if (mementoStatisticId != null) {
            int statIndex = 0;
            for (final TourbookStatistic statistic : getComboStatistics()) {
                if (mementoStatisticId.equalsIgnoreCase(statistic.statisticId)) {
                    prevStatIndex = statIndex;
                    break;
                }
                statIndex++;
            }
        }

        // select number of years
        try {
            final int numberOfYears = viewState.getInt(MEMENTO_NUMBER_OF_YEARS);
            _cboNumberOfYears.select(numberOfYears);
        } catch (final NumberFormatException e) {
            // select one year
            _cboNumberOfYears.select(0);
        }

        // select year
        int defaultYear;
        try {
            defaultYear = viewState.getInt(MEMENTO_SELECTED_YEAR);
        } catch (final NumberFormatException e) {
            defaultYear = -1;
        }
        refreshYearCombobox();
        selectYear(defaultYear);

        // select statistic item
        _cboStatistics.select(prevStatIndex);
        onSelectStatistic();

        // restore statistic state (e.g. reselect previous selection)
        if (viewState != null) {
            getComboStatistics().get(prevStatIndex).restoreState(viewState);
        }
    }

    /**
     * save statistic
     */
    void saveState(final IDialogSettings state) {

        final ArrayList<TourbookStatistic> comboStatistics = getComboStatistics();

        // keep statistic id for the selected statistic
        final int selectionIndex = _cboStatistics.getSelectionIndex();
        if (selectionIndex != -1) {
            state.put(MEMENTO_SELECTED_STATISTIC, comboStatistics.get(selectionIndex).statisticId);
        }

        for (final TourbookStatistic tourbookStatistic : comboStatistics) {
            tourbookStatistic.saveState(state);
        }

        state.put(MEMENTO_NUMBER_OF_YEARS, _cboNumberOfYears.getSelectionIndex());
        state.put(MEMENTO_SELECTED_YEAR, _selectedYear);
    }

    private void selectYear(final int defaultYear) {

        int selectedYearIndex = getActiveYearComboboxIndex(defaultYear);
        if (selectedYearIndex == -1) {

            /*
             * the active year was not found in the combo box, it's possible that the combo box
             * needs to be update
             */

            refreshYearCombobox();
            selectedYearIndex = getActiveYearComboboxIndex(defaultYear);

            if (selectedYearIndex == -1) {

                // year is still not selected
                final int yearCount = _cboYear.getItemCount();

                // reselect the youngest year if years are available
                if (yearCount > 0) {
                    selectedYearIndex = yearCount - 1;
                    _selectedYear = Integer.parseInt(_cboYear.getItem(yearCount - 1));
                }
            }
        }

        _cboYear.select(selectedYearIndex);
    }

    @Override
    public boolean setFocus() {
        return super.setFocus();
    }

    private void updateUI() {

        // fill combobox with number of years
        for (int years = 1; years <= 50; years++) {
            _cboNumberOfYears.add(Integer.toString(years));
        }

        // fill combobox with statistic names
        for (final TourbookStatistic statistic : getComboStatistics()) {
            _cboStatistics.add(statistic.visibleName);
        }
    }

    /**
     * each statistic has it's own toolbar
     */
    private void updateUIToolbar() {

        // update view toolbar
        final IToolBarManager tbm = _viewSite.getActionBars().getToolBarManager();
        tbm.removeAll();
        tbm.add(_actionSynchChartScale);

        // add actions from the statistic
        _activeStatistic.updateToolBar(true);

        _tbm.update(false);
        _statContainer.layout();
    }
}