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