View Javadoc
1   /*
2    * Copyright 2013-2023 Medical Information Systems Research Group (https://medical.zcu.cz),
3    * Department of Computer Science and Engineering, University of West Bohemia.
4    * Address: Univerzitni 8, 306 14 Plzen, Czech Republic.
5    *
6    * This file is part of Sparkle project.
7    *
8    * Sparkle is free software: you can redistribute it and/or modify
9    * it under the terms of the GNU General Public License as published by
10   * the Free Software Foundation, either version 3 of the License.
11   *
12   * Sparkle is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with Sparkle. If not, see <http://www.gnu.org/licenses/>.
19   */
20  package cz.zcu.mre.sparkle.gui.dialogs.query;
21  
22  import cz.zcu.mre.sparkle.Messages;
23  import cz.zcu.mre.sparkle.data.DataAgent;
24  import cz.zcu.mre.sparkle.data.PrefixesStorage;
25  import cz.zcu.mre.sparkle.gui.tools.AbstractDialogController;
26  import cz.zcu.mre.sparkle.gui.tools.FormControllerFactory;
27  import cz.zcu.mre.sparkle.gui.tools.ReferenceKeeper;
28  import cz.zcu.mre.sparkle.tools.Utils;
29  import javafx.application.Platform;
30  import javafx.beans.Observable;
31  import javafx.beans.binding.BooleanExpression;
32  import javafx.beans.property.*;
33  import javafx.collections.FXCollections;
34  import javafx.collections.ObservableList;
35  import javafx.fxml.FXML;
36  import javafx.scene.control.Button;
37  import javafx.scene.control.ListView;
38  import javafx.scene.control.ProgressIndicator;
39  import javafx.scene.layout.Region;
40  import javafx.stage.Window;
41  import org.apache.jena.rdf.model.Property;
42  import org.apache.jena.rdf.model.Resource;
43  import java.io.IOException;
44  import java.util.LinkedList;
45  import java.util.List;
46  import java.util.Set;
47  import java.util.concurrent.ExecutionException;
48  import java.util.logging.Level;
49  import java.util.logging.Logger;
50  
51  /**
52   * Kontroler dialogu pro vyhledání cest.
53   *
54   * @author Jan Smucr
55   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
56   */
57  public final class PathsDialog
58          extends AbstractDialogController {
59  
60      private static final Logger LOG = Logger.getLogger(PathsDialog.class.getName());
61  
62      /**
63       * Položka v seznamu cest
64       */
65      private static final class PathsListItem {
66  
67          private final ObjectProperty<LinkedList<Resource>> pathProperty = new SimpleObjectProperty<>(null);
68          private final Resource startNode, endNode;
69          private final StringProperty itemTextProperty = new SimpleStringProperty();
70          private final DataAgent dataAgent;
71          private final String startNodeText;
72          private final PrefixesStorage prefixesStorage;
73          private final BooleanProperty searchingProperty = new SimpleBooleanProperty(false);
74          private boolean cancelled = false;
75  
76          public PathsListItem(final DataAgent dataAgent, final PrefixesStorage prefixesStorage, final Resource startNode,
77                  final Resource endNode) {
78              this.startNode = startNode;
79              this.endNode = endNode;
80              this.dataAgent = dataAgent;
81              this.prefixesStorage = prefixesStorage;
82              this.startNodeText = prefixesStorage.shortForm(startNode.toString()) + " ... "; //$NON-NLS-1$
83              itemTextProperty.set(startNodeText + Messages.getString("WAITING")); //$NON-NLS-1$
84  
85          }
86  
87          /**
88           * Spustí hledání v samostatném vlákně. Text položky se během hledání
89           * automaticky upravuje.
90           */
91          public final void startSearch() {
92              cancelled = false;
93  
94              new Thread(()
95                      -> {
96                  if (cancelled) {
97                      return;
98                  }
99  
100                 try {
101                     Utils.runAndWait(()
102                             -> {
103                         searchingProperty.set(true);
104                         itemTextProperty.set(startNodeText + Messages.getString("SEARCHING")); //$NON-NLS-1$
105                     });
106                 } catch (final InterruptedException | ExecutionException e) {
107                     LOG.log(Level.SEVERE, "Exception: ", e);
108                 }
109 
110                 final LinkedList<Resource> result = dataAgent.findForwardOntologyPath(startNode, endNode);
111 
112                 try {
113                     Platform.runLater(()
114                             -> {
115                         synchronized (pathProperty) {
116                             pathProperty.set(result);
117                         }
118                         searchingProperty.set(false);
119                     });
120                 } catch (final Exception e) {
121                     LOG.log(Level.SEVERE, "Exception: ", e);
122                 }
123 
124                 if (cancelled) {
125                     return;
126                 }
127 
128                 if (result == null) {
129                     Platform.runLater(
130                             () -> itemTextProperty.set(startNodeText + Messages.getString("NOT_FOUND"))); //$NON-NLS-1$
131                     return;
132                 }
133 
134                 // Sestavení textu položky z článků nalezené cesty
135                 final StringBuilder sb = new StringBuilder();
136                 result.stream().map((resource) -> {
137                     sb.append(prefixesStorage.shortForm(resource.toString()));
138                     return resource;
139                 }).filter((resource) -> (!resource.equals(endNode))).forEach((_item) -> {
140                     sb.append(" -> "); //$NON-NLS-1$
141                 });
142 
143                 Platform.runLater(() -> itemTextProperty.set(sb.toString()));
144             }).start();
145         }
146 
147         /**
148          * Požádá o zrušení hledání.
149          */
150         public final void cancelSearch() {
151             cancelled = true;
152         }
153 
154         public final boolean isCancelled() {
155             return cancelled;
156         }
157 
158         public final LinkedList<Resource> getPath() {
159             synchronized (pathProperty) {
160                 return pathProperty.get();
161             }
162         }
163 
164         @SuppressWarnings("unused")
165         public final ReadOnlyObjectProperty<LinkedList<Resource>> getPathProperty() {
166             return new ReadOnlyObjectPropertyBase<LinkedList<Resource>>() {
167                 @Override
168                 public LinkedList<Resource> get() {
169                     return pathProperty.get();
170                 }
171 
172                 @Override
173                 public Object getBean() {
174                     return pathProperty.getBean();
175                 }
176 
177                 @Override
178                 public String getName() {
179                     return pathProperty.getName();
180                 }
181             };
182         }
183 
184         @Override
185         public final String toString() {
186             return itemTextProperty.get();
187         }
188     }
189 
190     private Set<Resource> startNodeTypes;
191     private Property endProperty;
192     private PrefixesStorage prefixesStorage;
193     private DataAgent dataAgent;
194     private LinkedList<Resource> result = null;
195     private final ReferenceKeeper keeper = new ReferenceKeeper();
196 
197     @FXML
198     private ListView<PathsListItem> pathsList;
199     @FXML
200     private Button selectButton, cancelButton;
201     @FXML
202     private ProgressIndicator progressIndicator;
203     @FXML
204     private Region dummyRegion;
205 
206     public static final LinkedList<Resource> open(final Window owner, final DataAgent dataAgent,
207             final PrefixesStorage prefixesStorage,
208             final Set<Resource> startNodeTypes, final Property endProperty) {
209         try {
210             final PathsDialog dlg = FormControllerFactory.load(owner, PathsDialog.class);
211             dlg.dataAgent = dataAgent;
212             dlg.prefixesStorage = prefixesStorage;
213             dlg.startNodeTypes = startNodeTypes;
214             dlg.endProperty = endProperty;
215             dlg.showAndWait();
216 
217             return dlg.result;
218         } catch (final IOException e) {
219             LOG.log(Level.SEVERE, "Exception: ", e);
220         }
221         return null;
222     }
223 
224     @Override
225     protected void onDialogInitialized() {
226         Utils.makeInvisibleUnmanaged(progressIndicator, dummyRegion);
227 
228         dummyRegion.visibleProperty().bind(progressIndicator.visibleProperty());
229 
230         getStage().setResizable(true);
231 
232         pathsList.getSelectionModel().selectedItemProperty().addListener(keeper.toWeak((observable, oldValue, newValue)
233                 -> {
234             updateSelectButton();
235         }));
236 
237         // Nastavení ukazatele průběhu
238         pathsList.itemsProperty().addListener(keeper.toWeak((observable, oldValue, newValue)
239                 -> {
240             BooleanExpression ex = BooleanProperty.booleanExpression(new SimpleBooleanProperty(false));
241 
242             if (newValue != null) {
243                 for (final PathsListItem pathsListItem : newValue) {
244                     ex = ex.or(pathsListItem.searchingProperty);
245                 }
246             }
247 
248             // prop.bind(pathsList.itemsProperty().isNotNull().and(ex));
249             progressIndicator.visibleProperty().bind(ex);
250         }));
251     }
252 
253     /**
254      * Blokuje tlačítko Select, pokud je v seznamu vybrána položka bez nalezené
255      * cesty.
256      */
257     private void updateSelectButton() {
258         final PathsListItem item = pathsList.getSelectionModel().getSelectedItem();
259         selectButton.setDisable((item == null) || (item.getPath() == null));
260     }
261 
262     @Override
263     protected void onShowing() {
264         // Tvorba položky pro každý počáteční bod cesty
265         final ObservableList<PathsListItem> listItems = FXCollections.observableArrayList();
266         for (final Resource startNodeType : startNodeTypes) {
267             final PathsListItem listItem = new PathsListItem(dataAgent, prefixesStorage, startNodeType, endProperty);
268 
269             // "Předávání žezla". Hledání se aktivuje sekvenčně.
270             listItem.searchingProperty.addListener((observable, oldValue, newValue)
271                     -> {
272                 if (!(oldValue && !newValue && !listItem.isCancelled())) {
273                     return;
274                 }
275                 while (true) {
276                     final int myIndex = listItems.indexOf(listItem);
277                     if (pathsList.getSelectionModel().isSelected(myIndex)) {
278                         updateSelectButton();
279                     }
280 
281                     final int nextIndex = myIndex + 1;
282                     if ((nextIndex < listItems.size()) && (nextIndex > 0)) {
283                         final PathsListItem nextItem = listItems.get(nextIndex);
284                         if (nextItem.isCancelled()) {
285                             continue;
286                         }
287 
288                         nextItem.startSearch();
289                     }
290                     break;
291                 }
292             });
293 
294             // Aktualizace textu položky v seznamu při změně stavu (je nutné ručně)
295             listItem.itemTextProperty.addListener((final Observable observable)
296                     -> {
297                 final int index = listItems.indexOf(listItem);
298                 if (index != -1) {
299                     listItems.set(index, null);
300                     listItems.set(index, listItem);
301                 }
302             });
303             listItems.add(listItem);
304         }
305 
306         // Naplnění seznamu
307         pathsList.setItems(listItems);
308     }
309 
310     @Override
311     protected void onShown() {
312         final List<PathsListItem> items = pathsList.getItems();
313         if ((items != null) && !items.isEmpty()) {
314             items.get(0).startSearch();
315         }
316     }
317 
318     /**
319      * Zrušení hledání všech cest.
320      */
321     private void cancelSearch() {
322         final List<PathsListItem> items = pathsList.getItems();
323         if (items != null) {
324             items.stream().forEach((item) -> {
325                 item.cancelSearch();
326             });
327         }
328     }
329 
330     @FXML
331     private void selectButtonOnAction() {
332         cancelSearch();
333 
334         final PathsListItem item = pathsList.getSelectionModel().getSelectedItem();
335         result = item == null ? null : item.getPath();
336 
337         close();
338     }
339 
340     @FXML
341     private void cancelButtonOnAction() {
342         cancelSearch();
343         close();
344     }
345 
346     @Override
347     protected boolean onCloseRequest() {
348         cancelSearch();
349         return true;
350     }
351 
352 }