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.queryTypes.select;
21  
22  import cz.zcu.mre.sparkle.gui.query.QueryFormPane;
23  import cz.zcu.mre.sparkle.gui.query.helpers.*;
24  import cz.zcu.mre.sparkle.gui.query.other.VariableLabel;
25  import cz.zcu.mre.sparkle.gui.tools.Components;
26  import cz.zcu.mre.sparkle.gui.tools.ReferenceKeeper;
27  import cz.zcu.mre.sparkle.tools.Changeable;
28  import cz.zcu.mre.sparkle.tools.Definitions;
29  import cz.zcu.mre.sparkle.tools.Saveable;
30  import cz.zcu.mre.sparkle.tools.SparqlParser;
31  import javafx.beans.InvalidationListener;
32  import javafx.beans.binding.BooleanExpression;
33  import javafx.collections.FXCollections;
34  import javafx.collections.ObservableList;
35  import javafx.scene.Node;
36  import javafx.scene.input.Dragboard;
37  import javafx.scene.input.TransferMode;
38  import javafx.scene.layout.VBox;
39  import org.antlr.v4.runtime.tree.ParseTree;
40  import org.w3c.dom.Element;
41  import org.w3c.dom.NodeList;
42  import javax.xml.parsers.ParserConfigurationException;
43  import javax.xml.transform.TransformerException;
44  import java.util.ArrayList;
45  import java.util.HashSet;
46  import java.util.List;
47  import java.util.Set;
48  import org.slf4j.Logger;
49  import org.slf4j.LoggerFactory;
50  
51  /**
52   * It represents a base pane of SELECT clause.
53   *
54   * @author Josef Kazak
55   * @author Klara Hlavacova
56   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
57   */
58  public class SelectClauseBasePane
59          extends VBox
60          implements PartialQueryGenerator, VariablesGenerator, VariablesCollector, Saveable, Changeable {
61  
62      private static final Logger LOG = LoggerFactory.getLogger(SelectClauseBasePane.class);
63  
64      private static final String XML_ELEMENT_NAME = "select"; //$NON-NLS-1$
65      public static final String XML_DISTINCT = "distinct"; //$NON-NLS-1$
66  
67      private BooleanExpression arrowVisibilityCondition;
68      private final QueryFormPane<?> parentQueryFormPane;
69      private final Set<VariablesCollector> variablesCollectors = new HashSet<>();
70      private final ReferenceKeeper keeper = new ReferenceKeeper();
71      private final Set<InvalidationListener> observers = new HashSet<>();
72      private final SelectClausePane selectClausePane;
73  
74      public SelectClauseBasePane(final QueryFormPane<?> parentQueryFormPane) {
75          Components.load(this);
76          this.parentQueryFormPane = parentQueryFormPane;
77          selectClausePane = addSelectClausePane();
78  
79          setOnDragOver((event)
80                  -> {
81              if ((event.getGestureSource() != this) && event.getDragboard().hasContent(SelectVarPane.DRAG_FORMAT)) {
82                  event.acceptTransferModes(TransferMode.MOVE);
83              }
84              event.consume();
85          });
86  
87          setOnDragDropped((event)
88                  -> {
89              final Dragboard db = event.getDragboard();
90              boolean success = false;
91              if (db.hasContent(SelectVarPane.DRAG_FORMAT)) {
92                  try {
93                      addSelectVarPane().fromXML((String) db.getContent(SelectVarPane.DRAG_FORMAT));
94                      success = true;
95                  } catch (final TransformerException | ParserConfigurationException | Saveable.LoadException e) {
96                      LOG.error("Exception: ", e); //$NON-NLS-1$
97                  }
98              }
99              event.setDropCompleted(success);
100             event.consume();
101         });
102 
103         //Auto tracking of nested components changes
104         watch(selectClausePane);
105         autoWatch(getChildren(), FXCollections.observableArrayList(SelectVarPane.class));
106     }
107 
108     /**
109      * It removes all obsolete {@link VariableLabel} in whole select clause.
110      */
111     public void removeVariableLabels() {
112         getChildren().stream().filter((child) -> (child instanceof SelectVarPane)).map((child) -> (SelectVarPane) child)
113                 .forEach((selectVarPane) -> {
114                     VariableLabel variableLabel = selectVarPane.getSelectedVariableLabel();
115                     if (variableLabel != null) {
116                         if (!parentQueryFormPane.getNetVariables().contains(variableLabel.getVariableName())) {
117                             selectVarPane.removeVariableLabel(variableLabel);
118                         }
119                     }
120                 });
121     }
122 
123     public final QueryFormPane<?> getParentQueryFormPane() {
124         return parentQueryFormPane;
125     }
126 
127     /**
128      * It adds new instance of {@link SelectClausePane}.
129      *
130      * @return New instance of {@link SelectClausePane}.
131      */
132     private SelectClausePane addSelectClausePane() {
133         final SelectClausePane selectClausePaneVar = new SelectClausePane(this);
134         getChildren().add(selectClausePaneVar);
135         return selectClausePaneVar;
136     }
137 
138     /**
139      * It adds new instance of {@link SelectVarPane}.
140      *
141      * @return New instance of {@link SelectVarPane}.
142      */
143     public final SelectVarPane addSelectVarPane() {
144         final ObservableList<Node> children = getChildren();
145         final SelectVarPane selectVarPane = new SelectVarPane(this, children);
146         selectVarPane.addVariablesCollector(this);
147         children.add(selectVarPane);
148         addArrowVisiblePropertyBinding(selectVarPane.getArrow().visibleProperty());
149 
150         selectVarPane.setOnRemovalRequested(keeper.toWeak((final ObjectRelatedActionEvent<SelectVarPane> event) -> {
151             children.remove(event.relatedObject);
152             final Set<String> variablesRemoved = event.relatedObject.getVariables();
153             variablesRemoved.removeAll(getVariables());
154             variablesRemoved.stream().forEach(this::notifyVariableRemoved);
155         }));
156         return selectVarPane;
157     }
158 
159     private void addArrowVisiblePropertyBinding(final BooleanExpression property) {
160         if (arrowVisibilityCondition == null) {
161             arrowVisibilityCondition = property;
162         } else {
163             arrowVisibilityCondition = arrowVisibilityCondition.or(property);
164         }
165     }
166 
167     @Override
168     public final String getQueryPart() {
169         final StringBuilder sb = new StringBuilder();
170         sb.append(XML_ELEMENT_NAME).append(Definitions.SPACE).append(selectClausePane.getQueryPart())
171                 .append(Definitions.SPACE);
172         if (sb.indexOf(Definitions.ALL_VARIABLES)
173                 < 0) { // There isn't a "*" in selectClausePane => loading variables from SelectVarPanes
174             getChildren().stream().filter((node) -> (node instanceof SelectVarPane)).forEach((node) -> sb.append(((SelectVarPane) node).getQueryPart()).append(Definitions.SPACE));
175         }
176         return sb.toString();
177     }
178 
179     /**
180      * It returns list of aliases or selected variables from this clause. It is
181      * used during creating a new query with nested queries.
182      *
183      * @return List of aliases or selected variables.
184      */
185     public final ObservableList<String> getSubSelectVariables() {
186         List<String> list = new ArrayList<>();
187         getChildren().stream().filter((node) -> (node instanceof SelectVarPane)).forEach((node) -> {
188             String variableQueryPart = ((SelectVarPane) node).getSubSelectVariable();
189             if (variableQueryPart != null) {
190                 list.add(variableQueryPart);
191             }
192         });
193         return FXCollections.observableList(list);
194     }
195 
196     @Override
197     public final Set<String> getVariables() {
198         final Set<String> vars = new HashSet<>();
199         getChildren().stream().filter((node) -> (node instanceof SimpleVariablesGenerator)).forEach((node) -> vars.addAll(((SimpleVariablesGenerator) node).getVariables()));
200         return vars;
201     }
202 
203     @Override
204     public final void onVariableAdded(final String variableName) {
205         notifyVariableAdded(variableName);
206     }
207 
208     @Override
209     public final void onVariableRemoved(final String variableName) {
210         notifyVariableRemoved(variableName);
211     }
212 
213     @Override
214     public final void notifyVariableRemoved(final String variableName) {
215         if (getVariables().contains(variableName)) {
216             return;
217         }
218         variablesCollectors.stream().forEach((collector) -> collector.onVariableRemoved(variableName));
219     }
220 
221     @Override
222     public final void notifyVariableAdded(final String variableName) {
223         variablesCollectors.stream().forEach((collector) -> collector.onVariableAdded(variableName));
224     }
225 
226     @Override
227     public final void addVariablesCollector(final VariablesCollector collector) {
228         variablesCollectors.add(collector);
229     }
230 
231     @Override
232     public final void removeVariablesCollector(final VariablesCollector collector) {
233         variablesCollectors.remove(collector);
234     }
235 
236     @Override
237     public final void onVariableChanged(final String oldVariableName, final String newVariableName) {
238         notifyVariableChanged(oldVariableName, newVariableName);
239     }
240 
241     @Override
242     public final void notifyVariableChanged(final String oldVariableName, final String newVariableName) {
243         if (getVariables().contains(oldVariableName)) {
244             variablesCollectors.stream().forEach((collector) -> collector.onVariableAdded(newVariableName));
245         } else {
246             variablesCollectors.stream().forEach((collector) -> collector.onVariableChanged(oldVariableName, newVariableName));
247         }
248     }
249 
250     @Override
251     public final void save(final Element e) {
252         Saveable.defaultSave(e, getChildren());
253     }
254 
255     @Override
256     public final void load(final Object e) throws LoadException {
257         if (e instanceof Element) {
258 
259             Element eNode = (Element) e;
260             boolean globalVariableAssigned = false;
261             final NodeList globalOptions = eNode.getChildNodes();
262             final int globalOptionsCount = globalOptions.getLength();
263 
264             for (int nodeIndex = 0; nodeIndex < globalOptionsCount; nodeIndex++) {
265                 final org.w3c.dom.Node childNode = globalOptions.item(nodeIndex);
266 
267                 if (childNode instanceof Element) {
268                     Element currentNode = (Element) childNode;
269                     if (currentNode.getTagName().equals(SelectVarPane.XML_ELEMENT_NAME)) {
270                         addSelectVarPane().load(currentNode);
271 
272                     } else if (currentNode.getTagName().equals(SelectClausePane.XML_ELEMENT_NAME)) {
273                         if (!globalVariableAssigned) {
274                             selectClausePane
275                                     .load(currentNode); // It loads global options (keywords) of select query part
276                             globalVariableAssigned = true;
277                         }
278                     }
279                 }
280             }
281         } else if (e instanceof SparqlParser.SelectClauseContext) {
282             SparqlParser.SelectClauseContext eNode = (SparqlParser.SelectClauseContext) e;
283             selectClausePane.load(eNode);
284             if (!selectClausePane.getAllCheckBoxState()) {
285                 for (ParseTree node : eNode.children) {
286                     if (node instanceof SparqlParser.SelectVariablesContext) {
287                         addSelectVarPane().load(node);
288                     }
289                 }
290             }
291         }
292     }
293 
294     @Override
295     public String getXMLElementName() {
296         return XML_ELEMENT_NAME;
297     }
298 
299     @Override
300     public final Set<InvalidationListener> getObservers() {
301         return observers;
302     }
303 }