001package conexp.fx.gui.exploration; 002 003/* 004 * #%L 005 * Concept Explorer FX 006 * %% 007 * Copyright (C) 2010 - 2023 Francesco Kriegel 008 * %% 009 * This program is free software: you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as 011 * published by the Free Software Foundation, either version 3 of the 012 * License, or (at your option) any later version. 013 * 014 * This program is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public 020 * License along with this program. If not, see 021 * <http://www.gnu.org/licenses/gpl-3.0.html>. 022 * #L% 023 */ 024 025import java.util.Collections; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.ConcurrentHashMap; 029import java.util.concurrent.CountDownLatch; 030import java.util.concurrent.atomic.AtomicBoolean; 031import java.util.concurrent.atomic.AtomicReference; 032 033import conexp.fx.core.algorithm.exploration.CounterExample; 034import conexp.fx.core.algorithm.exploration.Expert; 035import conexp.fx.core.collections.relation.RelationEvent; 036import conexp.fx.core.collections.relation.RelationEventHandler; 037import conexp.fx.core.context.Context; 038import conexp.fx.core.context.Implication; 039import conexp.fx.core.context.MatrixContext; 040import conexp.fx.core.context.MatrixContext.AutomaticMode; 041import conexp.fx.core.util.IdGenerator; 042import conexp.fx.gui.context.MatrixContextWidget; 043import conexp.fx.gui.dialog.ErrorDialog; 044import conexp.fx.gui.util.Platform2; 045import javafx.scene.Scene; 046import javafx.scene.control.Button; 047import javafx.scene.control.Label; 048import javafx.scene.layout.BorderPane; 049import javafx.scene.layout.HBox; 050import javafx.stage.Stage; 051import javafx.stage.StageStyle; 052 053public final class HumanExpertP<M> implements Expert<String, M> { 054 055 private final class Question { 056 057 private final String object; 058 private final Implication<String, M> implication; 059 060 private final CountDownLatch cdl = new CountDownLatch(1); 061 private final AtomicReference<Set<CounterExample<String, M>>> ref = new AtomicReference<>(); 062 063 private final HBox buttonsBox = new HBox(); 064 private final RelationEventHandler<Implication<String, M>, M> eventHandler; 065 066 private Question(final Implication<String, M> implication) { 067 super(); 068 this.object = "Counter-Example " + IdGenerator.getNextId(cxt); 069 this.implication = implication; 070 final Button acceptButton = new Button("accept"); 071 final Button declineButton = new Button("decline"); 072 acceptButton.maxHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight); 073 acceptButton.minHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight); 074 acceptButton.prefHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight); 075 declineButton.maxHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight); 076 declineButton.minHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight); 077 declineButton.prefHeightProperty().bind(counterExamplesWidget.rowHeaderPane.rowHeight); 078 acceptButton.setMaxWidth(60); 079 acceptButton.setMinWidth(60); 080 acceptButton.setPrefWidth(60); 081 declineButton.setMaxWidth(60); 082 declineButton.setMinWidth(60); 083 declineButton.setPrefWidth(60); 084 acceptButton.setStyle("-fx-padding:0; -fx-background-radius: 5 0 0 5, 5 0 0 5, 4 0 0 4, 3 0 0 3;"); 085 declineButton.setStyle("-fx-padding:0; -fx-background-radius: 0 5 5 0, 0 5 5 0, 0 4 4 0, 0 3 3 0;"); 086 acceptButton.setOnAction(__ -> { 087 ref.set(Collections.emptySet()); 088 cancel(); 089 }); 090 declineButton.setOnAction(__ -> { 091 cancel(); 092 }); 093 buttonsBox.getChildren().addAll(acceptButton, declineButton); 094 synchronized (counterExamples.rowHeads()) { 095 counterExamples.rowHeads().add(implication); 096 counterExamples.row(implication).addAll(implication.getPremise()); 097 } 098 ref.set(Collections.singleton(new CounterExample<String, M>(object, implication.getPremise()))); 099 final AtomicBoolean blocked = new AtomicBoolean(false); 100 eventHandler = __ -> { 101 if (blocked.get() || !counterExamples.rowHeads().contains(implication)) 102 return; 103 if (!counterExamples.row(implication).containsAll(implication.getPremise()) 104 || counterExamples.row(implication).containsAll(implication.getConclusion())) { 105 blocked.set(true); 106 counterExamples.row(implication).clear(); 107 counterExamples.row(implication).addAll(implication.getPremise()); 108 new ErrorDialog( 109 stage, 110 "Invalid Counterexample", 111 "A counterexample must contain all premise attributes, but must not contain all conclusion attributes.") 112 .showAndWait(); 113 blocked.set(false); 114 } 115 ref.set(Collections.singleton(new CounterExample<String, M>(object, counterExamples.row(implication)))); 116 }; 117 counterExamples.addEventHandler(eventHandler, RelationEvent.ENTRIES); 118 counterExamplesWidget.addRowDecoration(implication, buttonsBox); 119 } 120 121 private final void cancel() { 122 synchronized (counterExamples.rowHeads()) { 123 counterExamples.removeEventHandler(RelationEvent.ENTRIES, eventHandler); 124 counterExamples.rowHeads().remove(implication); 125 pendingQuestions.remove(this); 126 } 127 cdl.countDown(); 128 } 129 130 } 131 132 private Stage stage; 133 private final Map<Implication<String, M>, Question> pendingQuestions = new ConcurrentHashMap<>(); 134 private boolean isCancelled = false; 135 136 private final Context<String, M> cxt; 137 private MatrixContext<Implication<String, M>, M> counterExamples; 138 private MatrixContextWidget<Implication<String, M>, M> counterExamplesWidget; 139 140 public HumanExpertP(final Context<String, M> cxt) { 141 super(); 142 this.cxt = cxt; 143 } 144 145 public final void init() { 146 this.stage = new Stage(StageStyle.UTILITY); 147// this.stage.setResizable(false); 148 this.stage.setTitle("Parallel Attribute Exploration"); 149 this.stage.setWidth(800); 150 this.stage.setHeight(500); 151 this.counterExamples = new MatrixContext<>(false, AutomaticMode.NONE); 152 this.counterExamples.colHeads().addAll(cxt.colHeads()); 153 this.counterExamplesWidget = new MatrixContextWidget<>(null, true, false, this.counterExamples); 154// this.counterExamplesWidget.rowHeaderPane.highlight.set(true); 155// this.counterExamplesWidget.colHeaderPane.highlight.set(true); 156// this.counterExamplesWidget.contextPane.highlight.set(true); 157 this.stage.setScene( 158 new Scene( 159 new BorderPane( 160 this.counterExamplesWidget, 161 new Label("Please decide whether the following implications are valid."), 162 null, 163 null, 164 null))); 165 this.stage.setOnHiding(__ -> { 166 this.isCancelled = true; 167 for (Question question : pendingQuestions.values()) { 168 question.ref.set(Collections.emptySet()); 169 question.cancel(); 170 } 171 }); 172 } 173 174 public final void show() { 175 stage.show(); 176 } 177 178 public final void hide() { 179 stage.hide(); 180 } 181 182 @Override 183 public Set<CounterExample<String, M>> getCounterExamples(final Implication<String, M> implication) 184 throws InterruptedException { 185 Platform2.runOnFXThreadAndWaitTryCatch(() -> pendingQuestions.put(implication, new Question(implication))); 186// return new Future<Set<CounterExample<String, M>>>() { 187// 188// private 189 final Question question = pendingQuestions.get(implication); 190// private boolean isCancelled = false; 191// 192// @Override 193// public boolean cancel(boolean mayInterruptIfRunning) { 194// if (question.cdl.getCount() == 0) 195// return false; 196// isCancelled = true; 197// question.cancel(); 198// return true; 199// } 200// 201// @Override 202// public boolean isCancelled() { 203// return isCancelled; 204// } 205// 206// @Override 207// public boolean isDone() { 208// return question.cdl.getCount() == 0; 209// } 210// 211// @Override 212// public Set<CounterExample<String, M>> get() throws InterruptedException, ExecutionException { 213 question.cdl.await(); 214 if (isCancelled) 215 throw new InterruptedException(); 216 return question.ref.get(); 217// } 218// 219// @Override 220// public Set<CounterExample<String, M>> get(long timeout, TimeUnit unit) 221// throws InterruptedException, ExecutionException, TimeoutException { 222// if (question.cdl.await(timeout, unit)) 223// return question.ref.get(); 224// else 225// throw new TimeoutException(); 226// } 227// }; 228 } 229 230}