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.io.IOException;
17  import java.util.Objects;
18  import java.util.function.Function;
19  
20  import javax.annotation.Nullable;
21  
22  import org.openrdf.model.Resource;
23  import org.openrdf.model.Statement;
24  import org.openrdf.model.URI;
25  import org.openrdf.model.Value;
26  import org.openrdf.query.algebra.StatementPattern;
27  import org.openrdf.query.algebra.Var;
28  
29  // This looks more like a StatementMapper
30  
31  public final class StatementTemplate implements Function<Statement, Statement> {
32  
33      private final Object subj;
34  
35      private final Object pred;
36  
37      private final Object obj;
38  
39      @Nullable
40      private final Object ctx;
41  
42      private final byte subjIndex;
43  
44      private final byte predIndex;
45  
46      private final byte objIndex;
47  
48      private final byte ctxIndex;
49  
50      public StatementTemplate(final Object subj, final Object pred, final Object obj,
51              @Nullable final Object ctx) {
52  
53          this.subj = check(subj);
54          this.pred = check(pred);
55          this.obj = check(obj);
56          this.ctx = check(ctx);
57  
58          this.subjIndex = this.subj instanceof Resource ? 4 : ((StatementComponent) this.subj)
59                  .getIndex();
60          this.predIndex = this.pred instanceof Resource ? 5 : ((StatementComponent) this.pred)
61                  .getIndex();
62          this.objIndex = this.obj instanceof Resource ? 6 : ((StatementComponent) this.obj)
63                  .getIndex();
64          this.ctxIndex = this.ctx == null || this.ctx instanceof Resource ? 7
65                  : ((StatementComponent) this.ctx).getIndex();
66      }
67  
68      public StatementTemplate(final StatementPattern head) {
69          this(componentFor(head.getSubjectVar()), componentFor(head.getPredicateVar()),
70                  componentFor(head.getObjectVar()), componentFor(head.getContextVar()));
71      }
72  
73      public StatementTemplate(final StatementPattern head, final StatementPattern body) {
74          this(componentFor(head.getSubjectVar(), body), componentFor(head.getPredicateVar(), body),
75                  componentFor(head.getObjectVar(), body), componentFor(head.getContextVar(), body));
76      }
77  
78      public StatementTemplate normalize(final Function<? super Value, ?> normalizer) {
79          final Object nsubj = this.subj instanceof StatementComponent ? (Object) this.subj
80                  : normalizer.apply((Value) this.subj);
81          final Object npred = this.pred instanceof StatementComponent ? (Object) this.pred
82                  : normalizer.apply((Value) this.pred);
83          final Object nobj = this.obj instanceof StatementComponent ? (Object) this.obj
84                  : normalizer.apply((Value) this.obj);
85          final Object nctx = this.ctx == null //
86                  || this.ctx instanceof StatementComponent ? (Object) this.ctx : normalizer
87                  .apply((Value) this.ctx);
88          return nsubj == this.subj && npred == this.pred && nobj == this.obj && nctx == this.ctx ? this
89                  : new StatementTemplate(nsubj, npred, nobj, nctx);
90      }
91  
92      @Override
93      public Statement apply(final Statement stmt) {
94          try {
95              final URI p = (URI) resolve(stmt, this.predIndex);
96              final Resource s = (Resource) resolve(stmt, this.subjIndex);
97              final Resource c = (Resource) resolve(stmt, this.ctxIndex);
98              final Value o = (Value) resolve(stmt, this.objIndex);
99              return Statements.VALUE_FACTORY.createStatement(s, p, o, c);
100         } catch (final Throwable ex) {
101             return null;
102         }
103     }
104 
105     public Statement apply(final Statement stmt, final StatementDeduplicator deduplicator) {
106         try {
107             final URI p = (URI) resolve(stmt, this.predIndex);
108             final Resource s = (Resource) resolve(stmt, this.subjIndex);
109             final Resource c = (Resource) resolve(stmt, this.ctxIndex);
110             final Value o = (Value) resolve(stmt, this.objIndex);
111             if (deduplicator.add(s, p, o, c)) {
112                 return Statements.VALUE_FACTORY.createStatement(s, p, o, c);
113             }
114         } catch (final Throwable ex) {
115         }
116         return null;
117     }
118 
119     private Object resolve(final Statement stmt, final byte index) {
120         switch (index) {
121         case 0:
122             return stmt.getSubject();
123         case 1:
124             return stmt.getPredicate();
125         case 2:
126             return stmt.getObject();
127         case 3:
128             return stmt.getContext();
129         case 4:
130             return this.subj;
131         case 5:
132             return this.pred;
133         case 6:
134             return this.obj;
135         case 7:
136             return this.ctx;
137         default:
138             throw new Error();
139         }
140     }
141 
142     @Override
143     public boolean equals(final Object object) {
144         if (object == this) {
145             return true;
146         }
147         if (!(object instanceof StatementTemplate)) {
148             return false;
149         }
150         final StatementTemplate other = (StatementTemplate) object;
151         return this.subj.equals(other.subj) && this.pred.equals(other.pred)
152                 && this.obj.equals(other.obj) && Objects.equals(this.ctx, other.ctx);
153     }
154 
155     @Override
156     public int hashCode() {
157         return Objects.hash(this.subj, this.pred, this.obj, this.ctx);
158     }
159 
160     @Override
161     public String toString() {
162         final StringBuilder builder = new StringBuilder();
163         toStringHelper(this.subj, builder);
164         builder.append(' ');
165         toStringHelper(this.pred, builder);
166         builder.append(' ');
167         toStringHelper(this.obj, builder);
168         builder.append(' ');
169         toStringHelper(this.ctx, builder);
170         return builder.toString();
171     }
172 
173     private void toStringHelper(final Object component, final StringBuilder builder) {
174         if (component instanceof StatementComponent) {
175             builder.append('?').append(((StatementComponent) component).getLetter());
176         } else if (component == null) {
177             builder.append("null");
178         } else {
179             try {
180                 Statements.formatValue((Value) component, Namespaces.DEFAULT, builder);
181             } catch (final IOException ex) {
182                 throw new Error(ex);
183             }
184         }
185     }
186 
187     private static Object check(final Object component) {
188         if (component != null && !(component instanceof Value)
189                 && !(component instanceof StatementComponent)) {
190             throw new IllegalArgumentException("Illegal component " + component);
191         }
192         return component;
193     }
194 
195     private static Object componentFor(final Var var) {
196         if (var == null) {
197             return null;
198         } else if (var.hasValue()) {
199             return var.getValue();
200         } else {
201             switch (var.getName().toLowerCase()) {
202             case "s":
203                 return StatementComponent.SUBJECT;
204             case "p":
205                 return StatementComponent.PREDICATE;
206             case "o":
207                 return StatementComponent.OBJECT;
208             case "c":
209                 return StatementComponent.CONTEXT;
210             default:
211                 throw new IllegalArgumentException("Could not extract component from "
212                         + var.getName());
213             }
214         }
215     }
216 
217     private static Object componentFor(final Var var, final StatementPattern body) {
218         if (var == null) {
219             return null;
220         } else if (var.hasValue()) {
221             return var.getValue();
222         } else {
223             final String name = var.getName();
224             final Var subjVar = body.getSubjectVar();
225             final Var predVar = body.getPredicateVar();
226             final Var objVar = body.getObjectVar();
227             final Var ctxVar = body.getContextVar();
228             if (!subjVar.hasValue() && name.equals(subjVar.getName())) {
229                 return StatementComponent.SUBJECT;
230             } else if (!predVar.hasValue() && name.equals(predVar.getName())) {
231                 return StatementComponent.PREDICATE;
232             } else if (!objVar.hasValue() && name.equals(objVar.getName())) {
233                 return StatementComponent.OBJECT;
234             } else if (ctxVar != null && !ctxVar.hasValue() && name.equals(ctxVar.getName())) {
235                 return StatementComponent.CONTEXT;
236             }
237             throw new IllegalArgumentException("Could not find variable " + var.getName()
238                     + " in pattern " + body);
239         }
240     }
241 }