Quellcode durchsuchen

Merge branch 'feat/controller/io'

* feat/controller/io:
  Properly delineate file loading and controller responsibilities.
  Make IOController a bit more functional.
  Fix NPE w/ Ingredient handling.
  Start building IOController and the Menu features associated with it.
Sam Jaffe vor 5 Jahren
Ursprung
Commit
24ed298fea

+ 5 - 0
pom.xml

@@ -98,5 +98,10 @@
       <version>1.3</version>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.leumasjaffe</groupId>
+      <artifactId>container</artifactId>
+      <version>0.3.0</version>
+    </dependency>
   </dependencies>
 </project>

+ 80 - 0
src/main/lombok/org/leumasjaffe/recipe/controller/FileController.java

@@ -0,0 +1,80 @@
+package org.leumasjaffe.recipe.controller;
+
+import java.awt.Component;
+import java.io.File;
+import java.io.IOException;
+
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+
+import org.leumasjaffe.container.functional.Result;
+import org.leumasjaffe.recipe.model.Recipe;
+import org.leumasjaffe.recipe.util.IOUtil;
+
+import lombok.AccessLevel;
+import lombok.RequiredArgsConstructor;
+import lombok.experimental.FieldDefaults;
+import lombok.experimental.NonFinal;
+
+@RequiredArgsConstructor
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class FileController<T extends Component & FileController.Model> {
+	public static interface Model {
+		void setModel(Recipe model);
+	}
+	
+	JFileChooser chooser = new JFileChooser();
+	T owner;
+	@NonFinal File filename = null;
+	@NonFinal Recipe model = null;
+	
+	public void create() {
+		setModel(new Recipe());
+	}
+	
+	public void save() {
+		if (filename == null) {
+			if (chooser.showSaveDialog(owner) == JFileChooser.APPROVE_OPTION) {
+				// TODO Store the file instead
+				filename = chooser.getSelectedFile();
+			}
+			return;
+		}
+		try {
+			IOUtil.save(filename, model);
+		} catch (IOException ioe) {
+			errorPopup(ioe);
+		}
+	}
+	
+	public void saveAs() {
+		this.filename = null;
+		save();
+	}
+	
+	public void open() {
+		if (chooser.showOpenDialog(owner) == JFileChooser.APPROVE_OPTION) {
+			// TODO Store the file instead
+			filename = chooser.getSelectedFile();
+		}
+		Result.maybe(IOUtil::load).apply(filename)
+			.consume(this::setModel, this::errorPopup);
+	}
+
+	@Deprecated
+	public void open(final String newFilename) {
+		this.filename = new File(newFilename);
+		Result.maybe(IOUtil::load).apply(filename)
+			.consume(this::setModel, this::errorPopup);
+	}
+
+	private void errorPopup(final Exception ex) {
+		JOptionPane.showMessageDialog(owner, ex.getLocalizedMessage(),
+				"File Error", JOptionPane.ERROR_MESSAGE);
+	}
+	
+	private void setModel(Recipe recipe) {
+		this.model = recipe;
+		owner.setModel(recipe);
+	}
+}

+ 4 - 0
src/main/lombok/org/leumasjaffe/recipe/model/Ingredient.java

@@ -13,6 +13,10 @@ public class Ingredient extends Observable.Instance {
 	String preparation = "";
 	Amount amount;
 	
+	boolean hasPreparation() {
+		return preparation != null && !preparation.isEmpty();
+	}
+	
 	Ingredient plus(final Ingredient rhs) {
 		if (!name.equals(rhs.name)) {
 			throw new IllegalArgumentException("Combining ingredients of differing types");

+ 1 - 1
src/main/lombok/org/leumasjaffe/recipe/model/Preparation.java

@@ -19,7 +19,7 @@ public class Preparation implements RecipeComponent {
 	
 	@JsonIgnore
 	public List<Ingredient> getIngredients() {
-		return producer.get().stream().filter(i -> !i.getPreparation().isEmpty())
+		return producer.get().stream().filter(Ingredient::hasPreparation)
 				.collect(Collectors.toList());
 	}
 }

+ 47 - 0
src/main/lombok/org/leumasjaffe/recipe/util/IOUtil.java

@@ -0,0 +1,47 @@
+package org.leumasjaffe.recipe.util;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import org.leumasjaffe.recipe.model.Recipe;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+
+import lombok.NonNull;
+import lombok.experimental.UtilityClass;
+
+@UtilityClass
+public class IOUtil {	
+	Recipe load(final @NonNull Reader in) throws IOException {
+		ObjectMapper mapper = new ObjectMapper();
+		mapper.registerModule(new Jdk8Module());
+		return mapper.readValue(in, Recipe.class);
+	}
+	
+	void save(final @NonNull Writer out, final @NonNull Recipe recipe) throws IOException {
+		ObjectMapper mapper = new ObjectMapper();
+		mapper.registerModule(new Jdk8Module());
+		mapper.writeValue(out, recipe);
+	}
+
+	public Recipe load(final @NonNull File handle) throws IOException {
+		try (FileReader file = new FileReader(handle);
+				BufferedReader buf = new BufferedReader(file)) {
+			return load(buf);
+		}
+	}
+	
+	public void save(final @NonNull File handle, final @NonNull Recipe recipe) throws IOException {
+		try (FileWriter file = new FileWriter(handle);
+				BufferedWriter buf = new BufferedWriter(file)) {
+			save(buf, recipe);
+		}
+	}
+}

+ 65 - 20
src/main/lombok/org/leumasjaffe/recipe/view/RecipeFrame.java

@@ -1,43 +1,88 @@
 package org.leumasjaffe.recipe.view;
 
-import java.io.File;
-import java.io.IOException;
-
 import javax.swing.JFrame;
 import javax.swing.JTabbedPane;
 
+import org.leumasjaffe.recipe.controller.FileController;
 import org.leumasjaffe.recipe.model.Product;
 import org.leumasjaffe.recipe.model.Recipe;
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import lombok.AccessLevel;
+import lombok.experimental.FieldDefaults;
+
+import javax.swing.JMenuBar;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.KeyStroke;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowEvent;
+import java.awt.Toolkit;
+import java.awt.event.InputEvent;
 
 @SuppressWarnings("serial")
-public class RecipeFrame extends JFrame {
+@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
+public class RecipeFrame extends JFrame implements FileController.Model {
+	SummaryPanel summaryPanel;
+	JTabbedPane tabbedPane;
+	
 	public RecipeFrame() {
+		FileController<RecipeFrame> fileController = new FileController<>(this);
+
+		final int MASK = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
 		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 		
-		JTabbedPane tabbedPane = new JTabbedPane();
+		JMenuBar menuBar = new JMenuBar();
+		setJMenuBar(menuBar);
+		
+		JMenu mnFile = new JMenu("File");
+		menuBar.add(mnFile);
+		
+		JMenuItem mntmNew = new JMenuItem("New");
+		mntmNew.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, MASK));
+		mntmNew.addActionListener(e -> fileController.create());
+		mnFile.add(mntmNew);
+		
+		JMenuItem mntmOpen = new JMenuItem("Open");
+		mntmOpen.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, MASK));
+		mntmNew.addActionListener(e -> fileController.open());
+		mnFile.add(mntmOpen);
+		
+		JMenuItem mntmSave = new JMenuItem("Save");
+		mntmSave.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, MASK));
+		mntmNew.addActionListener(e -> fileController.save());
+		mnFile.add(mntmSave);
+		
+		JMenuItem mntmSaveAs = new JMenuItem("Save As");
+		mntmSaveAs.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, MASK | InputEvent.SHIFT_MASK));
+		mntmNew.addActionListener(e -> fileController.saveAs());
+		mnFile.add(mntmSaveAs);
+		
+		JMenuItem mntmQuit = new JMenuItem("Quit");
+		mntmQuit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, MASK));
+		mntmQuit.addActionListener( e -> { this.dispatchEvent(new WindowEvent(this, WindowEvent.WINDOW_CLOSING)); } );
+		mnFile.add(mntmQuit);
+		
+		tabbedPane = new JTabbedPane();
 		setContentPane(tabbedPane);
 
-		SummaryPanel summaryPanel = new SummaryPanel();
+		summaryPanel = new SummaryPanel();
 		tabbedPane.addTab("Summary", summaryPanel);
 		
-		ObjectMapper mapper = new ObjectMapper();
-		mapper.registerModule(new Jdk8Module());
-		try {
-			Recipe recipe = mapper.readValue(new File("src/test/resources/example.json"), Recipe.class);
-			for (Product comp : recipe.getProducts()) {
-				summaryPanel.addProduct(comp);
-				tabbedPane.addTab(comp.getName(), new ProductPanel(comp));
-			}
-		} catch (IOException e) {
-			// TODO Auto-generated catch block
-			e.printStackTrace();
-		}
+//		fileController.create();
+		fileController.open("src/test/resources/example.json");
 
 		pack();
 		repaint();
 		setVisible(true);
 	}
+	
+	@Override
+	public void setModel(final Recipe recipe) {
+		// TODO Clear underlying panels
+		for (Product comp : recipe.getProducts()) {
+			summaryPanel.addProduct(comp);
+			tabbedPane.addTab(comp.getName(), new ProductPanel(comp));
+		}
+	}
 }