Skip to content

Commit

Permalink
Merge pull request #46 from sillydan1/feat/less-boilerplate
Browse files Browse the repository at this point in the history
Feature: AutoProperty construct to reduce boilerplate code for new syntaxes
  • Loading branch information
sillydan1 authored Mar 7, 2024
2 parents 73cabcf + 606ff03 commit 8c83d52
Show file tree
Hide file tree
Showing 18 changed files with 543 additions and 622 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/dk/gtz/graphedit/util/RetryUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static <T> T tryTimes(int maxAttempts, int sleepMillis, Supplier<T> f) {
try {
return f.get();
} catch(Exception e) {
logger.warn("'{}' {}/{} attempts left", e.getMessage(), attempts+1, maxAttempts);
logger.trace("'{}' {}/{} attempts left", e.getMessage(), attempts+1, maxAttempts);
sleep(sleepMillis);
}
}
Expand Down
16 changes: 12 additions & 4 deletions core/src/main/java/dk/gtz/graphedit/view/ModelEditorToolbar.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class ModelEditorToolbar extends ToolBar {
private final IToolbox toolbox;
private final ObjectProperty<ITool> selectedTool;
private final ViewModelProjectResource resource;
private ComboBox<String> syntaxSelector;

/**
* Create a new instance
Expand All @@ -47,8 +48,15 @@ public ModelEditorToolbar(IToolbox toolbox, ObjectProperty<ITool> selectedTool,
* @return Builder-pattern style reference to this
*/
public ModelEditorToolbar withSyntaxSelector() {
if(resource.metadata().containsKey("graphedit_syntax"))
addSyntaxSelector();
if(resource.metadata().containsKey("graphedit_syntax")) {
var factories = DI.get(SyntaxFactoryCollection.class);
syntaxSelector = addSyntaxSelector(factories);
factories.addChangeListener(e -> {
getItems().remove(syntaxSelector);
syntaxSelector = addSyntaxSelector(factories);
});
}

addSeparator();
return this;
}
Expand All @@ -74,8 +82,7 @@ private void setupContent() {
}
}

private void addSyntaxSelector() {
var factories = DI.get(SyntaxFactoryCollection.class);
private ComboBox<String> addSyntaxSelector(SyntaxFactoryCollection factories) {
ObservableList<String> list = FXCollections.observableArrayList();
for(var factory : factories.entrySet())
list.add(factory.getKey());
Expand All @@ -98,6 +105,7 @@ protected void onChanged(String oldValue, String newValue) {
}
};
cmb.getSelectionModel().selectedItemProperty().addListener(listener);
return cmb;
}

private void addButton(ITool tool) {
Expand Down
71 changes: 41 additions & 30 deletions core/src/main/java/dk/gtz/graphedit/view/StatusBarController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import java.util.List;

import dk.gtz.graphedit.spi.ILanguageServer;
import dk.gtz.graphedit.viewmodel.LanguageServerCollection;
import dk.yalibs.yadi.DI;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.collections.MapChangeListener;
import javafx.geometry.Insets;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
Expand Down Expand Up @@ -57,6 +59,8 @@ public StatusBarController() {
// debugMouseHover();
}

// NOTE: kept for future use
@SuppressWarnings("unused")
private void debugMouseHover() {
Platform.runLater(() -> {
var w = DI.get(Window.class);
Expand All @@ -78,37 +82,44 @@ private void initializeLabels() {

private void initializeLSPs() {
var lsps = DI.get(LanguageServerCollection.class);
for(var server : lsps.values()) {
server.addProgressCallback(p -> {
Platform.runLater(() -> {
lspLabel.setText(p.title()+":");
messageLabel.setText(p.message());
});
switch(p.type()) {
case PROGRESS:
case BEGIN:
if(!spinnerThread.isAlive())
startSpinnerThread();
break;
case END:
if(spinnerThread.isAlive())
spinnerThread.interrupt();
Platform.runLater(() -> spinnerString.set("\u2714")); // checkmark unicode character
break;
case END_FAIL:
if(spinnerThread.isAlive())
spinnerThread.interrupt();
Platform.runLater(() -> spinnerString.set("\u2717")); // x mark unicode character
break;
default:
if(spinnerThread.isAlive())
spinnerThread.interrupt();
Platform.runLater(() -> spinnerString.set("?"));
break;

}
for(var lsp : lsps.values())
addSpinner(lsp);
lsps.addListener((MapChangeListener<String,ILanguageServer>)e -> {
if(e.wasAdded())
addSpinner(e.getValueAdded());
});
}

private void addSpinner(ILanguageServer server) {
server.addProgressCallback(p -> {
Platform.runLater(() -> {
lspLabel.setText(p.title()+":");
messageLabel.setText(p.message());
});
}
switch(p.type()) {
case PROGRESS:
case BEGIN:
if(!spinnerThread.isAlive())
startSpinnerThread();
break;
case END:
if(spinnerThread.isAlive())
spinnerThread.interrupt();
Platform.runLater(() -> spinnerString.set("\u2714")); // checkmark unicode character
break;
case END_FAIL:
if(spinnerThread.isAlive())
spinnerThread.interrupt();
Platform.runLater(() -> spinnerString.set("\u2717")); // x mark unicode character
break;
default:
if(spinnerThread.isAlive())
spinnerThread.interrupt();
Platform.runLater(() -> spinnerString.set("?"));
break;

}
});
}

private void startSpinnerThread() {
Expand Down
213 changes: 213 additions & 0 deletions core/src/main/java/dk/gtz/graphedit/viewmodel/AutoProperty.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
package dk.gtz.graphedit.viewmodel;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javafx.beans.InvalidationListener;
import javafx.beans.property.Property;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.beans.value.WritableObjectValue;

public abstract class AutoProperty<T extends Property<T>> implements Property<T> {
private static Logger logger = LoggerFactory.getLogger(AutoProperty.class);
private T value;
private List<Field> fields;
private final Map<Field, ChangeListener<? super Object>> changeListeners;
private final Map<Field, InvalidationListener> invalidationListeners;

protected AutoProperty() {
this.changeListeners = new HashMap<>();
this.invalidationListeners = new HashMap<>();
}

protected void loadFields(Class<?> clazz, T value) {
this.value = value;
fields = new ArrayList<>();
for(var field : clazz.getFields())
if(List.of(field.getAnnotations())
.stream()
.anyMatch(a -> a
.annotationType()
.getSimpleName()
.equals(Autolisten.class.getSimpleName())))
fields.add(field);
}

@Override
public void addListener(ChangeListener<? super T> listener) {
for(var field : fields) {
var isObservable = ObservableValue.class.isAssignableFrom(field.getType());
if(!isObservable)
continue;
try {
var observable = (ObservableValue<?>)field.get(value);
if(!changeListeners.containsKey(field))
changeListeners.put(field, (e,o,n) -> listener.changed(value, value, value));
observable.addListener(changeListeners.get(field));
} catch (IllegalAccessException e) {
logger.error("error adding listener '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
public void removeListener(ChangeListener<? super T> listener) {
for(var field : fields) {
var isObservable = ObservableValue.class.isAssignableFrom(field.getType());
if(!isObservable)
continue;
try {
var observable = (ObservableValue<?>)field.get(value);
if(changeListeners.containsKey(field))
observable.addListener(changeListeners.get(field));
else // BUG: This probably doesnt work, but it doesn't hurt to try
observable.addListener((e,o,n) -> listener.changed(value, value, value));
} catch (IllegalAccessException e) {
logger.error("error removing listener '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
public void addListener(InvalidationListener listener) {
for(var field : fields) {
if(!field.getType().isAssignableFrom(ObservableValue.class))
continue;
try {
var observable = (ObservableValue<?>)field.get(value);
if(!invalidationListeners.containsKey(field))
invalidationListeners.put(field, listener);
observable.addListener(invalidationListeners.get(field));
} catch (IllegalAccessException e) {
logger.error("error adding invalidation listener '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
public void removeListener(InvalidationListener listener) {
for(var field : fields) {
var isObservable = ObservableValue.class.isAssignableFrom(field.getType());
if(!isObservable)
continue;
try {
var observable = (ObservableValue<?>)field.get(value);
if(invalidationListeners.containsKey(field))
observable.addListener(invalidationListeners.get(field));
else // BUG: This probably doesnt work, but it doesn't hurt to try
observable.addListener(listener);
} catch (IllegalAccessException e) {
logger.error("error removing invalidation listener '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void bind(ObservableValue<? extends T> observable) {
for(var field : fields) {
if(!field.getType().isAssignableFrom(Property.class))
continue;
try {
var thisObservable = (Property)field.get(value);
var thatObservable = (Property)field.get(observable.getValue());
thisObservable.bind(thatObservable);
} catch (IllegalAccessException e) {
logger.error("error binding '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
public void unbind() {
for(var field : fields) {
var isProperty = Property.class.isAssignableFrom(field.getType());
if(!isProperty)
continue;
try {
((Property<?>)field.get(value)).unbind();
} catch (IllegalAccessException e) {
logger.error("error binding '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
public boolean isBound() {
for(var field : fields) {
var isProperty = Property.class.isAssignableFrom(field.getType());
if(!isProperty)
continue;
try {
if(((Property<?>)field.get(value)).isBound())
return true;
} catch (IllegalAccessException e) {
logger.error("isBound error '{}': {}", field.getName(), e.getMessage(), e);
}
}
return false;
}

@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void bindBidirectional(Property<T> other) {
for(var field : fields) {
var isProperty = Property.class.isAssignableFrom(field.getType());
if(!isProperty)
continue;
try {
var thisObservable = (Property)field.get(value);
var thatObservable = (Property)field.get(other);
thisObservable.bindBidirectional(thatObservable);
} catch (IllegalAccessException e) {
logger.error("error binding bidirectionally '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public void unbindBidirectional(Property<T> other) {
for(var field : fields) {
var isProperty = Property.class.isAssignableFrom(field.getType());
if(!isProperty)
continue;
try {
var thisObservable = (Property)field.get(value);
var thatObservable = (Property)field.get(other);
thisObservable.unbindBidirectional(thatObservable);
} catch (IllegalAccessException e) {
logger.error("error unbinding bidirectionally '{}': {}", field.getName(), e.getMessage(), e);
}
}
}

@Override
public Object getBean() {
return null;
}

@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setValue(T value) {
for(var field : fields) {
var isWritable = WritableObjectValue.class.isAssignableFrom(field.getType());
if(!isWritable)
continue;
try {
var thisObservable = (WritableObjectValue)field.get(this.value);
var thatObservable = (WritableObjectValue)field.get(value);
thisObservable.set(thatObservable.get());
} catch (IllegalAccessException e) {
logger.error("error setting value '{}': {}", field.getName(), e.getMessage(), e);
}
}
}
}
7 changes: 7 additions & 0 deletions core/src/main/java/dk/gtz/graphedit/viewmodel/Autolisten.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package dk.gtz.graphedit.viewmodel;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface Autolisten {}
Loading

0 comments on commit 8c83d52

Please sign in to comment.