diff --git a/resources/validator.xsd b/resources/validator.xsd new file mode 100644 index 0000000..54b1a64 --- /dev/null +++ b/resources/validator.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/containers/Backpack.java b/src/containers/Backpack.java new file mode 100644 index 0000000..54527cd --- /dev/null +++ b/src/containers/Backpack.java @@ -0,0 +1,68 @@ +package containers; + +import data.Shape; +import exception.BackpackOverflow; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Spliterator; +import java.util.function.Consumer; + +public class Backpack implements Iterable { + private ArrayList elements; + private final int n; + + public Backpack(int n) { + this.n = n; + try { + elements = new ArrayList<>(); + } catch (NegativeArraySizeException e) { + System.out.println("NegativeArraySize"); + } + } + + public int add(T element) { + if (elements.size() == n) { + throw new BackpackOverflow(); + } else { + elements.add(element); + sort(); + return elements.indexOf(element); + } + } + + public void remove(int i) { + if (elements.size() != 0) { + elements.remove(i); + } + } + + public int size() { + return elements.size(); + } + + public void sort() { + elements.sort((a, b) -> (int) (b.getVolume() - a.getVolume())); + } + + public void createFromArrayList(ArrayList arrayList) { + elements = arrayList; + } + + @Override + public Iterator iterator() { + return elements.iterator(); + } + + @Override + public void forEach(Consumer consumer) { + for (T element : elements) { + consumer.accept(element); + } + } + + @Override + public Spliterator spliterator() { + return null; + } +} diff --git a/src/controller/Controller.java b/src/controller/Controller.java new file mode 100644 index 0000000..9e0c9ac --- /dev/null +++ b/src/controller/Controller.java @@ -0,0 +1,115 @@ +package controller; + +import containers.Backpack; +import data.Ball; +import data.Cube; +import data.Shape; +import data.Tetrahedron; +import gui.GUI; +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.Validator; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; + +public class Controller { + private final int BACKPACK_CAPACITY = 100; + private final Backpack backpack = new Backpack<>(BACKPACK_CAPACITY); + private final File xsdFile; + + public Controller() { + GUI app = new GUI(this); + app.setVisible(true); + xsdFile = new File("resources/validator.xsd"); + } + + public String backpackStat() { + return "Backpack Size: " + backpack.size() + ", Backpack Capacity: " + BACKPACK_CAPACITY; + } + + public int addElementToBackpack(Shape new_element) { + return backpack.add(new_element); + } + + public void remove(int i) { + backpack.remove(i); + } + + public void saveToXML(File file) throws TransformerException, FileNotFoundException, ParserConfigurationException { + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + + Element root = document.createElement("Shapes"); + for (Shape shape : backpack) { + Element xml_shape = document.createElement("Shape"); + xml_shape.setAttribute("name", shape.getType()); + xml_shape.setAttribute("edge", String.valueOf(shape.getEdge())); + root.appendChild(xml_shape); + } + document.appendChild(root); + + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + DOMSource source = new DOMSource(document); + StreamResult result = new StreamResult(new FileOutputStream(file.getAbsolutePath())); + transformer.transform(source, result); + } + + public void validateXMLSchema(File xmlFile) throws SAXException, IOException { + SchemaFactory factory = + SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + Schema schema = factory.newSchema(xsdFile); + Validator validator = schema.newValidator(); + validator.validate(new StreamSource(xmlFile)); + } + + public ArrayList loadFromXML(File file) throws IOException, SAXException, ParserConfigurationException { + validateXMLSchema(file); + + Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(file); + NodeList nodeList = document.getElementsByTagName("Shape"); + ArrayList arrayList = new ArrayList<>(); + + for (int i = 0; i < nodeList.getLength(); i++) { + Node node = nodeList.item(i); + if (node.getNodeType() == Node.ELEMENT_NODE) { + NamedNodeMap nodeMap = node.getAttributes(); + String shapeType = nodeMap.getNamedItem("name").getNodeValue(); + double edge = Double.parseDouble(nodeMap.getNamedItem("edge").getNodeValue()); + + Shape shape; + switch (shapeType) { + case "Ball": + shape = new Ball(edge); + break; + case "Cube": + shape = new Cube(edge); + break; + case "Tetrahedron": + shape = new Tetrahedron(edge); + break; + default: + throw new IllegalStateException("Unexpected value: " + shapeType); + } + arrayList.add(shape); + } + } + + backpack.createFromArrayList(arrayList); + return arrayList; + } +} + diff --git a/src/data/Ball.java b/src/data/Ball.java new file mode 100644 index 0000000..e9c7b06 --- /dev/null +++ b/src/data/Ball.java @@ -0,0 +1,29 @@ +package data; + +public class Ball extends Shape { + double radius; + + public Ball(double radius) { + this.radius = radius; + } + + @Override + public double getVolume() { + return 4.0 / 3 * Math.PI * Math.pow(radius, 3); + } + + @Override + public String toString() { + return "Ball with radius " + radius + " and Volume " + RoundAvoid(getVolume(), 4); + } + + @Override + public String getType() { + return "Ball"; + } + + @Override + public double getEdge() { + return radius; + } +} diff --git a/src/data/Cube.java b/src/data/Cube.java new file mode 100644 index 0000000..516a081 --- /dev/null +++ b/src/data/Cube.java @@ -0,0 +1,29 @@ +package data; + +public class Cube extends Shape { + double edge; + + public Cube(double edge) { + this.edge = edge; + } + + @Override + public double getVolume() { + return Math.pow(edge, 3); + } + + @Override + public String toString() { + return "Cube with edge " + edge + " and Volume " + RoundAvoid(getVolume(), 4); + } + + @Override + public String getType() { + return "Cube"; + } + + @Override + public double getEdge() { + return edge; + } +} diff --git a/src/data/Shape.java b/src/data/Shape.java new file mode 100644 index 0000000..efb54d6 --- /dev/null +++ b/src/data/Shape.java @@ -0,0 +1,13 @@ +package data; + +public abstract class Shape { + public abstract double getEdge(); + public abstract double getVolume(); + public abstract String getType(); + + protected double RoundAvoid(double value, int places) { + double scale = Math.pow(10, places); + return Math.round(value * scale) / scale; + } +} + diff --git a/src/data/Tetrahedron.java b/src/data/Tetrahedron.java new file mode 100644 index 0000000..729e1b6 --- /dev/null +++ b/src/data/Tetrahedron.java @@ -0,0 +1,29 @@ +package data; + +public class Tetrahedron extends Shape { + double edge; + + public Tetrahedron(double edge) { + this.edge = edge; + } + + @Override + public double getVolume() { + return Math.pow(edge, 3) / (6 * Math.sqrt(2)); + } + + @Override + public String toString() { + return "Tetrahedron with edge " + edge + " and Volume " + RoundAvoid(getVolume(), 4); + } + + @Override + public String getType() { + return "Tetrahedron"; + } + + @Override + public double getEdge() { + return edge; + } +} diff --git a/src/exception/BackpackOverflow.java b/src/exception/BackpackOverflow.java new file mode 100644 index 0000000..0c5af45 --- /dev/null +++ b/src/exception/BackpackOverflow.java @@ -0,0 +1,7 @@ +package exception; + +public class BackpackOverflow extends RuntimeException { + public BackpackOverflow() { + super("Can`t add anymore to backpack"); + } +} diff --git a/src/gui/GUI.java b/src/gui/GUI.java new file mode 100644 index 0000000..3864ca0 --- /dev/null +++ b/src/gui/GUI.java @@ -0,0 +1,224 @@ +package gui; + +import controller.Controller; +import data.Ball; +import data.Cube; +import data.Shape; +import data.Tetrahedron; +import org.xml.sax.SAXException; + +import javax.swing.*; +import javax.swing.filechooser.FileNameExtensionFilter; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.TransformerException; +import java.awt.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class GUI extends JFrame { + private final DefaultListModel listModel = new DefaultListModel<>(); + private final JList list = new JList<>(listModel); + + private final JButton addCube = new JButton(); + private final JButton addBall = new JButton(); + private final JButton addTetrahedron = new JButton(); + private final JButton deleteComponent = new JButton(); + + private final JLabel backpackStat = new JLabel(); + + private final Controller controller; + private final String[] statuses = new String[]{"edge of cube", "ball radius", "edge of tetrahedron"}; + + public GUI(Controller controller) { + this.controller = controller; + JScrollPane scrollPane = new JScrollPane(); + scrollPane.add(list); + + setMinimumSize(new Dimension(800, 800)); + setTitle("Backpack v0.1"); + setLocationRelativeTo(null); + setDefaultCloseOperation(EXIT_ON_CLOSE); + + createLayout(getContentPane()); + setElementsInfo(); + setJMenuBar(createMenuBar()); + } + + private void createLayout(Container pane) { + BorderLayout layout = new BorderLayout(); + pane.setLayout(layout); + pane.add(list, BorderLayout.CENTER); + pane.add(backpackStat, BorderLayout.SOUTH); + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(0, 1)); + panel.add(addCube); + panel.add(addBall); + panel.add(addTetrahedron); + panel.add(deleteComponent); + pane.add(panel, BorderLayout.EAST); + } + + private final Pattern NUM_EX = Pattern.compile("-?\\d+(\\.\\d+)?"); + + private boolean isNumeric(String strNum) { + return NUM_EX.matcher(strNum).matches(); + } + + private void setElementsInfo() { + addCube.setText("Add cube"); + addCube.addActionListener(actionEvent -> displayInputDialog(0)); + addBall.setText("Add ball"); + addBall.addActionListener(actionEvent -> displayInputDialog(1)); + addTetrahedron.setText("Add tetrahedron"); + addTetrahedron.addActionListener(actionEvent -> displayInputDialog(2)); + + deleteComponent.setText("Remove selected shape from Backpack"); + deleteComponent.addActionListener(actionEvent -> { + if (!list.isSelectionEmpty()) { + int index = list.getSelectedIndex(); + listModel.remove(index); + controller.remove(index); + backpackStat.setText(controller.backpackStat()); + } else { + JOptionPane.showMessageDialog(GUI.this, + "Select shape to delete first", + "Error", + JOptionPane.ERROR_MESSAGE); + } + }); + + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setLayoutOrientation(JList.HORIZONTAL_WRAP); + list.setVisibleRowCount(-1); + + backpackStat.setText(controller.backpackStat()); + } + + private void displayInputDialog(int type) { + String message = JOptionPane.showInputDialog( + GUI.this, + "

Enter " + statuses[type] + " (non-negative number)"); + if (message != null) { + if (isNumeric(message) && Integer.parseInt(message) >= 0) { + switch (type) { + case 0: { + addToBackpack(new Cube(Integer.parseInt(message))); + break; + } + case 1: { + addToBackpack(new Ball(Integer.parseInt(message))); + break; + } + case 2: { + addToBackpack(new Tetrahedron(Integer.parseInt(message))); + break; + } + } + } else { + inputError(); + } + } + } + + private void addToBackpack(Shape shape) { + int index = controller.addElementToBackpack(shape); + listModel.add(index, shape); + backpackStat.setText(controller.backpackStat()); + } + + private void inputError() { + JOptionPane.showMessageDialog(GUI.this, + "Seems, you enter something that is not a non-negative number :)", + "Error", + JOptionPane.ERROR_MESSAGE); + } + + private JMenuBar createMenuBar() { + JMenuBar menuBar = new JMenuBar(); + menuBar.add(createFileMenu()); + return menuBar; + } + + private JMenu createFileMenu() { + JMenu menu = new JMenu("File"); + JMenuItem load = new JMenuItem("Load"); + JMenuItem save = new JMenuItem("Save"); + menu.add(load); + menu.add(save); + load.addActionListener(actionEvent -> loadFile()); + save.addActionListener(actionEvent -> saveFile()); + return menu; + } + + private void loadFile() { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Load table to xml file"); + FileNameExtensionFilter xmlFilter = new FileNameExtensionFilter("xml files (*.xml)", "xml"); + fileChooser.addChoosableFileFilter(xmlFilter); + fileChooser.setFileFilter(xmlFilter); + + int userSelection = fileChooser.showOpenDialog(this); + if (userSelection == JFileChooser.APPROVE_OPTION) { + ArrayList newListModel; + try { + newListModel = controller.loadFromXML(fileChooser.getSelectedFile()); + } catch (IOException | SAXException | ParserConfigurationException e) { + JOptionPane.showMessageDialog(GUI.this, + "Can`t read XML file (It can be corrupted or \"Backpack v0.1\" " + + "have insufficient access rights)", + "Error", + JOptionPane.ERROR_MESSAGE); + return; + } + listModel.clear(); + for (Shape shape : newListModel) { + listModel.addElement(shape); + } + + backpackStat.setText(controller.backpackStat()); + } + } + + private void saveFile() { + JFileChooser fileChooser = new JFileChooser(); + fileChooser.setDialogTitle("Save table to xml file"); + FileNameExtensionFilter xmlFilter = new FileNameExtensionFilter("xml files (*.xml)", "xml"); + fileChooser.addChoosableFileFilter(xmlFilter); + fileChooser.setFileFilter(xmlFilter); + + int userSelection = fileChooser.showSaveDialog(this); + if (userSelection == JFileChooser.APPROVE_OPTION) { + String s = fileChooser.getSelectedFile().getAbsolutePath(); + if (s.length() <= 4 || !s.endsWith(".xml")) { + s += ".xml"; + } + File file = new File(s); + + try { + if (!file.createNewFile()) { + PrintWriter writer = new PrintWriter(file); + writer.print(""); + writer.close(); + } + } catch (IOException e) { + JOptionPane.showMessageDialog(GUI.this, + "Can`t create new XML file", + "Error", + JOptionPane.ERROR_MESSAGE); + return; + } + try { + controller.saveToXML(file); + } catch (TransformerException | ParserConfigurationException | FileNotFoundException e) { + JOptionPane.showMessageDialog(GUI.this, + "Can`t write data to this XML file", + "Error", + JOptionPane.ERROR_MESSAGE); + } + } + } +} diff --git a/src/main/Main.java b/src/main/Main.java new file mode 100644 index 0000000..459103d --- /dev/null +++ b/src/main/Main.java @@ -0,0 +1,9 @@ +package main; + +import controller.Controller; + +public class Main { + public static void main(String[] args) { + Controller controller = new Controller(); + } +}