content: package org.aksw.rdfunit.model.impl.shacl;
  
  import com.google.common.collect.ImmutableSet;
  import java.util.Collection;
  import java.util.Optional;
  import java.util.Set;
  import java.util.stream.Collectors;
  import lombok.Builder;
  import lombok.EqualsAndHashCode;
  import lombok.Getter;
  import lombok.NonNull;
  import lombok.ToString;
  import org.aksw.rdfunit.enums.ShapeTargetType;
  import org.aksw.rdfunit.model.impl.ResultAnnotationImpl;
  import org.aksw.rdfunit.model.interfaces.ResultAnnotation;
  import org.aksw.rdfunit.model.interfaces.TestCase;
  import org.aksw.rdfunit.model.interfaces.TestCaseAnnotation;
  import org.aksw.rdfunit.model.interfaces.shacl.PrefixDeclaration;
  import org.aksw.rdfunit.model.interfaces.shacl.ShapeTarget;
  import org.aksw.rdfunit.model.interfaces.shacl.TargetBasedTestCase;
  import org.aksw.rdfunit.utils.CommonNames;
  import org.aksw.rdfunit.utils.JenaUtils;
  import org.aksw.rdfunit.vocabulary.SHACL;
  import org.apache.jena.query.ParameterizedSparqlString;
  import org.apache.jena.query.QuerySolution;
  import org.apache.jena.query.QuerySolutionMap;
  import org.apache.jena.rdf.model.RDFNode;
  import org.apache.jena.rdf.model.Resource;
  import org.apache.jena.rdf.model.ResourceFactory;
  
  /**
   * Used mainly for SHACL in order to inject a target from a shape to a sparql constraint
   *
   * @author Dimitris Kontokostas
   * @since 14/2/2016 12:55 μμ
   */
  @Builder
  @ToString
  @EqualsAndHashCode(exclude = {"resource"})
  public class TestCaseWithTarget implements TestCase, TargetBasedTestCase {
  
    private static final String thisVar = "\\$this_asdf_1234_qwer";
    @Getter
    @NonNull
    private final ShapeTarget target;
    @Getter
    @NonNull
    private final String filterSparql;
    @Getter
    @NonNull
    private final TestCase testCase;
    @Getter
    @NonNull
    private final Resource element = ResourceFactory.createProperty(JenaUtils.getUniqueIri());
  
    public TestCaseWithTarget(ShapeTarget target, String filterSparql, TestCase testCase) {
      this.target = target;
      this.filterSparql = filterSparql;
      this.testCase = testCase;
    }
  
    private static TestCaseAnnotation createFromReferences(TestCaseAnnotation annotation,
        Collection<Resource> references) {
  
      ImmutableSet.Builder<String> referencesBuilder = ImmutableSet.builder();
      ImmutableSet<String> finalReferences = referencesBuilder
          .addAll(references.stream().map(Resource::getURI).collect(Collectors.toSet()))
          .addAll(annotation.getReferences()).build();
  
      ImmutableSet.Builder<ResultAnnotation> resultAnnotationBuilder = ImmutableSet.builder();
      resultAnnotationBuilder
          .addAll(annotation.getResultAnnotations())
          .addAll(annotation.getVariableAnnotations());
  
      return new TestCaseAnnotation(
          annotation.getElement(),
          annotation.getGenerated(),
          annotation.getAutoGeneratorURI(),
          annotation.getAppliesTo(),
          annotation.getSourceUri(),
          finalReferences,
          annotation.getDescription(),
          annotation.getTestCaseLogLevel(),
          resultAnnotationBuilder.build()
      );
    }
  
    @Override
    public String getSparqlWhere() {
      String withFocus = testCase.getSparqlWhere();
      if (target instanceof ShapeTargetValueShape) {
        // inserting the focus node in the group statement
        withFocus = withFocus.replaceFirst("(?i)GROUP BY\\s+\\?" + CommonNames.This,
            "GROUP BY ?" + CommonNames.This + " ?focusNode");
      }
      return injectTargetInSparql(withFocus, this.target);
    }
  
    @Override
    public String getSparqlPrevalence() {
  
      if (testCase.getSparqlPrevalence().trim().isEmpty()
          // skip node targets
          && (!target.getTargetType().equals(ShapeTargetType.NodeTarget))) {
  
        return "SELECT (count(?this) AS ?total) WHERE { " + target.getPattern() + "}";
      }
  
      return testCase.getSparqlPrevalence();
  
    }
  
    @Override
    public RDFNode getFocusNode(QuerySolution solution) {
  
      String focusVar = getVariableAnnotations().stream()
          .filter(ra -> ra.getAnnotationProperty().equals(SHACL.focusNode))
          .map(ResultAnnotation::getAnnotationVarName)
          .filter(Optional::isPresent)
          .map(Optional::get)
          .findFirst()
          .orElse(CommonNames.This);
      return solution.get(focusVar);
    }
  
    @Override
    public TestCaseAnnotation getTestCaseAnnotation() {
      // inject SHACL annotations
      // TODO this is not efficient, recreates it on every request but needs major refactoring to improve
      return createFromReferences(testCase.getTestCaseAnnotation(),
          target.getRelatedOntologyResources());
    }
  
    @Override
    public Set<ResultAnnotation> getVariableAnnotations() {
      ImmutableSet.Builder<ResultAnnotation> builder = ImmutableSet.builder();
      if (ShapeTargetValueShape.class.isAssignableFrom(target.getClass())) {
  
        getTestCaseAnnotation().getVariableAnnotations().forEach(va -> {
          if (!va.getAnnotationProperty().equals(SHACL.focusNode)) {
            builder.add(va);
          }
        });
        // add also a new variable annotation for the focus node
        builder
            .add(new ResultAnnotationImpl.Builder(ResourceFactory.createResource(), SHACL.focusNode)
                .setVariableName(SHACL.focusNode.getLocalName()).build());
      } else {
        builder.addAll(getTestCaseAnnotation().getVariableAnnotations());
      }
      return builder.build();
    }
  
    @Override
    public Collection<PrefixDeclaration> getPrefixDeclarations() {
      return testCase.getPrefixDeclarations();
    }
  
    private String injectSparqlSnippet(String sparqlQuery, String sparqlSnippet) {
      int bracketIndex = sparqlQuery.indexOf('{');
      return sparqlQuery.substring(0, bracketIndex + 1) + sparqlSnippet + sparqlQuery
          .substring(bracketIndex + 1);
    }
  
    private String injectTargetInSparql(String sparqlQuery, ShapeTarget target) {
  
      String queryWithFilter = injectSparqlSnippet(sparqlQuery, filterSparql);
      String queryWithTarget = injectTarget(queryWithFilter, target);
  
      return queryWithTarget;
    }
  
    private String injectTarget(String sparqlQuery, ShapeTarget target) {
      String changedQuery = sparqlQuery;
  
      if (target.getTargetType().equals(ShapeTargetType.NodeTarget)) {
        changedQuery = sparqlQuery
            .replaceFirst("[\\$\\?]this", thisVar)
            .replaceAll("(?i)BOUND\\s*\\(\\s*[\\$\\?]this\\s*\\)", "BOUND\\(" + thisVar + "\\)")
            .replaceAll("(?i)GROUP\\s+BY\\s+[\\$\\?]this", "GROUP BY " + thisVar);
  
        QuerySolutionMap qsm = new QuerySolutionMap();
        qsm.add(CommonNames.This, target.getNode());
        ParameterizedSparqlString pq = new ParameterizedSparqlString(changedQuery, qsm);
        changedQuery = pq.toString();
  
        changedQuery = changedQuery
            .replaceFirst(thisVar, "\\$this")
            .replaceAll("(?i)BOUND\\(" + thisVar + "\\)", "BOUND\\(\\?this\\)")
            .replaceAll("(?i)GROUP BY " + thisVar, "GROUP BY \\$this");
      }
  
      return injectSparqlSnippet(changedQuery, target.getPattern());
    }
  }
  