1   /*
2    * RDFpro - An extensible tool for building stream-oriented RDF processing libraries.
3    * 
4    * Written in 2014 by Francesco Corcoglioniti with support by Marco Amadori, Michele Mostarda,
5    * Alessio Palmero Aprosio and Marco Rospocher. Contact info on http://rdfpro.fbk.eu/
6    * 
7    * To the extent possible under law, the authors have dedicated all copyright and related and
8    * neighboring rights to this software to the public domain worldwide. This software is
9    * distributed without any warranty.
10   * 
11   * You should have received a copy of the CC0 Public Domain Dedication along with this software.
12   * If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13   */
14  package org.openrdf.query.algebra.evaluation;
15  
16  import java.util.AbstractSet;
17  import java.util.Collection;
18  import java.util.Iterator;
19  import java.util.NoSuchElementException;
20  import java.util.Set;
21  
22  import com.google.common.base.Objects;
23  
24  import org.openrdf.model.Value;
25  import org.openrdf.query.Binding;
26  import org.openrdf.query.BindingSet;
27  import org.openrdf.query.impl.BindingImpl;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  /*
32   * Alternative implementation of QueryBindingSet, supposedly more efficient (actually proved to
33   * improve execution times in rdfpro-rules). As QueryBindingSet is hard-coded into Sesame query
34   * algebra operators, there is no way to cleanly change implementation unless we reuse the same
35   * name and force the classloader to load this class instead of the default one (which is done by
36   * listing rdfpro-dist jar file first in the classpath). An INFO message is printed in case this
37   * class is loaded instead of the default one.
38   */
39  public final class QueryBindingSet implements BindingSet {
40  
41      private static final long serialVersionUID = 1L;
42  
43      private static final int DEFAULT_CAPACITY = 8;
44  
45      private static final Object DELETED = new Object();
46  
47      private static final Logger LOGGER = LoggerFactory.getLogger("eu.fbk.rdfpro");
48  
49      private Object[] data;
50  
51      private int size;
52  
53      private int slots;
54  
55      public QueryBindingSet() {
56          this(DEFAULT_CAPACITY);
57      }
58  
59      public QueryBindingSet(final int capacity) {
60          int power = DEFAULT_CAPACITY;
61          while (power < capacity) {
62              power *= 2;
63          }
64          this.data = new Object[power * 2];
65          this.size = 0;
66          this.slots = 0;
67      }
68  
69      public QueryBindingSet(final BindingSet bindings) {
70          this(bindings.size());
71          addAll(bindings);
72      }
73  
74      public void addAll(final BindingSet bindings) {
75          if (bindings instanceof QueryBindingSet) {
76              final QueryBindingSet other = (QueryBindingSet) bindings;
77              for (int i = 0; i < other.data.length; i += 2) {
78                  final Object key = other.data[i];
79                  if (key instanceof String) {
80                      addBinding((String) key, (Value) other.data[i + 1]);
81                  }
82              }
83          } else {
84              for (final Binding binding : bindings) {
85                  this.addBinding(binding.getName(), binding.getValue());
86              }
87          }
88      }
89  
90      public void addBinding(final Binding binding) {
91          addBinding(binding.getName(), binding.getValue());
92      }
93  
94      public void addBinding(final String name, final Value value) {
95          setBinding(name, value);
96      }
97  
98      public void setBinding(final Binding binding) {
99          setBinding(binding.getName(), binding.getValue());
100     }
101 
102     public void setBinding(final String name, final Value value) {
103         for (int i = getSlot(name);; i = nextSlot(i)) {
104             final Object key = this.data[i];
105             if (!(key instanceof String)) {
106                 this.data[i] = name;
107                 this.data[i + 1] = value;
108                 ++this.size;
109                 if (key == null) {
110                     ++this.slots;
111                     if (this.slots * 4 > this.data.length) {
112                         rehashSlots();
113                     }
114                 }
115                 return;
116             } else if (key instanceof String && key.hashCode() == name.hashCode()
117                     && key.equals(name)) {
118                 this.data[i + 1] = value;
119                 return;
120             }
121         }
122     }
123 
124     public void removeBinding(final String name) {
125         for (int i = getSlot(name);; i = nextSlot(i)) {
126             final Object key = this.data[i];
127             if (key == null) {
128                 return;
129             } else if (key instanceof String && key.hashCode() == name.hashCode()
130                     && key.equals(name)) {
131                 this.data[i] = DELETED;
132                 this.data[i + 1] = null;
133                 return;
134             }
135         }
136     }
137 
138     public void removeAll(final Collection<String> names) {
139         if (names instanceof Set<?>) {
140             for (int i = 0; i < this.data.length; i += 2) {
141                 final Object key = this.data[i];
142                 if (key instanceof String && names.contains(key)) {
143                     this.data[i] = DELETED;
144                     this.data[i + 1] = null;
145                 }
146             }
147         } else {
148             for (final String name : names) {
149                 removeBinding(name);
150             }
151         }
152     }
153 
154     public void retainAll(final Collection<String> names) {
155         for (int i = 0; i < this.data.length; i += 2) {
156             final Object key = this.data[i];
157             if (key instanceof String && !names.contains(key)) {
158                 this.data[i] = DELETED;
159                 this.data[i + 1] = null;
160             }
161         }
162     }
163 
164     @Override
165     public Set<String> getBindingNames() {
166         return new AbstractSet<String>() {
167 
168             @Override
169             public boolean contains(final Object object) {
170                 return object instanceof String
171                         && QueryBindingSet.this.hasBinding((String) object);
172             }
173 
174             @Override
175             public int size() {
176                 return QueryBindingSet.this.size();
177             }
178 
179             @Override
180             public Iterator<String> iterator() {
181                 return new NameIterator(QueryBindingSet.this.data);
182             }
183 
184         };
185     }
186 
187     @Override
188     public Value getValue(final String name) {
189         for (int i = getSlot(name);; i = nextSlot(i)) {
190             final Object key = this.data[i];
191             if (key == null) {
192                 return null;
193             } else if (key instanceof String && key.hashCode() == name.hashCode()
194                     && key.equals(name)) {
195                 return (Value) this.data[i + 1];
196             }
197         }
198     }
199 
200     @Override
201     public Binding getBinding(final String name) {
202         final Value value = getValue(name);
203         return value == null ? null : new BindingImpl(name, value);
204     }
205 
206     @Override
207     public boolean hasBinding(final String name) {
208         for (int i = getSlot(name);; i = nextSlot(i)) {
209             final Object key = this.data[i];
210             if (key == null) {
211                 return false;
212             } else if (key instanceof String && key.hashCode() == name.hashCode()
213                     && key.equals(name)) {
214                 return true;
215             }
216         }
217     }
218 
219     @Override
220     public Iterator<Binding> iterator() {
221         return new BindingIterator(this.data);
222     }
223 
224     @Override
225     public int size() {
226         return this.size;
227     }
228 
229     @Override
230     public boolean equals(final Object object) {
231 
232         if (object == this) {
233             return true;
234 
235         } else if (object instanceof QueryBindingSet) {
236             final QueryBindingSet other = (QueryBindingSet) object;
237             if (other.data.length == this.data.length) {
238                 for (int i = 0; i < this.data.length; i += 2) {
239                     final Object thisKey = this.data[i] instanceof String ? this.data[i] : null;
240                     final Object otherKey = other.data[i] instanceof String ? other.data[i] : null;
241                     if (!Objects.equal(thisKey, otherKey)
242                             || !Objects.equal(this.data[i + 1], other.data[i + 1])) {
243                         return false;
244                     }
245                 }
246                 return true;
247             }
248             if (size() != other.size()) {
249                 return false;
250             }
251             for (int i = 0; i < other.data.length; i += 2) {
252                 if (other.data[i] instanceof String) {
253                     final String name = (String) other.data[i];
254                     final Value otherValue = (Value) other.data[i + 1];
255                     final Value thisValue = getValue(name);
256                     if (!otherValue.equals(thisValue)) {
257                         return false;
258                     }
259                 }
260             }
261             return true;
262 
263         } else if (object instanceof BindingSet) {
264             final BindingSet other = (BindingSet) object;
265             if (size() != other.size()) {
266                 return false;
267             }
268             for (final Binding binding : other) {
269                 final Value thisValue = getValue(binding.getName());
270                 if (!binding.getValue().equals(thisValue)) {
271                     return false;
272                 }
273             }
274             return true;
275         }
276 
277         return false;
278     }
279 
280     @Override
281     public int hashCode() {
282         int hashCode = 0;
283         for (int i = 0; i < this.data.length; i += 2) {
284             final Object key = this.data[i];
285             if (key instanceof String) {
286                 hashCode = hashCode ^ key.hashCode() ^ this.data[i + 1].hashCode();
287             }
288         }
289         return hashCode;
290     }
291 
292     @Override
293     public String toString() {
294         final StringBuilder builder = new StringBuilder(8 * this.data.length);
295         builder.append('[');
296         String separator = "";
297         for (int i = 0; i < this.data.length; i += 2) {
298             final Object key = this.data[i];
299             if (key instanceof String) {
300                 builder.append(separator);
301                 builder.append(key);
302                 builder.append('=');
303                 builder.append(this.data[i + 1]);
304                 separator = ";";
305             }
306         }
307         builder.append(']');
308         return builder.toString();
309     }
310 
311     private void rehashSlots() {
312         final Object[] oldData = this.data;
313         this.data = new Object[oldData.length * 2];
314         for (int j = 0; j < oldData.length; j += 2) {
315             if (oldData[j] instanceof String) {
316                 final String name = (String) oldData[j];
317                 final Value value = (Value) oldData[j + 1];
318                 for (int i = getSlot(name);; i = nextSlot(i)) {
319                     if (this.data[i] == null) {
320                         this.data[i] = name;
321                         this.data[i + 1] = value;
322                         break;
323                     }
324                 }
325             }
326         }
327         this.slots = this.size;
328     }
329 
330     private int getSlot(final String bindingName) {
331         return (bindingName.hashCode() & (this.data.length >>> 1) - 1) << 1;
332     }
333 
334     private int nextSlot(final int slot) {
335         final int nextSlot = slot + 2;
336         return nextSlot < this.data.length ? nextSlot : 0;
337     }
338 
339     static {
340         LOGGER.debug("Using patched QueryBindingSet class");
341     }
342 
343     private static abstract class AbstractIterator<T> implements Iterator<T> {
344 
345         private final Object[] data;
346 
347         private int nextOffset;
348 
349         private int removeOffset;
350 
351         AbstractIterator(final Object[] data) {
352             this.data = data;
353             this.nextOffset = 0;
354             this.removeOffset = -1;
355         }
356 
357         abstract T elementAt(Object[] data, int offset);
358 
359         @Override
360         public boolean hasNext() {
361             while (this.nextOffset < this.data.length
362                     && !(this.data[this.nextOffset] instanceof String)) {
363                 this.nextOffset += 2;
364             }
365             return this.nextOffset < this.data.length;
366         }
367 
368         @Override
369         public T next() {
370             if (!hasNext()) {
371                 throw new NoSuchElementException();
372             }
373             this.removeOffset = this.nextOffset;
374             this.nextOffset += 2;
375             return elementAt(this.data, this.removeOffset);
376         }
377 
378         @Override
379         public void remove() {
380             if (this.removeOffset < 0) {
381                 throw new NoSuchElementException();
382             }
383             this.data[this.removeOffset] = DELETED;
384             this.data[this.removeOffset + 1] = null;
385             this.removeOffset = -1;
386         }
387 
388     }
389 
390     private static final class NameIterator extends AbstractIterator<String> {
391 
392         NameIterator(final Object[] data) {
393             super(data);
394         }
395 
396         @Override
397         String elementAt(final Object[] data, final int offset) {
398             return (String) data[offset];
399         }
400 
401     }
402 
403     private static final class BindingIterator extends AbstractIterator<Binding> {
404 
405         BindingIterator(final Object[] data) {
406             super(data);
407         }
408 
409         @Override
410         Binding elementAt(final Object[] data, final int offset) {
411             return new BindingImpl((String) data[offset], (Value) data[offset + 1]);
412         }
413 
414     }
415 
416 }