JDK 11 jdk.jconsole.jmod - JConsole Tool

JDK 11 jdk.jconsole.jmod is the JMOD file for JDK 11 JConsole tool, which can be invoked by the "jconsole" command.

JDK 11 JConsole tool compiled class files are stored in \fyicenter\jdk-11.0.1\jmods\jdk.jconsole.jmod.

JDK 11 JConsole tool compiled class files are also linked and stored in the \fyicenter\jdk-11.0.1\lib\modules JImage file.

JDK 11 JConsole tool source code files are stored in \fyicenter\jdk-11.0.1\lib\src.zip\jdk.jconsole.

You can click and view the content of each source code file in the list below.

✍: FYIcenter

sun/tools/jconsole/inspector/XOpenTypeViewer.java

/*
 * Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package sun.tools.jconsole.inspector;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Component;
import java.awt.Color;
import java.awt.Font;
import java.awt.event.*;
import java.awt.Dimension;
import java.util.*;
import java.lang.reflect.Array;

import javax.management.openmbean.*;

import sun.tools.jconsole.JConsole;
import sun.tools.jconsole.Messages;
import sun.tools.jconsole.Resources;

@SuppressWarnings("serial")
public class XOpenTypeViewer extends JPanel implements ActionListener {
    JButton prev, incr, decr, tabularPrev, tabularNext;
    JLabel compositeLabel, tabularLabel;
    JScrollPane container;
    XOpenTypeData current;
    XOpenTypeDataListener listener = new XOpenTypeDataListener();

    private static final String compositeNavigationSingle =
            Messages.MBEANS_TAB_COMPOSITE_NAVIGATION_SINGLE;
    private static final String tabularNavigationSingle =
            Messages.MBEANS_TAB_TABULAR_NAVIGATION_SINGLE;

    private static TableCellEditor editor =
            new Utils.ReadOnlyTableCellEditor(new JTextField());

    class XOpenTypeDataListener extends MouseAdapter {
        XOpenTypeDataListener() {
        }

        public void mousePressed(MouseEvent e) {
            if(e.getButton() == MouseEvent.BUTTON1) {
                if(e.getClickCount() >= 2) {
                    XOpenTypeData elem = getSelectedViewedOpenType();
                    if(elem != null) {
                        try {
                            elem.viewed(XOpenTypeViewer.this);
                        }catch(Exception ex) {
                            //Nothing to change, the element
                            //can't be displayed
                        }
                    }
                }
            }
        }

        private XOpenTypeData getSelectedViewedOpenType() {
            int row = XOpenTypeViewer.this.current.getSelectedRow();
            int col = XOpenTypeViewer.this.current.getSelectedColumn();
            Object elem =
                    XOpenTypeViewer.this.current.getModel().getValueAt(row, col);
            if(elem instanceof XOpenTypeData)
                return (XOpenTypeData) elem;
            else
                return null;
        }
    }

    static interface Navigatable {
        public void incrElement();
        public void decrElement();
        public boolean canDecrement();
        public boolean canIncrement();
        public int getElementCount();
        public int getSelectedElementIndex();
    }

    static interface XViewedTabularData extends Navigatable {
    }

    static interface XViewedArrayData extends Navigatable {
    }

    static abstract class XOpenTypeData extends JTable {
        XOpenTypeData parent;
        protected int col1Width = -1;
        protected int col2Width = -1;
        private boolean init;
        private Font normalFont, boldFont;
        protected XOpenTypeData(XOpenTypeData parent) {
            this.parent = parent;
        }

        public XOpenTypeData getViewedParent() {
            return parent;
        }

        public String getToolTip(int row, int col) {
            if(col == 1) {
                Object value = getModel().getValueAt(row, col);
                if (value != null) {
                    if(isClickableElement(value))
                        return Messages.DOUBLE_CLICK_TO_VISUALIZE
                        + ". " + value.toString();
                    else
                        return value.toString();
                }
            }
            return null;
        }

        public TableCellRenderer getCellRenderer(int row, int column) {
            DefaultTableCellRenderer tcr =
                    (DefaultTableCellRenderer)super.getCellRenderer(row,column);
            tcr.setToolTipText(getToolTip(row,column));
            return tcr;
        }

        public void renderKey(String key,  Component comp) {
            comp.setFont(normalFont);
        }

        public Component prepareRenderer(TableCellRenderer renderer,
                int row, int column) {
            Component comp = super.prepareRenderer(renderer, row, column);

            if (normalFont == null) {
                normalFont = comp.getFont();
                boldFont = normalFont.deriveFont(Font.BOLD);
            }

            Object o = ((DefaultTableModel) getModel()).getValueAt(row, column);
            if (column == 0) {
                String key = o.toString();
                renderKey(key, comp);
            } else {
                if (isClickableElement(o)) {
                    comp.setFont(boldFont);
                } else {
                    comp.setFont(normalFont);
                }
            }

            return comp;
        }

        protected boolean isClickableElement(Object obj) {
            if (obj instanceof XOpenTypeData) {
                if (obj instanceof Navigatable) {
                    return (((Navigatable) obj).getElementCount() != 0);
                } else {
                    return (obj instanceof XCompositeData);
                }
            }
            return false;
        }

        protected void updateColumnWidth() {
            if (!init) {
                TableColumnModel colModel = getColumnModel();
                if (col2Width == -1) {
                    col1Width = col1Width * 7;
                    if (col1Width <
                            getPreferredScrollableViewportSize().getWidth()) {
                        col1Width = (int)
                        getPreferredScrollableViewportSize().getWidth();
                    }
                    colModel.getColumn(0).setPreferredWidth(col1Width);
                    init = true;
                    return;
                }
                col1Width = (col1Width * 7) + 7;
                col1Width = Math.max(col1Width, 70);
                col2Width = (col2Width * 7) + 7;
                if (col1Width + col2Width <
                        getPreferredScrollableViewportSize().getWidth()) {
                    col2Width = (int)
                    getPreferredScrollableViewportSize().getWidth() -
                            col1Width;
                }
                colModel.getColumn(0).setPreferredWidth(col1Width);
                colModel.getColumn(1).setPreferredWidth(col2Width);
                init = true;
            }
        }

        public abstract void viewed(XOpenTypeViewer viewer) throws Exception;

        protected void initTable(String[] columnNames) {
            setRowSelectionAllowed(false);
            setColumnSelectionAllowed(false);
            getTableHeader().setReorderingAllowed(false);
            ((DefaultTableModel) getModel()).setColumnIdentifiers(columnNames);
            for (Enumeration<TableColumn> e = getColumnModel().getColumns();
            e.hasMoreElements();) {
                TableColumn tc = e.nextElement();
                tc.setCellEditor(editor);
            }
            addKeyListener(new Utils.CopyKeyAdapter());
            setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
            setPreferredScrollableViewportSize(new Dimension(350, 200));
        }

        protected void emptyTable() {
            invalidate();
            while (getModel().getRowCount()>0)
                ((DefaultTableModel) getModel()).removeRow(0);
            validate();
        }

        public void setValueAt(Object value, int row, int col) {
        }
    }

    static class TabularDataComparator implements Comparator<CompositeData> {

        private final List<String> indexNames;

        public TabularDataComparator(TabularType type) {
            indexNames = type.getIndexNames();
        }

        @SuppressWarnings("unchecked")
        public int compare(CompositeData o1, CompositeData o2) {
            for (String key : indexNames) {
                Object c1 = o1.get(key);
                Object c2 = o2.get(key);
                if (c1 instanceof Comparable && c2 instanceof Comparable) {
                    int result = ((Comparable<Object>) c1).compareTo(c2);
                    if (result != 0)
                        return result;
                }
            }
            return 0;
        }
    }

    static class XTabularData extends XCompositeData
            implements XViewedTabularData {

        final TabularData tabular;
        final TabularType type;
        int currentIndex = 0;
        final Object[] elements;
        final int size;
        private Font normalFont, italicFont;

        @SuppressWarnings("unchecked")
        public XTabularData(XOpenTypeData parent, TabularData tabular) {
            super(parent, accessFirstElement(tabular));
            this.tabular = tabular;
            type = tabular.getTabularType();
            size = tabular.values().size();
            if (size > 0) {
                // Order tabular data elements using index names
                List<CompositeData> data = new ArrayList<CompositeData>(
                        (Collection<CompositeData>) tabular.values());
                Collections.sort(data, new TabularDataComparator(type));
                elements = data.toArray();
                loadCompositeData((CompositeData) elements[0]);
            } else {
                elements = new Object[0];
            }
        }

        private static CompositeData accessFirstElement(TabularData tabular) {
            if(tabular.values().size() == 0) return null;
            return (CompositeData) tabular.values().toArray()[0];
        }

        public void renderKey(String key,  Component comp) {
            if (normalFont == null) {
                normalFont = comp.getFont();
                italicFont = normalFont.deriveFont(Font.ITALIC);
            }
            for(Object k : type.getIndexNames()) {
                if(key.equals(k))
                    comp.setFont(italicFont);
            }
        }

        public int getElementCount() {
            return size;
        }

        public int getSelectedElementIndex() {
            return currentIndex;
        }

        public void incrElement() {
            currentIndex++;
            loadCompositeData((CompositeData)elements[currentIndex]);
        }

        public void decrElement() {
            currentIndex--;
            loadCompositeData((CompositeData)elements[currentIndex]);
        }

        public boolean canDecrement() {
            if(currentIndex == 0)
                return false;
            else
                return true;
        }

        public boolean canIncrement(){
            if(size == 0 ||
                    currentIndex == size -1)
                return false;
            else
                return true;
        }

        public String toString() {
            return type == null ? "" : type.getDescription();
        }
    }

    static class XCompositeData extends XOpenTypeData {
        protected final String[] columnNames = {
            Messages.NAME, Messages.VALUE
        };
        CompositeData composite;

        public XCompositeData() {
            super(null);
            initTable(columnNames);
        }

        //In sync with array, no init table.
        public XCompositeData(XOpenTypeData parent) {
            super(parent);
        }

        public XCompositeData(XOpenTypeData parent,
                CompositeData composite) {
            super(parent);
            initTable(columnNames);
            if(composite != null) {
                this.composite = composite;
                loadCompositeData(composite);
            }
        }

        public void viewed(XOpenTypeViewer viewer) throws Exception {
            viewer.setOpenType(this);
            updateColumnWidth();
        }

        public String toString() {
            return composite == null ? "" :
                composite.getCompositeType().getTypeName();
        }

        protected Object formatKey(String key) {
            return key;
        }

        private void load(CompositeData data) {
            CompositeType type = data.getCompositeType();
            Set<String> keys = type.keySet();
            Iterator<String> it = keys.iterator();
            Object[] rowData = new Object[2];
            while (it.hasNext()) {
                String key = it.next();
                Object val = data.get(key);
                rowData[0] = formatKey(key);
                if (val == null) {
                    rowData[1] = "";
                } else {
                    OpenType<?> openType = type.getType(key);
                    if (openType instanceof CompositeType) {
                        rowData[1] =
                                new XCompositeData(this, (CompositeData) val);
                    } else if (openType instanceof ArrayType) {
                        rowData[1] =
                                new XArrayData(this, (ArrayType<?>) openType, val);
                    } else if (openType instanceof SimpleType) {
                        rowData[1] = val;
                    } else if (openType instanceof TabularType) {
                        rowData[1] = new XTabularData(this, (TabularData) val);
                    }
                }
                // Update column width
                String str = null;
                if (rowData[0] != null) {
                    str = rowData[0].toString();
                    if (str.length() > col1Width) {
                        col1Width = str.length();
                    }
                }
                if (rowData[1] != null) {
                    str = rowData[1].toString();
                    if (str.length() > col2Width) {
                        col2Width = str.length();
                    }
                }
                ((DefaultTableModel) getModel()).addRow(rowData);
            }
        }

        protected void loadCompositeData(CompositeData data) {
            composite = data;
            emptyTable();
            load(data);
            DefaultTableModel tableModel = (DefaultTableModel) getModel();
            tableModel.newDataAvailable(new TableModelEvent(tableModel));
        }
    }

    static class XArrayData extends XCompositeData
            implements XViewedArrayData {

        private int dimension;
        private int size;
        private OpenType<?> elemType;
        private Object val;
        private boolean isCompositeType;
        private boolean isTabularType;
        private int currentIndex;
        private CompositeData[] elements;
        private final String[] arrayColumns = {Messages.VALUE};
        private Font normalFont, boldFont;

        XArrayData(XOpenTypeData parent, ArrayType<?> type, Object val) {
            this(parent, type.getDimension(), type.getElementOpenType(), val);
        }

        XArrayData(XOpenTypeData parent, int dimension,
                OpenType<?> elemType, Object val) {
            super(parent);
            this.dimension = dimension;
            this.elemType = elemType;
            this.val = val;
            String[] columns = null;

            if (dimension > 1) return;

            isCompositeType = (elemType instanceof CompositeType);
            isTabularType = (elemType instanceof TabularType);
            columns = isCompositeType ? columnNames : arrayColumns;

            initTable(columns);
            loadArray();
        }

        public void viewed(XOpenTypeViewer viewer) throws Exception {
            if (size == 0)
                throw new Exception(Messages.EMPTY_ARRAY);
            if (dimension > 1)
                throw new Exception(Messages.DIMENSION_IS_NOT_SUPPORTED_COLON +
                        dimension);
            super.viewed(viewer);
        }

        public int getElementCount() {
            return size;
        }

        public int getSelectedElementIndex() {
            return currentIndex;
        }

        public void renderKey(String key,  Component comp) {
            if (normalFont == null) {
                normalFont = comp.getFont();
                boldFont = normalFont.deriveFont(Font.BOLD);
            }
            if (isTabularType) {
                comp.setFont(boldFont);
            }
        }

        public void incrElement() {
            currentIndex++;
            loadCompositeData(elements[currentIndex]);
        }

        public void decrElement() {
            currentIndex--;
            loadCompositeData(elements[currentIndex]);
        }

        public boolean canDecrement() {
            if (isCompositeType && currentIndex > 0) {
                return true;
            }
            return false;
        }

        public boolean canIncrement() {
            if (isCompositeType && currentIndex < size - 1) {
                return true;
            }
            return false;
        }

        private void loadArray() {
            if (isCompositeType) {
                elements = (CompositeData[]) val;
                size = elements.length;
                if (size != 0) {
                    loadCompositeData(elements[0]);
                }
            } else {
                load();
            }
        }

        private void load() {
            Object[] rowData = new Object[1];
            size = Array.getLength(val);
            for (int i = 0; i < size; i++) {
                rowData[0] = isTabularType ?
                    new XTabularData(this, (TabularData) Array.get(val, i)) :
                    Array.get(val, i);
                String str = rowData[0].toString();
                if (str.length() > col1Width) {
                    col1Width = str.length();
                }
                ((DefaultTableModel) getModel()).addRow(rowData);
            }
        }

        public String toString() {
            if (dimension > 1) {
                return Messages.DIMENSION_IS_NOT_SUPPORTED_COLON +
                        dimension;
            } else {
                return elemType.getTypeName() + "[" + size + "]";
            }
        }
    }

    /**
     * The supplied value is viewable iff:
     * - it's a CompositeData/TabularData, or
     * - it's a non-empty array of CompositeData/TabularData, or
     * - it's a non-empty Collection of CompositeData/TabularData.
     */
    public static boolean isViewableValue(Object value) {
        // Check for CompositeData/TabularData
        //
        if (value instanceof CompositeData || value instanceof TabularData) {
            return true;
        }
        // Check for non-empty array of CompositeData/TabularData
        //
        if (value instanceof CompositeData[] || value instanceof TabularData[]) {
            return Array.getLength(value) > 0;
        }
        // Check for non-empty Collection of CompositeData/TabularData
        //
        if (value instanceof Collection) {
            Collection<?> c = (Collection<?>) value;
            if (c.isEmpty()) {
                // Empty Collections are not viewable
                //
                return false;
            } else {
                // Only Collections of CompositeData/TabularData are viewable
                //
                return Utils.isUniformCollection(c, CompositeData.class) ||
                        Utils.isUniformCollection(c, TabularData.class);
            }
        }
        return false;
    }

    public static Component loadOpenType(Object value) {
        Component comp = null;
        if(isViewableValue(value)) {
            XOpenTypeViewer open =
                    new XOpenTypeViewer(value);
            comp = open;
        }
        return comp;
    }

    private XOpenTypeViewer(Object value) {
        XOpenTypeData comp = null;
        if (value instanceof CompositeData) {
            comp = new XCompositeData(null, (CompositeData) value);
        } else if (value instanceof TabularData) {
            comp = new XTabularData(null, (TabularData) value);
        } else if (value instanceof CompositeData[]) {
            CompositeData cda[] = (CompositeData[]) value;
            CompositeType ct = cda[0].getCompositeType();
            comp = new XArrayData(null, 1, ct, cda);
        } else if (value instanceof TabularData[]) {
            TabularData tda[] = (TabularData[]) value;
            TabularType tt = tda[0].getTabularType();
            comp = new XArrayData(null, 1, tt, tda);
        } else if (value instanceof Collection) {
            // At this point we know 'value' is a uniform collection, either
            // Collection<CompositeData> or Collection<TabularData>, because
            // isViewableValue() has been called before calling the private
            // XOpenTypeViewer() constructor.
            //
            Object e = ((Collection<?>) value).iterator().next();
            if (e instanceof CompositeData) {
                Collection<?> cdc = (Collection<?>) value;
                CompositeData cda[] = cdc.toArray(new CompositeData[0]);
                CompositeType ct = cda[0].getCompositeType();
                comp = new XArrayData(null, 1, ct, cda);
            } else if (e instanceof TabularData) {
                Collection<?> tdc = (Collection<?>) value;
                TabularData tda[] = tdc.toArray(new TabularData[0]);
                TabularType tt = tda[0].getTabularType();
                comp = new XArrayData(null, 1, tt, tda);
            }
        }
        setupDisplay(comp);
        try {
            comp.viewed(this);
        } catch (Exception e) {
            // Nothing to change, the element can't be displayed
            if (JConsole.isDebug()) {
                System.out.println("Exception viewing openType : " + e);
                e.printStackTrace();
            }
        }
    }

    void setOpenType(XOpenTypeData data) {
        if (current != null) {
            current.removeMouseListener(listener);
        }

        current = data;

        // Enable/Disable the previous (<<) button
        if (current.getViewedParent() == null) {
            prev.setEnabled(false);
        } else {
            prev.setEnabled(true);
        }

        // Set the listener to handle double-click mouse events
        current.addMouseListener(listener);

        // Enable/Disable the tabular buttons
        if (!(data instanceof XViewedTabularData)) {
            tabularPrev.setEnabled(false);
            tabularNext.setEnabled(false);
            tabularLabel.setText(tabularNavigationSingle);
            tabularLabel.setEnabled(false);
        } else {
            XViewedTabularData tabular = (XViewedTabularData) data;
            tabularNext.setEnabled(tabular.canIncrement());
            tabularPrev.setEnabled(tabular.canDecrement());
            boolean hasMoreThanOneElement =
                    tabular.canIncrement() || tabular.canDecrement();
            if (hasMoreThanOneElement) {
                tabularLabel.setText(
                        Resources.format(Messages.MBEANS_TAB_TABULAR_NAVIGATION_MULTIPLE,
                        String.format("%d", tabular.getSelectedElementIndex() + 1),
                        String.format("%d", tabular.getElementCount())));
            } else {
                tabularLabel.setText(tabularNavigationSingle);
            }
            tabularLabel.setEnabled(hasMoreThanOneElement);
        }

        // Enable/Disable the composite buttons
        if (!(data instanceof XViewedArrayData)) {
            incr.setEnabled(false);
            decr.setEnabled(false);
            compositeLabel.setText(compositeNavigationSingle);
            compositeLabel.setEnabled(false);
        } else {
            XViewedArrayData array = (XViewedArrayData) data;
            incr.setEnabled(array.canIncrement());
            decr.setEnabled(array.canDecrement());
            boolean hasMoreThanOneElement =
                    array.canIncrement() || array.canDecrement();
            if (hasMoreThanOneElement) {
                compositeLabel.setText(
                        Resources.format(Messages.MBEANS_TAB_COMPOSITE_NAVIGATION_MULTIPLE,
                        String.format("%d", array.getSelectedElementIndex() + 1),
                        String.format("%d", array.getElementCount())));
            } else {
                compositeLabel.setText(compositeNavigationSingle);
            }
            compositeLabel.setEnabled(hasMoreThanOneElement);
        }

        container.invalidate();
        container.setViewportView(current);
        container.validate();
    }

    public void actionPerformed(ActionEvent event) {
        if (event.getSource() instanceof JButton) {
            JButton b = (JButton) event.getSource();
            if (b == prev) {
                XOpenTypeData parent = current.getViewedParent();
                try {
                    parent.viewed(this);
                } catch (Exception e) {
                    //Nothing to change, the element can't be displayed
                }
            } else if (b == incr) {
                ((XViewedArrayData) current).incrElement();
                try {
                    current.viewed(this);
                } catch (Exception e) {
                    //Nothing to change, the element can't be displayed
                }
            } else if (b == decr) {
                ((XViewedArrayData) current).decrElement();
                try {
                    current.viewed(this);
                } catch (Exception e) {
                    //Nothing to change, the element can't be displayed
                }
            } else if (b == tabularNext) {
                ((XViewedTabularData) current).incrElement();
                try {
                    current.viewed(this);
                } catch (Exception e) {
                    //Nothing to change, the element can't be displayed
                }
            } else if (b == tabularPrev) {
                ((XViewedTabularData) current).decrElement();
                try {
                    current.viewed(this);
                } catch (Exception e) {
                    //Nothing to change, the element can't be displayed
                }
            }
        }
    }

    private void setupDisplay(XOpenTypeData data) {
        setBackground(Color.white);
        container =
                new JScrollPane(data,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

        JPanel buttons = new JPanel(new FlowLayout(FlowLayout.LEFT));
        tabularPrev = new JButton(Messages.LESS_THAN);
        tabularNext = new JButton(Messages.GREATER_THAN);
        JPanel tabularButtons = new JPanel(new FlowLayout(FlowLayout.LEFT));
        tabularButtons.add(tabularPrev);
        tabularPrev.addActionListener(this);
        tabularLabel = new JLabel(tabularNavigationSingle);
        tabularLabel.setEnabled(false);
        tabularButtons.add(tabularLabel);
        tabularButtons.add(tabularNext);
        tabularNext.addActionListener(this);
        tabularButtons.setBackground(Color.white);

        prev = new JButton(Messages.A_LOT_LESS_THAN);
        prev.addActionListener(this);
        buttons.add(prev);

        incr = new JButton(Messages.GREATER_THAN);
        incr.addActionListener(this);
        decr = new JButton(Messages.LESS_THAN);
        decr.addActionListener(this);

        JPanel array = new JPanel();
        array.setBackground(Color.white);
        array.add(decr);
        compositeLabel = new JLabel(compositeNavigationSingle);
        compositeLabel.setEnabled(false);
        array.add(compositeLabel);
        array.add(incr);

        buttons.add(array);
        setLayout(new BorderLayout());
        buttons.setBackground(Color.white);

        JPanel navigationPanel = new JPanel(new BorderLayout());
        navigationPanel.setBackground(Color.white);
        navigationPanel.add(tabularButtons, BorderLayout.NORTH);
        navigationPanel.add(buttons, BorderLayout.WEST);
        add(navigationPanel, BorderLayout.NORTH);

        add(container, BorderLayout.CENTER);
        Dimension d = new Dimension((int)container.getPreferredSize().
                getWidth() + 20,
                (int)container.getPreferredSize().
                getHeight() + 20);
        setPreferredSize(d);
    }
}

sun/tools/jconsole/inspector/XOpenTypeViewer.java

 

Or download all of them as a single archive file:

File name: jdk.jconsole-11.0.1-src.zip
File size: 164713 bytes
Release date: 2018-11-04
Download 

 

JDK 11 jdk.jdeps.jmod - JDeps Tool

JDK 11 jdk.jcmd.jmod - JCmd Tool

Download and Use JDK 11

⇑⇑ FAQ for JDK (Java Development Kit)

2020-07-07, 19803👍, 0💬