001/**
002 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
003 *   This file is part of the LDP4j Project:
004 *     http://www.ldp4j.org/
005 *
006 *   Center for Open Middleware
007 *     http://www.centeropenmiddleware.com/
008 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
009 *   Copyright (C) 2014 Center for Open Middleware.
010 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
011 *   Licensed under the Apache License, Version 2.0 (the "License");
012 *   you may not use this file except in compliance with the License.
013 *   You may obtain a copy of the License at
014 *
015 *             http://www.apache.org/licenses/LICENSE-2.0
016 *
017 *   Unless required by applicable law or agreed to in writing, software
018 *   distributed under the License is distributed on an "AS IS" BASIS,
019 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
020 *   See the License for the specific language governing permissions and
021 *   limitations under the License.
022 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
023 *   Artifact    : org.ldp4j.framework:ldp4j-application-api:0.2.0
024 *   Bundle      : ldp4j-application-api-0.2.0.jar
025 * #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=#
026 */
027package org.ldp4j.application.data.constraints;
028
029import static com.google.common.base.Preconditions.checkArgument;
030import static com.google.common.base.Preconditions.checkNotNull;
031
032import java.io.Serializable;
033import java.net.URI;
034import java.util.Collection;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038
039import org.ldp4j.application.data.DataSet;
040import org.ldp4j.application.data.Individual;
041import org.ldp4j.application.data.Literal;
042import org.ldp4j.application.data.Value;
043import org.ldp4j.application.data.ValueVisitor;
044
045import com.google.common.base.MoreObjects;
046import com.google.common.collect.ImmutableList;
047import com.google.common.collect.ImmutableList.Builder;
048import com.google.common.collect.ImmutableSet;
049import com.google.common.collect.Lists;
050import com.google.common.collect.Maps;
051import com.google.common.collect.Sets;
052
053public final class Constraints implements Serializable {
054
055  public enum NodeKind {
056    NODE("Node"),
057    BLANK_NODE_OR_IRI("BlankNodeOrIRI"),
058    BLANK_NODE_OR_LITERAL("BlankNodeOrLiteral"),
059    LITERAL_OR_IRI("LiteralOrIRI"),
060    BLANK_NODE("BlankNode"),
061    IRI("IRI"),
062    LITERAL("Literal")
063    ;
064
065    private final String localName;
066
067    private NodeKind(String localName) {
068      this.localName = localName;
069    }
070
071    public String localName() {
072      return localName;
073    }
074  }
075
076  public interface Describable {
077
078    String label();
079    String comment();
080  }
081
082  public static final class Cardinality implements Serializable {
083
084    private static final long serialVersionUID = 7262473645776142538L;
085
086    private int min;
087    private int max;
088
089    private Cardinality(int min, int max) {
090      this.min = min;
091      this.max = max;
092    }
093
094    public int min() {
095      return this.min;
096    }
097
098    public int max() {
099      return this.max;
100    }
101
102    /**
103     * {@inheritDoc}
104     */
105    @Override
106    public String toString() {
107      return
108        MoreObjects.
109          toStringHelper(getClass()).
110            add("min", this.min).
111            add("max", this.max).
112            toString();
113    }
114
115    public static Cardinality mandatory() {
116      return new Cardinality(1,1);
117    }
118
119    public static Cardinality atMost(int max) {
120      return new Cardinality(0,max);
121    }
122
123    public static Cardinality atLeast(int min) {
124      return new Cardinality(min,-1);
125    }
126
127    public static Cardinality create(int min, int max) {
128      return new Cardinality(min,max);
129    }
130
131    public static Cardinality optional() {
132      return new Cardinality(0,1);
133    }
134
135    public static Cardinality unbound() {
136      return new Cardinality(0,-1);
137    }
138
139  }
140
141
142  public abstract static class AbstractPropertyConstraint<T extends AbstractPropertyConstraint<T>> implements Serializable {
143
144    private static final long serialVersionUID = 6473281395518369031L;
145
146    private static final class ValueCollector implements ValueVisitor {
147
148      private final Collection<Serializable> individuals;
149      private final Collection<Literal<?>> literals;
150
151      private ValueCollector(Collection<Serializable> individuals, Collection<Literal<?>> lLiterals) {
152        this.individuals = individuals;
153        this.literals = lLiterals;
154      }
155
156      @Override
157      public void visitLiteral(Literal<?> value) {
158        literals.add(value);
159      }
160
161      @Override
162      public void visitIndividual(Individual<?, ?> value) {
163        individuals.add(value.id());
164      }
165
166    }
167
168    private final URI predicate;
169    private String comment;
170    private String label;
171    private Set<Serializable> allowedIndividuals;
172    private Set<Literal<?>> allowedLiterals;
173    private List<Serializable> individuals;
174    private List<Literal<?>> literals;
175    private URI datatype;
176    private NodeKind nodeKind;
177    private Shape valueShape;
178    private URI valueType;
179    private Cardinality cardinality;
180
181    protected AbstractPropertyConstraint(URI predicate) {
182      this.predicate=predicate;
183    }
184
185    protected abstract T delegate();
186
187    public T withLabel(String label) {
188      checkNotNull(label,"Label cannot be null");
189      this.label=label;
190      return delegate();
191    }
192
193    public String label() {
194      return this.label;
195    }
196
197    public T withComment(String comment) {
198      checkNotNull(comment,"Comment cannot be null");
199      this.comment=comment;
200      return delegate();
201    }
202
203    public String comment() {
204      return this.comment;
205    }
206
207    public URI predicate() {
208      return this.predicate;
209    }
210
211    public T withCardinality(Cardinality cardinality) {
212      checkNotNull(cardinality,"Cardinality cannot be null");
213      this.cardinality=cardinality;
214      return delegate();
215    }
216
217    public Cardinality cardinality() {
218      Cardinality result = this.cardinality;
219      if(result==null) {
220        result=Cardinality.unbound();
221      }
222      return result;
223    }
224
225    public T withAllowedValues(Value... allowedValues) {
226      checkNotNull(allowedValues,"Allowed values cannot be null");
227      this.allowedLiterals=Sets.newLinkedHashSet();
228      this.allowedIndividuals=Sets.newLinkedHashSet();
229      ValueCollector valueCollector=new ValueCollector(this.allowedIndividuals,this.allowedLiterals);
230      for(Value value:allowedValues) {
231        value.accept(valueCollector);
232      }
233      return delegate();
234    }
235
236    public Set<Literal<?>> allowedLiterals() {
237      Set<Literal<?>> result=this.allowedLiterals;
238      if(result==null) {
239        result=Sets.newLinkedHashSet();
240      }
241      return ImmutableSet.copyOf(result);
242    }
243
244    public Set<Individual<?,?>> allowedIndividuals(DataSet dataSet) {
245      Set<Serializable> result=this.allowedIndividuals;
246      if(result==null) {
247        result=Sets.newLinkedHashSet();
248      }
249      return ConstraintsHelper.getOrCreateIndividuals(dataSet,result);
250    }
251
252    public T withDatatype(URI datatype) {
253      checkNotNull(datatype,"Datatype cannot be null");
254      this.datatype = datatype;
255      return delegate();
256    }
257
258    public URI datatype() {
259      return this.datatype;
260    }
261
262    public T withValue(Value... values) {
263      checkNotNull(values,"Value cannot be null");
264      this.literals=Lists.newArrayList();
265      this.individuals=Lists.newArrayList();
266      ValueCollector valueCollector=new ValueCollector(this.individuals,this.literals);
267      for(Value value:values) {
268        value.accept(valueCollector);
269      }
270      return delegate();
271    }
272
273    public List<Literal<?>> literals() {
274      List<Literal<?>> result=this.literals;
275      if(result==null) {
276        result=Lists.newArrayList();
277      }
278      return ImmutableList.copyOf(result);
279    }
280
281    public List<Individual<?,?>> individuals(DataSet dataSet) {
282      List<Serializable> result=this.individuals;
283      if(result==null) {
284        result=Lists.newArrayList();
285      }
286      return ConstraintsHelper.getOrCreateIndividuals(dataSet,result);
287    }
288
289    public T withNodeKind(NodeKind nodeKind) {
290      checkNotNull(nodeKind,"Node kind cannot be null");
291      this.nodeKind=nodeKind;
292      return delegate();
293    }
294
295    public NodeKind nodeKind() {
296      return this.nodeKind;
297    }
298
299    public T withValueShape(Shape valueShape) {
300      this.valueShape = valueShape;
301      return delegate();
302    }
303
304    public Shape valueShape() {
305      return this.valueShape;
306    }
307
308    public T withValueType(URI valueType) {
309      this.valueType = valueType;
310      return delegate();
311    }
312
313    public URI valueType() {
314      return this.valueType;
315    }
316
317    @Override
318    public String toString() {
319      return
320        MoreObjects.
321          toStringHelper(getClass()).
322            omitNullValues().
323            add("predicate", this.predicate).
324            add("label", this.label).
325            add("comment", this.comment).
326            add("cardinality", this.cardinality()).
327            add("allowedLiterals", this.allowedLiterals).
328            add("allowedIndividuals", this.allowedIndividuals).
329            add("datatype", this.datatype).
330            add("literals", this.literals).
331            add("literals", this.individuals).
332            add("nodeKind", this.nodeKind).
333            add("valueShape", this.valueShape).
334            add("valueType", this.valueType).
335            toString();
336    }
337
338  }
339
340  public static final class PropertyConstraint extends AbstractPropertyConstraint<PropertyConstraint> implements Describable {
341
342    private static final long serialVersionUID = -2646499801130951583L;
343
344    private PropertyConstraint(URI predicate) {
345      super(predicate);
346    }
347
348    @Override
349    protected PropertyConstraint delegate() {
350      return this;
351    }
352
353  }
354
355  public static final class InversePropertyConstraint extends AbstractPropertyConstraint<InversePropertyConstraint> implements Describable {
356
357    private static final long serialVersionUID = -6328974380403084873L;
358
359    private InversePropertyConstraint(URI predicate) {
360      super(predicate);
361    }
362
363    @Override
364    protected InversePropertyConstraint delegate() {
365      return this;
366    }
367
368  }
369
370  public static final class Shape implements Describable, Serializable {
371
372    private static final long serialVersionUID = 3966457418001884744L;
373
374    private Map<URI,AbstractPropertyConstraint<?>> constraints; // NOSONAR
375    private String label;
376    private String comment;
377
378    private Shape() {
379      this.constraints=Maps.newLinkedHashMap();
380    }
381
382    public Shape withLabel(String label) {
383      this.label=label;
384      return this;
385    }
386
387    /**
388     * {@inheritDoc}
389     */
390    @Override
391    public String label() {
392      return this.label;
393    }
394
395    public Shape withComment(String comment) {
396      this.comment=comment;
397      return this;
398    }
399
400    /**
401     * {@inheritDoc}
402     */
403    @Override
404    public String comment() {
405      return this.comment;
406    }
407
408    public Shape withPropertyConstraint(PropertyConstraint constraint) {
409      checkNotNull(constraint);
410      URI predicate = constraint.predicate();
411      checkArgument(!this.constraints.containsKey(predicate),"Shape already defines constraints for predicate '"+predicate+"'");
412      this.constraints.put(predicate,constraint);
413      return this;
414    }
415
416    public Shape withPropertyConstraint(InversePropertyConstraint constraint) {
417      checkNotNull(constraint);
418      URI predicate = constraint.predicate();
419      checkArgument(!this.constraints.containsKey(predicate),"Shape already defines constraints for predicate '"+predicate+"'");
420      this.constraints.put(predicate,constraint);
421      return this;
422    }
423
424    public List<PropertyConstraint> propertyConstraints() {
425      final Builder<PropertyConstraint> builder=ImmutableList.<PropertyConstraint>builder();
426      filter(builder,PropertyConstraint.class);
427      return builder.build();
428    }
429
430    public List<InversePropertyConstraint> inversePropertyConstraints() {
431      final Builder<InversePropertyConstraint> builder=ImmutableList.<InversePropertyConstraint>builder();
432      filter(builder,InversePropertyConstraint.class);
433      return builder.build();
434    }
435
436    private <T extends AbstractPropertyConstraint<?>> void filter(Builder<T> builder, Class<? extends T> clazz) {
437      for(AbstractPropertyConstraint<?> c:this.constraints.values()) {
438        if(clazz.isInstance(c)) {
439          builder.add(clazz.cast(c));
440        }
441      }
442    }
443
444    @Override
445    public String toString() {
446      return
447        MoreObjects.
448          toStringHelper(getClass()).
449            omitNullValues().
450            add("label", this.label).
451            add("comment", this.comment).
452            add("constraints",this.constraints).
453            toString();
454    }
455
456  }
457
458  private static final long serialVersionUID = 4368698694568719975L;
459
460  private Map<Serializable,Shape> nodeShapes; // NOSONAR
461  private Map<URI,Shape> typeShapes; // NOSONAR
462
463  private Constraints() {
464    this.nodeShapes=Maps.newLinkedHashMap();
465    this.typeShapes=Maps.newLinkedHashMap();
466  }
467
468  public List<Shape> shapes() {
469    return
470      ImmutableList.
471        <Shape>builder().
472          addAll(this.typeShapes.values()).
473          addAll(this.nodeShapes.values()).
474          build();
475  }
476
477  public Set<URI> types() {
478    return ImmutableSet.copyOf(this.typeShapes.keySet());
479  }
480
481  public Set<Individual<?,?>> nodes(DataSet dataSet) {
482    checkNotNull(dataSet,"Data set cannot be null");
483    return
484      ConstraintsHelper.
485        getOrCreateIndividuals(
486          dataSet,
487          this.nodeShapes.keySet());
488  }
489
490  public Shape typeShape(URI type) {
491    return this.typeShapes.get(type);
492  }
493
494  public Shape nodeShape(Individual<?,?> individual) {
495    return this.nodeShapes.get(individual.id());
496  }
497
498  public Constraints withTypeShape(URI type, Shape shape) {
499    checkNotNull(type,"Type URI cannot be null");
500    checkNotNull(shape,"Shape cannot be null");
501    checkArgument(!this.typeShapes.containsKey(type),"A shape is already defined for type '"+type+"'");
502    this.typeShapes.put(type,shape);
503    return this;
504  }
505
506  public Constraints withNodeShape(Individual<?,?> individual, Shape shape) {
507    checkNotNull(individual,"Type URI cannot be null");
508    checkNotNull(shape,"Shape cannot be null");
509    checkArgument(!this.nodeShapes.containsKey(individual.id()),"A shape is already defined for individual '"+individual.id()+"'");
510    this.nodeShapes.put(individual.id(),shape);
511    return this;
512  }
513
514  @Override
515  public String toString() {
516    return
517      MoreObjects.
518        toStringHelper(getClass()).
519          add("typeShapes", this.typeShapes).
520          add("nodeShapes", this.nodeShapes).
521          toString();
522  }
523
524  public static Shape shape() {
525    return new Shape();
526  }
527
528  public static Constraints constraints() {
529    return new Constraints();
530  }
531
532  public static PropertyConstraint propertyConstraint(URI predicate) {
533    return new PropertyConstraint(predicate);
534  }
535
536  public static InversePropertyConstraint inversePropertyConstraint(URI predicate) {
537    return new InversePropertyConstraint(predicate);
538  }
539
540}