/**
  * This method changes the query object so that all propertyName references are resolved to simple
  * attribute names against the schema of the feature source.
  *
  * <p>For example, this method ensures that propertyName's such as "gml:name" are rewritten as
  * simply "name".
  */
 public static Query resolvePropertyNames(Query query, SimpleFeatureType schema) {
   Filter resolved = resolvePropertyNames(query.getFilter(), schema);
   if (resolved == query.getFilter()) {
     return query;
   }
   Query newQuery = new Query(query);
   newQuery.setFilter(resolved);
   return newQuery;
 }
 /**
  * Copy contructor, clones the state of a generic Query into a DefaultQuery
  *
  * @param query
  */
 public DefaultQuery(Query query) {
   this(
       query.getTypeName(),
       query.getNamespace(),
       query.getFilter(),
       query.getMaxFeatures(),
       query.getProperties(),
       query.getHandle());
   this.sortBy = query.getSortBy();
   this.coordinateSystem = query.getCoordinateSystem();
   this.coordinateSystemReproject = query.getCoordinateSystemReproject();
   this.version = query.getVersion();
   this.hints = query.getHints();
   this.startIndex = query.getStartIndex();
   this.alias = query.getAlias();
   this.joins = query.getJoins();
 }
  // Jody - Recomend moving to the following
  // When we are ready for CoordinateSystem support
  public FeatureReader<SimpleFeatureType, SimpleFeature> getFeatureReader(
      Query query, Transaction transaction) throws IOException {
    Filter filter = query.getFilter();
    String typeName = query.getTypeName();
    String propertyNames[] = query.getPropertyNames();

    if (filter == null) {
      throw new NullPointerException(
          "getFeatureReader requires Filter: " + "did you mean Filter.INCLUDE?");
    }
    if (typeName == null) {
      throw new NullPointerException(
          "getFeatureReader requires typeName: "
              + "use getTypeNames() for a list of available types");
    }
    if (transaction == null) {
      throw new NullPointerException(
          "getFeatureReader requires Transaction: "
              + "did you mean to use Transaction.AUTO_COMMIT?");
    }
    SimpleFeatureType featureType = getSchema(query.getTypeName());

    if (propertyNames != null || query.getCoordinateSystem() != null) {
      try {
        featureType =
            DataUtilities.createSubType(featureType, propertyNames, query.getCoordinateSystem());
      } catch (SchemaException e) {
        LOGGER.log(Level.FINEST, e.getMessage(), e);
        throw new DataSourceException("Could not create Feature Type for query", e);
      }
    }
    if (filter == Filter.EXCLUDE || filter.equals(Filter.EXCLUDE)) {
      return new EmptyFeatureReader<SimpleFeatureType, SimpleFeature>(featureType);
    }
    // GR: allow subclases to implement as much filtering as they can,
    // by returning just it's unsupperted filter
    filter = getUnsupportedFilter(typeName, filter);
    if (filter == null) {
      throw new NullPointerException(
          "getUnsupportedFilter shouldn't return null. Do you mean Filter.INCLUDE?");
    }

    // There are cases where the readers have to lock.  Take shapefile for example.  Getting a
    // Reader causes
    // the file to be locked.  However on a commit TransactionStateDiff locks before a writer is
    // obtained.  In order to
    // prevent deadlocks either the diff has to obtained first or the reader has to be obtained
    // first.
    // Because shapefile writes to a buffer first the actual write lock is not flipped until the
    // transaction has most of the work
    // done.  As a result I suggest getting the diff first then getting the reader.
    // JE
    Diff diff = null;
    if (transaction != Transaction.AUTO_COMMIT) {
      TransactionStateDiff state = state(transaction);
      if (state != null) {
        diff = state.diff(typeName);
      }
    }

    // This calls our subclass "simple" implementation
    // All other functionality will be built as a reader around
    // this class
    //
    FeatureReader<SimpleFeatureType, SimpleFeature> reader = getFeatureReader(typeName, query);

    if (diff != null)
      reader =
          new DiffFeatureReader<SimpleFeatureType, SimpleFeature>(reader, diff, query.getFilter());

    if (!filter.equals(Filter.INCLUDE)) {
      reader = new FilteringFeatureReader<SimpleFeatureType, SimpleFeature>(reader, filter);
    }

    if (!featureType.equals(reader.getFeatureType())) {
      LOGGER.fine("Recasting feature type to subtype by using a ReTypeFeatureReader");
      reader = new ReTypeFeatureReader(reader, featureType, false);
    }

    if (query.getMaxFeatures() != Query.DEFAULT_MAX) {
      reader =
          new MaxFeatureReader<SimpleFeatureType, SimpleFeature>(reader, query.getMaxFeatures());
    }

    return reader;
  }
  /**
   * Takes two {@link Query}objects and produce a new one by mixing the restrictions of both of
   * them.
   *
   * <p>The policy to mix the queries components is the following:
   *
   * <ul>
   *   <li>typeName: type names MUST match (not checked if some or both queries equals to <code>
   *       Query.ALL</code>)
   *   <li>handle: you must provide one since no sensible choice can be done between the handles of
   *       both queries
   *   <li>maxFeatures: the lower of the two maxFeatures values will be used (most restrictive)
   *   <li>attributeNames: the attributes of both queries will be joined in a single set of
   *       attributes. IMPORTANT: only <b><i>explicitly</i></b> requested attributes will be joint,
   *       so, if the method <code>retrieveAllProperties()</code> of some of the queries returns
   *       <code>true</code> it does not means that all the properties will be joined. You must
   *       create the query with the names of the properties you want to load.
   *   <li>filter: the filtets of both queries are or'ed
   *   <li><b>any other query property is ignored</b> and no guarantees are made of their return
   *       values, so client code shall explicitly care of hints, startIndex, etc., if needed.
   * </ul>
   *
   * @param firstQuery Query against this DataStore
   * @param secondQuery DOCUMENT ME!
   * @param handle DOCUMENT ME!
   * @return Query restricted to the limits of definitionQuery
   * @throws NullPointerException if some of the queries is null
   * @throws IllegalArgumentException if the type names of both queries do not match
   */
  public static Query mixQueries(Query firstQuery, Query secondQuery, String handle) {
    if ((firstQuery == null) && (secondQuery == null)) {
      // throw new NullPointerException("Cannot combine two null queries");
      return Query.ALL;
    }
    if (firstQuery == null || firstQuery.equals(Query.ALL)) {
      return secondQuery;
    } else if (secondQuery == null || secondQuery.equals(Query.ALL)) {
      return firstQuery;
    }
    if ((firstQuery.getTypeName() != null) && (secondQuery.getTypeName() != null)) {
      if (!firstQuery.getTypeName().equals(secondQuery.getTypeName())) {
        String msg =
            "Type names do not match: "
                + firstQuery.getTypeName()
                + " != "
                + secondQuery.getTypeName();
        throw new IllegalArgumentException(msg);
      }
    }

    // mix versions, if possible
    String version;
    if (firstQuery.getVersion() != null) {
      if (secondQuery.getVersion() != null
          && !secondQuery.getVersion().equals(firstQuery.getVersion()))
        throw new IllegalArgumentException("First and second query refer different versions");
      version = firstQuery.getVersion();
    } else {
      version = secondQuery.getVersion();
    }

    // none of the queries equals Query.ALL, mix them
    // use the more restrictive max features field
    int maxFeatures = Math.min(firstQuery.getMaxFeatures(), secondQuery.getMaxFeatures());

    // join attributes names
    String[] propNames =
        joinAttributes(firstQuery.getPropertyNames(), secondQuery.getPropertyNames());

    // join filters
    Filter filter = firstQuery.getFilter();
    Filter filter2 = secondQuery.getFilter();

    if ((filter == null) || filter.equals(Filter.INCLUDE)) {
      filter = filter2;
    } else if ((filter2 != null) && !filter2.equals(Filter.INCLUDE)) {
      filter = ff.and(filter, filter2);
    }
    Integer start = 0;
    if (firstQuery.getStartIndex() != null) {
      start = firstQuery.getStartIndex();
    }
    if (secondQuery.getStartIndex() != null) {
      start += secondQuery.getStartIndex();
    }
    // collect all hints
    Hints hints = new Hints();
    if (firstQuery.getHints() != null) {
      hints.putAll(firstQuery.getHints());
    }
    if (secondQuery.getHints() != null) {
      hints.putAll(secondQuery.getHints());
    }
    // build the mixed query
    String typeName =
        firstQuery.getTypeName() != null ? firstQuery.getTypeName() : secondQuery.getTypeName();

    Query mixed = new Query(typeName, filter, maxFeatures, propNames, handle);
    mixed.setVersion(version);
    mixed.setHints(hints);
    if (start != 0) {
      mixed.setStartIndex(start);
    }
    return mixed;
  }