1   /*
2    * RDFpro - An extensible tool for building stream-oriented RDF processing libraries.
3    * 
4    * Written in 2014 by Francesco Corcoglioniti with support by Marco Amadori, Michele Mostarda,
5    * Alessio Palmero Aprosio and Marco Rospocher. Contact info on http://rdfpro.fbk.eu/
6    * 
7    * To the extent possible under law, the authors have dedicated all copyright and related and
8    * neighboring rights to this software to the public domain worldwide. This software is
9    * distributed without any warranty.
10   * 
11   * You should have received a copy of the CC0 Public Domain Dedication along with this software.
12   * If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13   */
14  package eu.fbk.rdfpro;
15  
16  import java.math.BigDecimal;
17  import java.math.BigInteger;
18  import java.util.Date;
19  import java.util.GregorianCalendar;
20  import java.util.Objects;
21  import java.util.UUID;
22  import java.util.regex.Pattern;
23  
24  import javax.annotation.Nullable;
25  import javax.xml.datatype.DatatypeConfigurationException;
26  import javax.xml.datatype.DatatypeFactory;
27  import javax.xml.datatype.XMLGregorianCalendar;
28  
29  import org.openrdf.model.BNode;
30  import org.openrdf.model.Literal;
31  import org.openrdf.model.URI;
32  import org.openrdf.model.Value;
33  import org.openrdf.model.datatypes.XMLDatatypeUtil;
34  import org.openrdf.model.vocabulary.RDF;
35  import org.openrdf.model.vocabulary.XMLSchema;
36  import org.openrdf.query.algebra.evaluation.ValueExprEvaluationException;
37  import org.openrdf.query.algebra.evaluation.function.BooleanCast;
38  import org.openrdf.query.algebra.evaluation.function.DateTimeCast;
39  import org.openrdf.query.algebra.evaluation.function.DecimalCast;
40  import org.openrdf.query.algebra.evaluation.function.DoubleCast;
41  import org.openrdf.query.algebra.evaluation.function.FloatCast;
42  import org.openrdf.query.algebra.evaluation.function.Function;
43  import org.openrdf.query.algebra.evaluation.function.IntegerCast;
44  import org.openrdf.query.algebra.evaluation.function.datetime.Day;
45  import org.openrdf.query.algebra.evaluation.function.datetime.Hours;
46  import org.openrdf.query.algebra.evaluation.function.datetime.Minutes;
47  import org.openrdf.query.algebra.evaluation.function.datetime.Month;
48  import org.openrdf.query.algebra.evaluation.function.datetime.Now;
49  import org.openrdf.query.algebra.evaluation.function.datetime.Seconds;
50  import org.openrdf.query.algebra.evaluation.function.datetime.Timezone;
51  import org.openrdf.query.algebra.evaluation.function.datetime.Tz;
52  import org.openrdf.query.algebra.evaluation.function.datetime.Year;
53  import org.openrdf.query.algebra.evaluation.function.hash.MD5;
54  import org.openrdf.query.algebra.evaluation.function.hash.SHA1;
55  import org.openrdf.query.algebra.evaluation.function.hash.SHA256;
56  import org.openrdf.query.algebra.evaluation.function.hash.SHA384;
57  import org.openrdf.query.algebra.evaluation.function.hash.SHA512;
58  import org.openrdf.query.algebra.evaluation.function.numeric.Abs;
59  import org.openrdf.query.algebra.evaluation.function.numeric.Ceil;
60  import org.openrdf.query.algebra.evaluation.function.numeric.Floor;
61  import org.openrdf.query.algebra.evaluation.function.numeric.Rand;
62  import org.openrdf.query.algebra.evaluation.function.numeric.Round;
63  import org.openrdf.query.algebra.evaluation.function.rdfterm.StrDt;
64  import org.openrdf.query.algebra.evaluation.function.rdfterm.StrLang;
65  import org.openrdf.query.algebra.evaluation.function.string.Concat;
66  import org.openrdf.query.algebra.evaluation.function.string.Contains;
67  import org.openrdf.query.algebra.evaluation.function.string.EncodeForUri;
68  import org.openrdf.query.algebra.evaluation.function.string.LowerCase;
69  import org.openrdf.query.algebra.evaluation.function.string.Replace;
70  import org.openrdf.query.algebra.evaluation.function.string.StrAfter;
71  import org.openrdf.query.algebra.evaluation.function.string.StrBefore;
72  import org.openrdf.query.algebra.evaluation.function.string.StrEnds;
73  import org.openrdf.query.algebra.evaluation.function.string.StrLen;
74  import org.openrdf.query.algebra.evaluation.function.string.StrStarts;
75  import org.openrdf.query.algebra.evaluation.function.string.Substring;
76  import org.openrdf.query.algebra.evaluation.function.string.UpperCase;
77  import org.openrdf.query.algebra.evaluation.util.QueryEvaluationUtil;
78  
79  import eu.fbk.rdfpro.GroovyProcessor.GroovyLiteral;
80  import eu.fbk.rdfpro.util.Hash;
81  import eu.fbk.rdfpro.util.Statements;
82  
83  final class SparqlFunctions {
84  
85      private static final DatatypeFactory DATATYPE_FACTORY;
86  
87      static {
88          try {
89              DATATYPE_FACTORY = DatatypeFactory.newInstance();
90          } catch (final DatatypeConfigurationException ex) {
91              throw new Error("Could not instantiate javax.xml.datatype.DatatypeFactory", ex);
92          }
93      }
94  
95      public static boolean isiri(final Object arg) {
96          return toRDF(arg) instanceof URI;
97      }
98  
99      public static boolean isblank(final Object arg) {
100         return toRDF(arg) instanceof BNode;
101     }
102 
103     public static boolean isliteral(final Object arg) {
104         return toRDF(arg) instanceof Literal;
105     }
106 
107     public static boolean isnumeric(final Object arg) {
108         final Value value = toRDF(arg);
109         if (value instanceof Literal) {
110             final URI datatype = ((Literal) value).getDatatype();
111             return XMLDatatypeUtil.isNumericDatatype(datatype);
112         }
113         return false;
114     }
115 
116     public static String str(final Object arg) {
117         final Value value = toRDF(arg);
118         if (value instanceof URI || value instanceof Literal) {
119             return value.stringValue();
120         }
121         throw new IllegalArgumentException("str() argument is not an URI or literal");
122     }
123 
124     public static String lang(final Object arg) {
125         final Value value = toRDF(arg);
126         if (value instanceof Literal) {
127             final String lang = ((Literal) value).getLanguage();
128             return lang == null ? "" : lang;
129         }
130         throw new IllegalArgumentException("lang() argument is not a literal");
131     }
132 
133     public static URI datatype(final Object arg) {
134         final Value value = toRDF(arg);
135         if (value instanceof Literal) {
136             final Literal literal = (Literal) value;
137             final URI datatype = literal.getDatatype();
138             if (datatype != null) {
139                 return datatype;
140             } else if (literal.getLanguage() != null) {
141                 return RDF.LANGSTRING;
142             } else {
143                 return XMLSchema.STRING;
144             }
145         }
146         throw new IllegalArgumentException("datatype() argument is not a literal");
147     }
148 
149     public static URI iri(final Object arg) {
150         if (arg instanceof URI) {
151             return (URI) arg;
152         }
153         Objects.requireNonNull(arg);
154         return Statements.VALUE_FACTORY.createURI(arg.toString());
155     }
156 
157     public static BNode bnode() {
158         return Statements.VALUE_FACTORY.createBNode();
159     }
160 
161     public static BNode bnode(final Object arg) {
162         if (arg instanceof BNode) {
163             return (BNode) arg;
164         }
165         Objects.requireNonNull(arg);
166         return Statements.VALUE_FACTORY.createBNode(Hash.murmur3(arg.toString()).toString());
167     }
168 
169     public static Literal strdt(final Object value, final Object datatype) {
170         return (Literal) evaluate(new StrDt(), value, datatype);
171     }
172 
173     public static Literal strlang(@Nullable final Object value, @Nullable final Object lang) {
174         return (Literal) evaluate(new StrLang(), value, lang);
175     }
176 
177     public static URI uuid() {
178         return Statements.VALUE_FACTORY.createURI("urn:uuid:" + UUID.randomUUID().toString());
179     }
180 
181     public static String struuid() {
182         return UUID.randomUUID().toString();
183     }
184 
185     public static int strlen(final Object arg) {
186         return ((Literal) evaluate(new StrLen(), arg)).intValue();
187     }
188 
189     public static Literal substr(final Object string, final Object from) {
190         return (Literal) evaluate(new Substring(), string, from);
191     }
192 
193     public static Literal substr(final Object string, final Object from, final Object length) {
194         return (Literal) evaluate(new Substring(), string, from);
195     }
196 
197     public static Literal ucase(final Object arg) {
198         return (Literal) evaluate(new UpperCase(), arg);
199     }
200 
201     public static Literal lcase(final Object arg) {
202         return (Literal) evaluate(new LowerCase(), arg);
203     }
204 
205     public static boolean strstarts(final Object arg1, final Object arg2) {
206         return ((Literal) evaluate(new StrStarts(), arg1, arg2)).booleanValue();
207     }
208 
209     public static boolean strends(final Object arg1, final Object arg2) {
210         return ((Literal) evaluate(new StrEnds(), arg1, arg2)).booleanValue();
211     }
212 
213     public static boolean contains(final Object arg1, final Object arg2) {
214         return ((Literal) evaluate(new Contains(), arg1, arg2)).booleanValue();
215     }
216 
217     public static Literal strbefore(final Object arg1, final Object arg2) {
218         return (Literal) evaluate(new StrBefore(), arg1, arg2);
219     }
220 
221     public static Literal strafter(final Object arg1, final Object arg2) {
222         return (Literal) evaluate(new StrAfter(), arg1, arg2);
223     }
224 
225     public static String encode_for_uri(final Object arg) {
226         return ((Literal) evaluate(new EncodeForUri(), arg)).stringValue();
227     }
228 
229     public static Literal concat(final Object... args) {
230         return (Literal) evaluate(new Concat(), args);
231     }
232 
233     public static boolean langmatches(final Object languageTag, final Object languageRange) {
234 
235         final Value tagValue = toRDF(languageTag);
236         final Value rangeValue = toRDF(languageRange);
237 
238         if (QueryEvaluationUtil.isSimpleLiteral(tagValue)
239                 && QueryEvaluationUtil.isSimpleLiteral(rangeValue)) {
240 
241             final String tag = ((Literal) tagValue).getLabel();
242             final String range = ((Literal) rangeValue).getLabel();
243 
244             if (range.equals("*")) {
245                 return tag.length() > 0;
246             } else if (tag.length() == range.length()) {
247                 return tag.equalsIgnoreCase(range);
248             } else if (tag.length() > range.length()) {
249                 final String prefix = tag.substring(0, range.length());
250                 return prefix.equalsIgnoreCase(range) && tag.charAt(range.length()) == '-';
251             } else {
252                 return false;
253             }
254         }
255 
256         throw new IllegalArgumentException("LANGMATCHES() cannot be applied to " + languageTag
257                 + ", " + languageRange);
258     }
259 
260     public static boolean regex(final Object text, final Object pattern) {
261         return regex(text, pattern, "");
262     }
263 
264     public static boolean regex(final Object text, final Object pattern,
265             @Nullable final Object flags) {
266 
267         final Value textValue = toRDF(text);
268         final Value patternValue = toRDF(pattern);
269         final Value flagsValue = toRDF(flags);
270 
271         if (QueryEvaluationUtil.isStringLiteral(textValue)
272                 && QueryEvaluationUtil.isSimpleLiteral(patternValue)
273                 && QueryEvaluationUtil.isSimpleLiteral(flagsValue)) {
274 
275             final String textStr = ((Literal) textValue).getLabel();
276             final String patternStr = ((Literal) patternValue).getLabel();
277             final String flagsStr = ((Literal) flagsValue).getLabel();
278 
279             int f = 0;
280             for (final char c : flagsStr.toCharArray()) {
281                 switch (c) {
282                 case 's':
283                     f |= Pattern.DOTALL;
284                     break;
285                 case 'm':
286                     f |= Pattern.MULTILINE;
287                     break;
288                 case 'i':
289                     f |= Pattern.CASE_INSENSITIVE;
290                     break;
291                 case 'x':
292                     f |= Pattern.COMMENTS;
293                     break;
294                 case 'd':
295                     f |= Pattern.UNIX_LINES;
296                     break;
297                 case 'u':
298                     f |= Pattern.UNICODE_CASE;
299                     break;
300                 default:
301                     throw new IllegalArgumentException("REGEX() flag not valid: " + c);
302                 }
303             }
304             return Pattern.compile(patternStr, f).matcher(textStr).find();
305         }
306 
307         throw new IllegalArgumentException("REGEX() arguments not valid: " + text + ", " + pattern
308                 + (flags == null ? "" : ", " + flags));
309     }
310 
311     public static Literal replace(final Object arg, final Object pattern, final Object replacement) {
312         return replace(arg, pattern, replacement, "");
313     }
314 
315     public static Literal replace(final Object arg, final Object pattern,
316             final Object replacement, final Object flags) {
317         return (Literal) evaluate(new Replace(), arg, pattern, replacement, flags);
318     }
319 
320     public static double abs(final Object arg) {
321         return ((Literal) evaluate(new Abs(), arg)).doubleValue();
322     }
323 
324     public static long round(final Object arg) {
325         return ((Literal) evaluate(new Round(), arg)).longValue();
326     }
327 
328     public static long ceil(final Object arg) {
329         return ((Literal) evaluate(new Ceil(), arg)).longValue();
330     }
331 
332     public static long floor(final Object arg) {
333         return ((Literal) evaluate(new Floor(), arg)).longValue();
334     }
335 
336     public static double rand() {
337         return ((Literal) evaluate(new Rand())).doubleValue();
338     }
339 
340     public static Literal now() {
341         return (Literal) evaluate(new Now());
342     }
343 
344     public static int year(final Object arg) {
345         return ((Literal) evaluate(new Year(), arg)).intValue();
346     }
347 
348     public static int month(final Object arg) {
349         return ((Literal) evaluate(new Month(), arg)).intValue();
350     }
351 
352     public static int day(final Object arg) {
353         return ((Literal) evaluate(new Day(), arg)).intValue();
354     }
355 
356     public static int hours(final Object arg) {
357         return ((Literal) evaluate(new Hours(), arg)).intValue();
358     }
359 
360     public static int minutes(final Object arg) {
361         return ((Literal) evaluate(new Minutes(), arg)).intValue();
362     }
363 
364     public static double seconds(final Object arg) {
365         return ((Literal) evaluate(new Seconds(), arg)).doubleValue();
366     }
367 
368     public static Literal timezone(final Object arg) {
369         return (Literal) evaluate(new Timezone(), arg);
370     }
371 
372     public static String tz(final Object arg) {
373         return ((Literal) evaluate(new Tz(), arg)).stringValue();
374     }
375 
376     public static String md5(final Object arg) {
377         return ((Literal) evaluate(new MD5(), arg)).stringValue();
378     }
379 
380     public static String sha1(final Object arg) {
381         return ((Literal) evaluate(new SHA1(), arg)).stringValue();
382     }
383 
384     public static String sha256(final Object arg) {
385         return ((Literal) evaluate(new SHA256(), arg)).stringValue();
386     }
387 
388     public static String sha384(final Object arg) {
389         return ((Literal) evaluate(new SHA384(), arg)).stringValue();
390     }
391 
392     public static String sha512(final Object arg) {
393         return ((Literal) evaluate(new SHA512(), arg)).stringValue();
394     }
395 
396     public static boolean bool(final Object arg) {
397         return ((Literal) evaluate(new BooleanCast(), arg)).booleanValue();
398     }
399 
400     public static double dbl(final Object arg) {
401         return ((Literal) evaluate(new DoubleCast(), arg)).doubleValue();
402     }
403 
404     public static float flt(final Object arg) {
405         return ((Literal) evaluate(new FloatCast(), arg)).floatValue();
406     }
407 
408     public static BigDecimal dec(final Object arg) {
409         return ((Literal) evaluate(new DecimalCast(), arg)).decimalValue();
410     }
411 
412     public static BigInteger integer(final Object arg) {
413         return ((Literal) evaluate(new IntegerCast(), arg)).integerValue();
414     }
415 
416     public static Literal dt(final Object arg) {
417         return (Literal) evaluate(new DateTimeCast(), arg);
418     }
419 
420     public static Value evaluate(final Function function, final Object... args) {
421         final Value[] values = new Value[args.length];
422         for (int i = 0; i < args.length; i++) {
423             values[i] = toRDF(args[i]);
424         }
425         try {
426             return function.evaluate(Statements.VALUE_FACTORY, values);
427         } catch (final ValueExprEvaluationException ex) {
428             throw new IllegalArgumentException(ex);
429         }
430     }
431 
432     public static Value toRDF(final Object object) {
433         if (object instanceof Value) {
434             return (Value) object;
435         } else if (object instanceof Long) {
436             return new GroovyLiteral(object.toString(), XMLSchema.LONG);
437         } else if (object instanceof Integer) {
438             return new GroovyLiteral(object.toString(), XMLSchema.INT);
439         } else if (object instanceof Short) {
440             return new GroovyLiteral(object.toString(), XMLSchema.SHORT);
441         } else if (object instanceof Byte) {
442             return new GroovyLiteral(object.toString(), XMLSchema.BYTE);
443         } else if (object instanceof Double) {
444             return new GroovyLiteral(object.toString(), XMLSchema.DOUBLE);
445         } else if (object instanceof Float) {
446             return new GroovyLiteral(object.toString(), XMLSchema.FLOAT);
447         } else if (object instanceof Boolean) {
448             return new GroovyLiteral(object.toString(), XMLSchema.BOOLEAN);
449         } else if (object instanceof XMLGregorianCalendar) {
450             final XMLGregorianCalendar c = (XMLGregorianCalendar) object;
451             return new GroovyLiteral(c.toXMLFormat(), XMLDatatypeUtil.qnameToURI(c
452                     .getXMLSchemaType()));
453         } else if (object instanceof Date) {
454             final GregorianCalendar c = new GregorianCalendar();
455             c.setTime((Date) object);
456             final XMLGregorianCalendar xc = DATATYPE_FACTORY.newXMLGregorianCalendar(c);
457             return new GroovyLiteral(xc.toXMLFormat(), XMLDatatypeUtil.qnameToURI(xc
458                     .getXMLSchemaType()));
459         } else if (object instanceof CharSequence) {
460             return new GroovyLiteral(object.toString(), XMLSchema.STRING);
461         } else if (object != null) {
462             return new GroovyLiteral(object.toString());
463         }
464         throw new NullPointerException();
465     }
466 
467 }