From 0e2b9a4e7e859ae2d9256266178c065eaa921034 Mon Sep 17 00:00:00 2001 From: Niclas Thobaben Date: Fri, 28 Feb 2020 17:40:53 +0100 Subject: [PATCH] first impl --- src/main/java/de/synolo/lib/fw/TestMain.java | 97 +++++++++ .../lib/fw/app/AbstractApplication.java | 48 +++++ .../synolo/lib/fw/app/ApplicationContext.java | 24 +++ .../java/de/synolo/lib/fw/cmd/Command.java | 17 ++ .../de/synolo/lib/fw/cmd/UndoManager.java | 90 +++++++++ .../java/de/synolo/lib/fw/utils/LogLevel.java | 26 +++ .../java/de/synolo/lib/fw/utils/Logging.java | 187 ++++++++++++++++++ .../utils/MultipleSelectionModelWrapper.java | 107 ++++++++++ .../synolo/lib/fw/utils/TypedProperties.java | 42 ++++ .../resources/META-INF/maven/archetype.xml | 9 + .../resources/archetype-resources/pom.xml | 15 ++ .../src/test/java/AppTest.java | 38 ++++ 12 files changed, 700 insertions(+) create mode 100644 src/main/java/de/synolo/lib/fw/TestMain.java create mode 100644 src/main/java/de/synolo/lib/fw/app/AbstractApplication.java create mode 100644 src/main/java/de/synolo/lib/fw/app/ApplicationContext.java create mode 100644 src/main/java/de/synolo/lib/fw/cmd/Command.java create mode 100644 src/main/java/de/synolo/lib/fw/cmd/UndoManager.java create mode 100644 src/main/java/de/synolo/lib/fw/utils/LogLevel.java create mode 100644 src/main/java/de/synolo/lib/fw/utils/Logging.java create mode 100644 src/main/java/de/synolo/lib/fw/utils/MultipleSelectionModelWrapper.java create mode 100644 src/main/java/de/synolo/lib/fw/utils/TypedProperties.java create mode 100644 src/main/resources/META-INF/maven/archetype.xml create mode 100644 src/main/resources/archetype-resources/pom.xml create mode 100644 src/main/resources/archetype-resources/src/test/java/AppTest.java diff --git a/src/main/java/de/synolo/lib/fw/TestMain.java b/src/main/java/de/synolo/lib/fw/TestMain.java new file mode 100644 index 0000000..e031f1c --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/TestMain.java @@ -0,0 +1,97 @@ +package de.synolo.lib.fw; + +import de.synolo.lib.fw.cmd.Command; +import de.synolo.lib.fw.cmd.UndoManager; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +public class TestMain extends Application { + + static class TestCommand implements Command { + + @Override + public void execute() { + System.out.println("Execute command!"); + + } + + @Override + public boolean isUndoable() { + return true; + } + + @Override + public boolean isCollapsable(Command other) { + // TODO Auto-generated method stub + return false; + } + + @Override + public void collapse(Command other) { + // TODO Auto-generated method stub + + } + + @Override + public void undo() { + System.out.println("Undo command"); + + } + + @Override + public void redo() { + System.out.println("Redo command"); + + } + + @Override + public String getId() { + return "command.test"; + } + + } + + public static void main(String[] args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) throws Exception { + + UndoManager undoManager = new UndoManager(); + + BorderPane bp = new BorderPane(); + Scene scene = new Scene(bp); + + Button btn = new Button("Execute command"); + bp.setCenter(btn); + + Button btnUndo = new Button("Undo"); + btnUndo.disableProperty().bind(undoManager.undoAvailableProperty().not()); + btnUndo.setOnAction(e -> undoManager.undo()); + + + Button btnRedo = new Button("Redo"); + btnRedo.disableProperty().bind(undoManager.redoAvailableProperty().not()); + btnRedo.setOnAction(e -> undoManager.redo()); + + btn.setOnAction(e -> undoManager.execute(new TestCommand())); + + VBox vbox = new VBox(); + vbox.getChildren().addAll(btnUndo, btnRedo); + + bp.setBottom(vbox); + + primaryStage.setScene(scene); + primaryStage.show(); + + } + + + +} diff --git a/src/main/java/de/synolo/lib/fw/app/AbstractApplication.java b/src/main/java/de/synolo/lib/fw/app/AbstractApplication.java new file mode 100644 index 0000000..b4eaa11 --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/app/AbstractApplication.java @@ -0,0 +1,48 @@ +package de.synolo.lib.fw.app; + +import de.synolo.lib.fw.utils.Logging; +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; + +public abstract class AbstractApplication extends Application { + + private C context; + private Stage primaryStage; + + + + @Override + public void init() throws Exception { + super.init(); + Logging.init(); + } + + @Override + public void start(Stage primaryStage) throws Exception { + this.primaryStage = primaryStage; + this.context = initContext(); + + buildApplication(primaryStage); + } + + protected abstract void buildApplication(Stage primaryStage) throws Exception; + + public C getContext() { return this.context; } + protected abstract C initContext(); + + public Stage getPrimaryStage() { return this.primaryStage; } + + public Stage showDialog(Modality modality, StageStyle style, Scene scene) { + Stage dialog = new Stage(); + dialog.initOwner(this.primaryStage); + dialog.initModality(modality); + dialog.initStyle(style); + dialog.setScene(scene); + dialog.show(); + return dialog; + } + +} diff --git a/src/main/java/de/synolo/lib/fw/app/ApplicationContext.java b/src/main/java/de/synolo/lib/fw/app/ApplicationContext.java new file mode 100644 index 0000000..3809143 --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/app/ApplicationContext.java @@ -0,0 +1,24 @@ +package de.synolo.lib.fw.app; + +import java.util.prefs.Preferences; + +import de.synolo.lib.fw.cmd.UndoManager; +import de.synolo.lib.fw.utils.TypedProperties; + +public abstract class ApplicationContext { + + protected TypedProperties properties; + protected Preferences preferences; + protected UndoManager undoManager; + + public ApplicationContext() {} + + public TypedProperties getProperties() { return this.properties; } + public Preferences getPreferences() { return this.preferences; } + public UndoManager getUndoManager() { return this.undoManager; } + + public abstract String getApplicationName(); + public abstract String getApplicationVersion(); + + +} diff --git a/src/main/java/de/synolo/lib/fw/cmd/Command.java b/src/main/java/de/synolo/lib/fw/cmd/Command.java new file mode 100644 index 0000000..f65369d --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/cmd/Command.java @@ -0,0 +1,17 @@ +package de.synolo.lib.fw.cmd; + +public interface Command { + + public void execute(); + + public boolean isUndoable(); + public boolean isCollapsable(Command other); + + public void collapse(Command other); + + public void undo(); + public void redo(); + + public String getId(); + +} diff --git a/src/main/java/de/synolo/lib/fw/cmd/UndoManager.java b/src/main/java/de/synolo/lib/fw/cmd/UndoManager.java new file mode 100644 index 0000000..fe3a8b3 --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/cmd/UndoManager.java @@ -0,0 +1,90 @@ +package de.synolo.lib.fw.cmd; + +import java.util.Stack; + +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; + +public class UndoManager { + + private Stack undoStack; + private Stack redoStack; + + private ReadOnlyBooleanWrapper undoAvailalableProperty = new ReadOnlyBooleanWrapper(false); + private ReadOnlyBooleanWrapper redoAvailalableProperty = new ReadOnlyBooleanWrapper(false); + + private int max; + + public UndoManager(int maxCommands) { + this.max = maxCommands; + this.undoStack = new Stack<>(); + this.redoStack = new Stack<>(); + } + + public UndoManager() { + this(100); + } + + public int getMaxCommands() { return this.max; } + public void setMaxCommands(int max) { this.max = max; } + + public boolean isUndoAvailable() { + return !this.undoStack.isEmpty(); + } + public ReadOnlyBooleanProperty undoAvailableProperty() { return this.undoAvailalableProperty.getReadOnlyProperty(); } + + public boolean isRedoAvailable() { + return !this.redoStack.isEmpty(); + } + public ReadOnlyBooleanProperty redoAvailableProperty() { return this.redoAvailalableProperty.getReadOnlyProperty(); } + + public void execute(Command cmd) { + Command last = null; + + if(!this.undoStack.isEmpty() && (last = this.undoStack.peek()) != null) { + if(last.isCollapsable(cmd)) { + last.collapse(cmd); + return; + }else { + this.redoStack.clear(); + this.undoStack.push(cmd); + } + }else if(cmd.isUndoable()) { + this.redoStack.clear(); + this.undoStack.push(cmd); + } + if(this.undoStack.size() > this.max) { + this.undoStack.remove(0); + } + cmd.execute(); + update(); + + } + + public void undo() { + System.out.println("undo..."); + if(isUndoAvailable()) { + Command cmd = this.undoStack.pop(); + this.redoStack.push(cmd); + cmd.undo(); + } + update(); + } + + public void redo() { + if(isRedoAvailable()) { + Command cmd = this.redoStack.pop(); + this.undoStack.push(cmd); + cmd.redo(); + } + update(); + } + + private void update() { + this.undoAvailalableProperty.set(isUndoAvailable()); + this.redoAvailalableProperty.set(isRedoAvailable()); + System.out.println(this.undoStack); + } + + +} diff --git a/src/main/java/de/synolo/lib/fw/utils/LogLevel.java b/src/main/java/de/synolo/lib/fw/utils/LogLevel.java new file mode 100644 index 0000000..dd4c3d1 --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/utils/LogLevel.java @@ -0,0 +1,26 @@ +package de.synolo.lib.fw.utils; + +public enum LogLevel { + TRACE(16), + DEBUG(8), + INFO(4), + WARNING(2), + ERROR(0); + + private int val; + + private LogLevel(final int val) { this.val = val; } + + public int val() { return this.val; } + + public static LogLevel forVal(int val) { + LogLevel found = ERROR; + LogLevel[] levels = LogLevel.class.getEnumConstants(); + for(LogLevel level : levels) { + if(val >= level.val() && found.val() < level.val()) { + found = level; + } + } + return found; + } +} diff --git a/src/main/java/de/synolo/lib/fw/utils/Logging.java b/src/main/java/de/synolo/lib/fw/utils/Logging.java new file mode 100644 index 0000000..3a500a0 --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/utils/Logging.java @@ -0,0 +1,187 @@ +package de.synolo.lib.fw.utils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; + +public class Logging { + + public static final String DEFAULT_FORMAT = "[%date][%level] %msg\n"; + public static final DateFormat DEFAULT_DATEFORMAT = new SimpleDateFormat("YYYY-MM-dd HH:mm:ss.SSS"); + + public static Logging out; + + public static void init() { + out = new Logging(null); + } + public static void init(String lvl) { + LogLevel level = lvl == null ? null : LogLevel.valueOf(lvl.toUpperCase()); + out = new Logging(level); + } + + protected String format; + protected LogLevel level; + protected PrintStream filestream, defaultstream; + protected File file = null; + protected DateFormat dateformat; + protected ArrayList messages = new ArrayList(); + protected Thread loggingThread; + + protected Logging(LogLevel level) { + this.format = DEFAULT_FORMAT; + this.dateformat = DEFAULT_DATEFORMAT; + this.level = level == null ? LogLevel.INFO : level; + this.defaultstream = System.out; + System.setErr(new PrintStream(new LoggingOutputStream(LogLevel.ERROR))); + System.setOut(new PrintStream(new LoggingOutputStream(LogLevel.INFO))); + log("Initialized Logging with LogLevel: %s", this.level.toString()); + } + + private synchronized void writeToFile(String write) { + if (filestream != null) { + filestream.println(write); + filestream.flush(); + } + defaultstream.print(write); + } + + public void log(LogLevel level, String format, Object...args) { + if(level.val() > this.level.val()) { + return; + } + String dateStr = this.dateformat.format(new Date()); + String levelStr = String.format("%-8s", level.toString()); + String msgStr = String.format(format, args); + + String formatted = String.format("[%s][%s] %s \n", dateStr, levelStr, msgStr); + writeToFile(formatted); + + } + public void log(String format, Object...args) { + log(LogLevel.INFO, format, args); + } + public void log(String msg) { + log(msg, ""); + } + + public void err(String format, Object...args) { + log(LogLevel.ERROR, format, args); + } + public void err(String msg) { + err(msg, ""); + } + public void err(Object obj) { + err(obj.toString()); + } + + public void warn(String format, Object...args) { + log(LogLevel.WARNING, format, args); + } + public void warn(String msg) { + warn(msg, ""); + } + + public void debug(String format, Object...args) { + log(LogLevel.DEBUG, format, args); + } + public void debug(String msg) { + debug(msg, ""); + } + + public void detail(String format, Object...args) { + log(LogLevel.TRACE, format, args); + } + public void detail(String msg) { + detail(msg, ""); + } + + public String getFormat() { + return format; + } + public void setFormat(String format) { + this.format = format; + } + + public LogLevel getLevel() { + return level; + } + public void setLevel(LogLevel level) { + this.level = level; + } + public void setLevel(int level) { + this.level = LogLevel.forVal(level); + } + + public File getFile() { + return file; + } + public void setFile(File file){ + try { + if(!file.exists()) { + file.createNewFile(); + } + this.filestream = new PrintStream(new FileOutputStream(file, true)); + }catch(IOException e) { + e.printStackTrace(); + } + this.file = file; + } + + public DateFormat getDateformat() { + return dateformat; + } + public void setDateformat(DateFormat dateformat) { + this.dateformat = dateformat; + } + + protected class LoggingOutputStream extends OutputStream { + + byte[] buffer; + int bufferPosition; + + LogLevel logLevel; + + public LoggingOutputStream() { + buffer = new byte[4096]; + bufferPosition = 0; + this.logLevel = LogLevel.INFO; + } + + public LoggingOutputStream(LogLevel logLevel) { + buffer = new byte[4096]; + bufferPosition = 0; + this.logLevel = logLevel; + } + + @Override + public void flush(){ + if (bufferPosition > 0){ + String str = new String(buffer,0,bufferPosition); + log(this.logLevel, str, ""); + bufferPosition = 0; + }; + } + + @Override + public void write(int b) throws IOException { + if (b == 0x0A){ + + flush(); + + } else { + buffer[bufferPosition++] = (byte)b; + + if (bufferPosition == buffer.length){ + flush(); + } + }; + + } + } +} diff --git a/src/main/java/de/synolo/lib/fw/utils/MultipleSelectionModelWrapper.java b/src/main/java/de/synolo/lib/fw/utils/MultipleSelectionModelWrapper.java new file mode 100644 index 0000000..cb8ddab --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/utils/MultipleSelectionModelWrapper.java @@ -0,0 +1,107 @@ +package de.synolo.lib.fw.utils; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.scene.control.MultipleSelectionModel; + +public class MultipleSelectionModelWrapper extends MultipleSelectionModel{ + + private ObservableList items; + private ObservableList indices = FXCollections.observableArrayList(); + private ObservableList selectedItems = FXCollections.observableArrayList(); + + public MultipleSelectionModelWrapper(ObservableList list) { + this.items = list; + } + + @Override + public ObservableList getSelectedIndices() { return this.indices; } + + @Override + public ObservableList getSelectedItems() { return this.selectedItems; } + + @Override + public void selectIndices(int index, int... indices) { + select(index); + for(int idx : indices) + select(idx); + + } + @Override + public void selectAll() { + clearSelection(); + for(int i = 0; i < this.items.size(); i++) { + this.selectedItems.add(this.items.get(i)); + this.indices.add(i); + } + } + + @Override + public void selectFirst() { + clearSelection(); + select(0); + } + @Override + public void selectLast() { + clearSelection(); + select(this.items.size()-1); + } + @Override + public void clearAndSelect(int index) { + clearSelection(); + select(index); + } + @Override + public void select(int index) { + if(index < this.items.size() && index >= 0) { + T item = this.items.get(index); + if(!this.selectedItems.contains(item)) { + this.selectedItems.add(item); + this.indices.add(index); + } + } + } + @Override + public void select(T obj) { + if(this.items.contains(obj) && !this.selectedItems.contains(obj)) { + int index = this.items.indexOf(obj); + this.selectedItems.add(obj); + this.indices.add(index); + } + } + + @Override + public void clearSelection(int index) { + if(this.indices.contains(index)) { + T item = this.items.get(index); + this.selectedItems.remove(item); + this.indices.remove(index); + } + + } + @Override + public void clearSelection() { + this.indices.clear(); + this.selectedItems.clear(); + } + @Override + public boolean isSelected(int index) { return this.indices.contains(index); } + + @Override + public boolean isEmpty() { return this.selectedItems.isEmpty(); } + + @Override + public void selectPrevious() { + int index = this.indices.get(this.indices.size()-1); + select(index-1); + + } + @Override + public void selectNext() { + int index = this.indices.get(this.indices.size()-1); + select(index+1); + } + + + +} diff --git a/src/main/java/de/synolo/lib/fw/utils/TypedProperties.java b/src/main/java/de/synolo/lib/fw/utils/TypedProperties.java new file mode 100644 index 0000000..a61b08c --- /dev/null +++ b/src/main/java/de/synolo/lib/fw/utils/TypedProperties.java @@ -0,0 +1,42 @@ +package de.synolo.lib.fw.utils; + +import java.util.Properties; + +public class TypedProperties extends Properties { + + private static final long serialVersionUID = 1L; + + interface Converter { + T convert(String input) throws Exception; + } + + public T getObject(String key, T defaultValue, Converter converter) { + T val = defaultValue; + try { + val = converter.convert(getProperty(key)); + }catch(Exception e) { + val = defaultValue; + } + return val; + } + + public byte getByte(String key, byte defaultValue) { return getObject(key, defaultValue, Byte::parseByte); } + public byte getByte(String key) { return getByte(key, (byte)0); } + + public short getShort(String key, short defaultValue) { return getObject(key, defaultValue, Short::parseShort); } + public short getShort(String key) { return getShort(key, (short)0); } + + public int getInt(String key, int defaultValue) { return getObject(key, defaultValue, Integer::parseInt); } + public int getInt(String key) { return getInt(key, 0); } + + public long getLong(String key, long defaultValue) { return getObject(key, defaultValue, Long::parseLong); } + public long getLong(String key) { return getLong(key, (byte)0); } + + public float getFloat(String key, float defaultValue) { return getObject(key, defaultValue, Float::parseFloat); } + public float getFloat(String key) { return getFloat(key, (byte)0); } + + public double getDouble(String key, double defaultValue) { return getObject(key, defaultValue, Double::parseDouble); } + public double getDouble(String key) { return getDouble(key, (byte)0); } + + +} diff --git a/src/main/resources/META-INF/maven/archetype.xml b/src/main/resources/META-INF/maven/archetype.xml new file mode 100644 index 0000000..ccac6fe --- /dev/null +++ b/src/main/resources/META-INF/maven/archetype.xml @@ -0,0 +1,9 @@ + + lib-fw + + src/main/java/App.java + + + src/test/java/AppTest.java + + diff --git a/src/main/resources/archetype-resources/pom.xml b/src/main/resources/archetype-resources/pom.xml new file mode 100644 index 0000000..3f5cc23 --- /dev/null +++ b/src/main/resources/archetype-resources/pom.xml @@ -0,0 +1,15 @@ + + 4.0.0 + $de.synolo + $lib-fw + $0.0.1-SNAPSHOT + + + junit + junit + 3.8.1 + test + + + diff --git a/src/main/resources/archetype-resources/src/test/java/AppTest.java b/src/main/resources/archetype-resources/src/test/java/AppTest.java new file mode 100644 index 0000000..bfb0a05 --- /dev/null +++ b/src/main/resources/archetype-resources/src/test/java/AppTest.java @@ -0,0 +1,38 @@ +package $de.synolo.lib.fw; + +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * Unit test for simple App. + */ +public class AppTest + extends TestCase +{ + /** + * Create the test case + * + * @param testName name of the test case + */ + public AppTest( String testName ) + { + super( testName ); + } + + /** + * @return the suite of tests being tested + */ + public static Test suite() + { + return new TestSuite( AppTest.class ); + } + + /** + * Rigourous Test :-) + */ + public void testApp() + { + assertTrue( true ); + } +}