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.query.modifiers;
21  
22  import cz.zcu.mre.sparkle.Messages;
23  import cz.zcu.mre.sparkle.gui.query.QueryFormPane;
24  import cz.zcu.mre.sparkle.gui.query.autoComplete.AutoCompleteListHandler;
25  import cz.zcu.mre.sparkle.gui.query.triplePane.TriplePane;
26  import cz.zcu.mre.sparkle.gui.query.autoComplete.PrefixedNamesAutoCompleteListHandler;
27  import cz.zcu.mre.sparkle.gui.query.clause.SingleClausePane;
28  import cz.zcu.mre.sparkle.gui.query.helpers.*;
29  import cz.zcu.mre.sparkle.gui.query.other.TypedTextField;
30  import cz.zcu.mre.sparkle.gui.query.queryMainParts.WhereClausePane;
31  import cz.zcu.mre.sparkle.gui.query.queryTypes.construct.ConstructClausePane;
32  import cz.zcu.mre.sparkle.gui.query.queryTypes.subselect.SubSelectPane;
33  import cz.zcu.mre.sparkle.gui.query.queryTypes.subselect.SubSelectQueryFormPane;
34  import cz.zcu.mre.sparkle.gui.tools.Components;
35  import cz.zcu.mre.sparkle.gui.tools.ReferenceKeeper;
36  import cz.zcu.mre.sparkle.gui.tools.ShortCutHandler;
37  import cz.zcu.mre.sparkle.gui.tools.ShortCutHandler.Modifier;
38  import cz.zcu.mre.sparkle.tools.*;
39  import cz.zcu.mre.sparkle.tools.SparqlParser;
40  import cz.zcu.mre.sparkle.tools.sparqlParser.SparqlParserUtils;
41  import javafx.beans.InvalidationListener;
42  import javafx.beans.binding.BooleanExpression;
43  import javafx.beans.property.BooleanProperty;
44  import javafx.beans.property.ObjectProperty;
45  import javafx.beans.property.SimpleBooleanProperty;
46  import javafx.beans.property.SimpleObjectProperty;
47  import javafx.beans.value.ChangeListener;
48  import javafx.collections.FXCollections;
49  import javafx.collections.ListChangeListener.Change;
50  import javafx.collections.ObservableList;
51  import javafx.event.ActionEvent;
52  import javafx.event.EventHandler;
53  import javafx.fxml.FXML;
54  import javafx.scene.Node;
55  import javafx.scene.control.Button;
56  import javafx.scene.control.CheckBox;
57  import javafx.scene.input.Dragboard;
58  import javafx.scene.input.KeyCode;
59  import javafx.scene.input.TransferMode;
60  import javafx.scene.layout.HBox;
61  import javafx.scene.layout.Region;
62  import javafx.scene.layout.VBox;
63  import javafx.scene.shape.Path;
64  import org.antlr.v4.runtime.misc.ParseCancellationException;
65  import org.antlr.v4.runtime.tree.ParseTree;
66  import org.w3c.dom.Element;
67  import org.w3c.dom.NodeList;
68  import javax.xml.parsers.ParserConfigurationException;
69  import javax.xml.transform.TransformerException;
70  import java.util.*;
71  import org.slf4j.Logger;
72  import org.slf4j.LoggerFactory;
73  import static cz.zcu.mre.sparkle.tools.sparqlParser.SparqlParserUtils.getFieldType;
74  import static cz.zcu.mre.sparkle.tools.sparqlParser.SparqlParserUtils.searchNode;
75  
76  /**
77   * Kontroler kontejnerové komponenty představující v dotazu podgraf nebo
78   * nepřesně skupinu trojic. Může obsahovat komponenty
79   * {@link TriplePane}, {@link FilterPane} a další {@link GroupGraphPatternPane}.
80   * Lze nastavit tak, aby šel použít jako {@link WhereClausePane} nebo
81   * {@link ConstructClausePane}.
82   *
83   * @author Jan Smucr
84   * @author Klara Hlavacova
85   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
86   */
87  public class GroupGraphPatternPane
88          extends VBox
89          implements VariableConstraintsGenerator, PrefixesUser, VariablesGenerator, VariablesCollector, Saveable,
90          Changeable {
91  
92      private static final Logger LOG = LoggerFactory.getLogger(GroupGraphPatternPane.class);
93  
94      private static final String XML_GROUP_TYPE_FILTER_NOT_EXISTS = "filterNotExists"; //$NON-NLS-1$
95      private static final String XML_GROUP_TYPE_FILTER_EXISTS = "filterExists"; //$NON-NLS-1$
96      private static final String XML_GROUP_TYPE_MINUS = "minus"; //$NON-NLS-1$
97      private static final String XML_GRAPH_TEXT = "graph.text"; //$NON-NLS-1$
98      private static final String XML_GRAPH_TYPE = "graph.type"; //$NON-NLS-1$
99      private static final String XML_GROUP_TYPE_GRAPH = "graph"; //$NON-NLS-1$
100     private static final String XML_GROUP_TYPE_OPTIONAL = "optional"; //$NON-NLS-1$
101     private static final String XML_GROUP_TYPE_UNION = "union"; //$NON-NLS-1$
102     private static final String XML_GROUP_TYPE = "type"; //$NON-NLS-1$
103     private static final String XML_ELEMENT_NAME = "GroupGraph"; //$NON-NLS-1$
104     private static final boolean INVISIBLE_GROUP = false;
105     private static final boolean VISIBLE_GROUP = true;
106 
107     @FXML
108     private VBox container;
109     @FXML
110     private CheckBox optionalCheckBox, unionCheckBox, graphCheckBox, minusCheckBox, filterExistsCheckBox,
111             filterNotExistsCheckBox;
112     @FXML
113     private Button removeButton, moveUpButton, moveDownButton, addFilterButton, addBindButton, addGroupButton,
114             addSubSelectButton, addValuesButton;
115     @FXML
116     private Path arrow;
117     @FXML
118     private Region springRegion;
119     @FXML
120     private HBox graphFieldContainer;
121 
122     private BooleanExpression arrowVisibilityCondition;
123 
124     private final TypedTextField graphField;
125     private final AutoCompleteListHandler nodesAutoCompleteListHandler;
126     private final ObservableList<Node> siblings;
127     private final QueryFormPane<?> parentQueryFormPane;
128     private final BooleanProperty enableFilters = new SimpleBooleanProperty();
129     private final BooleanProperty enableSubSelect = new SimpleBooleanProperty();
130     private final BooleanProperty enableGroups = new SimpleBooleanProperty();
131     private final BooleanProperty enableOnlyGraphGroup = new SimpleBooleanProperty();
132     private final BooleanProperty isNotRootGroup = new SimpleBooleanProperty();
133     private final BooleanProperty enableVariables = new SimpleBooleanProperty();
134     private final BooleanProperty enableBlankNodes = new SimpleBooleanProperty();
135     private final BooleanProperty enableValues = new SimpleBooleanProperty();
136     private final Set<VariablesCollector> variablesCollectors = new HashSet<>();
137     private final ObjectProperty<EventHandler<ObjectRelatedActionEvent<GroupGraphPatternPane>>> onRemovalRequested
138             = new SimpleObjectProperty<>();
139     private final GroupGraphPatternPane parentGroupGraphPatternPane;
140     private final ReferenceKeeper keeper = new ReferenceKeeper();
141     private final Set<InvalidationListener> observers = new HashSet<>();
142 
143     /**
144      * @param parentQueryFormPane Query core.
145      * @param parentGroupGraphPatternPane Parent group.
146      * @param enableGroups If it is <code>false</code>: GROUPs are not allowed.
147      * @param enableFilters If it is <code>false</code>: FILTERs are not
148      * allowed.
149      * @param enableOnlyGraphGroup If it is <code>false</code> then it will
150      * allow to insert only GRAPH group.
151      * @param enableVariables If it is <code>false</code>: variables are not
152      * allowed.
153      * @param enableBlankNodes If it is <code>false</code>: blank nodes are not
154      * allowed.
155      * @param enableSubSelect If it is <code>false</code>: SUBSELECTs are not
156      * allowed.
157      * @param enableValues If it is <code>false</code>: VALUES clause is not
158      * allowed.
159      */
160     @SuppressWarnings("unchecked")
161     protected GroupGraphPatternPane(final QueryFormPane<?> parentQueryFormPane,
162             final GroupGraphPatternPane parentGroupGraphPatternPane, final boolean enableGroups,
163             final boolean enableFilters, final boolean enableOnlyGraphGroup, final boolean enableVariables,
164             final boolean enableBlankNodes, final boolean enableSubSelect, final boolean enableValues) {
165         Components.load(this);
166 
167         this.enableGroups.set(enableGroups);
168         this.enableOnlyGraphGroup.set(enableOnlyGraphGroup);
169         this.enableFilters.set(enableFilters);
170         this.enableSubSelect.set(enableSubSelect);
171         this.parentQueryFormPane = parentQueryFormPane;
172         this.enableVariables.set(enableVariables);
173         this.enableBlankNodes.set(enableBlankNodes);
174         this.enableValues.set(enableValues);
175 
176         // Automatické sledování změn vnořených komponent
177         autoWatch(container.getChildren(),
178                 FXCollections.observableArrayList(TriplePane.class,
179                         GroupGraphPatternPane.class,
180                         FilterPane.class));
181 
182         // Skupina na nejnižší úrovni?
183         isNotRootGroup.set(parentGroupGraphPatternPane != null);
184         Utils.makeInvisibleUnmanaged(optionalCheckBox, unionCheckBox, unionCheckBox, removeButton, moveUpButton,
185                 moveDownButton, addGroupButton,
186                 addFilterButton, addBindButton, graphFieldContainer, graphCheckBox, springRegion,
187                 minusCheckBox, filterExistsCheckBox, filterNotExistsCheckBox);
188 
189         final BooleanProperty groupTypeNotSelected = new SimpleBooleanProperty();
190         groupTypeNotSelected.bind(graphCheckBox.selectedProperty().not().and(optionalCheckBox.selectedProperty().not())
191                 .and(unionCheckBox.selectedProperty().not())
192                 .and(graphCheckBox.selectedProperty().not())
193                 .and(minusCheckBox.selectedProperty().not())
194                 .and(filterExistsCheckBox.selectedProperty().not())
195                 .and(filterNotExistsCheckBox.selectedProperty().not()));
196 
197         graphFieldContainer.visibleProperty().bind(graphCheckBox.selectedProperty());
198         springRegion.visibleProperty().bind(graphCheckBox.selectedProperty().not());
199 
200         removeButton.visibleProperty().bind(isNotRootGroup);
201         moveUpButton.visibleProperty().bind(isNotRootGroup);
202         moveDownButton.visibleProperty().bind(isNotRootGroup);
203 
204         addFilterButton.visibleProperty().bind(this.enableFilters);
205         addBindButton.visibleProperty().bind(this.enableFilters);
206         addSubSelectButton.visibleProperty().bind(this.enableSubSelect);
207         addValuesButton.visibleProperty().bind(this.enableValues);
208 
209         nodesAutoCompleteListHandler
210                 = new PrefixedNamesAutoCompleteListHandler(parentQueryFormPane.getQueryResourcesStorage(), false, true);
211         AutoCompleteListHandler variablesAutoCompleteListHandler
212                 = parentQueryFormPane.DEFAULT_VARIABLES_AUTOCOMPLETE_LIST_HANDLER;
213 
214         // INSERT and DELETE may have only GRAPH
215         if (enableOnlyGraphGroup) {
216             optionalCheckBox.visibleProperty().set(INVISIBLE_GROUP);
217             unionCheckBox.visibleProperty().set(INVISIBLE_GROUP);
218             graphCheckBox.visibleProperty().bind(isNotRootGroup);
219             minusCheckBox.visibleProperty().set(INVISIBLE_GROUP);
220             filterExistsCheckBox.visibleProperty().set(INVISIBLE_GROUP);
221             filterNotExistsCheckBox.visibleProperty().set(INVISIBLE_GROUP);
222             if (isNotRootGroup.get()) {
223                 addGroupButton.visibleProperty().set(INVISIBLE_GROUP);
224             } else {
225                 addGroupButton.visibleProperty().set(VISIBLE_GROUP);
226             }
227             graphField = new TypedTextField(parentQueryFormPane.getQueryPrefixesStorage(), true, FieldType.IRI,
228                     FieldType.PrefixedName);
229         } else {
230             optionalCheckBox.visibleProperty()
231                     .bind(isNotRootGroup.and(optionalCheckBox.selectedProperty().or(groupTypeNotSelected)));
232             unionCheckBox.visibleProperty()
233                     .bind(isNotRootGroup.and(unionCheckBox.selectedProperty().or(groupTypeNotSelected)));
234             graphCheckBox.visibleProperty()
235                     .bind(isNotRootGroup.and(graphCheckBox.selectedProperty().or(groupTypeNotSelected)));
236             minusCheckBox.visibleProperty()
237                     .bind(isNotRootGroup.and(minusCheckBox.selectedProperty().or(groupTypeNotSelected)));
238             filterExistsCheckBox.visibleProperty().bind(isNotRootGroup.and(filterExistsCheckBox.selectedProperty()
239                     .or(groupTypeNotSelected)));
240             filterNotExistsCheckBox.visibleProperty().bind(isNotRootGroup.and(filterNotExistsCheckBox.selectedProperty()
241                     .or(groupTypeNotSelected)));
242 
243             addGroupButton.visibleProperty().bind(this.enableGroups);
244             graphField = new TypedTextField(parentQueryFormPane.getQueryPrefixesStorage(), true, FieldType.Variable,
245                     FieldType.PrefixedName, FieldType.IRI);
246             graphField.setAutoCompleteListHandler(FieldType.Variable, variablesAutoCompleteListHandler);
247         }
248 
249         graphField.setAutoCompleteListHandler(FieldType.PrefixedName, nodesAutoCompleteListHandler);
250         graphField.setPlaceholder(Messages.getString("GRAPH")); //$NON-NLS-1$
251         graphField.addVariablesCollector(this);
252         graphFieldContainer.getChildren().add(graphField);
253         graphCheckBox.selectedProperty().addListener((observable, oldValue, newValue)
254                 -> {
255             if (newValue) {
256                 graphField.getVariables().forEach(this::notifyVariableAdded);
257             } else {
258                 graphField.getVariables().forEach(this::notifyVariableRemoved);
259             }
260         });
261 
262         this.parentGroupGraphPatternPane = parentGroupGraphPatternPane;
263         if (parentGroupGraphPatternPane == null) {
264             siblings = null;
265         } else {
266             siblings = parentGroupGraphPatternPane.container.getChildren();
267             siblings.addListener(this::onPanePositionChanged);
268         }
269 
270         // Zabránění v použití některých kombinací modifikátorů skupin
271         createProtectingListeners();
272 
273         // Sledování změn jednotlivých komponent
274         watch(optionalCheckBox.selectedProperty());
275         watch(unionCheckBox.selectedProperty());
276         watch(graphCheckBox.selectedProperty());
277         watch(minusCheckBox.selectedProperty());
278         watch(filterExistsCheckBox.selectedProperty());
279         watch(filterNotExistsCheckBox.selectedProperty());
280         watch(graphField);
281 
282         addArrowVisiblePropertyBinding(hoverProperty());
283         addArrowVisiblePropertyBinding(graphField.textFieldFocusedProperty());
284 
285         createShortCuts(enableGroups, enableFilters);
286 
287         setOnDragOver((event)
288                 -> {
289             if ((event.getGestureSource() != this) && event.getDragboard().hasContent(TriplePane.DRAG_FORMAT)) {
290                 event.acceptTransferModes(TransferMode.MOVE);
291             }
292             event.consume();
293         });
294 
295         setOnDragDropped((event)
296                 -> {
297             final Dragboard db = event.getDragboard();
298             boolean success = false;
299             if (db.hasContent(TriplePane.DRAG_FORMAT)) {
300                 try {
301                     addTriple(null, false).fromXML((String) db.getContent(TriplePane.DRAG_FORMAT));
302                     success = true;
303                 } catch (final TransformerException | ParserConfigurationException | LoadException e) {
304                     LOG.error("Exception: ", e);
305                 }
306             }
307             event.setDropCompleted(success);
308             event.consume();
309         });
310     }
311 
312     /**
313      * Vytvoří posluchače, kteří brání tomu, aby měla tato skupina nastavený
314      * modifikátor UNION zatímco předchozí má nastaveno GRAPH nebo OPTIONAL.
315      */
316     private void createProtectingListeners() {
317         unionCheckBox.selectedProperty().addListener(keeper.toWeak((observable, oldValue, newValue)
318                 -> {
319             if (newValue) {
320                 final int myIndex = siblings.indexOf(GroupGraphPatternPane.this);
321                 if (myIndex < 1) {
322                     return;
323                 }
324 
325                 final Node node = siblings.get(myIndex - 1);
326                 if (!(node instanceof GroupGraphPatternPane)) {
327                     return;
328                 }
329 
330                 final GroupGraphPatternPane pane = (GroupGraphPatternPane) node;
331                 pane.optionalCheckBox.setSelected(false);
332                 pane.graphCheckBox.setSelected(false);
333             }
334         }));
335 
336         final ChangeListener<Boolean> protectingListener = (observable, oldValue, newValue)
337                 -> {
338             if (newValue) {
339                 final int myIndex = siblings.indexOf(GroupGraphPatternPane.this);
340                 if (myIndex == -1) {
341                     return;
342                 }
343 
344                 final int othersIndex = myIndex + 1;
345                 if (othersIndex == siblings.size()) {
346                     return;
347                 }
348 
349                 final Node node = siblings.get(othersIndex);
350                 if (!(node instanceof GroupGraphPatternPane)) {
351                     return;
352                 }
353 
354                 final GroupGraphPatternPane pane = (GroupGraphPatternPane) node;
355                 pane.unionCheckBox.setSelected(false);
356             }
357         };
358 
359         graphCheckBox.selectedProperty().addListener(keeper.toWeak(protectingListener));
360         optionalCheckBox.selectedProperty().addListener(keeper.toWeak(protectingListener));
361     }
362 
363     private void createShortCuts(final boolean enableGroups, final boolean enableFilters) {
364         // Ctrl+G pro vložení skupiny
365         if (enableGroups) {
366             new ShortCutHandler(KeyCode.G, Modifier.CONTROL) {
367                 @Override
368                 public final boolean handle(final Object source) {
369                     return addGroupGraphPatternPane() != null;
370                 }
371             }.attach(this);
372         }
373 
374         // Ctrl+F pro vložení filtru
375         if (enableFilters) {
376             new ShortCutHandler(KeyCode.F, Modifier.CONTROL) {
377                 @Override
378                 public final boolean handle(final Object source) {
379                     final FilterPane fp = addFilter();
380                     if (fp != null) {
381                         fp.requestFocus();
382                         return true;
383                     }
384                     return false;
385                 }
386             }.attach(this);
387         }
388 
389         // Ctrl+T pro vložení trojice
390         new ShortCutHandler(KeyCode.T, Modifier.CONTROL) {
391             @Override
392             public final boolean handle(final Object source) {
393                 final TriplePane tp = addTriple(null, true);
394                 if (tp != null) {
395                     tp.requestFocus();
396                     return true;
397                 }
398                 return false;
399             }
400         }.attach(this);
401     }
402 
403     public final GroupGraphPatternPane getParentGroupGraphPatternPane() {
404         return parentGroupGraphPatternPane;
405     }
406 
407     /**
408      * Vloží do kontejneru novou trojici.
409      *
410      * @param addAfter Trojice, za kterou by se měla nová trojice objevit nebo
411      * <code>null</code>.
412      * @param checkValues Check values
413      *
414      * @return Nová trojice.
415      */
416     public final TriplePane addTriple(final TriplePane addAfter, final boolean checkValues) {
417         final ObservableList<Node> children = container.getChildren();
418 
419         final TriplePane triplePane
420                 = new TriplePane(this, children, checkValues, enableVariables.get(), enableBlankNodes.get());
421         triplePane.addVariablesCollector(this);
422 
423         final int addIndex;
424         if (addAfter == null) {
425             addIndex = children.size();
426         } else {
427             final int addAfterIndex = children.indexOf(addAfter);
428             if (addAfterIndex == -1) {
429                 addIndex = children.size();
430             } else {
431                 addIndex = addAfterIndex + 1;
432             }
433         }
434 
435         children.add(addIndex, triplePane);
436 
437         addArrowVisiblePropertyBinding(triplePane.getArrow().visibleProperty());
438 
439         triplePane.setOnRemovalRequested(keeper.toWeak((final ObjectRelatedActionEvent<TriplePane> event)
440                 -> {
441             children.remove(event.relatedObject);
442 
443             final Set<String> variablesRemoved = event.relatedObject.getVariables();
444             variablesRemoved.removeAll(getVariables());
445 
446             variablesRemoved.stream().forEach(this::notifyVariableRemoved);
447         }));
448 
449         return triplePane;
450     }
451 
452     private VBox getContainer() {
453         return this.container;
454     }
455 
456     /**
457      * Vloží do kontejneru novou skupinu.
458      *
459      * @return Nově vytvořená skupina.
460      */
461     public final GroupGraphPatternPane addGroupGraphPatternPane() {
462         final GroupGraphPatternPane groupPane
463                 = new GroupGraphPatternPane(parentQueryFormPane, this, enableGroups.get(), enableFilters.get(),
464                         enableOnlyGraphGroup.get(), enableVariables.get(), enableBlankNodes.get(),
465                         enableSubSelect.get(), enableValues.get());
466         groupPane.addVariablesCollector(this);
467         container.getChildren().add(groupPane);
468         addArrowVisiblePropertyBinding(groupPane.getArrow().visibleProperty());
469 
470         groupPane.setOnRemovalRequested(keeper.toWeak((final ObjectRelatedActionEvent<GroupGraphPatternPane> event)
471                 -> {
472             event.relatedObject.getSubSelectPanes().stream().forEach(SubSelectPane::removeButtonOnAction);
473             container.getChildren().remove(event.relatedObject);
474 
475             final Set<String> variablesRemoved = event.relatedObject.getVariables();
476             variablesRemoved.removeAll(getVariables());
477 
478             variablesRemoved.stream().forEach(this::notifyVariableRemoved);
479         }));
480 
481         return groupPane;
482     }
483 
484     /**
485      * It adds new VALUES pane.
486      *
487      * @return Pane of new VALUES query part.
488      */
489     public ValuesPane addValuesPane() {
490         ValuesPane valuesPane = new ValuesPane(parentQueryFormPane, container.getChildren());
491         addArrowVisiblePropertyBinding(valuesPane.getArrow().visibleProperty());
492         valuesPane.setOnRemovalRequested(keeper.toWeak(
493                 (final ObjectRelatedActionEvent<ValuesPane> event) -> container.getChildren()
494                         .remove(event.relatedObject)));
495         valuesPane.requestFocus();
496         valuesPane.addVariablesCollector(this);
497 
498         container.getChildren().add(valuesPane);
499 
500         return valuesPane;
501     }
502 
503     /**
504      * It adds new SUBSELECT query.
505      *
506      * @return Pane of new SUBSELECT.
507      */
508     private SubSelectPane addSubSelectPane() {
509         final SubSelectPane subSelectPane
510                 = new SubSelectPane(parentQueryFormPane, container.getChildren(), this);
511 
512         addArrowVisiblePropertyBinding(subSelectPane.getArrow().visibleProperty());
513         subSelectPane.setOnRemovalRequested(keeper.toWeak(
514                 (final ObjectRelatedActionEvent<SubSelectPane> event)
515                 -> container.getChildren().remove(event.relatedObject)));
516         subSelectPane.requestFocus();
517 
518         container.getChildren().add(subSelectPane);
519 
520         return subSelectPane;
521     }
522 
523     /**
524      * Vloží do kontejneru nový filtr.
525      *
526      * @return Nově vytvořený filtr.
527      */
528     private FilterPane addFilter() {
529         final ObservableList<Node> children = container.getChildren();
530         final FilterPane filterPane = new FilterPane(parentQueryFormPane, children);
531         // filterPane.addVariablesCollector(this);
532         container.getChildren().add(filterPane);
533         addArrowVisiblePropertyBinding(filterPane.getArrow().visibleProperty());
534 
535         filterPane.setOnRemovalRequested(
536                 keeper.toWeak((final ObjectRelatedActionEvent<SingleClausePane> event) -> container.getChildren()
537                 .remove(event.relatedObject)));
538 
539         filterPane.requestFocus();
540 
541         return filterPane;
542     }
543 
544     public final BindPane addBind() {
545         final ObservableList<Node> children = container.getChildren();
546         final BindPane bindPane = new BindPane(parentQueryFormPane, children);
547         bindPane.addVariablesCollector(this);
548 
549         container.getChildren().add(bindPane);
550         addArrowVisiblePropertyBinding(bindPane.getArrow().visibleProperty());
551 
552         bindPane.setOnRemovalRequested(
553                 keeper.toWeak((final ObjectRelatedActionEvent<SingleClausePane> event) -> container.getChildren()
554                 .remove(event.relatedObject)));
555 
556         bindPane.requestFocus();
557 
558         return bindPane;
559     }
560 
561     private Path getArrow() {
562         return arrow;
563     }
564 
565     private void addArrowVisiblePropertyBinding(final BooleanExpression property) {
566         if (arrowVisibilityCondition == null) {
567             arrowVisibilityCondition = property;
568         } else {
569             arrowVisibilityCondition = arrowVisibilityCondition.or(property);
570         }
571 
572         arrow.visibleProperty().bind(arrowVisibilityCondition);
573     }
574 
575     public final QueryFormPane<?> getParentQueryFormPane() {
576         return parentQueryFormPane;
577     }
578 
579     @FXML
580     @SuppressWarnings("unused")
581     private void addGroupButtonOnAction() {
582         addGroupGraphPatternPane();
583     }
584 
585     @FXML
586     @SuppressWarnings("unused")
587     private void addTripleButtonOnAction() {
588         addTriple(null, true).requestFocus();
589     }
590 
591     @FXML
592     @SuppressWarnings("unused")
593     private void addFilterButtonOnAction() {
594         addFilter();
595     }
596 
597     @FXML
598     @SuppressWarnings("unused")
599     private void addBindButtonOnAction() {
600         addBind();
601     }
602 
603     @FXML
604     @SuppressWarnings("unused")
605     private void addValuesButtonOnAction() {
606         addValuesPane().addInitialValues();
607     }
608 
609     @FXML
610     @SuppressWarnings("unused")
611     private void addSubSelectButtonOnAction() {
612         SubSelectPane pane = addSubSelectPane();
613         pane.bindCheckingChanges();
614         ((SubSelectQueryFormPane) pane.getQueryPane()).runVariableColletors();
615     }
616 
617     @FXML
618     @SuppressWarnings("unused")
619     private void moveUpButtonOnAction(final ActionEvent event) {
620         if (siblings == null) {
621             return;
622         }
623         final int currentPosition = siblings.indexOf(this);
624         if (currentPosition == -1) {
625             return;
626         }
627         if (unionCheckBox.isSelected()) {
628             final Node nodeAbove = siblings.get(currentPosition - 1);
629             if ((nodeAbove instanceof GroupGraphPatternPane)
630                     && !((GroupGraphPatternPane) nodeAbove).unionCheckBox.isSelected()) {
631                 unionCheckBox.setSelected(false);
632             }
633         }
634 
635         siblings.remove(currentPosition);
636         siblings.add(currentPosition - 1, this);
637     }
638 
639     @FXML
640     @SuppressWarnings("unused")
641     private void moveDownButtonOnAction(final ActionEvent event) {
642         if (siblings == null) {
643             return;
644         }
645         final int currentPosition = siblings.indexOf(this);
646         if ((currentPosition > -1) && (currentPosition < (siblings.size() - 1))) {
647             siblings.remove(currentPosition);
648             siblings.add(currentPosition + 1, this);
649         }
650     }
651 
652     /**
653      * Voláno při změně pozice této skupiny v jiné skupině. Na tuto událost je
654      * potřeba reagovat hlavně z důvodu kontroly, zda lze umožnit uživateli
655      * nastavit této skupině modifikátor UNION.
656      *
657      * @param change Změna.
658      */
659     private void onPanePositionChanged(final Change<? extends Node> change) {
660         final ObservableList<? extends Node> list = change.getList();
661         final int index = list.indexOf(this);
662         if (index >= 0) {
663             moveUpButton.setDisable(index == 0);
664             moveDownButton.setDisable((index + 1) >= list.size());
665         }
666         unionCheckBox.setDisable((index < 1) || !(list.get(index - 1) instanceof GroupGraphPatternPane));
667     }
668 
669     /**
670      * Vloží do {@link StringBuilder}u aktuálně nastavený modifikátor skupiny.
671      *
672      * @param sb Instance {@link StringBuilder}.
673      */
674     private void appendGroupModifier(final StringBuilder sb) {
675         if (unionCheckBox.isSelected()) {
676             sb.append("UNION "); //$NON-NLS-1$
677         } else if (optionalCheckBox.isSelected()) {
678             sb.append("OPTIONAL "); //$NON-NLS-1$
679         } else if (graphCheckBox.isSelected()) {
680             sb.append("GRAPH ").append(graphField.getValue()).append(" "); //$NON-NLS-1$
681         } else if (minusCheckBox.isSelected()) {
682             sb.append("MINUS "); //$NON-NLS-1$
683         } else if (filterExistsCheckBox.isSelected()) {
684             sb.append("FILTER EXISTS "); //$NON-NLS-1$
685         } else if (filterNotExistsCheckBox.isSelected()) {
686             sb.append("FILTER NOT EXISTS "); //$NON-NLS-1$
687         }
688     }
689 
690     @Override
691     public final String getQueryPart() {
692         if (container.getChildren().size() > 0) {
693             final StringBuilder sb = new StringBuilder();
694             appendGroupModifier(sb);
695             sb.append("{ "); //$NON-NLS-1$
696             container.getChildren().stream().filter((node) -> (node instanceof PartialQueryGenerator))
697                     .forEach((node) -> sb.append(((PartialQueryGenerator) node).getQueryPart()));
698 
699             sb.append("} "); //$NON-NLS-1$
700             return sb.toString();
701         } else {
702             return "{}"; //$NON-NLS-1$
703         }
704     }
705 
706     @Override
707     public final Set<String> getPrefixesUsed(final boolean appendDelimiter) {
708         final HashSet<String> prefixes = new HashSet<>();
709 
710         if (graphCheckBox.isSelected()) {
711             prefixes.addAll(graphField.getPrefixesUsed(appendDelimiter));
712         }
713 
714         container.getChildren().stream().filter((node) -> (node instanceof PrefixesUser)).forEach((node) -> prefixes.addAll(((PrefixesUser) node).getPrefixesUsed(appendDelimiter)));
715 
716         return prefixes;
717     }
718 
719     @Override
720     public final Set<String> getVariables() {
721         final Set<String> vars = new HashSet<>();
722 
723         if (graphCheckBox.isSelected()) {
724             vars.addAll(graphField.getVariables());
725         }
726 
727         container.getChildren().stream().filter((node) -> (node instanceof SimpleVariablesGenerator))
728                 .forEach((node) -> vars.addAll(((SimpleVariablesGenerator) node).getVariables()));
729         return vars;
730     }
731 
732     @Override
733     public final void onVariableAdded(final String variableName) {
734         notifyVariableAdded(variableName);
735     }
736 
737     @Override
738     public final void onVariableRemoved(final String variableName) {
739         notifyVariableRemoved(variableName);
740     }
741 
742     @Override
743     public final void notifyVariableRemoved(final String variableName) {
744         if (getVariables().contains(variableName)) {
745             return;
746         }
747         variablesCollectors.stream().forEach((collector) -> collector.onVariableRemoved(variableName));
748     }
749 
750     @Override
751     public final void notifyVariableAdded(final String variableName) {
752         variablesCollectors.stream().forEach((collector) -> collector.onVariableAdded(variableName));
753     }
754 
755     @Override
756     public final void addVariablesCollector(final VariablesCollector collector) {
757         variablesCollectors.add(collector);
758     }
759 
760     @Override
761     public final void removeVariablesCollector(final VariablesCollector collector) {
762         variablesCollectors.remove(collector);
763     }
764 
765     @Override
766     public final void onVariableChanged(final String oldVariableName, final String newVariableName) {
767         notifyVariableChanged(oldVariableName, newVariableName);
768     }
769 
770     @Override
771     public final void notifyVariableChanged(final String oldVariableName, final String newVariableName) {
772         if (getVariables().contains(oldVariableName)) {
773             variablesCollectors.stream().forEach((collector) -> collector.onVariableAdded(newVariableName));
774         } else {
775             variablesCollectors.stream().forEach((collector) -> collector.onVariableChanged(oldVariableName, newVariableName));
776         }
777     }
778 
779     // -------
780     @FXML
781     @SuppressWarnings("unused")
782     private void removeButtonOnAction(final ActionEvent event) {
783         if (onRemovalRequested.get() != null) {
784             onRemovalRequested.get().handle(new ObjectRelatedActionEvent<>(event.getSource(), event.getTarget(), this));
785         }
786     }
787 
788     public final ObjectProperty<EventHandler<ObjectRelatedActionEvent<GroupGraphPatternPane>>> onRemovalRequestedProperty() {
789         return onRemovalRequested;
790     }
791 
792     private void setOnRemovalRequested(
793             final EventHandler<ObjectRelatedActionEvent<GroupGraphPatternPane>> handler) {
794         onRemovalRequested.set(handler);
795     }
796 
797     public final EventHandler<ObjectRelatedActionEvent<GroupGraphPatternPane>> getOnRemovalRequested() {
798         return onRemovalRequested.get();
799     }
800 
801     @Override
802     public final void fillInVariableConstraints(String variableName, final VariableConstraintsGenerator nodeToSkip,
803             final Set<String> prefixes,
804             final Set<String> constraints, final Set<String> dependingVariables) {
805         if (!getVariables().contains(variableName)) {
806             return;
807         }
808 
809         // Hledání omezujících podmínek pro stanovení hodnoty proměnné v dotazu
810         final StringBuilder sb = new StringBuilder();
811         appendGroupModifier(sb);
812         sb.append("{ "); //$NON-NLS-1$
813 
814         final Set<String> groupConstraints = new HashSet<>();
815 
816         for (final Node node : container.getChildren()) {
817             if (!(node instanceof VariableConstraintsGenerator)) {
818                 continue;
819             }
820 
821             final Set<String> dependenciesToProcess = new HashSet<>();
822             final Set<String> processedDependencies = new HashSet<>();
823 
824             dependenciesToProcess.add(variableName);
825 
826             do {
827                 variableName = dependenciesToProcess.iterator().next();
828                 dependenciesToProcess.remove(variableName);
829                 processedDependencies.add(variableName);
830 
831                 final VariableConstraintsGenerator g = (VariableConstraintsGenerator) node;
832                 final Set<String> localConstraints = new HashSet<>();
833                 final Set<String> localDependencies = new HashSet<>();
834                 final Set<String> localPrefixes = new HashSet<>();
835                 g.fillInVariableConstraints(variableName, nodeToSkip, localPrefixes, localConstraints,
836                         localDependencies);
837 
838                 if (localConstraints.isEmpty() && (node != nodeToSkip)) {
839                     continue;
840                 }
841 
842                 if (node != nodeToSkip) {
843                     localConstraints.stream().filter((string) -> (!groupConstraints.contains(string))).map((string) -> {
844                         groupConstraints.add(string);
845                         return string;
846                     }).forEach(sb::append); // Filtering constraints already mentioned
847 
848                     prefixes.addAll(localPrefixes);
849                 }
850 
851                 localDependencies.removeAll(processedDependencies);
852                 dependenciesToProcess.addAll(localDependencies);
853             } while (!dependenciesToProcess.isEmpty());
854         }
855 
856         sb.append("} "); //$NON-NLS-1$
857 
858         constraints.add(sb.toString());
859     }
860 
861     private ObservableList<Node> getGroupGraphPatternPaneChildren() {
862         return container.getChildren();
863     }
864 
865     public List<SubSelectPane> getSubSelectPanes() {
866         List<SubSelectPane> results = new LinkedList<>();
867         Queue<GroupGraphPatternPane> nodeQueue = new LinkedList<>();
868         nodeQueue.add(this);
869 
870         while (!nodeQueue.isEmpty()) {
871             GroupGraphPatternPane pane = nodeQueue.remove();
872             ObservableList<Node> nodes = pane.getGroupGraphPatternPaneChildren();
873 
874             nodes.stream().forEach((node) -> {
875                 if (node instanceof SubSelectPane) {
876                     results.add((SubSelectPane) node);
877                 } else if (node instanceof GroupGraphPatternPane) {
878                     nodeQueue.add((GroupGraphPatternPane) node);
879                 }
880             });
881         }
882 
883         return results;
884     }
885 
886     @Override
887     public final void save(final Element e) {
888         Saveable.defaultSave(e, container.getChildren());
889 
890         if (unionCheckBox.isSelected()) {
891             e.setAttribute(XML_GROUP_TYPE, XML_GROUP_TYPE_UNION);
892         } else if (optionalCheckBox.isSelected()) {
893             e.setAttribute(XML_GROUP_TYPE, XML_GROUP_TYPE_OPTIONAL);
894         } else if (graphCheckBox.isSelected()) {
895             final FieldType fieldType = graphField.getFieldType();
896             e.setAttribute(XML_GROUP_TYPE, XML_GROUP_TYPE_GRAPH);
897             e.setAttribute(XML_GRAPH_TYPE, fieldType.name().toLowerCase());
898             e.setAttribute(XML_GRAPH_TEXT, graphField.getText().trim());
899         } else if (minusCheckBox.isSelected()) {
900             e.setAttribute(XML_GROUP_TYPE, XML_GROUP_TYPE_MINUS);
901         } else if (filterExistsCheckBox.isSelected()) {
902             e.setAttribute(XML_GROUP_TYPE, XML_GROUP_TYPE_FILTER_EXISTS);
903         } else if (filterNotExistsCheckBox.isSelected()) {
904             e.setAttribute(XML_GROUP_TYPE, XML_GROUP_TYPE_FILTER_NOT_EXISTS);
905         }
906     }
907 
908     @Override
909     public final void load(final Object e) throws LoadException {
910         unionCheckBox.setSelected(false);
911         optionalCheckBox.setSelected(false);
912         graphCheckBox.setSelected(false);
913         minusCheckBox.setSelected(false);
914         filterExistsCheckBox.setSelected(false);
915         filterNotExistsCheckBox.setSelected(false);
916 
917         if (e instanceof Element) {
918             addGroupGraphPattern((Element) e);
919         } else if (e instanceof ParseTree) {
920             addGroupGraphPattern((ParseTree) e);
921         }
922     }
923 
924     private void addGroupGraphPattern(Element e) throws LoadException {
925         if (e.hasAttribute(XML_GROUP_TYPE)) {
926             final String groupTypeString = e.getAttribute(XML_GROUP_TYPE);
927             if (groupTypeString.equalsIgnoreCase(XML_GROUP_TYPE_UNION)) {
928                 if (unionCheckBox.isDisabled()) {
929                     throw new LoadException(e, XML_GROUP_TYPE);
930                 }
931                 unionCheckBox.setSelected(true);
932             } else if (groupTypeString.equalsIgnoreCase(XML_GROUP_TYPE_OPTIONAL)) {
933                 optionalCheckBox.setSelected(true);
934             } else if (groupTypeString.equalsIgnoreCase(XML_GROUP_TYPE_GRAPH)) {
935                 final String graphTypeString = e.getAttribute(XML_GRAPH_TYPE);
936                 final FieldType graphType = FieldType.valueOfIgnoreCase(graphTypeString);
937                 if ((graphType == null) || (!graphType.oneOf(graphField.getAllowedFieldTypes()))) {
938                     throw new LoadException(e, XML_GRAPH_TYPE);
939                 }
940                 graphCheckBox.setSelected(true);
941                 graphField.setFieldType(graphType);
942                 graphField.setText(e.getAttribute(XML_GRAPH_TEXT));
943             } else if (groupTypeString.equalsIgnoreCase(XML_GROUP_TYPE_MINUS)) {
944                 minusCheckBox.setSelected(true);
945             } else if (groupTypeString.equalsIgnoreCase(XML_GROUP_TYPE_FILTER_EXISTS)) {
946                 filterExistsCheckBox.setSelected(true);
947             } else if (groupTypeString.equalsIgnoreCase(XML_GROUP_TYPE_FILTER_NOT_EXISTS)) {
948                 filterNotExistsCheckBox.setSelected(true);
949             }
950         }
951 
952         final NodeList nodes = e.getChildNodes();
953         final int nodesCount = nodes.getLength();
954         for (int nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) {
955             final org.w3c.dom.Node childNode = nodes.item(nodeIndex);
956             if (childNode instanceof Element) {
957                 final Element childElement = (Element) childNode;
958                 final String tagName = childElement.getTagName();
959                 if (tagName.equals(GroupGraphPatternPane.XML_ELEMENT_NAME)) {
960                     loadGroupGraph(childElement);
961                     continue;
962                 }
963                 if (tagName.equals(TriplePane.XML_ELEMENT_NAME)) {
964                     addTriple(null, false).load(childElement);
965                     continue;
966                 }
967                 if (tagName.equals(FilterPane.XML_ELEMENT_NAME)) {
968                     loadFilter(childElement);
969                 }
970 
971                 if (tagName.equals(SubSelectPane.XML_ELEMENT_NAME)) {
972                     addSubSelectPane().load(childElement);
973                 }
974 
975                 if (tagName.equals(ValuesPane.XML_ELEMENT_NAME)) {
976                     addValuesPane().load(childElement);
977                 }
978 
979                 if (tagName.equals(BindPane.XML_ELEMENT_NAME)) {
980                     addBind().load(childElement);
981                 }
982             }
983         }
984     }
985 
986     private void addGroupGraphPattern(ParseTree e) throws LoadException {
987         ParseTree parentNode = e.getParent();
988         if (parentNode instanceof SparqlParser.OptionalGraphPatternContext) {
989             optionalCheckBox.setSelected(true);
990         } else if (parentNode instanceof SparqlParser.GraphGraphPatternContext
991                 || parentNode instanceof SparqlParser.QuadsNotTriplesContext) {
992             ParseTree graphName = searchNode(parentNode, SparqlParser.VarOrIRIContext.class);
993             if (graphName != null) {
994 
995                 final FieldType graphType = getFieldType(graphName);
996                 if ((graphType == null) || (!graphType.oneOf(graphField.getAllowedFieldTypes()))) {
997                     throw new ParseCancellationException("error during loading field type of: \"" + XML_GROUP_TYPE_GRAPH
998                             + "\" - unsupported field type");
999                 }
1000                 graphCheckBox.setSelected(true);
1001                 graphField.setFieldType(graphType);
1002 
1003                 graphField.setText(SparqlParserUtils.clearNodeValue(graphName.getText(), graphType));
1004             }
1005         } else if (parentNode instanceof SparqlParser.MinusGraphPatternContext) {
1006             minusCheckBox.setSelected(true);
1007         } else if (parentNode instanceof SparqlParser.ExistsFunctionContext) {
1008             filterExistsCheckBox.setSelected(true);
1009         } else if (parentNode instanceof SparqlParser.NotExistsFunctionContext) {
1010             filterNotExistsCheckBox.setSelected(true);
1011         } else if (parentNode instanceof SparqlParser.GroupOrUnionGraphPatternContext) {
1012             if (unionCheckBox.isDisabled()) {
1013                 throw new ParseCancellationException("error during loading field type of: \"" + XML_GROUP_TYPE_UNION
1014                         + "\" - unsupported field type");
1015             }
1016             unionCheckBox.setSelected(true);
1017         }
1018     }
1019 
1020     private void loadGroupGraph(final Element e) throws LoadException {
1021         addGroupGraphPatternPane().load(e);
1022     }
1023 
1024     public void loadFilter(final Object e) throws LoadException {
1025         if (e instanceof Element) {
1026             addFilter().load(e);
1027         } else if (e instanceof String) {
1028             addFilter().load(e);
1029         }
1030     }
1031 
1032     public void loadSubSelect(final ParseTree e) throws LoadException {
1033         addSubSelectPane().load(e);
1034     }
1035 
1036     @Override
1037     public String getXMLElementName() {
1038         return XML_ELEMENT_NAME;
1039     }
1040 
1041     @Override
1042     public final Set<InvalidationListener> getObservers() {
1043         return observers;
1044     }
1045 }