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.data;
21  
22  import cz.zcu.mre.sparkle.gui.query.helpers.PrefixesUser;
23  import cz.zcu.mre.sparkle.tools.*;
24  import cz.zcu.mre.sparkle.tools.SparqlParser;
25  import javafx.beans.InvalidationListener;
26  import javafx.collections.FXCollections;
27  import javafx.collections.MapChangeListener;
28  import javafx.collections.ObservableMap;
29  import org.apache.jena.shared.PrefixMapping;
30  import org.w3c.dom.Document;
31  import org.w3c.dom.Element;
32  import org.w3c.dom.NodeList;
33  import java.io.IOException;
34  import java.io.ObjectInputStream;
35  import java.io.ObjectOutputStream;
36  import java.io.Serializable;
37  import java.util.HashMap;
38  import java.util.HashSet;
39  import java.util.Map;
40  import java.util.Map.Entry;
41  import java.util.Set;
42  import java.util.regex.Matcher;
43  import static cz.zcu.mre.sparkle.tools.sparqlParser.SparqlParserUtils.IRI_PATTERN;
44  import static cz.zcu.mre.sparkle.tools.sparqlParser.SparqlParserUtils.PREFIX_PATTERN;
45  
46  /**
47   * Třída pro mapování prefixů na IRI jmenných prostorů a opačně.
48   *
49   * @author Jan Smucr
50   * @author Petr Vcelak (vcelak@kiv.zcu.cz)
51   * @author Klara Hlavacova
52   */
53  public final class PrefixesStorage
54          implements Serializable, PrefixesUser, PrefixMapping, Saveable, Changeable {
55  
56      private static final long serialVersionUID = -5472647693723537955L;
57      private static final String XML_ELEMENT_NAME = "Prefixes";//$NON-NLS-2$
58      private static final String XML_ENTRY_ELEMENT_NAME = "Entry";//$NON-NLS-2$
59      private static final String XML_ENTRY_NAMESPACE = "ns";//$NON-NLS-2$
60      private static final String XML_ENTRY_ALT_NAMESPACE = "altNs";//$NON-NLS-2$
61      private static final String XML_ENTRY_PREFIX = "prefix";//$NON-NLS-2$
62  
63      private transient ObservableMap<String, String> prefixToIri;
64      private transient ObservableMap<String, String> iriToPrefix;
65      private transient ObservableMap<String, String> iriToPrefixReadOnly;
66      private transient ObservableMap<String, String> iriToAlternativeIri;
67      private transient Set<InvalidationListener> observers;
68  
69      public PrefixesStorage() {
70          init();
71      }
72  
73      /**
74       * @return Instance {@link PrefixesStorage} obsahující vestavěná mapování.
75       */
76      public static final PrefixesStorage getBuiltIn() {
77          final PrefixesStorage prefixesStorage = new PrefixesStorage();
78  
79          BuiltInVocabulary builtInNamespaces = new BuiltInVocabulary();
80  
81          builtInNamespaces.getBuiltInNamespaces().forEach((entry) -> {
82              registerPrefix(prefixesStorage, entry.getUri(), entry.getPrefix(), entry.getAlternativeUri());
83          });
84  
85          return prefixesStorage;
86      }
87  
88      /**
89       * Inicializace objektu mimo konstruktor kvůli možnosti inicializace před
90       * deserializací.
91       */
92      private void init() {
93          observers = new HashSet<>();
94          prefixToIri = FXCollections.observableHashMap();
95          iriToPrefix = FXCollections.observableHashMap();
96          iriToPrefixReadOnly = FXCollections.unmodifiableObservableMap(iriToPrefix);
97          iriToAlternativeIri = FXCollections.observableHashMap();
98          prefixToIriMapper();
99      }
100 
101     /**
102      * Vlozeni prefixu
103      *
104      * @param prefix prefix
105      * @param iri iri
106      * @param alternativeIri alternativni iri
107      */
108     public void addPrefix(String prefix, String iri, String alternativeIri) {
109         iriToAlternativeIri.put(iri, alternativeIri);
110         prefixToIri.put(prefix, iri);
111 
112     }
113 
114     private void removePrefix(String prefix, String iri) {
115         iriToAlternativeIri.remove(iri);
116         prefixToIri.remove(prefix);
117 
118     }
119 
120     /**
121      * Mapovani prefixu na iri
122      */
123     private void prefixToIriMapper() {
124         // Automatické mapování opačným směrem
125         // FIXME toto nebude fungovat pro dvě stejná IRI mapovaná na dva různé prefixy
126 
127         prefixToIri.addListener((final MapChangeListener.Change<? extends String, ? extends String> change) -> {
128 
129             final String valueAdded = change.getValueAdded();
130             final String valueRemoved = change.getValueRemoved();
131 
132             if (valueRemoved != null) {
133                 iriToPrefix.remove(valueRemoved);
134             }
135 
136             if (valueAdded != null) {
137                 iriToPrefix.put(valueAdded, change.getKey());
138             }
139 
140             notifyChanged();
141         });
142     }
143 
144     /**
145      *
146      * @param prefixesStorage
147      * @param iri
148      * @param prefix
149      * @param alternativeIri
150      */
151     private static void registerPrefix(final PrefixesStorage prefixesStorage, final String iri, final String prefix,
152             String alternativeIri) {
153 
154         final String prefixToRemove = prefixesStorage.iriToPrefix.get(iri);
155 
156         if (prefixToRemove != null && alternativeIri == null) {
157             //prefix nacitany z dotazu v textovem souboru
158             return;
159         }
160 
161         if (prefixToRemove != null) {
162             prefixesStorage.removePrefix(prefixToRemove, iri);
163         }
164 
165         prefixesStorage.addPrefix(prefix, iri, alternativeIri);
166     }
167 
168     /**
169      * Přidá mapování z odkazované instance {@link PrefixesStorage}.
170      *
171      * @param prefixesStorage Instance {@link PrefixesStorage}.
172      */
173     public final void add(final PrefixesStorage prefixesStorage) {
174         iriToAlternativeIri.putAll(prefixesStorage.getIriToAlternativeIri());
175         setNsPrefixes(prefixesStorage);
176 
177     }
178 
179     @Override
180     public final PrefixMapping removeNsPrefix(final String prefix) {
181         iriToAlternativeIri.remove(getPrefixToIri().get(prefix));
182         getPrefixToIri().remove(prefix);
183 
184         return this;
185     }
186 
187     /**
188      * Přejmenuje prefix.
189      *
190      * @param oldPrefix Starý název.
191      * @param newPrefix Nový název.
192      */
193     public final void renamePrefix(final String oldPrefix, final String newPrefix) {
194         final String iri = prefixToIri.get(oldPrefix);
195         iriToPrefix.put(iri, newPrefix);
196         prefixToIri.put(newPrefix, iri);
197         prefixToIri.remove(oldPrefix);
198     }
199 
200     @Override
201     public final PrefixMapping setNsPrefix(final String prefix, final String iri) {
202         String oldIri = prefixToIri.get(prefix);
203         String altIri = iriToAlternativeIri.remove(oldIri);
204 
205         return setNsPrefix(prefix, iri, altIri);
206     }
207 
208     public final PrefixMapping setNsPrefix(final String prefix, final String iri, String alternativeIri) {
209         iriToAlternativeIri.put(iri, alternativeIri);
210         prefixToIri.put(prefix, iri);
211         return this;
212     }
213 
214     /**
215      * Metoda volaná při serializaci.
216      *
217      * @param out Výstupní proud.
218      *
219      * @throws IOException on IO exception.
220      */
221     private void writeObject(final ObjectOutputStream out) throws IOException {
222         out.defaultWriteObject();
223 
224         final Set<Entry<String, String>> entrySet = prefixToIri.entrySet();
225         out.writeInt(entrySet.size());
226 
227         for (final Entry<String, String> entry : entrySet) {
228             out.writeUTF(entry.getKey());
229             String altIri = iriToAlternativeIri.get(entry.getValue());
230             out.writeUTF(entry.getValue());
231             out.writeUTF(altIri);
232         }
233     }
234 
235     /**
236      * Metoda volaná při deserializaci.
237      *
238      * @param in Vstupní proud.
239      *
240      * @throws IOException on IO exception.
241      * @throws ClassNotFoundException on missing class.
242      */
243     private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
244         in.defaultReadObject();
245 
246         init();
247 
248         final int size = in.readInt();
249         for (int index = 0; index < size; index++) {
250             final String key = in.readUTF();
251             final String iri = in.readUTF();
252             final String alternativeIri = in.readUTF();
253 
254             registerPrefix(this, iri, key, alternativeIri);
255         }
256     }
257 
258     /**
259      * @return Mapování Prefix - IRI. Lze modifikovat.
260      */
261     public final ObservableMap<String, String> getPrefixToIri() {
262         return prefixToIri;
263     }
264 
265     /**
266      * @return Read-only mapování IRI - Prefix.
267      */
268     public final ObservableMap<String, String> getIriToPrefixReadOnly() {
269         return iriToPrefixReadOnly;
270     }
271 
272     @Override
273     public final Set<String> getPrefixesUsed(final boolean appendDelimiter) {
274         final Set<String> result = new HashSet<>();
275         prefixToIri.keySet().forEach((prefix) -> result.add(appendDelimiter ? prefix + Definitions.PREFIXED_NAME_PREFIX_DELIMITER : prefix));
276 
277         return result;
278     }
279 
280     @Override
281     public final PrefixMapping setNsPrefixes(final PrefixMapping other) {
282         return setNsPrefixes(other.getNsPrefixMap());
283     }
284 
285     @Override
286     public final PrefixMapping setNsPrefixes(final Map<String, String> map) {
287         prefixToIri.putAll(map);
288         return this;
289     }
290 
291     public void setAlternativeIri(String iri, String alternatiteIri) {
292         iriToAlternativeIri.put(iri, alternatiteIri);
293     }
294 
295     @Override
296     public final PrefixMapping withDefaultMappings(final PrefixMapping map) {
297         map.getNsPrefixMap().forEach((key, value) -> prefixToIri.putIfAbsent(key, value));
298         return this;
299     }
300 
301     @Override
302     public final String getNsPrefixURI(final String prefix) {
303         return prefixToIri.get(prefix);
304     }
305 
306     @Override
307     public final String getNsURIPrefix(final String iri) {
308         return iriToPrefix.get(iri);
309     }
310 
311     @Override
312     public final Map<String, String> getNsPrefixMap() {
313         return new HashMap<>(prefixToIri);
314     }
315 
316     @Override
317     public final String expandPrefix(final String prefixed) {
318         final String prefix = Utils.extractPrefix(prefixed);
319         if (prefix != null) {
320             final String iri = prefixToIri.get(prefix);
321             if (iri == null) {
322                 return prefixed;
323             }
324             return Utils.tryReplacePrefixForIRI(prefix, iri, prefixed);
325         }
326         return prefixed;
327     }
328 
329     @Override
330     public final String shortForm(final String iri) {
331 
332         for (Map.Entry<String, String> storedIriMapEntry : iriToPrefix.entrySet()) {
333             final String storedIriKey = storedIriMapEntry.getKey();
334             //final String value = storedIriMapEntry.getValue();
335             if (iri.startsWith(storedIriKey)) {
336                 return iriToPrefix.get(storedIriKey) + Definitions.PREFIXED_NAME_PREFIX_DELIMITER
337                         + iri.substring(storedIriKey.length());
338             }
339         }
340 
341         return iri;
342     }
343 
344     @Override
345     public final String qnameFor(final String iri) {
346         String longestStoredIri = null;
347         int longestLength = 0;
348         for (final String storedIri : iriToPrefix.keySet()) {
349             final int length = storedIri.length();
350             if (length <= longestLength) {
351                 continue;
352             }
353             if (iri.startsWith(storedIri)) {
354                 longestStoredIri = storedIri;
355                 longestLength = length;
356             }
357         }
358         if (longestStoredIri == null) {
359             return null;
360         }
361         return iriToPrefix.get(longestStoredIri) + Definitions.PREFIXED_NAME_PREFIX_DELIMITER
362                 + iri.substring(longestLength);
363     }
364 
365     @Override
366     public final PrefixMapping lock() {
367         // Not implemented
368         return this;
369     }
370 
371     @Override
372     public final boolean samePrefixMappingAs(final PrefixMapping other) {
373         if (other == null) {
374             return false;
375         }
376         if (other instanceof PrefixesStorage) {
377             return prefixToIri.equals(((PrefixesStorage) other).prefixToIri);
378         }
379         return prefixToIri.equals(other.getNsPrefixMap());
380     }
381 
382     @Override
383     public final void save(final Element e) {
384         final Document doc = e.getOwnerDocument();
385         prefixToIri.entrySet().stream().map((entry) -> {
386             final Element entryElement = doc.createElement(XML_ENTRY_ELEMENT_NAME);
387             entryElement.setAttribute(XML_ENTRY_NAMESPACE, entry.getValue());
388 
389             String altUri = iriToAlternativeIri.get(entry.getValue());
390             entryElement.setAttribute(XML_ENTRY_ALT_NAMESPACE, altUri);
391 
392             entryElement.setAttribute(XML_ENTRY_PREFIX, entry.getKey());
393 
394             return entryElement;
395         }).forEachOrdered(e::appendChild);
396     }
397 
398     @Override
399     public final void load(final Object e) throws LoadException {
400         if (e instanceof Element) {
401 
402             final NodeList list = ((Element) e).getElementsByTagName(XML_ENTRY_ELEMENT_NAME);
403             final int nodesCount = list.getLength();
404             for (int index = 0; index < nodesCount; index++) {
405                 final Element entryElement = (Element) list.item(index);
406 
407                 registerPrefix(this, entryElement.getAttribute(XML_ENTRY_NAMESPACE),
408                         entryElement.getAttribute(XML_ENTRY_PREFIX),
409                         entryElement.getAttribute(XML_ENTRY_ALT_NAMESPACE));
410             }
411 
412         } else if (e instanceof SparqlParser.PrefixDeclContext) {
413 
414             SparqlParser.PrefixDeclContext root = (SparqlParser.PrefixDeclContext) e;
415 
416             Matcher prefixMatcher = PREFIX_PATTERN.matcher(root.PNAME_NS().getText());
417             Matcher iriMatcher = IRI_PATTERN.matcher(root.IRIREF().getText());
418 
419             if (prefixMatcher.matches() && iriMatcher.matches()) {
420                 registerPrefix(this, iriMatcher.group(1), prefixMatcher.group(1), null);
421             }
422         }
423     }
424 
425     @Override
426     public final String getXMLElementName() {
427         return XML_ELEMENT_NAME;
428     }
429 
430     @Override
431     public final Set<InvalidationListener> getObservers() {
432         return observers;
433     }
434 
435     @Override
436     public int numPrefixes() {
437         return prefixToIri.size();
438     }
439 
440     @Override
441     public PrefixMapping clearNsPrefixMap() {
442         this.prefixToIri.clear();
443         this.iriToPrefix.clear();
444         this.iriToAlternativeIri.clear();
445 
446         return this;
447     }
448 
449     public ObservableMap<String, String> getIriToAlternativeIri() {
450         return iriToAlternativeIri;
451     }
452 
453 }