AnnotationQuery.java

/*
 *  AnnotationQuery.java
 *
 *  Copyright (c) 2007-2011, The University of Sheffield.
 *
 *  This file is part of GATE Mímir (see http://gate.ac.uk/family/mimir.html), 
 *  and is free software, licenced under the GNU Lesser General Public License,
 *  Version 3, June 2007 (also included with this distribution as file
 *  LICENCE-LGPL3.html).
 *
 *  Valentin Tablan, 4 Mar 2009
 *
 *  $Id: AnnotationQuery.java 17236 2014-01-17 15:31:02Z valyt $
 */
package gate.mimir.search.query;


import gate.mimir.Constraint;
import gate.mimir.ConstraintType;
import gate.mimir.SemanticAnnotationHelper;
import gate.mimir.search.QueryEngine;
import gate.mimir.search.terms.AnnotationTermsQuery;
import gate.mimir.search.terms.TermsResultSet;
import it.unimi.di.big.mg4j.index.Index;
import it.unimi.di.big.mg4j.search.visitor.DocumentIteratorVisitor;
import it.unimi.dsi.fastutil.objects.ReferenceSet;
import it.unimi.dsi.fastutil.objects.ReferenceSets;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * A query for the annotations index. 
 */
public class AnnotationQuery implements QueryNode {

  private static final long serialVersionUID = 5996543707885867821L;


  public static class AnnotationQueryExecutor extends AbstractQueryExecutor{

    
    /**
     * @param engine
     * @param query
     */
    public AnnotationQueryExecutor(AnnotationQuery query, QueryEngine engine) throws IOException {
      super(engine, query);
      this.query = query;
      buildQuery();
    }
    
    /**
     * The query being executed.
     */
    private AnnotationQuery query;
    
    /**
     * Logger for this class.
     */
    private static Logger logger = LoggerFactory.getLogger(AnnotationQueryExecutor.class);

    /**
     * The underlying OrQuery executor that actually does the work.
     */
    private QueryExecutor underlyingExecutor;
    
    private transient boolean isInDocumentMode;
    
    /**
     * Build the underlying OrQuery executor that this annotation query uses.
     */
    protected void buildQuery() throws IOException {
      SemanticAnnotationHelper helper = engine.getAnnotationHelper(query);
      isInDocumentMode = (helper.getMode() == 
          SemanticAnnotationHelper.Mode.DOCUMENT);
      // get the mention URIs
      TermsResultSet trs = new AnnotationTermsQuery(query).execute(engine);
      if(trs.termStrings != null && trs.termStrings.length > 0 && 
         trs.termLengths != null) {
        QueryNode[] disjuncts = new QueryNode[trs.termStrings.length];
        for(int index = 0; index < trs.termStrings.length; index++) {
          // create a term query for the mention URI
          disjuncts[index] = new TermQuery(query.annotationType, 
              trs.termStrings[index], trs.termLengths[index]);
        }
        QueryNode underlyingQuery = new OrQuery(disjuncts);
        underlyingExecutor = underlyingQuery.getQueryExecutor(engine);        
      } else {
        // no results from the helper => no results from us
        latestDocument = -1;
      }
    }
    
    
    /* (non-Javadoc)
     * @see gate.mimir.search.query.QueryExecutor#close()
     */
    public void close() throws IOException {
      if(closed) return;
      super.close();
      if(underlyingExecutor != null) underlyingExecutor.close();
    }

    /* (non-Javadoc)
     * @see gate.mimir.search.query.QueryExecutor#getLatestDocument()
     */
    public long getLatestDocument() {
      if(closed || latestDocument == -1) return -1;
      return underlyingExecutor.getLatestDocument();
    }

    /* (non-Javadoc)
     * @see gate.mimir.search.query.QueryExecutor#nextDocument(int)
     */
    public long nextDocument(long greaterThan) throws IOException {
      if(closed || latestDocument == -1) return -1;
      return latestDocument = underlyingExecutor.nextDocument(greaterThan);
    }

    /* (non-Javadoc)
     * @see gate.mimir.search.query.QueryExecutor#nextHit()
     */
    public Binding nextHit() throws IOException {
      if(closed || latestDocument == -1) return null;
      Binding underlyingHit = underlyingExecutor.nextHit();
      if(underlyingHit == null) return null;
      long doc = underlyingHit.getDocumentId();
      if(isInDocumentMode) {
        return new Binding(query, doc, 0, engine.getIndex().getDocumentSize(doc),
          null);
      } else {
        return new Binding(query, doc,
          underlyingHit.getTermPosition(),
          underlyingHit.getLength(),
          null);
      }
    }
   
    @Override
    public ReferenceSet<Index> indices() {
      if(underlyingExecutor != null) {
        return underlyingExecutor.indices();
      } else {
        return ReferenceSets.EMPTY_SET;
      }
    }
    
    public <T> T accept( final DocumentIteratorVisitor<T> visitor ) throws IOException {
      if(underlyingExecutor == null) return null;
      if ( ! visitor.visitPre( this ) ) return null;
      final T[] a = visitor.newArray( 1 );
      if ( a == null ) {
        if ( underlyingExecutor.accept( visitor ) == null ) return null;
      }
      else {
        if ( ( a[ 0 ] = underlyingExecutor.accept( visitor ) ) == null ) return null;
      }
      return visitor.visitPost( this, a );
    }

    public <T> T acceptOnTruePaths( final DocumentIteratorVisitor<T> visitor ) throws IOException {
      if(underlyingExecutor == null) return null;
      if ( ! visitor.visitPre( this ) ) return null;
      final T[] a = visitor.newArray( 1 );
      if ( a == null ) {
        if ( underlyingExecutor.acceptOnTruePaths( visitor ) == null ) return null;     
      }
      else {
        if ( ( a[ 0 ] = underlyingExecutor.acceptOnTruePaths( visitor ) ) == null ) return null;
      }
      return visitor.visitPost( this, a );
    }
  }
  
  /**
   * Constructs a new {@link AnnotationQuery}.
   * 
   * Convenience variant of {@link #AnnotationQuery(String, List)} 
   * for cases where all predicates are of type 
   * {@link SemanticAnnotationHelper.ConstraintType#EQ}.
   * 
   * @param annotationType the desired annotation type, for the annotations to 
   * be matched.
   * @param featureConstraints the constraints over the features of the 
   * annotations to be found. This is represented as a {@link Map} from feature
   * name (a {@link String}) to feature value (also a {@link String}).
   * 
   * @see AnnotationQuery#AnnotationQuery(String, List)  
   */
  public AnnotationQuery(String annotationType,
          Map<String, String> featureConstraints) {
    if(featureConstraints == null){
      featureConstraints = new HashMap<String, String>();
    }
    this.annotationType = annotationType;
    this.constraints = new ArrayList<Constraint>(featureConstraints.size());
    for(Map.Entry<String, String> entry : featureConstraints.entrySet()){
      this.constraints.add(new Constraint(ConstraintType.EQ,
              entry.getKey(), entry.getValue()));
    }
  }

  /**
   * Constructs a new Annotation Query.
   *  
   * @param annotationType the type of annotation being sought.
   * @param constraints a list of constraints placed on the feature values. An 
   * empty constraints list will make no requests regarding the feature values,
   * hence it will match all annotations of the right type. 
   */
  public AnnotationQuery(String annotationType, List<Constraint> constraints) {
    this.annotationType = annotationType;
    this.constraints = constraints == null ? new ArrayList<Constraint>() :constraints;
  }
  
  /* (non-Javadoc)
   * @see gate.mimir.search.query.QueryNode#getQueryExecutor(java.util.Map)
   */
  public QueryExecutor getQueryExecutor(QueryEngine engine)
          throws IOException {
    return new AnnotationQueryExecutor(this, engine);
  }
  
  
  /**
   * Gets the annotation type for this query. 
   * @return the annotationType
   */
  public String getAnnotationType() {
    return annotationType;
  }

  /**
   * Gets the feature constraints, represented as a {@link Map} from 
   * feature name (a {@link String}) to feature value (also a {@link String}). 
   * @return the featureConstraints
   */
  public List<Constraint> getConstraints() {
    return constraints;
  }


  /**
   * The annotation type for this query.
   */
  private String annotationType;
  
  /**
   * The constrains over the annotation features.
   */
  private List<Constraint> constraints;
  
  
  public String toString() {
    return "Annotation ( type = " + 
    annotationType + ", features=" + 
    (constraints != null ? constraints.toString() : "[]") +
    ")";
  }

}