com.sri.ai.praise.sgsolver.demo.perspective.AbstractPerspective Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aic-praise Show documentation
Show all versions of aic-praise Show documentation
SRI International's AIC PRAiSE (Probabilistic Reasoning As Symbolic Evaluation) Library (for Java 1.8+)
/*
* Copyright (c) 2015, SRI International
* All rights reserved.
* Licensed under the The BSD 3-Clause License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at:
*
* http://opensource.org/licenses/BSD-3-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of the aic-praise nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.sri.ai.praise.sgsolver.demo.perspective;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.fxmisc.undo.UndoManager;
import org.fxmisc.undo.UndoManagerFactory;
import org.reactfx.EventSource;
import com.google.common.annotations.Beta;
import com.google.common.base.Objects;
import com.google.common.io.Files;
import com.sri.ai.praise.lang.ModelLanguage;
import com.sri.ai.praise.model.common.io.ModelPage;
import com.sri.ai.praise.model.common.io.PagedModelContainer;
import com.sri.ai.praise.sgsolver.demo.FXUtil;
import com.sri.ai.praise.sgsolver.demo.editor.ModelPageEditor;
import com.sri.ai.praise.sgsolver.demo.model.ExamplePages;
import com.sri.ai.util.base.Pair;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.MapProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyMapProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
@Beta
public abstract class AbstractPerspective implements Perspective {
private IntegerProperty currentModelPageIndexProperty;
//
private BooleanProperty canUndoModelPageEdit = new SimpleBooleanProperty(false);
private BooleanProperty canRedoModelPageEdit = new SimpleBooleanProperty(false);
private MapProperty> modelPageEditors = new SimpleMapProperty<>(FXCollections.observableHashMap());
//
private BooleanProperty canUndoPageChange = new SimpleBooleanProperty(false);
private BooleanProperty canRedoPageChange = new SimpleBooleanProperty(false);
private EventSource pageChanges = new EventSource<>();
private UndoManager pageChangeUndoManager = newPageChangeUndoManager(); // NOTE: must be after pageChanges declaration.
//
private ObjectProperty modelFile = new SimpleObjectProperty<>();
private BooleanProperty saveRequired = new SimpleBooleanProperty(false);
//
// START-Perspective default implementations
@Override
public void setCurrentModelPageIndexProperty(IntegerProperty currentModelPageIndexProperty) {
this.currentModelPageIndexProperty = currentModelPageIndexProperty;
this.currentModelPageIndexProperty.addListener((observer, oldValue, newValue) -> {
Integer currentPageIdx = newValue.intValue();
bindModelPageEditorUndoManager(currentPageIdx);
});
}
@Override
public boolean isCanUndoModelPageEdit() {
return canUndoModelPageEdit.get();
}
@Override
public ReadOnlyBooleanProperty canUndoModelPageEditProperty() {
return canUndoModelPageEdit;
}
@Override
public boolean isCanRedoModelPageEdit() {
return canRedoModelPageEdit.get();
}
@Override
public ReadOnlyBooleanProperty canRedoModelPageEditProperty() {
return canRedoModelPageEdit;
}
@Override
public boolean isCanUndoPageChange() {
return canUndoPageChange.get();
}
@Override
public ReadOnlyBooleanProperty canUndoPageChange() {
return canUndoPageChange;
}
@Override
public boolean isCanRedoPageChange() {
return canRedoPageChange.get();
}
@Override
public ReadOnlyBooleanProperty canRedoPageChange() {
return canRedoPageChange;
}
@Override
public void undoPageChange() {
pageChangeUndoManager.undo();
}
@Override
public void redoPageChange() {
pageChangeUndoManager.redo();
}
@Override
public ObservableMap> getModelPageEditors() {
return modelPageEditorsProperty().get();
}
@Override
public ReadOnlyMapProperty> modelPageEditorsProperty() {
return modelPageEditors;
}
@Override
public void newModel(String contents, List defaultQueries) {
newModel(() -> FXCollections.observableMap(Collections.singletonMap(0, new ModelPageEditorSupplier(contents, defaultQueries))));
modelFile.set(null);
}
@Override
public void newModel(File modelFile) {
newModel(new ExamplePages(modelFile.getName(), ExamplePages.getExamplePagesFromFile(modelFile)));
this.modelFile.set(modelFile);
}
@Override
public void newModel(ExamplePages examples) {
newModel(() -> {
List pages = examples.getPages();
Map> newModelPageIdxs = new HashMap<>();
for (int i = 0; i < pages.size(); i++) {
ModelPage page = pages.get(i);
newModelPageIdxs.put(i, new ModelPageEditorSupplier(page.getModel(), page.getDefaultQueriesToRun()));
}
return FXCollections.observableMap(newModelPageIdxs);
});
modelFile.set(null);
}
@Override
public void addPage(Integer atPageIndex) {
Platform.runLater(() -> {
final ModelPageEditor currentPage = modelPageEditors.get(atPageIndex).get();
Supplier addSupplier = new ModelPageEditorSupplier(currentPage.getCurrentPageContents(), currentPage.getCurrentQueries());
addPage(atPageIndex, addSupplier);
pageChanges.emit(new AddPageChange(atPageIndex, addSupplier));
});
}
@Override
public void removePage(Integer pageIndex) {
Platform.runLater(() -> {
Supplier removeSupplier = removePageAt(pageIndex);
pageChanges.emit(new RemovePageChange(pageIndex, removeSupplier));
});
}
@Override
public File getModelFile() {
return modelFile.get();
}
@Override
public ReadOnlyObjectProperty modelFileProperty() {
return modelFile;
}
@Override
public boolean isSaveRequired() {
return saveRequired.get();
}
@Override
public ReadOnlyBooleanProperty saveRequiredProperty() {
return saveRequired;
}
@Override
public void save() {
saveAs(getModelFile());
}
@Override
public void saveAs(File file) {
List>> pageContents = new ArrayList<>();
this.modelPageEditors.values().forEach(mes -> {
ModelPageEditorSupplier mpeSupplier = (ModelPageEditorSupplier) mes;
if (mpeSupplier.modelPageEditor == null) {
// has not been initialized on screen, use the initial values supplied to it
pageContents.add(new Pair<>(mpeSupplier.modelPage, mpeSupplier.defaultQueries));
}
else {
// the page editor has been initialized and displayed to the screen, therefore
// get its most current contents as the user may have changed them
ModelPageEditor mpe = mpeSupplier.get();
pageContents.add(new Pair<>(mpe.getCurrentPageContents(), mpe.getCurrentQueries()));
}
});
String model = getPagedModelInternalContainerRepresentation(pageContents);
try {
Files.write(model.getBytes(PagedModelContainer.FILE_CHARSET), file);
// If saving to a different file
if (file != getModelFile()) {
modelFile.set(file);
}
// Once saved we can clear the undo histories
undoManagersForgetHistory();
}
catch (IOException ioe) {
FXUtil.exception(ioe);
}
}
@Override
public void gotoModelEditor() {
getCurrentModelPageEditor().gotoModelEditor();
}
@Override
public void gotoQueryEditor() {
getCurrentModelPageEditor().gotoQueryEditor();
}
@Override
public void executeQuery() {
getCurrentModelPageEditor().executeQuery();
}
// END-Perspective default implementations
//
protected ModelPageEditor getCurrentModelPageEditor() {
ModelPageEditor result = modelPageEditors.get(currentModelPageIndexProperty.get()).get();
return result;
}
protected abstract ModelLanguage getModelLanguage();
protected abstract ModelPageEditor create(String modelPage, List defaultQueries);
protected String getPagedModelInternalContainerRepresentation(List>> pageContents) {
String result = PagedModelContainer.toInternalContainerRepresentation(getModelLanguage(), pageContents);
return result;
}
protected void newModel(Supplier>> initialModelPagesSupplier) {
undoManagersForgetHistory();
callUndoManagers(um -> um.undoAvailableProperty().removeListener(this::checkGlobalUndoState));
modelPageEditors.set(initialModelPagesSupplier.get());
// Unbind and create a new page change undo manager for the new model
canUndoPageChange.unbind();
canRedoPageChange.unbind();
pageChangeUndoManager.close();
pageChangeUndoManager = newPageChangeUndoManager();
canUndoPageChange.bind(pageChangeUndoManager.undoAvailableProperty());
canRedoPageChange.bind(pageChangeUndoManager.redoAvailableProperty());
bindModelPageEditorUndoManager(0);
currentModelPageIndexProperty.set(0);
callUndoManagers(um -> um.undoAvailableProperty().addListener(this::checkGlobalUndoState));
}
protected void undoManagersForgetHistory() {
callUndoManagers(um -> um.forgetHistory());
}
protected void checkGlobalUndoState(ObservableValue extends Boolean> observable, Boolean oldValue, Boolean newValue) {
AtomicBoolean isASaveRequired = new AtomicBoolean(false);
callUndoManagers(um -> {
if (um.isUndoAvailable()) {
isASaveRequired.set(true);
}
});
saveRequired.set(isASaveRequired.get());
}
protected void bindModelPageEditorUndoManager(Integer currentPageIdx) {
if (modelPageEditors.containsKey(currentPageIdx)) {
ModelPageEditor mpe = modelPageEditors.get().get(currentPageIdx).get();
canUndoModelPageEdit.unbind();
canRedoModelPageEdit.unbind();
canUndoModelPageEdit.bind(mpe.getUndoManager().undoAvailableProperty());
canRedoModelPageEdit.bind(mpe.getUndoManager().redoAvailableProperty());
}
}
protected void callUndoManagers(Consumer umConsumer) {
umConsumer.accept(pageChangeUndoManager);
modelPageEditors.values().forEach(meps -> {
ModelPageEditorSupplier mepSupplier = (ModelPageEditorSupplier) meps;
if (mepSupplier.modelPageEditor != null) {
umConsumer.accept(mepSupplier.get().getUndoManager());
}
});
}
protected void addPage(Integer atPageIndex, Supplier modelPageEditorSupplier) {
Map> newModelPageIdxs = new HashMap<>();
modelPageEditors.get().entrySet().forEach(e -> {
if (e.getKey() > atPageIndex) {
newModelPageIdxs.put(e.getKey()+1, e.getValue());
}
else {
newModelPageIdxs.put(e.getKey(), e.getValue());
}
});
newModelPageIdxs.put(atPageIndex+1, modelPageEditorSupplier);
modelPageEditorSupplier.get().getUndoManager().undoAvailableProperty().addListener(this::checkGlobalUndoState);
modelPageEditors.set(FXCollections.observableMap(newModelPageIdxs));
currentModelPageIndexProperty.set(atPageIndex+1);
}
protected Supplier removePageAt(Integer pageIndex) {
Supplier result = modelPageEditors.get(pageIndex);
Map> newModelPageIdxs = new HashMap<>();
modelPageEditors.get().entrySet().forEach(e -> {
// Skip the page to be removed
if (!e.getKey().equals(pageIndex)) {
if (e.getKey() > pageIndex) {
newModelPageIdxs.put(e.getKey()-1, e.getValue());
}
else {
newModelPageIdxs.put(e.getKey(), e.getValue());
}
}
});
result.get().getUndoManager().undoAvailableProperty().removeListener(this::checkGlobalUndoState);
modelPageEditors.set(FXCollections.observableMap(newModelPageIdxs));
if (pageIndex >= modelPageEditors.size()) {
currentModelPageIndexProperty.set(modelPageEditors.size()-1);
}
else {
currentModelPageIndexProperty.set(pageIndex);
}
return result;
}
protected class ModelPageEditorSupplier implements Supplier {
private ModelPageEditor modelPageEditor = null;
private String modelPage;
private List defaultQueries = new ArrayList<>();
public ModelPageEditorSupplier(String modelPage, List defaultQueries) {
this.modelPage = modelPage;
this.defaultQueries.addAll(defaultQueries);
}
@Override
public ModelPageEditor get() {
if (modelPageEditor == null) {
modelPageEditor = create(modelPage, defaultQueries);
modelPageEditor.getUndoManager().undoAvailableProperty().addListener(AbstractPerspective.this::checkGlobalUndoState);
}
return modelPageEditor;
}
}
protected abstract class PageChange {
protected int pageIdx;
protected Supplier pageEditorSupplier;
protected PageChange(int pageIdx, Supplier pageEditorSupplier) {
this.pageIdx = pageIdx;
this.pageEditorSupplier = pageEditorSupplier;
}
abstract void apply();
abstract PageChange invert();
Optional mergeWith(PageChange other) {
// don't merge changes by default
return Optional.empty();
}
}
protected class AddPageChange extends PageChange {
public AddPageChange(int pageIdx, Supplier pageEditorSupplier) {
super(pageIdx, pageEditorSupplier);
}
@Override
void apply() {
addPage(pageIdx, pageEditorSupplier);
pageChanges.push(this);
}
@Override
PageChange invert() {
return new RemovePageChange(pageIdx+1, pageEditorSupplier);
}
@Override
public boolean equals(Object other) {
if (other instanceof AddPageChange) {
AddPageChange that = (AddPageChange) other;
return Objects.equal(this.pageIdx, that.pageIdx) && Objects.equal(this.pageEditorSupplier, that.pageEditorSupplier);
}
else {
return false;
}
}
}
protected class RemovePageChange extends PageChange {
public RemovePageChange(int pageIdx, Supplier pageEditorSupplier) {
super(pageIdx, pageEditorSupplier);
}
@Override
void apply() {
if (pageEditorSupplier != removePageAt(pageIdx)) {
throw new IllegalStateException("Page change remove redo history appears corrupted.");
}
pageChanges.push(this);
}
@Override
PageChange invert() {
return new AddPageChange(pageIdx-1, pageEditorSupplier);
}
@Override
public boolean equals(Object other) {
if (other instanceof RemovePageChange) {
RemovePageChange that = (RemovePageChange) other;
return Objects.equal(this.pageIdx, that.pageIdx) && Objects.equal(this.pageEditorSupplier, that.pageEditorSupplier);
}
else {
return false;
}
}
}
private UndoManager newPageChangeUndoManager() {
UndoManager result = UndoManagerFactory.unlimitedHistoryUndoManager(
pageChanges, // stream of changes to observe
c -> c.invert(), // function to invert a change
c -> c.apply(), // function to apply a change
(c1, c2) -> c1.mergeWith(c2));
return result;
}
}