Skip to content

Commit

Permalink
Various memory leak fixes.
Browse files Browse the repository at this point in the history
  • Loading branch information
dlemmermann committed Jan 13, 2023
1 parent 40b46dd commit 054991c
Show file tree
Hide file tree
Showing 20 changed files with 327 additions and 290 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,11 @@
import com.calendarfx.view.DayViewBase.GridType;
import com.calendarfx.view.ResourcesView;
import com.calendarfx.view.ResourcesView.Type;
import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.DatePicker;
Expand Down Expand Up @@ -89,6 +91,30 @@ public Node getControlPanel() {
numberOfResourcesBox.setValue(resourcesView.getResources().size());
numberOfResourcesBox.valueProperty().addListener(it -> resourcesView.getResources().setAll(createResources(numberOfResourcesBox.getValue())));

Button memoryTestButton = new Button("Test Heap");
memoryTestButton.setOnAction(evt -> {
Thread thread = new Thread(() -> {
Runtime r = Runtime.getRuntime();
int counter = 0;
while (true) {
counter++;
final int fc = counter;
Platform.runLater(() -> {
resourcesView.getResources().setAll(createResources(5));
System.out.println(fc + ": free: " + (r.freeMemory() / 1_000) + " kb");
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.setName("Memory Test Thread");
thread.setDaemon(true);
thread.start();
});

ChoiceBox<Integer> clicksBox = new ChoiceBox<>();
clicksBox.getItems().setAll(1, 2, 3);
clicksBox.setValue(resourcesView.getCreateEntryClickCount());
Expand Down Expand Up @@ -177,7 +203,7 @@ public Layout fromString(String string) {
slider.setMax(1);
slider.valueProperty().bindBidirectional(resourcesView.entryViewAvailabilityEditingOpacityProperty());

return new VBox(10, availabilityButton, new Label("View type"), typeBox, layoutBox, datePicker, infiniteScrolling, adjustBox, new Label("Number of resources"), numberOfResourcesBox, new Label("Number of days"), daysBox, new Label("Clicks to create"), clicksBox,
return new VBox(10, availabilityButton, new Label("View type"), typeBox, layoutBox, datePicker, infiniteScrolling, adjustBox, memoryTestButton, new Label("Number of resources"), numberOfResourcesBox, new Label("Number of days"), daysBox, new Label("Clicks to create"), clicksBox,
new Label("Availability Behaviour"), behaviourBox, new Label("Availability Opacity"), slider, new Label("Grid Type"), gridTypeBox, scrollbarBox, timescaleBox, allDayBox, detailsBox, flipBox);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ public final void bind(DateControl dateControl) {
}

public final void unbind(DateControl dateControl) {
// important, otherwise we end up with a memory leak
getCalendarVisibilityMap().clear();

Bindings.unbindContentBidirectional(calendars, dateControl.getCalendars());
Bindings.unbindContentBidirectional(calendarVisibilityMap, dateControl.getCalendarVisibilityMap());
}
Expand Down
75 changes: 39 additions & 36 deletions CalendarFXView/src/main/java/com/calendarfx/view/DateControl.java
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@
*/
public abstract class DateControl extends CalendarFXControl {

private int entryCounter = 1;

private Boolean usesOwnContextMenu;

private final InvalidationListener updateCalendarListListener = (Observable it) -> updateCalendarList();
Expand Down Expand Up @@ -216,12 +214,12 @@ protected DateControl() {
* The default calendar provider returns the first calendar which is visible and not read-only.
*/
setDefaultCalendarProvider(control -> {
List<CalendarSource> sources = getCalendarSources();
List<CalendarSource> sources = control.getCalendarSources();
for (CalendarSource s : sources) {
List<? extends Calendar> calendars = s.getCalendars();
if (calendars != null && !calendars.isEmpty()) {
for (Calendar c : calendars) {
if (!c.isReadOnly() && isCalendarVisible(c)) {
if (!c.isReadOnly() && control.isCalendarVisible(c)) {
return c;
}
}
Expand All @@ -232,11 +230,12 @@ protected DateControl() {
});

setEntryFactory(param -> {
DateControl control = param.getDateControl();
DateControl dateControl = param.getDateControl();

VirtualGrid grid = control.getVirtualGrid();
ZonedDateTime time = param.getZonedDateTime();
DayOfWeek firstDayOfWeek = getFirstDayOfWeek();
DayOfWeek firstDayOfWeek = dateControl.getFirstDayOfWeek();

VirtualGrid grid = dateControl.getVirtualGrid();
ZonedDateTime lowerTime = grid.adjustTime(time, false, firstDayOfWeek);
ZonedDateTime upperTime = grid.adjustTime(time, true, firstDayOfWeek);

Expand All @@ -246,11 +245,11 @@ protected DateControl() {
time = upperTime;
}

Entry<Object> entry = new Entry<>(MessageFormat.format(Messages.getString("DateControl.DEFAULT_ENTRY_TITLE"), entryCounter++));
Entry<Object> entry = new Entry<>(Messages.getString("DateControl.DEFAULT_ENTRY_TITLE"));
Interval interval = new Interval(time.toLocalDateTime(), time.toLocalDateTime().plusHours(1), time.getZone());
entry.setInterval(interval);

if (control instanceof AllDayView) {
if (dateControl instanceof AllDayView) {
entry.setFullDay(true);
}

Expand All @@ -262,11 +261,11 @@ protected DateControl() {
if (evt instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) evt;
if (mouseEvent.getClickCount() == 2) {
showEntryDetails(param.getEntry(), param.getNode(), param.getOwner(), param.getScreenY());
param.getDateControl().showEntryDetails(param.getEntry(), param.getNode(), param.getOwner(), param.getScreenY());
return true;
}
} else {
showEntryDetails(param.getEntry(), param.getNode(), param.getOwner(), param.getScreenY());
param.getDateControl().showEntryDetails(param.getEntry(), param.getNode(), param.getOwner(), param.getScreenY());
return true;
}

Expand All @@ -276,12 +275,12 @@ protected DateControl() {
setDateDetailsCallback(param -> {
InputEvent evt = param.getInputEvent();
if (evt == null) {
showDateDetails(param.getOwner(), param.getLocalDate());
param.getDateControl().showDateDetails(param.getOwner(), param.getLocalDate());
return true;
} else if (evt instanceof MouseEvent) {
MouseEvent mouseEvent = (MouseEvent) evt;
if (mouseEvent.getClickCount() == 1) {
showDateDetails(param.getOwner(), param.getLocalDate());
param.getDateControl().showDateDetails(param.getOwner(), param.getLocalDate());
return true;
}
}
Expand All @@ -302,11 +301,10 @@ protected DateControl() {
*/
MenuItem informationItem = new MenuItem(Messages.getString("DateControl.MENU_ITEM_INFORMATION"));
informationItem.setOnAction(evt -> {
Callback<EntryDetailsParameter, Boolean> detailsCallback = getEntryDetailsCallback();
Callback<EntryDetailsParameter, Boolean> detailsCallback = param.getDateControl().getEntryDetailsCallback();
if (detailsCallback != null) {
ContextMenuEvent ctxEvent = param.getContextMenuEvent();
EntryDetailsParameter entryDetailsParam = new EntryDetailsParameter(ctxEvent, DateControl.this, entryView.getEntry(), entryView, entryView, ctxEvent.getScreenX(), ctxEvent.getScreenY());
detailsCallback.call(entryDetailsParam);
detailsCallback.call(new EntryDetailsParameter(ctxEvent, param.getDateControl(), entryView.getEntry(), entryView, entryView, ctxEvent.getScreenX(), ctxEvent.getScreenY()));
}
});
contextMenu.getItems().add(informationItem);
Expand All @@ -317,7 +315,7 @@ protected DateControl() {
* Assign entry to different calendars.
*/
Menu calendarMenu = new Menu(Messages.getString("DateControl.MENU_CALENDAR"));
for (Calendar calendar : getCalendars()) {
for (Calendar calendar : param.getDateControl().getCalendars()) {
RadioMenuItem calendarItem = new RadioMenuItem(calendar.getName());
calendarItem.setOnAction(evt -> entry.setCalendar(calendar));
calendarItem.setDisable(calendar.isReadOnly());
Expand All @@ -343,7 +341,7 @@ protected DateControl() {
calendarMenu.setDisable(param.getCalendar().isReadOnly());
contextMenu.getItems().add(calendarMenu);

if (getEntryEditPolicy().call(new EntryEditParameter(this, entry, EditOperation.DELETE))) {
if (param.getDateControl().getEntryEditPolicy().call(new EntryEditParameter(param.getDateControl(), entry, EditOperation.DELETE))) {
/*
* Delete calendar entry.
*/
Expand Down Expand Up @@ -433,13 +431,11 @@ public final BooleanProperty getCalendarVisibilityProperty(Calendar calendar) {
}

public final boolean isCalendarVisible(Calendar calendar) {
BooleanProperty prop = getCalendarVisibilityProperty(calendar);
return prop.get();
return getCalendarVisibilityProperty(calendar).get();
}

public final void setCalendarVisibility(Calendar calendar, boolean visible) {
BooleanProperty prop = getCalendarVisibilityProperty(calendar);
prop.set(visible);
getCalendarVisibilityProperty(calendar).set(visible);
}

public enum Layer {
Expand Down Expand Up @@ -625,8 +621,6 @@ public final Entry<?> createEntryAt(ZonedDateTime time, Calendar calendar, boole
Callback<CreateEntryParameter, Entry<?>> factory = getEntryFactory();
Entry<?> entry = factory.call(param);

System.out.println(calendar);

/*
* This is OK. The factory can return NULL. In this case we
* assume that the application does not allow to create an entry
Expand Down Expand Up @@ -1471,6 +1465,8 @@ private void updateCalendarList() {

calendars.addAll(newCalendars);
calendars.removeAll(removedCalendars);

removedCalendars.forEach(calendar -> calendarVisibilityMap.remove(calendar));
}

private abstract static class DetailsParameter {
Expand Down Expand Up @@ -2506,7 +2502,9 @@ public final WeakList<DateControl> getBoundDateControls() {
public final void unbindAll() {
WeakList<DateControl> controls = getBoundDateControls();
for (DateControl next : controls) {
unbind(next);
if (next != null) {
unbind(next);
}
}
}

Expand Down Expand Up @@ -2669,8 +2667,6 @@ public final void bind(DateControl otherControl, boolean bindDate) {

// bind properties
Bindings.bindBidirectional(otherControl.suspendUpdatesProperty(), suspendUpdatesProperty());
Bindings.bindBidirectional(otherControl.entryFactoryProperty(), entryFactoryProperty());
Bindings.bindBidirectional(otherControl.defaultCalendarProviderProperty(), defaultCalendarProviderProperty());
Bindings.bindBidirectional(otherControl.virtualGridProperty(), virtualGridProperty());
Bindings.bindBidirectional(otherControl.draggedEntryProperty(), draggedEntryProperty());
Bindings.bindBidirectional(otherControl.requestedTimeProperty(), requestedTimeProperty());
Expand Down Expand Up @@ -2701,13 +2697,15 @@ public final void bind(DateControl otherControl, boolean bindDate) {
Bindings.bindBidirectional(otherControl.createEntryClickCountProperty(), createEntryClickCountProperty());

// bind callbacks
Bindings.bindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Bindings.bindBidirectional(otherControl.dateDetailsCallbackProperty(), dateDetailsCallbackProperty());
Bindings.bindBidirectional(otherControl.contextMenuCallbackProperty(), contextMenuCallbackProperty());
Bindings.bindBidirectional(otherControl.entryContextMenuCallbackProperty(), entryContextMenuCallbackProperty());
Bindings.bindBidirectional(otherControl.calendarSourceFactoryProperty(), calendarSourceFactoryProperty());
Bindings.bindBidirectional(otherControl.entryDetailsPopOverContentCallbackProperty(), entryDetailsPopOverContentCallbackProperty());
Bindings.bindBidirectional(otherControl.entryEditPolicyProperty(), entryEditPolicyProperty());
Bindings.bindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Bindings.bindBidirectional(otherControl.dateDetailsCallbackProperty(), dateDetailsCallbackProperty());
Bindings.bindBidirectional(otherControl.entryContextMenuCallbackProperty(), entryContextMenuCallbackProperty());
Bindings.bindBidirectional(otherControl.entryFactoryProperty(), entryFactoryProperty());
Bindings.bindBidirectional(otherControl.defaultCalendarProviderProperty(), defaultCalendarProviderProperty());
}

/**
Expand All @@ -2720,6 +2718,9 @@ public final void bind(DateControl otherControl, boolean bindDate) {
public final void unbind(DateControl otherControl) {
requireNonNull(otherControl);

// important, otherwise we end up with a memory leak
otherControl.getCalendarVisibilityMap().clear();

// unbind collections
Bindings.unbindContentBidirectional(otherControl.getCalendarVisibilityMap(), getCalendarVisibilityMap());
Bindings.unbindContentBidirectional(otherControl.getCalendarSources(), getCalendarSources());
Expand All @@ -2729,8 +2730,6 @@ public final void unbind(DateControl otherControl) {

// unbind properties
Bindings.unbindBidirectional(otherControl.suspendUpdatesProperty(), suspendUpdatesProperty());
Bindings.unbindBidirectional(otherControl.entryFactoryProperty(), entryFactoryProperty());
Bindings.unbindBidirectional(otherControl.defaultCalendarProviderProperty(), defaultCalendarProviderProperty());
Bindings.unbindBidirectional(otherControl.virtualGridProperty(), virtualGridProperty());
Bindings.unbindBidirectional(otherControl.draggedEntryProperty(), draggedEntryProperty());
Bindings.unbindBidirectional(otherControl.requestedTimeProperty(), requestedTimeProperty());
Expand Down Expand Up @@ -2759,13 +2758,17 @@ public final void unbind(DateControl otherControl) {
Bindings.unbindBidirectional(otherControl.createEntryClickCountProperty(), createEntryClickCountProperty());

// unbind callbacks
Bindings.unbindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Bindings.unbindBidirectional(otherControl.dateDetailsCallbackProperty(), dateDetailsCallbackProperty());
Bindings.unbindBidirectional(otherControl.contextMenuCallbackProperty(), contextMenuCallbackProperty());
Bindings.unbindBidirectional(otherControl.entryContextMenuCallbackProperty(), entryContextMenuCallbackProperty());
Bindings.unbindBidirectional(otherControl.calendarSourceFactoryProperty(), calendarSourceFactoryProperty());
Bindings.unbindBidirectional(otherControl.entryDetailsPopOverContentCallbackProperty(), entryDetailsPopOverContentCallbackProperty());
Bindings.unbindBidirectional(otherControl.entryDetailsCallbackProperty(), entryDetailsCallbackProperty());
Bindings.unbindBidirectional(otherControl.entryEditPolicyProperty(), entryEditPolicyProperty());

Bindings.unbindBidirectional(otherControl.entryDetailsPopOverContentCallbackProperty(), entryDetailsPopOverContentCallbackProperty());
Bindings.unbindBidirectional(otherControl.dateDetailsCallbackProperty(), dateDetailsCallbackProperty());
Bindings.unbindBidirectional(otherControl.entryContextMenuCallbackProperty(), entryContextMenuCallbackProperty());
Bindings.unbindBidirectional(otherControl.entryFactoryProperty(), entryFactoryProperty());
Bindings.unbindBidirectional(otherControl.defaultCalendarProviderProperty(), defaultCalendarProviderProperty());

}

private final BooleanProperty suspendUpdates = new SimpleBooleanProperty(this, "suspendUpdates", false);
Expand Down
15 changes: 9 additions & 6 deletions CalendarFXView/src/main/java/com/calendarfx/view/DayView.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import com.calendarfx.model.Calendar;
import com.calendarfx.model.Entry;
import impl.com.calendarfx.view.DayViewSkin;
import javafx.beans.Observable;
import javafx.beans.InvalidationListener;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Skin;
Expand Down Expand Up @@ -61,13 +61,16 @@ public class DayView extends DayViewBase {
public DayView() {
getStyleClass().add(DAY_VIEW);

showTodayProperty().addListener(it -> updateStyleClasses());
todayProperty().addListener(evt -> updateStyleClasses());
dateProperty().addListener(evt -> updateStyleClasses());
selectionModeProperty().addListener(evt -> getSelections().clear());
getWeekendDays().addListener((Observable it) -> updateStyleClasses());
InvalidationListener updateStyleClassListener = it -> updateStyleClasses();

showTodayProperty().addListener(updateStyleClassListener);
todayProperty().addListener(updateStyleClassListener);
dateProperty().addListener(updateStyleClassListener);
getWeekendDays().addListener(updateStyleClassListener);
updateStyleClasses();

selectionModeProperty().addListener(evt -> getSelections().clear());

setEntryViewFactory(DayEntryView::new);
setMinWidth(0); // important, so that multi day views apply same width for all day views

Expand Down
Loading

0 comments on commit 054991c

Please sign in to comment.