Package org.solarus.editor.gui

Source Code of org.solarus.editor.gui.SpriteTree

package org.solarus.editor.gui;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.NoSuchElementException;
import java.util.Observable;
import java.util.Observer;

import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import org.solarus.editor.Project;
import org.solarus.editor.Sprite;
import org.solarus.editor.SpriteAnimation;
import org.solarus.editor.SpriteAnimationDirection;
import org.solarus.editor.SpriteException;

/**
* A sprite view composed of:
* - a tree of animations and directions on the left,
* - some buttons on the right to add/remove/rename animations and directions.
*/
public class SpriteTree extends JPanel implements Observer, TreeSelectionListener {

    /**
     * The sprite observed.
     */
    private Sprite sprite;

    /**
     * The tree.
     */
    private JTree tree;

    // Buttons on the right.
    private JButton createButton;
    private JMenuItem createAnimationItem;
    private JMenuItem createDirectionItem;
    private JButton cloneButton;
    private JButton renameButton;
    private JButton deleteButton;

    /**
     * Constructor.
     */
    public SpriteTree() {
        super(new GridBagLayout());

        // Left part: the tree.
        tree = new JTree();
        buildTree();
        tree.addTreeSelectionListener(this);

        JScrollPane scroller = new JScrollPane(tree);

        GridBagConstraints constraints = new GridBagConstraints();
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.anchor = GridBagConstraints.FIRST_LINE_START;
        constraints.weightx = 1.0;
        constraints.weighty = 1.0;
        constraints.fill = GridBagConstraints.BOTH;
        add(scroller, constraints);

        // Right part: the buttons.
        JPanel buttonsPanel = new JPanel(new GridBagLayout());
        Dimension buttonSize = new Dimension(24, 24);

        createButton = new JButton(Project.getEditorImageIconOrEmpty("icon_add.png"));
        createButton.setMinimumSize(buttonSize);
        createButton.setMaximumSize(buttonSize);
        createButton.setPreferredSize(buttonSize);
        createButton.setToolTipText("Create animation or direction");
        createButton.addActionListener(new ActionCreate());
        createAnimationItem = new JMenuItem("Create animation");
        createAnimationItem.addActionListener(new ActionCreateAnimation());
        createDirectionItem = new JMenuItem("Create direction");
        createDirectionItem.addActionListener(new ActionCreateDirection());
        constraints.gridx = 0;
        constraints.gridy = 0;
        constraints.anchor = GridBagConstraints.FIRST_LINE_START;
        constraints.weightx = 0.0;
        constraints.weighty = 0.0;
        constraints.fill = GridBagConstraints.NONE;
        buttonsPanel.add(createButton, constraints);

        cloneButton = new JButton(Project.getEditorImageIconOrEmpty("icon_copy.png"));
        cloneButton.setMinimumSize(buttonSize);
        cloneButton.setMaximumSize(buttonSize);
        cloneButton.setPreferredSize(buttonSize);
        cloneButton.setToolTipText("Clone");
        cloneButton.addActionListener(new ActionClone());
        constraints.gridy++;
        buttonsPanel.add(cloneButton, constraints);

        renameButton = new JButton(Project.getEditorImageIconOrEmpty("icon_rename.png"));
        renameButton.setMinimumSize(buttonSize);
        renameButton.setMaximumSize(buttonSize);
        renameButton.setPreferredSize(buttonSize);
        renameButton.setToolTipText("Rename");
        renameButton.addActionListener(new ActionRenameAnimation());
        constraints.gridy++;
        buttonsPanel.add(renameButton, constraints);

        deleteButton = new JButton(Project.getEditorImageIconOrEmpty("icon_cross.png"));
        deleteButton.setMinimumSize(buttonSize);
        deleteButton.setMaximumSize(buttonSize);
        deleteButton.setPreferredSize(buttonSize);
        deleteButton.setToolTipText("Delete");
        deleteButton.addActionListener(new ActionDelete());
        constraints.gridy++;
        buttonsPanel.add(deleteButton, constraints);

        constraints.gridy++;
        constraints.weighty = 1.0;
        constraints.fill = GridBagConstraints.BOTH;
        buttonsPanel.add(new JLabel(), constraints)// Fill the rest with empty space.

        constraints.gridx = 1;
        constraints.gridy = 0;
        constraints.anchor = GridBagConstraints.FIRST_LINE_START;
        constraints.weightx = 0.0;
        constraints.weighty = 1.0;
        constraints.fill = GridBagConstraints.VERTICAL;
        add(buttonsPanel, constraints);

    }

    /**
     * Sets the sprite observed.
     * @param sprite the sprite
     */
    public void setSprite(Sprite sprite) {

        if (this.sprite != sprite) {
            if (this.sprite != null) {
                this.sprite.deleteObserver(this);
            }

            this.sprite = sprite;

            if (sprite != null) {
                sprite.addObserver(this);
            }
            update(sprite, null);
        }
    }

    /**
     * Builds or rebuilds all nodes of the tree.
     */
    private void buildTree() {

        tree.setModel(null);
        tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
        tree.setRowHeight(0)// To ask the height of each row to the cell renderer.
        tree.setCellRenderer(new SpriteCellRenderer());

        DefaultMutableTreeNode root = createSpriteNode();

        if (sprite == null) {
            return;
        }

        TreePath selectionPath = null;

        // Add a node for each animation.
        for (String animationName: sprite.getAnimationNames()) {
            SpriteAnimation animation = sprite.getAnimation(animationName);
            DefaultMutableTreeNode animationNode = createAnimationNode(animationName);
            root.add(animationNode);

            // Add a node for each direction of this animation.
            for (int i = 0; i < animation.getNbDirections(); i++) {
                DefaultMutableTreeNode directionNode = createDirectionNode(animationName, i);
                animationNode.add(directionNode);

                // Initially select the sprite's current animation and direction.
                if (animationName.equals(sprite.getSelectedAnimationName()) &&
                        i == sprite.getSelectedDirectionNb()) {
                    selectionPath = new TreePath(new Object[] {
                            root,
                            animationNode,
                            directionNode
                    });
                }
            }

            if (selectionPath == null &&
                    animationName.equals(sprite.getSelectedAnimationName())) {
                // Initially select the current animation and no direction.
                selectionPath = new TreePath(new Object[] {
                        root,
                        animationNode
                });
            }
        }

        DefaultTreeModel model = new DefaultTreeModel(root);
        model.setAsksAllowsChildren(true);
        tree.setModel(model);

        tree.expandRow(0)// Expand the root node.
        tree.expandPath(selectionPath)// Expand the selected animation and direction.
        tree.setSelectionPath(selectionPath);
        tree.scrollPathToVisible(selectionPath);
    }

    /**
     * Returns the tree model.
     */
    private DefaultTreeModel getTreeModel() {
        return (DefaultTreeModel) tree.getModel();
    }

    /**
     * Creates a node to be used as root element of the tree.
     * @return The sprite node created.
     */
    private DefaultMutableTreeNode createSpriteNode() {

        DefaultMutableTreeNode spriteNode = new DefaultMutableTreeNode(new NodeInfo(sprite, "Animations"));
        spriteNode.setAllowsChildren(true)// Show node as a folder even if empty.
        return spriteNode;
    }

    /**
     * Returns the node associated to the sprite, that is, the root node.
     */
    private DefaultMutableTreeNode getSpriteNode() {
        return (DefaultMutableTreeNode) tree.getModel().getRoot();
    }

    /**
     * Creates a node associated to the specified animation.
     * @param animationName Name of an animation.
     * @return The node created.
     */
    private DefaultMutableTreeNode createAnimationNode(String animationName) {

        SpriteAnimation animation = sprite.getAnimation(animationName);
        animation.addObserver(this);
        DefaultMutableTreeNode animationNode = new DefaultMutableTreeNode(
                new NodeInfo(animation, animationName));
        animationNode.setAllowsChildren(true);

        return animationNode;
    }

    /**
     * Returns the node associated to the specified animation.
     * @param animationName Name of an animation.
     * @return The corresponding node of the tree.
     */
    private DefaultMutableTreeNode getAnimationNode(String animationName) {

        DefaultMutableTreeNode root = getSpriteNode();
        for (int i = 0; i < root.getChildCount(); i++) {
            DefaultMutableTreeNode animationNode = (DefaultMutableTreeNode) root.getChildAt(i);
            NodeInfo info = (NodeInfo) animationNode.getUserObject();
            SpriteAnimation animation = (SpriteAnimation) info.getData();
            if (animation.getName().equals(animationName)) {
                return animationNode;
            }
        }

        throw new NoSuchElementException("No such animation: '" + animationName + "'");
    }

    /**
     * Creates a node for the specified animation and direction.
     * @param animationName Name of an animation.
     * @param directionIndex A direction.
     * @return The created node.
     */
    private DefaultMutableTreeNode createDirectionNode(String animationName, int directionIndex) {

        SpriteAnimation animation = sprite.getAnimation(animationName);
        SpriteAnimationDirection direction = animation.getDirection(directionIndex);
        direction.addObserver(this);
        DefaultMutableTreeNode directionNode = new DefaultMutableTreeNode(new NodeInfo(
                direction, getDirectionNodeText(animationName, directionIndex)
        ));
        directionNode.setAllowsChildren(false);

        return directionNode;
    }

    /**
     * Returns the node associated to the specified animation and direction.
     * @param animationName Name of an animation.
     * @param direction A direction.
     * @return The corresponding node of the tree.
     */
    private DefaultMutableTreeNode getDirectionNode(String animationName, int direction) {

        DefaultMutableTreeNode animationNode = getAnimationNode(animationName);
        return (DefaultMutableTreeNode) animationNode.getChildAt(direction);
    }

    /**
     * Returns the text to display for a direction node.
     * @param animationName A sprite animation.
     * @param direction A direction in this animation.
     * @return The appropriate text.
     */
    private String getDirectionNodeText(String animationName, int direction) {

        SpriteAnimation animation = sprite.getAnimation(animationName);
        return "Direction " + animation.getDirectionName(direction);
    }

    /**
     * Updates this component.
     * @param o The object that has changed.
     * @param info Info about what has changed, or null to update everything.
     */
    @Override
    public void update(Observable o, Object info) {

        if (o == null) {
            return;
        }

        if (o instanceof Sprite) {

            if (info == null) {
                // Build or rebuild everything.
                buildTree();
                updateButtons();
                return;
            }

            // Local change in the tree.
            Sprite.Change change = (Sprite.Change) info;
            switch (change.getWhatChanged()) {

            case ANIMATION_ADDED:
            {
                // Create a new node.
                String animationName = (String) change.getInfo();
                DefaultMutableTreeNode animationNode = createAnimationNode(animationName);
                DefaultMutableTreeNode root = getSpriteNode();
                getTreeModel().insertNodeInto(
                        animationNode,
                        root,
                        root.getChildCount()
                );

                // Maybe the new animation already has directions.
                SpriteAnimation animation = sprite.getAnimation(animationName);
                for (int i = 0; i < animation.getNbDirections(); i++) {
                    DefaultMutableTreeNode directionNode = createDirectionNode(
                            animationName,
                            i
                            );
                    getTreeModel().insertNodeInto(
                            directionNode,
                            animationNode,
                            animationNode.getChildCount()
                    );
                }

                updateDirectionTexts(animationNode);
                updateSelection();
            }
            break;

            case ANIMATION_REMOVED:
            {
                // Remove the node.
                String animationName = (String) change.getInfo();
                DefaultMutableTreeNode animationNode = getAnimationNode(animationName);
                getTreeModel().removeNodeFromParent(animationNode);
            }
            break;

            case ANIMATION_RENAMED:
            {
                // Update the node's text
                String newAnimationName = (String) change.getInfo(1);
                DefaultMutableTreeNode animationNode = getAnimationNode(newAnimationName);
                NodeInfo nodeInfo = (NodeInfo) animationNode.getUserObject();
                nodeInfo.setText(newAnimationName);
                getTreeModel().nodeChanged(animationNode);
            }
            break;

            case DEFAULT_ANIMATION_CHANGED:
                // Nothing to do.
                break;

            case SELECTED_ANIMATION_CHANGED:
                updateSelection();
                break;

            case DIRECTION_ADDED:
            {
                // Create a new node.
                String animationName = sprite.getSelectedAnimationName();
                int direction = (int) change.getInfo();
                DefaultMutableTreeNode directionNode = createDirectionNode(
                        animationName,
                        direction
                );
                DefaultMutableTreeNode animationNode = getAnimationNode(animationName);
                getTreeModel().insertNodeInto(
                        directionNode,
                        animationNode,
                        animationNode.getChildCount()
                );

                // The number of directions has changed: update direction texts.
                updateDirectionTexts(animationNode);

                updateSelection();
            }
            break;

            case DIRECTION_REMOVED:
            {
                // Remove the node.
                String animationName = (String) change.getInfo(0);
                int direction = (int) change.getInfo(1);
                DefaultMutableTreeNode directionNode = getDirectionNode(animationName, direction);
                getTreeModel().removeNodeFromParent(directionNode);

                // The number of directions has changed: update direction texts.
                updateDirectionTexts(getAnimationNode(animationName));
            }
            break;

            case SELECTED_DIRECTION_CHANGED:
                updateSelection();
                break;

            }
        }

        else if (o instanceof SpriteAnimation) {
            if (info instanceof Image) {
                // The source image has changed.
                repaint();
            }
        }
    }

    /**
     * Event called when the user changes the selection in the tree.
     * The corresponding element gets selected in the sprite.
     */
    @Override
    public void valueChanged(TreeSelectionEvent event) {

        try {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getLastSelectedPathComponent();

            if (node == null) {
                // Nothing is selected in the tree.
                sprite.unselectAnimation();
                return;
            }

            NodeInfo info = (NodeInfo) node.getUserObject();

            Object data = info.getData();
            if (data instanceof Sprite) {
                // The sprite itself: root node.
                sprite.unselectAnimation();
            }

            else if (data instanceof SpriteAnimation) {
                // A sprite animation: show it and show no direction.
                SpriteAnimation animation = (SpriteAnimation) data;
                sprite.unselectDirection();
                sprite.setSelectedAnimation(animation.getName());
            }

            else if (data instanceof SpriteAnimationDirection) {
                // A direction: show it and show its parent animation.
                SpriteAnimationDirection direction = (SpriteAnimationDirection) data;
                sprite.setSelectedAnimation(direction.getAnimation());
                sprite.setSelectedDirection(direction);
            }
        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Selects in the tree what is selected in the sprite.
     */
    private void updateSelection() {

        String animation = sprite.getSelectedAnimationName();
        int direction = sprite.getSelectedDirectionNb();
        TreePath path = null;

        if (animation.isEmpty()) {
            // Nothing is selected selected.
            path = new TreePath(new Object[] {
                    getSpriteNode()
            });
        }
        else if (direction == -1) {
            // An animation is selected and no direction is selected.
            path = new TreePath(new Object[] {
                    getSpriteNode(),
                    getAnimationNode(animation)
            });
        }
        else {
            // An animation and direction are selected.
            path = new TreePath(new Object[] {
                    getSpriteNode(),
                    getAnimationNode(animation),
                    getDirectionNode(animation, direction)
            });
        }
        tree.getSelectionModel().setSelectionPath(path);
        tree.scrollPathToVisible(path);

        updateButtons();
    }

    /**
     * Enables or disables the appropriate buttons depending on what is
     * selected.
     */
    private void updateButtons() {

        if (sprite.getSelectedAnimation() == null) {
            // Nothing is selected.
            createDirectionItem.setEnabled(false);
            cloneButton.setEnabled(false);
            deleteButton.setEnabled(false);
            renameButton.setEnabled(false);
        }
        else {
            createDirectionItem.setEnabled(true);
            cloneButton.setEnabled(true);
            deleteButton.setEnabled(true);
            if (sprite.getSelectedDirection() == null) {
                // An animation is selected but no direction is selected.
                renameButton.setEnabled(true);
            }
            else {
                // An animation and a direction are selected.
                renameButton.setEnabled(false);
            }
        }
    }

    /**
     * Sets the appropriate texts in the direction nodes under the specified
     * animation node.
     * @param animationNode An animation node.
     */
    private void updateDirectionTexts(DefaultMutableTreeNode animationNode) {

        NodeInfo animationNodeInfo = (NodeInfo) animationNode.getUserObject();
        SpriteAnimation animation = (SpriteAnimation) animationNodeInfo.getData();

        for (int i = 0; i < animation.getNbDirections(); i++) {
            DefaultMutableTreeNode directionNode = (DefaultMutableTreeNode) animationNode.getChildAt(i);
            NodeInfo nodeInfo = (NodeInfo) directionNode.getUserObject();
            nodeInfo.setText(getDirectionNodeText(animation.getName(), i));
            getTreeModel().nodeChanged(directionNode);
        }
    }

    /**
     * Data to be used as user object in the nodes of the tree.
     */
    private class NodeInfo {

        private Object data;

        private String text;

        /**
         * Constructor.
         * @param data Data associated to the node.
         * @param text Text representation to show for the node.
         */
        public NodeInfo(Object data, String text) {
            this.data = data;
            this.text = text;
        }

        public Object getData() {
            return data;
        }

        /**
         * Sets the text displayed for this node.
         * @param text The new text.
         */
        public void setText(String text) {
            this.text = text;
        }

        /**
         * Returns the text displayed for this node.
         * @return The text.
         */
        public String toString() {
            return text;
        }
    }

    /**
     * Cell renderer used to show a custom icon for each direction node.
     */
    private class SpriteCellRenderer extends DefaultTreeCellRenderer {

        @Override
        public Component getTreeCellRendererComponent(
                JTree tree,
                Object node,
                boolean selected,
                boolean expanded,
                boolean leaf,
                int row,
                boolean hasFocus
        ) {

            super.getTreeCellRendererComponent(
                    tree,
                    node,
                    selected,
                    expanded,
                    leaf,
                    row,
                    hasFocus
            );

            NodeInfo info = (NodeInfo) ((DefaultMutableTreeNode) node).getUserObject();
            if (info.getData() instanceof SpriteAnimationDirection) {
                // This is a direction node: use the appropriate icon.
                SpriteAnimationDirection direction = (SpriteAnimationDirection) info.getData();
                setIcon(new SpriteAnimationDirectionIcon(
                        sprite,
                        direction.getAnimation(),
                        direction)
                );
            }

            return this;
        }

    }

    /**
     * Action performed when the user clicks the "Rename animation" button.
     */
    private class ActionRenameAnimation implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {

            if (sprite == null) {
                return;
            }

            String oldAnimationName = sprite.getSelectedAnimationName();
            if (oldAnimationName.isEmpty()) {
                return;
            }

            String newAnimationName = (String) JOptionPane.showInputDialog(
                null,
                "New animation name:",
                "Rename animation '" + oldAnimationName + "'",
                JOptionPane.QUESTION_MESSAGE,
                null,
                null,
                oldAnimationName
            );
            if (newAnimationName != null &&
                    !newAnimationName.equals(oldAnimationName)) {
                try {
                    sprite.renameAnimation(oldAnimationName, newAnimationName);
                }
                catch (SpriteException ex) {
                    GuiTools.errorDialog("Cannot rename animation: " + ex.getMessage());
                }
            }
        }

    }

    /**
     * Action performed when the user clicks the
     * "Create animation or direction" button.
     */
    private class ActionCreate implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {

            // Show a popup menu to choose between creating animation or direction.
            JPopupMenu menu = new JPopupMenu();

            menu.add(createAnimationItem);
            menu.add(createDirectionItem);
            Component source = (Component) event.getSource();
            menu.show(source, source.getWidth(), 0);
        }

    }

    /**
     * Action performed when the user clicks the "Create animation" menu item.
     */
    private class ActionCreateAnimation implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {

            if (sprite == null) {
                return;
            }

            String animationName = (String) JOptionPane.showInputDialog(
                null,
                "Animation name:",
                "Create animation",
                JOptionPane.QUESTION_MESSAGE
            );
            if (animationName != null) {
                try {
                    sprite.addAnimation(animationName);
                }
                catch (SpriteException ex) {
                    GuiTools.errorDialog("Cannot rename animation: " + ex.getMessage());
                }
            }
        }

    }

    /**
     * Action performed when the user clicks the "Create direction" menu item.
     */
    private class ActionCreateDirection implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {

            if (sprite == null) {
                return;
            }

            SpriteAnimation animation = sprite.getSelectedAnimation();
            if (animation == null) {
                // No animation is selected.
                return;
            }

            try {
                sprite.addDirection(new Rectangle(0, 0, 16, 16));
            }
            catch (SpriteException ex) {
                GuiTools.errorDialog("Cannot create direction: " + ex.getMessage());
            }
        }

    }

    /**
     * Action performed when the user clicks the
     * "Clone" button.
     */
    private class ActionClone implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {

            if (sprite == null) {
                return;
            }

            // Clone the selected animation or direction.
            String animationName = sprite.getSelectedAnimationName();
            if (animationName.isEmpty()) {
                // No animation is selected.
                return;
            }

            int direction = sprite.getSelectedDirectionNb();
            if (direction != -1) {
                // Clone the direction.
                try {
                    sprite.cloneDirection();
                }
                catch (SpriteException ex) {
                    GuiTools.errorDialog("Cannot clone direction: " + ex.getMessage());
                }
            }
            else {
                // Clone the animation.
                String newAnimationName = (String) JOptionPane.showInputDialog(
                        null,
                        "New animation name:",
                        "Clone animation '" + animationName,
                        JOptionPane.QUESTION_MESSAGE
                );
                if (newAnimationName != null) {
                    try {
                        sprite.cloneAnimation(newAnimationName);
                    }
                    catch (SpriteException ex) {
                        GuiTools.errorDialog("Cannot clone animation '" + animationName + "': " + ex.getMessage());
                    }
                }
            }

        }

    }

    /**
     * Action performed when the user clicks the
     * "Delete" button.
     */
    private class ActionDelete implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {

            // After confirmation, delete the selected animation or direction.
            if (sprite == null) {
                return;
            }

            String animationName = sprite.getSelectedAnimationName();
            if (animationName.isEmpty()) {
                // No animation is selected.
                return;
            }

            String message;
            String title;
            int direction = sprite.getSelectedDirectionNb();
            if (direction == -1) {
                message = "Do you really want to delete animation '" + animationName + "' and all its directions?";
                title = "Remove animation";
            }
            else {
                message = "Do you really want to delete direction " + sprite.getSelectedDirectionName() + "?";
                title = "Remove direction";
            }
            int answer = JOptionPane.showConfirmDialog(
                    null,
                    message,
                    title,
                    JOptionPane.YES_NO_OPTION,
                    JOptionPane.WARNING_MESSAGE
            );

            if (answer != 0) {
                // Canceled.
                return;
            }

            try {
                if (direction == -1) {
                    // Remove the animation.
                    sprite.removeAnimation();
                }
                else {
                    // Remove the direction.
                    sprite.removeDirection();
                }
            }
            catch (SpriteException ex) {
                String what = (direction == -1) ? "animation" : "direction";
                GuiTools.errorDialog("Cannot delete " + what + ": " + ex.getMessage());
            }
        }

    }

}
TOP

Related Classes of org.solarus.editor.gui.SpriteTree

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.