All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sri.ai.praise.sgsolver.demo.perspective.AbstractPerspective Maven / Gradle / Ivy

Go to download

SRI International's AIC PRAiSE (Probabilistic Reasoning As Symbolic Evaluation) Library (for Java 1.8+)

There is a newer version: 1.3.2
Show newest version
/*
 * 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 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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy