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.describe;
21  
22  import cz.zcu.mre.sparkle.gui.query.helpers.*;
23  import cz.zcu.mre.sparkle.gui.tools.Components;
24  import cz.zcu.mre.sparkle.gui.tools.ReferenceKeeper;
25  import cz.zcu.mre.sparkle.tools.Changeable;
26  import cz.zcu.mre.sparkle.tools.Saveable;
27  import cz.zcu.mre.sparkle.tools.SparqlParser;
28  import javafx.beans.InvalidationListener;
29  import javafx.collections.ObservableList;
30  import javafx.event.EventHandler;
31  import javafx.fxml.FXML;
32  import javafx.scene.Node;
33  import javafx.scene.control.Button;
34  import javafx.scene.control.CheckBox;
35  import javafx.scene.layout.FlowPane;
36  import org.antlr.v4.runtime.ParserRuleContext;
37  import org.antlr.v4.runtime.tree.ParseTree;
38  import org.w3c.dom.Element;
39  import org.w3c.dom.NodeList;
40  import java.util.HashSet;
41  import java.util.List;
42  import java.util.Set;
43  import static cz.zcu.mre.sparkle.tools.sparqlParser.SparqlParserUtils.ALL_VALUES;
44  
45  /**
46   * Kontroler kontejnerové komponenty zastupující v dotazu celou klauzuli
47   * DESCRIBE. Obsahuje položky typu {@link DescribeClauseItem}.
48   *
49   * @author Jan Smucr
50   * @author Klara Hlavacova
51   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
52   */
53  @SuppressWarnings("SimplifyStreamApiCallChains")
54  public final class DescribeClausePane
55          extends FlowPane
56          implements VariablesGenerator, PartialQueryGenerator, VariablesCollector,
57          PrefixesUser, Saveable, Changeable {
58  
59      private static final String XML_DESCRIBE_ALL_FALSE = "0"; //$NON-NLS-1$
60      private static final String XML_DESCRIBE_ALL_TRUE = "1"; //$NON-NLS-1$
61      private static final String XML_DESCRIBE_ALL = "all"; //$NON-NLS-1$
62      private static final String XML_ELEMENT_NAME = "DescribeGroup"; //$NON-NLS-1$
63      @FXML
64      private CheckBox allCheckBox;
65      @FXML
66      private Button addItemButton;
67  
68      private final Set<InvalidationListener> observers = new HashSet<>();
69      private final Set<VariablesCollector> variablesCollectors = new HashSet<>();
70      private final DescribeQueryFormPane parentQueryPane;
71      /**
72       * Seznam proměnných používaných v klauzuli.
73       */
74      private final Set<String> currentVariables = new HashSet<>();
75      private final ReferenceKeeper keeper = new ReferenceKeeper();
76  
77      /**
78       * Handler řešící požadavky na odstranění položek.
79       */
80      private final EventHandler<ObjectRelatedActionEvent<DescribeClauseItem>> itemRemovalEventHandler = event
81              -> {
82          getChildren().remove(event.relatedObject);
83          event.relatedObject.getVariables().forEach(this::notifyVariableRemoved);
84      };
85  
86      public DescribeClausePane(final DescribeQueryFormPane parentQueryPane) {
87          Components.load(this);
88  
89          this.parentQueryPane = parentQueryPane;
90  
91          // Automatické sledování změn vnořených komponent
92          autoWatch(getChildren(), DescribeClauseItem.class);
93  
94          watch(allCheckBox.selectedProperty());
95  
96          // V počátečním stavu zobrazovat jedno pole
97          addItem();
98      }
99  
100     /**
101      * Přidá prázdné pole.
102      */
103     @FXML
104     private void addItem() {
105         addVariable(null).requestFocus();
106     }
107 
108     /**
109      * @return První vnořená komponenta nebo <code>null</code>.
110      */
111     private Node getFirstNode() {
112         final ObservableList<Node> children = getChildren();
113         return children.size() > 0 ? children.get(0) : null;
114     }
115 
116     /**
117      * Přidá pole s proměnnou daného názvu nebo využije prvního, pokud je
118      * prázdné.
119      *
120      * @param variableName Název proměnné nebo <code>null</code> pro prázdné
121      * pole.
122      *
123      * @return Nově vytvořené pole.
124      */
125     public final DescribeClauseItem addVariable(final String variableName) {
126         final Node firstNode = getFirstNode();
127 
128         if ((variableName != null) && (firstNode instanceof DescribeClauseItem)) {
129             final DescribeClauseItem item = (DescribeClauseItem) firstNode;
130             if (item.isEmpty()) {
131                 item.describeVariable(variableName);
132                 return item;
133             }
134         }
135 
136         final DescribeClauseItem item = createItem();
137         addItem(item);
138 
139         if (variableName != null) {
140             item.describeVariable(variableName);
141         }
142 
143         return item;
144     }
145 
146     /**
147      * Přidá do kontejneru komponentu pole.
148      *
149      * @param item Pole.
150      */
151     private void addItem(final DescribeClauseItem item) {
152         final ObservableList<Node> children = getChildren();
153         children.add(children.indexOf(addItemButton), item);
154         item.setOnRemovalRequested(keeper.toWeak(itemRemovalEventHandler));
155         item.addVariablesCollector(this);
156         updateOpacity();
157     }
158 
159     /**
160      * Vytvoří komponentu pole.
161      *
162      * @return Nová komponenta pole.
163      */
164     private DescribeClauseItem createItem() {
165         return new DescribeClauseItem(parentQueryPane, getFirstNode() instanceof DescribeClauseItem);
166     }
167 
168     @FXML
169     @SuppressWarnings("unused")
170     private void allCheckBoxOnAction() {
171         updateOpacity();
172     }
173 
174     /**
175      * Podle stavu {@link #allCheckBox} nastaví průhlednost všech polí na 0.7
176      * (zaškrtnuto) nebo 0 (nezaškrtnuto).
177      */
178     private void updateOpacity() {
179         final double opacity = allCheckBox.isSelected() ? 0.3 : 1.0;
180         addItemButton.setOpacity(opacity);
181 
182         getChildren().stream().filter((node) -> (node instanceof DescribeClauseItem)).forEach((node) -> node.setOpacity(opacity));
183     }
184 
185     @Override
186     public final String getQueryPart() {
187         final StringBuffer result = new StringBuffer("DESCRIBE "); //$NON-NLS-1$
188 
189         if (allCheckBox.isSelected()) {
190             result.append("* "); //$NON-NLS-1$
191         } else {
192             getChildren().stream().filter((node) -> (node instanceof PartialQueryGenerator)).forEach((node) -> result.append(((PartialQueryGenerator) node).getQueryPart()));
193         }
194 
195         return result.toString();
196     }
197 
198     @Override
199     public final Set<String> getVariables() {
200         final Set<String> result = new HashSet<>();
201         getChildren().stream().filter((node) -> (node instanceof SimpleVariablesGenerator)).forEach((node) -> result.addAll(((SimpleVariablesGenerator) node).getVariables()));
202         return result;
203     }
204 
205     @Override
206     public final void notifyVariableRemoved(final String variableName) {
207         currentVariables.remove(variableName);
208         variablesCollectors.stream().forEach((collector) -> collector.onVariableRemoved(variableName));
209     }
210 
211     @Override
212     public final void notifyVariableAdded(final String variableName) {
213         currentVariables.add(variableName);
214         variablesCollectors.stream().forEach((collector) -> collector.onVariableAdded(variableName));
215     }
216 
217     @Override
218     public final void addVariablesCollector(final VariablesCollector collector) {
219         variablesCollectors.add(collector);
220     }
221 
222     @Override
223     public final void removeVariablesCollector(final VariablesCollector collector) {
224         variablesCollectors.remove(collector);
225     }
226 
227     @Override
228     public final void notifyVariableChanged(final String oldVariableName, final String newVariableName) {
229         currentVariables.remove(oldVariableName);
230         currentVariables.add(newVariableName);
231         variablesCollectors.stream().forEach((collector) -> collector.onVariableChanged(oldVariableName, newVariableName));
232     }
233 
234     @Override
235     public final void onVariableAdded(final String variableName) {
236         if (!currentVariables.contains(variableName)) {
237             notifyVariableAdded(variableName);
238         }
239     }
240 
241     @Override
242     public final void onVariableRemoved(final String variableName) {
243         if (!getVariables().contains(variableName)) {
244             notifyVariableRemoved(variableName);
245         }
246     }
247 
248     @Override
249     public final void onVariableChanged(final String oldVariableName, final String newVariableName) {
250         final Set<String> newState = getVariables();
251         final boolean added = !currentVariables.contains(newVariableName) && newState.contains(newVariableName);
252         final boolean removed = currentVariables.contains(oldVariableName) && !newState.contains(oldVariableName);
253 
254         if (added && removed) {
255             notifyVariableChanged(oldVariableName, newVariableName);
256         } else if (added) {
257             notifyVariableAdded(newVariableName);
258         } else if (removed) {
259             notifyVariableRemoved(oldVariableName);
260         }
261     }
262 
263     @Override
264     public final Set<String> getPrefixesUsed(final boolean appendDelimiter) {
265         final Set<String> result = new HashSet<>();
266 
267         if (allCheckBox.isSelected()) {
268             return result;
269         }
270 
271         getChildren().stream().filter((node) -> (node instanceof PrefixesUser)).forEach((node) -> result.addAll(((PrefixesUser) node).getPrefixesUsed(appendDelimiter)));
272 
273         return result;
274     }
275 
276     @Override
277     public final void save(final Element e) {
278         Saveable.defaultSave(e, getChildren());
279         e.setAttribute(XML_DESCRIBE_ALL, allCheckBox.isSelected() ? XML_DESCRIBE_ALL_TRUE : XML_DESCRIBE_ALL_FALSE);
280     }
281 
282     @Override
283     public final void load(final Object e) throws LoadException {
284         removeAllItems(); // FIXME Odstranit by se měly obecně všechny položky
285 
286         if (e instanceof Element) {
287             Element eNode = (Element) e;
288             final String allString = eNode.getAttribute(XML_DESCRIBE_ALL);
289 
290             if (allString.equalsIgnoreCase(XML_DESCRIBE_ALL_TRUE)) {
291                 allCheckBox.setSelected(true);
292             } else if (allString.equalsIgnoreCase(XML_DESCRIBE_ALL_FALSE)) {
293                 allCheckBox.setSelected(false);
294             } else {
295                 throw new LoadException(eNode, XML_DESCRIBE_ALL);
296             }
297 
298             final NodeList nodes = eNode.getElementsByTagName(DescribeClauseItem.XML_ELEMENT_NAME);
299             final int nodesCount = nodes.getLength();
300 
301             for (int nodeIndex = 0; nodeIndex < nodesCount; nodeIndex++) {
302                 final org.w3c.dom.Node childNode = nodes.item(nodeIndex);
303                 if (childNode instanceof Element) {
304                     loadChild((Element) childNode);
305                 }
306             }
307         } else if (e instanceof SparqlParser.DescribeQueryContext) {
308             List<ParseTree> listNodes = ((ParserRuleContext) e).children;
309             for (ParseTree node : listNodes) {
310                 if (node instanceof SparqlParser.VarOrIRIContext) {
311                     final DescribeClauseItem item = createItem();
312                     addItem(item);
313                     item.load(node);
314                 } else if (node.getText().equals(ALL_VALUES)) {
315                     allCheckBox.setSelected(true);
316                     break;
317                 }
318             }
319         }
320     }
321 
322     private void removeAllItems() {
323         final Set<Node> toRemove = new HashSet<>();
324         getChildren().stream().filter((node) -> (node instanceof DescribeClauseItem)).forEach(toRemove::add);
325         getChildren().removeAll(toRemove);
326     }
327 
328     private void loadChild(final Element e) throws LoadException {
329         final DescribeClauseItem item = createItem();
330         addItem(item);
331         item.load(e);
332     }
333 
334     @Override
335     public final String getXMLElementName() {
336         return XML_ELEMENT_NAME;
337     }
338 
339     @Override
340     public final Set<InvalidationListener> getObservers() {
341         return observers;
342     }
343 }