1   /*
2    * RDFpro - An extensible tool for building stream-oriented RDF processing libraries.
3    * 
4    * Written in 2015 by Francesco Corcoglioniti with support by Alessio Palmero Aprosio and Marco
5    * 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.util;
15  
16  import java.util.ArrayList;
17  import java.util.Collections;
18  import java.util.HashMap;
19  import java.util.HashSet;
20  import java.util.LinkedHashSet;
21  import java.util.List;
22  import java.util.Map;
23  import java.util.Objects;
24  import java.util.Set;
25  import java.util.concurrent.atomic.AtomicInteger;
26  import java.util.concurrent.atomic.AtomicReference;
27  
28  import javax.annotation.Nullable;
29  
30  import org.openrdf.model.BNode;
31  import org.openrdf.model.Literal;
32  import org.openrdf.model.URI;
33  import org.openrdf.model.impl.URIImpl;
34  import org.openrdf.model.vocabulary.FN;
35  import org.openrdf.model.vocabulary.XMLSchema;
36  import org.openrdf.query.BindingSet;
37  import org.openrdf.query.Dataset;
38  import org.openrdf.query.algebra.Add;
39  import org.openrdf.query.algebra.And;
40  import org.openrdf.query.algebra.ArbitraryLengthPath;
41  import org.openrdf.query.algebra.Avg;
42  import org.openrdf.query.algebra.BNodeGenerator;
43  import org.openrdf.query.algebra.BinaryValueOperator;
44  import org.openrdf.query.algebra.BindingSetAssignment;
45  import org.openrdf.query.algebra.Bound;
46  import org.openrdf.query.algebra.Clear;
47  import org.openrdf.query.algebra.Coalesce;
48  import org.openrdf.query.algebra.Compare;
49  import org.openrdf.query.algebra.Compare.CompareOp;
50  import org.openrdf.query.algebra.CompareAll;
51  import org.openrdf.query.algebra.CompareAny;
52  import org.openrdf.query.algebra.Copy;
53  import org.openrdf.query.algebra.Count;
54  import org.openrdf.query.algebra.Create;
55  import org.openrdf.query.algebra.Datatype;
56  import org.openrdf.query.algebra.DeleteData;
57  import org.openrdf.query.algebra.DescribeOperator;
58  import org.openrdf.query.algebra.Difference;
59  import org.openrdf.query.algebra.Distinct;
60  import org.openrdf.query.algebra.EmptySet;
61  import org.openrdf.query.algebra.Exists;
62  import org.openrdf.query.algebra.Extension;
63  import org.openrdf.query.algebra.ExtensionElem;
64  import org.openrdf.query.algebra.Filter;
65  import org.openrdf.query.algebra.FunctionCall;
66  import org.openrdf.query.algebra.Group;
67  import org.openrdf.query.algebra.GroupConcat;
68  import org.openrdf.query.algebra.GroupElem;
69  import org.openrdf.query.algebra.IRIFunction;
70  import org.openrdf.query.algebra.If;
71  import org.openrdf.query.algebra.In;
72  import org.openrdf.query.algebra.InsertData;
73  import org.openrdf.query.algebra.Intersection;
74  import org.openrdf.query.algebra.IsBNode;
75  import org.openrdf.query.algebra.IsLiteral;
76  import org.openrdf.query.algebra.IsNumeric;
77  import org.openrdf.query.algebra.IsResource;
78  import org.openrdf.query.algebra.IsURI;
79  import org.openrdf.query.algebra.Join;
80  import org.openrdf.query.algebra.Label;
81  import org.openrdf.query.algebra.Lang;
82  import org.openrdf.query.algebra.LangMatches;
83  import org.openrdf.query.algebra.LeftJoin;
84  import org.openrdf.query.algebra.Like;
85  import org.openrdf.query.algebra.ListMemberOperator;
86  import org.openrdf.query.algebra.Load;
87  import org.openrdf.query.algebra.LocalName;
88  import org.openrdf.query.algebra.MathExpr;
89  import org.openrdf.query.algebra.MathExpr.MathOp;
90  import org.openrdf.query.algebra.Max;
91  import org.openrdf.query.algebra.Min;
92  import org.openrdf.query.algebra.Modify;
93  import org.openrdf.query.algebra.Move;
94  import org.openrdf.query.algebra.MultiProjection;
95  import org.openrdf.query.algebra.Namespace;
96  import org.openrdf.query.algebra.Not;
97  import org.openrdf.query.algebra.Or;
98  import org.openrdf.query.algebra.Order;
99  import org.openrdf.query.algebra.OrderElem;
100 import org.openrdf.query.algebra.Projection;
101 import org.openrdf.query.algebra.ProjectionElem;
102 import org.openrdf.query.algebra.ProjectionElemList;
103 import org.openrdf.query.algebra.QueryModelNode;
104 import org.openrdf.query.algebra.QueryModelVisitor;
105 import org.openrdf.query.algebra.QueryRoot;
106 import org.openrdf.query.algebra.Reduced;
107 import org.openrdf.query.algebra.Regex;
108 import org.openrdf.query.algebra.SameTerm;
109 import org.openrdf.query.algebra.Sample;
110 import org.openrdf.query.algebra.Service;
111 import org.openrdf.query.algebra.SingletonSet;
112 import org.openrdf.query.algebra.Slice;
113 import org.openrdf.query.algebra.StatementPattern;
114 import org.openrdf.query.algebra.StatementPattern.Scope;
115 import org.openrdf.query.algebra.Str;
116 import org.openrdf.query.algebra.Sum;
117 import org.openrdf.query.algebra.TupleExpr;
118 import org.openrdf.query.algebra.UnaryTupleOperator;
119 import org.openrdf.query.algebra.Union;
120 import org.openrdf.query.algebra.ValueConstant;
121 import org.openrdf.query.algebra.ValueExpr;
122 import org.openrdf.query.algebra.Var;
123 import org.openrdf.query.algebra.ZeroLengthPath;
124 import org.openrdf.query.algebra.helpers.QueryModelVisitorBase;
125 
126 final class SPARQLRenderer {
127 
128     private static final Map<String, String> NAMES;
129 
130     static {
131         final Map<String, String> names = new HashMap<>();
132         names.put("RAND", "RAND");
133         names.put("TZ", "TZ");
134         names.put("NOW", "NOW");
135         names.put("UUID", "UUID");
136         names.put("STRUUID", "STRUUID");
137         names.put("MD5", "MD5");
138         names.put("SHA1", "SHA1");
139         names.put("SHA256", "SHA256");
140         names.put("SHA384", "SHA384");
141         names.put("SHA512", "SHA512");
142         names.put("STRLANG", "STRLANG");
143         names.put("STRDT", "STRDT");
144         names.put(FN.STRING_LENGTH.stringValue(), "STRLEN");
145         names.put(FN.SUBSTRING.stringValue(), "SUBSTR");
146         names.put(FN.UPPER_CASE.stringValue(), "UCASE");
147         names.put(FN.LOWER_CASE.stringValue(), "LCASE");
148         names.put(FN.STARTS_WITH.stringValue(), "STRSTARTS");
149         names.put(FN.ENDS_WITH.stringValue(), "STRENDS");
150         names.put(FN.CONTAINS.stringValue(), "CONTAINS");
151         names.put(FN.SUBSTRING_BEFORE.stringValue(), "STRBEFORE");
152         names.put(FN.SUBSTRING_AFTER.stringValue(), "STRAFTER");
153         names.put(FN.ENCODE_FOR_URI.stringValue(), "ENCODE_FOR_URI");
154         names.put(FN.CONCAT.stringValue(), "CONCAT");
155         names.put(FN.NAMESPACE + "matches", "REGEX");
156         names.put(FN.REPLACE.stringValue(), "REPLACE");
157         names.put(FN.NUMERIC_ABS.stringValue(), "ABS");
158         names.put(FN.NUMERIC_ROUND.stringValue(), "ROUND");
159         names.put(FN.NUMERIC_CEIL.stringValue(), "CEIL");
160         names.put(FN.NUMERIC_FLOOR.stringValue(), "FLOOR");
161         names.put(FN.YEAR_FROM_DATETIME.stringValue(), "YEAR");
162         names.put(FN.MONTH_FROM_DATETIME.stringValue(), "MONTH");
163         names.put(FN.DAY_FROM_DATETIME.stringValue(), "DAY");
164         names.put(FN.HOURS_FROM_DATETIME.stringValue(), "HOURS");
165         names.put(FN.MINUTES_FROM_DATETIME.stringValue(), "MINUTES");
166         names.put(FN.SECONDS_FROM_DATETIME.stringValue(), "SECONDS");
167         names.put(FN.TIMEZONE_FROM_DATETIME.stringValue(), "TIMEZONE");
168         NAMES = Collections.unmodifiableMap(names);
169 
170     }
171 
172     private final Map<String, String> prefixes;
173 
174     private final boolean forceSelect;
175 
176     public SPARQLRenderer(@Nullable final Map<String, String> prefixes,
177             @Nullable final boolean forceSelect) {
178         this.prefixes = prefixes != null ? prefixes : Collections.<String, String>emptyMap();
179         this.forceSelect = forceSelect;
180     }
181 
182     public String render(final TupleExpr expr, @Nullable final Dataset dataset) {
183         final Rendering rendering = new Rendering(Algebra.normalizeVars(expr), dataset, true);
184         final StringBuilder builder = new StringBuilder();
185         boolean newline = false;
186         if (!rendering.namespaces.isEmpty()) {
187             final List<String> sortedNamespaces = new ArrayList<>(rendering.namespaces);
188             Collections.sort(sortedNamespaces);
189             for (final String namespace : sortedNamespaces) {
190                 final String prefix = this.prefixes.get(namespace);
191                 if ("bif".equals(prefix) && "http://www.openlinksw.com/schema/sparql/extensions#" //
192                         .equals(namespace)) {
193                     continue; // do not emit Virtuoso bif: binding, as Virtuoso will complain
194                 }
195                 builder.append("PREFIX ").append(prefix).append(": <");
196                 escape(namespace, builder);
197                 builder.append(">\n");
198                 newline = true;
199             }
200         }
201         if (rendering.base != null) {
202             builder.append("BASE <");
203             escape(rendering.base, builder);
204             builder.append(">\n");
205             newline = true;
206         }
207         if (newline) {
208             builder.append("\n");
209         }
210         builder.append(rendering.body);
211         return builder.toString();
212     }
213 
214     public String renderTupleExpr(final TupleExpr expr) {
215         return new Rendering(Algebra.normalizeVars(expr), null, false).body;
216     }
217 
218     // Helper functions
219 
220     private static void escape(final String label, final StringBuilder builder) {
221         final int length = label.length();
222         for (int i = 0; i < length; ++i) {
223             final char c = label.charAt(i);
224             if (c == '\\') {
225                 builder.append("\\\\");
226             } else if (c == '"') {
227                 builder.append("\\\"");
228             } else if (c == '\n') {
229                 builder.append("\\n");
230             } else if (c == '\r') {
231                 builder.append("\\r");
232             } else if (c == '\t') {
233                 builder.append("\\t");
234             }
235             // TODO: not accepted by Virtuoso :-(
236             // else if (c >= 0x0 && c <= 0x8 || c == 0xB || c == 0xC || c >= 0xE && c <= 0x1F
237             // || c >= 0x7F && c <= 0xFFFF) {
238             // builder.append("\\u").append(
239             // Strings.padStart(Integer.toHexString(c).toUpperCase(), 4, '0'));
240             // } else if (c >= 0x10000 && c <= 0x10FFFF) {
241             // builder.append("\\U").append(
242             // Strings.padStart(Integer.toHexString(c).toUpperCase(), 8, '0'));
243             // }
244             else {
245                 builder.append(Character.toString(c));
246             }
247         }
248     }
249 
250     private static String sanitize(final String string) {
251         final int length = string.length();
252         final StringBuilder builder = new StringBuilder(length + 10);
253         for (int i = 0; i < length; ++i) {
254             final char ch = string.charAt(i);
255             if (Character.isLetter(ch) || ch == '_' || i > 0 && Character.isDigit(ch)) {
256                 builder.append(ch);
257             } else {
258                 builder.append("_");
259             }
260         }
261         return builder.toString();
262     }
263 
264     private static <T> boolean equalOrNull(@Nullable final T first, @Nullable final T second) {
265         return first != null && first.equals(second) || first == null && second == null;
266     }
267 
268     private static <T> T defaultIfNull(@Nullable final T value, @Nullable final T defaultValue) {
269         return value != null ? value : defaultValue;
270     }
271 
272     private static List<StatementPattern> getBGP(final QueryModelNode n) {
273         if (n instanceof StatementPattern) {
274             return Collections.singletonList((StatementPattern) n);
275         } else if (!(n instanceof Join)) {
276             return null;
277         }
278         final Join j = (Join) n;
279         final List<StatementPattern> l = getBGP(j.getLeftArg());
280         final List<StatementPattern> r = getBGP(j.getRightArg());
281         if (l == null || r == null) {
282             return null;
283         }
284         if (l.isEmpty()) {
285             return r;
286         } else if (r.isEmpty()) {
287             return l;
288         } else if (!equalOrNull(l.get(0).getContextVar(), r.get(0).getContextVar())) {
289             return null;
290         } else {
291             final List<StatementPattern> s = new ArrayList<StatementPattern>(l.size() + r.size());
292             s.addAll(l);
293             s.addAll(r);
294             return s;
295         }
296     }
297 
298     private static int getVarRefs(final QueryModelNode node, final String name) {
299         final AtomicInteger count = new AtomicInteger(0);
300         node.visit(new QueryModelVisitorBase<RuntimeException>() {
301 
302             @Override
303             public void meet(final Var var) {
304                 if (var.getName().equals(name)) {
305                     count.set(count.get() + 1);
306                 }
307             }
308 
309         });
310         return count.get();
311     }
312 
313     private static ValueExpr getVarExpr(final QueryModelNode node, final String name) {
314         final AtomicReference<ValueExpr> result = new AtomicReference<ValueExpr>(null);
315         node.visit(new QueryModelVisitorBase<RuntimeException>() {
316 
317             @Override
318             protected void meetNode(final QueryModelNode node) throws RuntimeException {
319                 if (result.get() == null) {
320                     super.meetNode(node);
321                 }
322             }
323 
324             @Override
325             public void meet(final Var var) {
326                 if (var.getName().equals(name) && var.getValue() != null) {
327                     result.set(new ValueConstant(var.getValue()));
328                 }
329             }
330 
331             @Override
332             public void meet(final ExtensionElem node) throws RuntimeException {
333                 if (node.getName().equals(name)) {
334                     result.set(node.getExpr());
335                 } else {
336                     super.meet(node);
337                 }
338             }
339 
340         });
341         return result.get();
342     }
343 
344     private static void check(final boolean condition, final String errorMessage) {
345         if (!condition) {
346             throw new IllegalArgumentException(errorMessage);
347         }
348     }
349 
350     private static <T> List<T> list(final Iterable<? extends T> iterable) {
351         final List<T> result = new ArrayList<>();
352         for (final T element : iterable) {
353             result.add(element);
354         }
355         return result;
356     }
357 
358     private final class Rendering implements QueryModelVisitor<RuntimeException> {
359 
360         final TupleExpr root;
361 
362         @Nullable
363         final Dataset dataset;
364 
365         final String body;
366 
367         String base;
368 
369         private final StringBuilder builder;
370 
371         private final Set<String> namespaces;
372 
373         private int indent;
374 
375         Rendering(final TupleExpr node, @Nullable final Dataset dataset, final boolean query) {
376             this.root = new QueryRoot(Objects.requireNonNull(node));
377             this.dataset = dataset;
378             this.builder = new StringBuilder();
379             this.namespaces = new HashSet<>();
380             this.indent = 0;
381             if (query) {
382                 emit(Query.create(this.root, this.dataset, SPARQLRenderer.this.forceSelect));
383             } else {
384                 emit(node);
385             }
386             this.body = this.builder.toString();
387             this.builder.setLength(0);
388         }
389 
390         // BASIC RENDERING METHODS (STRING, VALUES, CONDITIONALS, NEWLINE AND BRACES, ERRORS)
391 
392         private Rendering emitIf(final boolean condition, final Object object) {
393             if (condition) {
394                 emit(object);
395             }
396             return this;
397         }
398 
399         private Rendering emit(final Iterable<?> values, final String separator) {
400             boolean first = true;
401             for (final Object value : values) {
402                 if (!first) {
403                     emit(separator);
404                 }
405                 emit(value);
406                 first = false;
407             }
408             return this;
409         }
410 
411         @SuppressWarnings("unchecked")
412         private Rendering emit(final Object value) {
413             if (value instanceof String) {
414                 return emit((String) value);
415             } else if (value instanceof QueryModelNode) {
416                 emit((QueryModelNode) value);
417             } else if (value instanceof BNode) {
418                 emit((BNode) value);
419             } else if (value instanceof URI) {
420                 emit((URI) value);
421             } else if (value instanceof Literal) {
422                 emit((Literal) value);
423             } else if (value instanceof List<?>) {
424                 emit((List<StatementPattern>) value);
425             } else if (value instanceof Query) {
426                 emit((Query) value);
427             }
428             return this;
429         }
430 
431         private Rendering emit(final String string) {
432             this.builder.append(string);
433             return this;
434         }
435 
436         private Rendering emit(final Literal literal) {
437             if (XMLSchema.INTEGER.equals(literal.getDatatype())) {
438                 this.builder.append(literal.getLabel());
439             } else {
440                 this.builder.append("\"");
441                 escape(literal.getLabel(), this.builder);
442                 this.builder.append("\"");
443                 if (literal.getLanguage() != null) {
444                     this.builder.append("@").append(literal.getLanguage());
445                 } else if (literal.getDatatype() != null) {
446                     this.builder.append("^^");
447                     emit(literal.getDatatype());
448                 }
449             }
450             return this;
451         }
452 
453         private Rendering emit(final BNode bnode) {
454             this.builder.append("_:").append(bnode.getID());
455             return this;
456         }
457 
458         private Rendering emit(final URI uri) {
459             if (uri.getNamespace().equals("http://www.openlinksw.com/schema/sparql/extensions#")) {
460                 this.builder.append("bif:").append(uri.getLocalName()); // for Virtuoso builtins
461             } else {
462                 final String prefix = SPARQLRenderer.this.prefixes.get(uri.getNamespace());
463                 if (prefix != null) {
464                     if (this.namespaces != null) {
465                         this.namespaces.add(uri.getNamespace());
466                     }
467                     this.builder.append(prefix).append(':').append(uri.getLocalName());
468                 } else {
469                     this.builder.append("<");
470                     escape(uri.toString(), this.builder);
471                     this.builder.append(">");
472                 }
473             }
474             return this;
475         }
476 
477         private Rendering emit(final List<StatementPattern> bgp) {
478             if (bgp.isEmpty()) {
479                 return this;
480             }
481             final Var c = bgp.get(0).getContextVar();
482             if (c != null) {
483                 emit("GRAPH ").emit(c).emit(" ").openBrace();
484             }
485             StatementPattern l = null;
486             for (final StatementPattern n : bgp) {
487                 final Var s = n.getSubjectVar();
488                 final Var p = n.getPredicateVar();
489                 final Var o = n.getObjectVar();
490                 if (l == null) {
491                     emit(s).emit(" ").emit(p).emit(" ").emit(o); // s p o
492                 } else if (!l.getSubjectVar().equals(n.getSubjectVar())) {
493                     emit(" .").newline().emit(s).emit(" ").emit(p).emit(" ").emit(o); // .\n s p o
494                 } else if (!l.getPredicateVar().equals(n.getPredicateVar())) {
495                     emit(" ;").newline().emit("\t").emit(p).emit(" ").emit(o); // ;\n\t p o
496                 } else if (!l.getObjectVar().equals(n.getObjectVar())) {
497                     emit(" , ").emit(o); // , o
498                 }
499                 l = n;
500             }
501             emit(" .");
502             if (c != null) {
503                 closeBrace();
504             }
505             return this;
506         }
507 
508         private Rendering emit(final Query query) {
509             if (query.root != this.root) {
510                 openBrace();
511             }
512             if (query.form == Form.ASK) {
513                 emit("ASK");
514             } else if (query.form == Form.CONSTRUCT) {
515                 emit("CONSTRUCT ").openBrace().emit(query.construct).closeBrace();
516             } else if (query.form == Form.DESCRIBE) {
517                 emit("DESCRIBE");
518                 for (final ProjectionElem p : query.select) {
519                     final ExtensionElem e = p.getSourceExpression();
520                     emit(" ").emit(
521                             e != null && e.getExpr() instanceof ValueConstant ? e.getExpr() : p);
522                 }
523             } else if (query.form == Form.SELECT) {
524                 emit("SELECT");
525                 if (query.modifier != null) {
526                     emit(" ").emit(query.modifier.toString().toUpperCase());
527                 }
528                 if (query.select.isEmpty()) {
529                     int count = 0;
530                     for (final String var : query.where.getBindingNames()) {
531                         final ValueExpr expr = getVarExpr(query.where, var);
532                         if (!var.startsWith("-")) {
533                             if (expr == null) {
534                                 emit(" ?").emit(var);
535                             } else {
536                                 emit(" (").emit(expr).emit(" AS ?").emit(var).emit(")");
537                             }
538                             ++count;
539                         }
540                     }
541                     if (count == 0) {
542                         emit(" *");
543                     }
544                 } else {
545                     emit(" ").emit(query.select, " ");
546                 }
547             }
548             if (query.from != null) {
549                 for (final URI uri : query.from.getDefaultGraphs()) {
550                     newline().emit("FROM ").emit(uri);
551                 }
552                 for (final URI uri : query.from.getNamedGraphs()) {
553                     newline().emit("FROM NAMED ").emit(uri);
554                 }
555             }
556             if (query.form != Form.DESCRIBE || !(query.where instanceof SingletonSet)) {
557                 newline().emit("WHERE ").openBrace().emit(query.where).closeBrace();
558             }
559             if (!query.groupBy.isEmpty()) {
560                 newline().emit("GROUP BY");
561                 for (final ProjectionElem n : query.groupBy) {
562                     emit(" ?").emit(n.getTargetName());
563                 }
564             }
565             if (query.having != null) {
566                 newline().emit("HAVING (").emit(query.having).emit(")");
567             }
568             if (!query.orderBy.isEmpty()) {
569                 newline().emit("ORDER BY ").emit(query.orderBy, " ");
570             }
571             if (query.form != Form.ASK) {
572                 if (query.offset != null) {
573                     newline().emit("OFFSET " + query.offset);
574                 }
575                 if (query.limit != null) {
576                     newline().emit("LIMIT " + query.limit);
577                     // newline().emit("LIMIT " + (query.limit + 1)); // TODO Virtuoso fix :-(
578                 }
579             }
580             if (query.root != this.root) {
581                 closeBrace();
582             }
583             return this;
584         }
585 
586         private Rendering emit(final QueryModelNode n) {
587             final QueryModelNode p = n.getParentNode();
588             final boolean braces = n instanceof TupleExpr && p != null
589                     && !(p instanceof TupleExpr);
590             if (braces) {
591                 openBrace();
592             }
593             n.visit(this);
594             if (braces) {
595                 closeBrace();
596             }
597             return this;
598         }
599 
600         private Rendering emit(final QueryModelNode node, final boolean parenthesis) { // TODO
601             if (parenthesis) {
602                 if (node instanceof TupleExpr) {
603                     openBrace();
604                 } else {
605                     emit("(");
606                 }
607             }
608             emit(node);
609             if (parenthesis) {
610                 if (node instanceof TupleExpr) {
611                     closeBrace();
612                 } else {
613                     emit(")");
614                 }
615             }
616             return this;
617         }
618 
619         private Rendering openBrace() {
620             emit("{");
621             ++this.indent;
622             newline();
623             return this;
624         }
625 
626         private Rendering closeBrace() {
627             --this.indent;
628             newline();
629             emit("}");
630             return this;
631         }
632 
633         private Rendering newline() {
634             emit("\n");
635             for (int i = 0; i < this.indent; ++i) {
636                 emit("\t");
637             }
638             return this;
639         }
640 
641         private Rendering fail(final String message, final QueryModelNode node) {
642             throw new IllegalArgumentException("SPARQL rendering failed. " + message
643                     + (node == null ? "null" : node.getClass().getSimpleName() + "\n" + node));
644         }
645 
646         // TupleExpr: root query nodes
647 
648         @Override
649         public void meet(final OrderElem n) {
650             emit(n.isAscending() ? "ASC(" : "DESC(").emit(n.getExpr()).emit(")");
651         }
652 
653         @Override
654         public void meet(final ProjectionElemList node) {
655             emit(node.getElements(), " ");
656         }
657 
658         @Override
659         public void meet(final ProjectionElem n) {
660 
661             final String source = n.getSourceName();
662             final String target = n.getTargetName();
663             final ValueExpr expr = n.getSourceExpression() == null ? null : n
664                     .getSourceExpression().getExpr();
665 
666             if (target.startsWith("-")) {
667                 if (expr != null) {
668                     emit("(").emit(expr).emit(" AS ?").emit(sanitize(target)).emit(")");
669                 }
670             } else if (expr != null) {
671                 emit("(").emit(expr).emit(" AS ?").emit(target).emit(")");
672             } else if (!equalOrNull(source, target)) {
673                 emit("(?").emit(source).emit(" AS ?").emit(target).emit(")");
674             } else {
675                 emit("?").emit(target);
676             }
677         }
678 
679         @Override
680         public void meet(final GroupElem n) {
681             final ProjectionElem e = new ProjectionElem();
682             e.setTargetName(n.getName());
683             e.setSourceName(n.getName());
684             e.setSourceExpression(new ExtensionElem(n.getOperator(), n.getName()));
685             meet(e);
686         }
687 
688         @Override
689         public void meet(final DescribeOperator n) {
690             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
691         }
692 
693         @Override
694         public void meet(final QueryRoot n) {
695             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
696         }
697 
698         @Override
699         public void meet(final Projection n) {
700             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
701         }
702 
703         @Override
704         public void meet(final MultiProjection n) {
705             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
706         }
707 
708         @Override
709         public void meet(final Distinct n) {
710             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
711         }
712 
713         @Override
714         public void meet(final Reduced n) {
715             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
716         }
717 
718         @Override
719         public void meet(final Group n) {
720             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
721         }
722 
723         @Override
724         public void meet(final Order n) {
725             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
726         }
727 
728         @Override
729         public void meet(final Slice n) {
730             emit(Query.create(n, null, SPARQLRenderer.this.forceSelect));
731         }
732 
733         // TupleExpr: leaf nodes
734 
735         @Override
736         public void meet(final EmptySet n) {
737             final QueryModelNode p = n.getParentNode();
738             if (p.getParentNode() != null && !(p.getParentNode() instanceof QueryRoot)) {
739                 throw new IllegalArgumentException(
740                         "Cannot translate EmptySet inside the body of a query / update operation");
741             }
742             emit("CONSTRUCT {} WHERE {}");
743         }
744 
745         @Override
746         public void meet(final SingletonSet n) {
747             // nothing to do: braces, if necessary, are emitted by parent
748         }
749 
750         @Override
751         public void meet(final BindingSetAssignment n) {
752 
753             final Set<String> names = n.getBindingNames();
754 
755             if (names.isEmpty()) {
756                 emit("VALUES {}");
757 
758             } else if (names.size() == 1) {
759                 final String name = names.iterator().next();
760                 emit("VALUES ?").emit(name).emit(" ").openBrace();
761                 boolean first = true;
762                 for (final BindingSet bindings : n.getBindingSets()) {
763                     emitIf(!first, " ").emit(defaultIfNull(bindings.getValue(name), "UNDEF"));
764                     first = false;
765                 }
766                 closeBrace();
767 
768             } else {
769                 emit("VALUES (?").emit(names, " ?").emit(") ").openBrace();
770                 boolean firstBinding = true;
771                 for (final BindingSet bindings : n.getBindingSets()) {
772                     if (!firstBinding) {
773                         newline();
774                     }
775                     emit("(");
776                     boolean first = true;
777                     for (final String name : names) {
778                         emitIf(!first, " ").emit(defaultIfNull(bindings.getValue(name), "UNDEF"));
779                         first = false;
780                     }
781                     emit(")");
782                     firstBinding = false;
783                 }
784                 closeBrace();
785             }
786         }
787 
788         @Override
789         public void meet(final StatementPattern n) {
790             emit(getBGP(n));
791         }
792 
793         // TupleExpr: unary
794 
795         @Override
796         public void meet(final Extension n) {
797             emit(n.getArg());
798             if (!(n.getArg() instanceof SingletonSet)) {
799                 newline();
800             }
801             boolean first = true;
802             for (final ExtensionElem e : n.getElements()) {
803                 final ValueExpr expr = e.getExpr();
804                 if (!(expr instanceof Var) || !((Var) expr).getName().equals(e.getName())) {
805                     if (!first) {
806                         newline();
807                     }
808                     emit("BIND (").emit(expr).emit(" AS ?").emit(e.getName()).emit(")");
809                     first = false;
810                 }
811             }
812         }
813 
814         @Override
815         public void meet(final ExtensionElem n) {
816             throw new Error("Should not be directly called");
817         }
818 
819         @Override
820         public void meet(final Filter n) {
821             final ValueExpr cond = n.getCondition();
822             final boolean nopar = cond instanceof Exists || cond instanceof Not
823                     && ((Not) cond).getArg() instanceof Exists;
824             emit(n.getArg());
825             if (!(n.getArg() instanceof SingletonSet)) {
826                 newline();
827             }
828             emit("FILTER ").emit(cond, !nopar);
829         }
830 
831         @Override
832         public void meet(final Service n) {
833             newline().emit("SERVICE ").emitIf(n.isSilent(), "SILENT ").openBrace()
834                     .emit(n.getServiceExpr()).closeBrace().emit(" ").emit(n.getServiceRef());
835         }
836 
837         // TupleExpr: binary
838 
839         @Override
840         public void meet(final Join n) {
841             final List<StatementPattern> bgp = getBGP(n);
842             if (bgp != null) {
843                 emit(bgp);
844             } else {
845                 final TupleExpr l = n.getLeftArg();
846                 final TupleExpr r = n.getRightArg();
847                 final boolean norpar = r instanceof Join || r instanceof StatementPattern
848                         || r instanceof SingletonSet || r instanceof Service || r instanceof Union
849                         || r instanceof BindingSetAssignment || r instanceof ArbitraryLengthPath;
850                 emit(l).newline().emit(r, !norpar);
851             }
852         }
853 
854         @Override
855         public void meet(final LeftJoin n) {
856             final TupleExpr l = n.getLeftArg();
857             final TupleExpr r = n.getCondition() == null ? n.getRightArg() : //
858                     new Filter(n.getRightArg(), n.getCondition());
859             emit(l);
860             if (!(l instanceof SingletonSet)) {
861                 newline();
862             }
863             emit("OPTIONAL ").emit(r, true);
864         }
865 
866         @Override
867         public void meet(final Union n) {
868             final TupleExpr l = n.getLeftArg();
869             final TupleExpr r = n.getRightArg();
870             final ZeroLengthPath p = l instanceof ZeroLengthPath ? (ZeroLengthPath) l
871                     : r instanceof ZeroLengthPath ? (ZeroLengthPath) r : null;
872             if (p == null) {
873                 emit(l, !(l instanceof Union)).emit(" UNION ").emit(r, !(r instanceof Union));
874             } else {
875                 final Var s = p.getSubjectVar();
876                 final Var o = p.getObjectVar();
877                 final Var c = p.getContextVar();
878                 if (c != null) {
879                     emit("GRAPH ").emit(c).emit(" ").openBrace();
880                 }
881                 emit(s).emit(" ").emitPropertyPath(n, s, o).emit(" ").emit(o);
882                 if (c != null) {
883                     closeBrace();
884                 }
885             }
886         }
887 
888         @Override
889         public void meet(final Difference n) {
890             final TupleExpr l = n.getLeftArg();
891             final TupleExpr r = n.getRightArg();
892             emit(l, true).emit(" MINUS ").emit(r, true);
893         }
894 
895         // TupleExpr: paths
896 
897         @Override
898         public void meet(final ArbitraryLengthPath n) {
899             final Var s = n.getSubjectVar();
900             final Var o = n.getObjectVar();
901             final Var c = n.getContextVar();
902             if (c != null) {
903                 emit("GRAPH ").emit(c).openBrace();
904             }
905             emit(s).emit(" ").emitPropertyPath(n, s, o).emit(" ").emit(o).emit(" .");
906             if (c != null) {
907                 closeBrace();
908             }
909         }
910 
911         @Override
912         public void meet(final ZeroLengthPath node) {
913             throw new Error("Should not be directly called");
914         }
915 
916         private Rendering emitPropertyPath(final TupleExpr node, final Var start, final Var end) {
917 
918             // Note: elt1 / elt2 and ^(complex exp) do not occur in Sesame algebra
919 
920             final boolean parenthesis = !(node instanceof StatementPattern)
921                     && (node.getParentNode() instanceof ArbitraryLengthPath || node
922                             .getParentNode() instanceof Union);
923 
924             emitIf(parenthesis, "(");
925 
926             if (node instanceof StatementPattern) {
927                 // handles iri, ^iri
928                 final StatementPattern pattern = (StatementPattern) node;
929                 final boolean inverse = isInversePath(pattern, start, end);
930                 if (!pattern.getPredicateVar().hasValue()
931                         || !pattern.getPredicateVar().isAnonymous()) {
932                     fail("Unsupported path expression. Check node: ", node);
933                 }
934                 emitIf(inverse, "^").emit(pattern.getPredicateVar().getValue());
935 
936             } else if (node instanceof Join) {
937                 final Join j = (Join) node;
938                 final TupleExpr l = j.getLeftArg();
939                 final TupleExpr r = j.getRightArg();
940                 final StatementPattern s = l instanceof StatementPattern ? (StatementPattern) l
941                         : r instanceof StatementPattern ? (StatementPattern) r : null;
942                 if (s == null) {
943                     return fail("Cannot process property path", node);
944                 }
945                 final Var m = s.getSubjectVar().equals(start) || s.getSubjectVar().equals(end) ? s
946                         .getObjectVar() : s.getSubjectVar();
947                 emitPropertyPath(l, start, m);
948                 emit("/");
949                 emitPropertyPath(r, m, end);
950 
951             } else if (node instanceof ArbitraryLengthPath) {
952                 // handles elt*, elt+
953                 final ArbitraryLengthPath path = (ArbitraryLengthPath) node;
954                 check(path.getMinLength() <= 1, "Invalid path length");
955                 emitPropertyPath(path.getPathExpression(), start, end).emit(
956                         path.getMinLength() == 0 ? "*" : "+");
957 
958             } else if (node instanceof Union) {
959                 // handles elt?, elt1|elt2|...
960                 final Union union = (Union) node;
961                 if (union.getLeftArg() instanceof ZeroLengthPath) {
962                     emitPropertyPath(union.getRightArg(), start, end).emit("?");
963                 } else if (union.getRightArg() instanceof ZeroLengthPath) {
964                     emitPropertyPath(union.getLeftArg(), start, end).emit("?");
965                 } else {
966                     emitPropertyPath(union.getLeftArg(), start, end);
967                     emit("|");
968                     emitPropertyPath(union.getRightArg(), start, end);
969                 }
970 
971             } else if (node instanceof Filter) {
972                 // handles !iri, !(iri1,iri2,...) with possibly inverse properties
973                 final Filter filter = (Filter) node;
974 
975                 check(filter.getArg() instanceof StatementPattern, "Invalid path expression");
976                 final StatementPattern pattern = (StatementPattern) filter.getArg();
977                 final boolean inverse = isInversePath(pattern, start, end);
978                 check(!pattern.getPredicateVar().hasValue()
979                         && pattern.getPredicateVar().isAnonymous(), "Invalid path expression");
980 
981                 final Set<URI> negatedProperties = new LinkedHashSet<>();
982                 extractNegatedProperties(filter.getCondition(), negatedProperties);
983 
984                 if (negatedProperties.size() == 1) {
985                     emit("!").emitIf(inverse, "^").emit(negatedProperties.iterator().next());
986 
987                 } else {
988                     emit("!(");
989                     boolean first = true;
990                     for (final URI negatedProperty : negatedProperties) {
991                         emitIf(!first, "|").emitIf(inverse, "^").emit(negatedProperty);
992                         first = false;
993                     }
994                     emit(")");
995                 }
996 
997             } else {
998                 fail("Unsupported path expression node", node);
999             }
1000 
1001             return emitIf(parenthesis, ")");
1002         }
1003 
1004         private void extractNegatedProperties(final ValueExpr condition,
1005                 final Set<URI> negatedProperties) {
1006             if (condition instanceof And) {
1007                 final And and = (And) condition;
1008                 extractNegatedProperties(and.getLeftArg(), negatedProperties);
1009                 extractNegatedProperties(and.getRightArg(), negatedProperties);
1010 
1011             } else if (condition instanceof Compare) {
1012                 final Compare compare = (Compare) condition;
1013                 check(compare.getOperator() == CompareOp.NE, "Invalid path expression");
1014                 if (compare.getLeftArg() instanceof ValueConstant) {
1015                     check(compare.getRightArg() instanceof Var, "Invalid path expression");
1016                     negatedProperties.add((URI) ((ValueConstant) compare.getLeftArg()).getValue());
1017                 } else if (compare.getRightArg() instanceof ValueConstant) {
1018                     check(compare.getLeftArg() instanceof Var, "Invalid path expression");
1019                     negatedProperties
1020                             .add((URI) ((ValueConstant) compare.getRightArg()).getValue());
1021                 } else {
1022                     fail("Unsupported path expression. Check condition node: ", condition);
1023                 }
1024             }
1025         }
1026 
1027         private boolean isInversePath(final StatementPattern node, final Var start, final Var end) {
1028             if (node.getSubjectVar().equals(start)) {
1029                 check(node.getObjectVar().equals(end), "Invalid path expression");
1030                 return false;
1031             } else if (node.getObjectVar().equals(start)) {
1032                 check(node.getSubjectVar().equals(end), "Invalid path expression");
1033                 return true;
1034             } else {
1035                 fail("Unsupported path expression. Check node: ", node);
1036                 return false;
1037             }
1038         }
1039 
1040         // TupleExpr: unsupported
1041 
1042         @Override
1043         public void meet(final Intersection n) {
1044             fail("Not a SPARQL 1.1 node", n);
1045         }
1046 
1047         // === SPARQL UPDATE ===
1048 
1049         @Override
1050         public void meet(final Add add) {
1051             throw new UnsupportedOperationException();
1052         }
1053 
1054         @Override
1055         public void meet(final Clear clear) {
1056             throw new UnsupportedOperationException();
1057         }
1058 
1059         @Override
1060         public void meet(final Copy copy) {
1061             throw new UnsupportedOperationException();
1062         }
1063 
1064         @Override
1065         public void meet(final Create create) {
1066             throw new UnsupportedOperationException();
1067         }
1068 
1069         @Override
1070         public void meet(final DeleteData deleteData) {
1071             throw new UnsupportedOperationException();
1072         }
1073 
1074         @Override
1075         public void meet(final InsertData insertData) {
1076             throw new UnsupportedOperationException();
1077         }
1078 
1079         @Override
1080         public void meet(final Load load) {
1081             throw new UnsupportedOperationException();
1082         }
1083 
1084         @Override
1085         public void meet(final Modify modify) {
1086             throw new UnsupportedOperationException();
1087         }
1088 
1089         @Override
1090         public void meet(final Move move) {
1091             throw new UnsupportedOperationException();
1092         }
1093 
1094         // === VALUE EXPR ===
1095 
1096         // ValueExpr: variables and constants
1097 
1098         @Override
1099         public void meet(final ValueConstant n) {
1100             emit(n.getValue());
1101         }
1102 
1103         @Override
1104         public void meet(final Var n) {
1105             final String name = n.getName();
1106             if (n.getValue() != null) {
1107                 emit(n.getValue());
1108             } else if (!n.isAnonymous()) {
1109                 emit("?" + n.getName());
1110             } else {
1111                 final ValueExpr expr = getVarExpr(this.root, n.getName());
1112                 if (expr != null) {
1113                     emit(expr);
1114                 } else if (getVarRefs(this.root, n.getName()) <= 1) {
1115                     emit("[]");
1116                 } else {
1117                     emit("?").emit(sanitize(name));
1118                 }
1119             }
1120         }
1121 
1122         // ValueExpr: comparison, math and boolean operators
1123 
1124         @Override
1125         public void meet(final Compare n) {
1126             final QueryModelNode p = n.getParentNode();
1127             final boolean par = p instanceof Not || p instanceof MathExpr;
1128             emitIf(par, "(").emit(n.getLeftArg()).emit(" ").emit(n.getOperator().getSymbol())
1129                     .emit(" ").emit(n.getRightArg()).emitIf(par, ")");
1130         }
1131 
1132         @Override
1133         public void meet(final ListMemberOperator n) {
1134             final QueryModelNode p = n.getParentNode();
1135             final boolean par = p instanceof Not || p instanceof MathExpr;
1136             final List<ValueExpr> args = n.getArguments();
1137             emitIf(par, "(").emit(args.get(0)).emit(" in (")
1138                     .emit(args.subList(1, args.size()), ", ").emit(")").emitIf(par, ")");
1139         }
1140 
1141         @Override
1142         public void meet(final MathExpr n) {
1143             final QueryModelNode p = n.getParentNode();
1144             final MathOp op = n.getOperator();
1145             final MathOp pop = p instanceof MathExpr ? ((MathExpr) p).getOperator() : null;
1146             final boolean r = p instanceof BinaryValueOperator
1147                     && n == ((BinaryValueOperator) p).getRightArg();
1148             final boolean par = p instanceof Not //
1149                     || (op == MathOp.PLUS || op == MathOp.MINUS)
1150                     && (pop == MathOp.MINUS && r //
1151                             || pop == MathOp.DIVIDE || pop == MathOp.MULTIPLY)
1152                     || (op == MathOp.MULTIPLY || op == MathOp.DIVIDE) && pop == MathOp.DIVIDE && r;
1153             emitIf(par, "(").emit(n.getLeftArg()).emit(" ").emit(op.getSymbol()).emit(" ")
1154                     .emit(n.getRightArg()).emitIf(par, ")");
1155         }
1156 
1157         @Override
1158         public void meet(final And n) {
1159             final QueryModelNode p = n.getParentNode();
1160             final boolean needPar = p instanceof Not || p instanceof MathExpr
1161                     || p instanceof ListMemberOperator || p instanceof Compare;
1162             emitIf(needPar, "(").emit(n.getLeftArg()).emit(" && ").emit(n.getRightArg())
1163                     .emitIf(needPar, ")");
1164         }
1165 
1166         @Override
1167         public void meet(final Or n) {
1168             final QueryModelNode p = n.getParentNode();
1169             final boolean needPar = p instanceof Not || p instanceof And || p instanceof MathExpr
1170                     || p instanceof ListMemberOperator || p instanceof Compare;
1171             emitIf(needPar, "(").emit(n.getLeftArg()).emit(" || ").emit(n.getRightArg())
1172                     .emitIf(needPar, ")");
1173         }
1174 
1175         @Override
1176         public void meet(final Not n) {
1177             final String op = n.getArg() instanceof Exists ? "NOT " : "!";
1178             emit(op).emit(n.getArg());
1179         }
1180 
1181         // ValueExpr: aggregates
1182 
1183         @Override
1184         public void meet(final Count node) {
1185             emit("COUNT(").emitIf(node.isDistinct(), "DISTINCT ")
1186                     .emit(defaultIfNull(node.getArg(), "*")).emit(")");
1187         }
1188 
1189         @Override
1190         public void meet(final Sum node) {
1191             emit("SUM(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1192         }
1193 
1194         @Override
1195         public void meet(final Min node) {
1196             emit("MIN(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1197         }
1198 
1199         @Override
1200         public void meet(final Max node) {
1201             emit("MAX(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1202         }
1203 
1204         @Override
1205         public void meet(final Avg node) {
1206             emit("AVG(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1207         }
1208 
1209         @Override
1210         public void meet(final Sample node) {
1211             emit("SAMPLE(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg()).emit(")");
1212         }
1213 
1214         @Override
1215         public void meet(final GroupConcat node) {
1216             emit("GROUP_CONCAT(").emitIf(node.isDistinct(), "DISTINCT ").emit(node.getArg());
1217             if (node.getSeparator() != null) {
1218                 emit(" ; separator=").emit(node.getSeparator());
1219             }
1220             emit(")");
1221         }
1222 
1223         // ValueExpr: function calls
1224 
1225         @Override
1226         public void meet(final Str n) {
1227             emit("STR(").emit(n.getArg()).emit(")");
1228         }
1229 
1230         @Override
1231         public void meet(final Lang n) {
1232             emit("LANG(").emit(n.getArg()).emit(")");
1233         }
1234 
1235         @Override
1236         public void meet(final LangMatches n) {
1237             emit("LANGMATCHES(").emit(n.getLeftArg()).emit(", ").emit(n.getRightArg()).emit(")");
1238         }
1239 
1240         @Override
1241         public void meet(final Datatype n) {
1242             emit("DATATYPE(").emit(n.getArg()).emit(")");
1243         }
1244 
1245         @Override
1246         public void meet(final Bound n) {
1247             emit("BOUND(").emit(n.getArg()).emit(")");
1248         }
1249 
1250         @Override
1251         public void meet(final IRIFunction n) {
1252             emit("IRI(").emit(n.getArg()).emit(")");
1253             if (n.getBaseURI() != null) {
1254                 this.base = n.getBaseURI();
1255             }
1256         }
1257 
1258         @Override
1259         public void meet(final BNodeGenerator n) {
1260             final ValueExpr expr = n.getNodeIdExpr();
1261             emit("BNODE(").emitIf(expr != null, expr).emit(")");
1262         }
1263 
1264         @Override
1265         public void meet(final FunctionCall n) {
1266             final String uri = n.getURI();
1267             String name = NAMES.get(uri);
1268             if (name == null && NAMES.values().contains(uri.toUpperCase())) {
1269                 name = n.getURI().toUpperCase();
1270             }
1271             emit(name != null ? name : new URIImpl(uri)).emit("(").emit(n.getArgs(), ", ")
1272                     .emit(")");
1273         }
1274 
1275         @Override
1276         public void meet(final Coalesce n) {
1277             emit("COALESCE(").emit(n.getArguments(), ", ").emit(")");
1278         }
1279 
1280         @Override
1281         public void meet(final If n) {
1282             emit("IF(").emit(n.getCondition()).emit(", ").emit(n.getResult()).emit(", ")
1283                     .emit(n.getAlternative()).emit(")");
1284         }
1285 
1286         @Override
1287         public void meet(final SameTerm n) {
1288             emit("sameTerm(").emit(n.getLeftArg()).emit(", ").emit(n.getRightArg()).emit(")");
1289         }
1290 
1291         @Override
1292         public void meet(final IsURI n) {
1293             emit("isIRI(").emit(n.getArg()).emit(")");
1294         }
1295 
1296         @Override
1297         public void meet(final IsBNode n) {
1298             emit("isBLANK(").emit(n.getArg()).emit(")");
1299         }
1300 
1301         @Override
1302         public void meet(final IsLiteral n) {
1303             emit("isLITERAL(").emit(n.getArg()).emit(")");
1304         }
1305 
1306         @Override
1307         public void meet(final IsNumeric n) {
1308             emit("isNUMERIC(").emit(n.getArg()).emit(")");
1309         }
1310 
1311         @Override
1312         public void meet(final Regex n) {
1313             emit("REGEX(").emit(n.getArg()).emit(", ").emit(n.getPatternArg());
1314             if (n.getFlagsArg() != null) {
1315                 emit(", ").emit(n.getFlagsArg());
1316             }
1317             emit(")");
1318         }
1319 
1320         @Override
1321         public void meet(final Exists node) {
1322             emit("EXISTS ").emit(node.getSubQuery());
1323         }
1324 
1325         // ValueExpr: unsupported nodes
1326 
1327         @Override
1328         public void meet(final IsResource n) {
1329             fail("Not a SPARQL 1.1 node", n);
1330         }
1331 
1332         @Override
1333         public void meet(final Label n) {
1334             fail("Not a SPARQL 1.1 node", n);
1335         }
1336 
1337         @Override
1338         public void meet(final Like n) {
1339             fail("Not a SPARQL 1.1 node", n);
1340         }
1341 
1342         @Override
1343         public void meet(final LocalName n) {
1344             fail("Not a SPARQL 1.1 node", n);
1345         }
1346 
1347         @Override
1348         public void meet(final Namespace n) {
1349             fail("Not a SPARQL 1.1 node", n);
1350         }
1351 
1352         @Override
1353         public void meet(final In n) {
1354             fail("Not a SPARQL 1.1 node", n);
1355         }
1356 
1357         @Override
1358         public void meet(final CompareAll n) {
1359             fail("Not a SPARQL 1.1 node", n);
1360         }
1361 
1362         @Override
1363         public void meet(final CompareAny n) {
1364             fail("Not a SPARQL 1.1 node", n);
1365         }
1366 
1367         // OTHER
1368 
1369         @Override
1370         public void meetOther(final QueryModelNode n) {
1371             fail("Unknown node", n);
1372         }
1373 
1374     }
1375 
1376     private enum Form {
1377         SELECT, CONSTRUCT, ASK, DESCRIBE
1378     }
1379 
1380     private enum Modifier {
1381         DISTINCT, REDUCED
1382     }
1383 
1384     private static final class Query {
1385 
1386         final QueryModelNode root;
1387 
1388         final Form form;
1389 
1390         @Nullable
1391         final Modifier modifier;
1392 
1393         final List<ProjectionElem> select;
1394 
1395         @Nullable
1396         final TupleExpr construct;
1397 
1398         @Nullable
1399         final Dataset from;
1400 
1401         final TupleExpr where;
1402 
1403         final List<ProjectionElem> groupBy;
1404 
1405         @Nullable
1406         final ValueExpr having;
1407 
1408         final List<OrderElem> orderBy;
1409 
1410         @Nullable
1411         final Long offset;
1412 
1413         @Nullable
1414         final Long limit;
1415 
1416         static Query create(final TupleExpr rootNode, @Nullable final Dataset dataset,
1417                 final boolean forceSelect) {
1418 
1419             Objects.requireNonNull(rootNode);
1420 
1421             // Handle special trivial case
1422             if (rootNode instanceof EmptySet) {
1423                 return new Query(rootNode, Form.CONSTRUCT, null, null, rootNode, dataset,
1424                         rootNode, null, null, null, null, null);
1425             }
1426 
1427             // Local variables
1428             Form form = null;
1429             Modifier modifier = null;
1430             final List<ProjectionElem> select = new ArrayList<>();
1431             TupleExpr construct = null;
1432             TupleExpr where = null;
1433             final List<ProjectionElem> groupBy = new ArrayList<>();
1434             ValueExpr having = null;
1435             final List<OrderElem> orderBy = new ArrayList<>();
1436             Long offset = null;
1437             Long limit = null;
1438 
1439             final List<UnaryTupleOperator> nodes = extractQueryNodes(rootNode, false);
1440 
1441             where = nodes.size() > 0 ? nodes.get(nodes.size() - 1).getArg() : rootNode;
1442 
1443             for (final UnaryTupleOperator node : nodes) {
1444 
1445                 if (node instanceof DescribeOperator) {
1446                     form = Form.DESCRIBE;
1447 
1448                 } else if (node instanceof Distinct) {
1449                     modifier = Modifier.DISTINCT;
1450 
1451                 } else if (node instanceof Reduced) {
1452                     modifier = Modifier.REDUCED;
1453 
1454                 } else if (node instanceof Projection) {
1455                     final Map<String, ExtensionElem> extensions = extractExtensions(node);
1456                     final List<ProjectionElem> projections = ((Projection) node)
1457                             .getProjectionElemList().getElements();
1458                     final boolean isConstruct = projections.size() >= 3
1459                             && "subject".equals(projections.get(0).getTargetName())
1460                             && "predicate".equals(projections.get(1).getTargetName())
1461                             && "object".equals(projections.get(2).getTargetName())
1462                             && (projections.size() == 3 || projections.size() == 4
1463                                     && "context".equals(projections.get(3).getTargetName()));
1464                     if (isConstruct && !forceSelect) {
1465                         form = Form.CONSTRUCT;
1466                         construct = extractConstructExpression(extensions,
1467                                 Collections.singleton(((Projection) node) //
1468                                         .getProjectionElemList()));
1469                     } else {
1470                         form = form == null ? Form.SELECT : form;
1471                         for (final ProjectionElem projection : projections) {
1472                             final String variable = projection.getTargetName();
1473                             ExtensionElem extension = extensions.get(variable);
1474                             if (extension == null && projection.getSourceName() != null) {
1475                                 extension = extensions.get(projection.getSourceName());
1476                             }
1477                             final ProjectionElem newProjection = new ProjectionElem();
1478                             newProjection.setTargetName(variable);
1479                             newProjection.setSourceExpression(extension);
1480                             newProjection.setSourceName(extension == null
1481                                     || !(extension.getExpr() instanceof Var) ? projection
1482                                     .getSourceName() : ((Var) extension.getExpr()).getName());
1483                             select.add(newProjection);
1484                         }
1485                     }
1486 
1487                 } else if (node instanceof MultiProjection) {
1488                     form = Form.CONSTRUCT;
1489                     construct = extractConstructExpression(extractExtensions(node),
1490                             ((MultiProjection) node).getProjections());
1491 
1492                 } else if (node instanceof Group) {
1493                     final Group group = (Group) node;
1494                     final Map<String, ExtensionElem> extensions = extractExtensions(group.getArg());
1495                     for (final String variableName : group.getGroupBindingNames()) {
1496                         final ExtensionElem extension = extensions.get(variableName);
1497                         final ProjectionElem projection = new ProjectionElem();
1498                         projection.setTargetName(variableName);
1499                         projection.setSourceExpression(extension);
1500                         projection.setSourceName(extension == null
1501                                 || !(extension.getExpr() instanceof Var) ? variableName
1502                                 : ((Var) extension.getExpr()).getName());
1503                         groupBy.add(projection);
1504                     }
1505 
1506                 } else if (node instanceof Order) {
1507                     orderBy.addAll(((Order) node).getElements());
1508 
1509                 } else if (node instanceof Slice) {
1510                     final Slice slice = (Slice) node;
1511                     offset = slice.getOffset() < 0 ? null : slice.getOffset();
1512                     limit = slice.getLimit() < 0 ? null : slice.getLimit();
1513                     if (form == null && slice.getOffset() == 0 && slice.getLimit() == 1) {
1514                         if (forceSelect) {
1515                             form = Form.SELECT;
1516                             limit = 1L;
1517                             // limit = 2L; // TODO: workaround for Virtuoso
1518                         } else {
1519                             form = Form.ASK;
1520                         }
1521                     }
1522 
1523                 } else if (node instanceof Filter) {
1524                     having = ((Filter) node).getCondition();
1525                 }
1526             }
1527 
1528             form = defaultIfNull(form, Form.SELECT);
1529             if (form == Form.CONSTRUCT && construct == null) {
1530                 construct = new SingletonSet();
1531             }
1532 
1533             return new Query(rootNode, form, modifier, select, construct, dataset, where, groupBy,
1534                     having, orderBy, offset, limit);
1535         }
1536 
1537         private static List<UnaryTupleOperator> extractQueryNodes(final TupleExpr rootNode,
1538                 final boolean haltOnGroup) {
1539             final List<UnaryTupleOperator> nodes = new ArrayList<>();
1540 
1541             TupleExpr queryNode = rootNode;
1542             while (queryNode instanceof UnaryTupleOperator) {
1543                 nodes.add((UnaryTupleOperator) queryNode);
1544                 queryNode = ((UnaryTupleOperator) queryNode).getArg();
1545             }
1546 
1547             boolean describeFound = false;
1548             boolean modifierFound = false;
1549             boolean projectionFound = false;
1550             boolean groupFound = false;
1551             boolean orderFound = false;
1552             boolean sliceFound = false;
1553             boolean extensionFound = false;
1554 
1555             int index = 0;
1556             while (index < nodes.size()) {
1557                 final UnaryTupleOperator node = nodes.get(index);
1558                 if (node instanceof DescribeOperator && !describeFound) {
1559                     describeFound = true;
1560 
1561                 } else if ((node instanceof Distinct || node instanceof Reduced) && !modifierFound
1562                         && !projectionFound) {
1563                     modifierFound = true;
1564 
1565                 } else if ((node instanceof Projection || node instanceof MultiProjection)
1566                         && !projectionFound) {
1567                     projectionFound = true;
1568 
1569                 } else if (node instanceof Group && !groupFound && !haltOnGroup) {
1570                     groupFound = true;
1571 
1572                 } else if (node instanceof Order && !orderFound) {
1573                     orderFound = true;
1574 
1575                 } else if (node instanceof Slice && !sliceFound) {
1576                     sliceFound = true;
1577 
1578                 } else if (node instanceof Filter && !groupFound && !haltOnGroup) {
1579                     int i = index + 1;
1580                     for (; i < nodes.size() && nodes.get(i) instanceof Extension;) {
1581                         ++i;
1582                     }
1583                     if (i < nodes.size() && nodes.get(i) instanceof Group) {
1584                         groupFound = true;
1585                         index = i;
1586                     } else {
1587                         break;
1588                     }
1589 
1590                 } else if (node instanceof Extension && !extensionFound) {
1591                     extensionFound = true;
1592 
1593                 } else if (!(node instanceof QueryRoot) || index > 0) {
1594                     break;
1595                 }
1596                 ++index;
1597             }
1598 
1599             return nodes.subList(0, index);
1600         }
1601 
1602         private static Map<String, ExtensionElem> extractExtensions(final TupleExpr rootNode) {
1603             final Map<String, ExtensionElem> map = new HashMap<>();
1604             for (final UnaryTupleOperator node : extractQueryNodes(rootNode, true)) {
1605                 if (node instanceof Extension) {
1606                     for (final ExtensionElem elem : ((Extension) node).getElements()) {
1607                         final String variable = elem.getName();
1608                         final ValueExpr expression = elem.getExpr();
1609                         if (!(expression instanceof Var)
1610                                 || !((Var) expression).getName().equals(variable)) {
1611                             map.put(variable, elem);
1612                         }
1613                     }
1614                 }
1615             }
1616             return map;
1617         }
1618 
1619         private static TupleExpr extractConstructExpression(
1620                 final Map<String, ExtensionElem> extensions,
1621                 final Iterable<? extends ProjectionElemList> multiProjections) {
1622             TupleExpr expression = null;
1623             for (final ProjectionElemList projections : multiProjections) {
1624                 final Var subj = extractConstructVar(extensions, projections.getElements().get(0));
1625                 final Var pred = extractConstructVar(extensions, projections.getElements().get(1));
1626                 final Var obj = extractConstructVar(extensions, projections.getElements().get(2));
1627                 final Var ctx = projections.getElements().size() < 4 ? null : extractConstructVar(
1628                         extensions, projections.getElements().get(3));
1629                 final StatementPattern pattern = new StatementPattern(
1630                         ctx == null ? Scope.DEFAULT_CONTEXTS : Scope.NAMED_CONTEXTS, subj, pred,
1631                         obj, ctx);
1632                 expression = expression == null ? pattern : new Join(expression, pattern);
1633             }
1634             return expression;
1635         }
1636 
1637         private static Var extractConstructVar(final Map<String, ExtensionElem> extensions,
1638                 final ProjectionElem projection) {
1639             final ExtensionElem extension = extensions.get(projection.getSourceName());
1640             String name = projection.getSourceName();
1641             if (name.startsWith("-anon-")) {
1642                 name += "-construct";
1643             }
1644             if (extension == null || extension.getExpr() instanceof BNodeGenerator) {
1645                 final Var var = new Var(name);
1646                 var.setAnonymous(name.startsWith("-anon-"));
1647                 return var;
1648             } else if (extension.getExpr() instanceof ValueConstant) {
1649                 final ValueConstant constant = (ValueConstant) extension.getExpr();
1650                 return new Var(name, constant.getValue());
1651             } else {
1652                 throw new UnsupportedOperationException(
1653                         "Unsupported extension in construct query: " + extension);
1654             }
1655         }
1656 
1657         private Query(//
1658                 final QueryModelNode root, //
1659                 final Form form, //
1660                 @Nullable final Modifier modifier, //
1661                 @Nullable final Iterable<? extends ProjectionElem> selectList, //
1662                 @Nullable final TupleExpr construct, //
1663                 @Nullable final Dataset from, //
1664                 final TupleExpr where, //
1665                 @Nullable final Iterable<? extends ProjectionElem> groupBy, //
1666                 @Nullable final ValueExpr having, //
1667                 @Nullable final Iterable<? extends OrderElem> orderBy, //
1668                 @Nullable final Long offset, //
1669                 @Nullable final Long limit) {
1670 
1671             this.root = Objects.requireNonNull(root);
1672             this.form = Objects.requireNonNull(form);
1673             this.modifier = modifier;
1674             this.select = selectList == null ? Collections.emptyList() : list(selectList);
1675             this.construct = construct;
1676             this.from = from;
1677             this.where = Objects.requireNonNull(where);
1678             this.groupBy = groupBy == null ? Collections.emptyList() : list(groupBy);
1679             this.having = having;
1680             this.orderBy = orderBy == null ? Collections.emptyList() : list(orderBy);
1681             this.offset = offset;
1682             this.limit = limit;
1683         }
1684 
1685     }
1686 
1687 }