/**
   * Given a feature id following the <typename>.<internalId> convention, the original type and the
   * destination type, this converts the id from <original>.<internalid> to <target>.<internalid>
   *
   * @param id
   * @param original
   * @param target
   * @return
   */
  public static FeatureId reTypeId(
      FeatureId sourceId, SimpleFeatureType original, SimpleFeatureType target) {
    final String originalTypeName = original.getName().getLocalPart();
    final String destTypeName = target.getName().getLocalPart();
    if (destTypeName.equals(originalTypeName)) return sourceId;

    final String prefix = originalTypeName + ".";
    if (sourceId.getID().startsWith(prefix)) {
      return new FeatureIdImpl(destTypeName + "." + sourceId.getID().substring(prefix.length()));
    } else return sourceId;
  }
  private OpenBitSet applySpatialFilter(
      Set<FeatureId> matches, Multimap<FeatureId, Integer> docIndexLookup, OpenBitSet bits)
      throws IOException {

    JeevesJCS jcs = getJCSCache();
    processCachedFeatures(jcs, matches, docIndexLookup, bits);

    while (!matches.isEmpty()) {
      Id fidFilter;
      if (matches.size() > MAX_FIDS_PER_QUERY) {
        FeatureId[] subset = new FeatureId[MAX_FIDS_PER_QUERY];
        int i = 0;
        Iterator<FeatureId> iter = matches.iterator();
        while (iter.hasNext() && i < MAX_FIDS_PER_QUERY) {
          subset[i] = iter.next();
          iter.remove();
          i++;
        }
        fidFilter = _filterFactory.id(subset);
      } else {
        fidFilter = _filterFactory.id(matches);
        matches = Collections.emptySet();
      }

      FeatureSource<SimpleFeatureType, SimpleFeature> _featureSource = sourceAccessor.one();
      String ftn = _featureSource.getSchema().getName().getLocalPart();
      String[] geomAtt = {_featureSource.getSchema().getGeometryDescriptor().getLocalName()};
      FeatureCollection<SimpleFeatureType, SimpleFeature> features =
          _featureSource.getFeatures(new org.geotools.data.Query(ftn, fidFilter, geomAtt));
      FeatureIterator<SimpleFeature> iterator = features.features();

      try {
        while (iterator.hasNext()) {
          SimpleFeature feature = iterator.next();
          FeatureId featureId = feature.getIdentifier();
          jcs.put(featureId.getID(), feature.getDefaultGeometry());
          if (evaluateFeature(feature)) {
            for (int doc : docIndexLookup.get(featureId)) {
              bits.set(doc);
            }
          }
        }
      } catch (CacheException e) {
        throw new Error(e);
      } finally {
        iterator.close();
      }
    }
    return bits;
  }
  static SimpleFeature retype(SimpleFeature source, SimpleFeatureBuilder builder)
      throws IllegalAttributeException {
    SimpleFeatureType target = builder.getFeatureType();
    for (int i = 0; i < target.getAttributeCount(); i++) {
      AttributeDescriptor attributeType = target.getDescriptor(i);
      Object value = null;

      if (source.getFeatureType().getDescriptor(attributeType.getName()) != null) {
        value = source.getAttribute(attributeType.getName());
      }

      builder.add(value);
    }

    FeatureId id = reTypeId(source.getIdentifier(), source.getFeatureType(), target);
    SimpleFeature retyped = builder.buildFeature(id.getID());
    retyped.getUserData().putAll(source.getUserData());
    return retyped;
  }
 private void processCachedFeatures(
     GroupCacheAccess jcs,
     Set<FeatureId> matches,
     Multimap<FeatureId, Integer> docIndexLookup,
     OpenBitSet bits) {
   for (java.util.Iterator<FeatureId> iter = matches.iterator(); iter.hasNext(); ) {
     FeatureId id = iter.next();
     Geometry geom = (Geometry) jcs.get(id.getID());
     if (geom != null) {
       iter.remove();
       final SimpleFeatureBuilder simpleFeatureBuilder =
           new SimpleFeatureBuilder(this.sourceAccessor.one().getSchema());
       simpleFeatureBuilder.set(
           this.sourceAccessor.one().getSchema().getGeometryDescriptor().getName(), geom);
       final SimpleFeature simpleFeature = simpleFeatureBuilder.buildFeature(id.getID());
       if (evaluateFeature(simpleFeature)) {
         for (int doc : docIndexLookup.get(id)) {
           bits.set(doc);
         }
       }
     }
   }
 }
  /**
   * Write SQL string to create tables in the test database based on the property files.
   *
   * @param propertyFiles Property files from app-schema-test suite.
   * @param parser The parser (WKT or an SC4O one for 3D tests)
   * @throws IllegalAttributeException
   * @throws NoSuchElementException
   * @throws IOException
   */
  private void createTables(Map<String, File> propertyFiles, String parser)
      throws IllegalAttributeException, NoSuchElementException, IOException {

    StringBuffer buf = new StringBuffer();
    StringBuffer spatialIndex = new StringBuffer();
    // drop table procedure I copied from Victor's Oracle_Data_ref_set.sql
    buf.append("CREATE OR REPLACE PROCEDURE DROP_TABLE_OR_VIEW(TabName in Varchar2) IS ")
        .append("temp number:=0;")
        .append(" tes VARCHAR2 (200) := TabName;")
        .append(" drp_stmt VARCHAR2 (200):=null;")
        .append("BEGIN select count(*) into temp from user_tables where TABLE_NAME = tes;")
        .append("if temp = 1 then drp_stmt := 'Drop Table '||tes;")
        .append("EXECUTE IMMEDIATE drp_stmt;")
        // drop views too
        .append("else select count(*) into temp from user_views where VIEW_NAME = tes;")
        .append("if temp = 1 then drp_stmt := 'Drop VIEW '||tes;")
        .append("EXECUTE IMMEDIATE drp_stmt;end if;end if;")
        .append("EXCEPTION WHEN OTHERS THEN ")
        .append(
            "raise_application_error(-20001,'An error was encountered - '||SQLCODE||' -ERROR- '||SQLERRM);")
        .append("END DROP_TABLE_OR_VIEW;\n");

    for (String fileName : propertyFiles.keySet()) {
      File file = new File(propertyFiles.get(fileName), fileName);

      try (PropertyFeatureReader reader = new PropertyFeatureReader("test", file)) {
        SimpleFeatureType schema = reader.getFeatureType();
        String tableName = schema.getName().getLocalPart().toUpperCase();
        // drop table if exists
        buf.append("CALL DROP_TABLE_OR_VIEW('").append(tableName).append("')\n");
        // create the table
        buf.append("CREATE TABLE ").append(tableName).append("(");
        // + pkey
        int size = schema.getAttributeCount() + 1;
        String[] fieldNames = new String[size];
        List<String> createParams = new ArrayList<String>();
        int j = 0;
        String type;
        String field;
        int spatialIndexCounter = 0;
        for (PropertyDescriptor desc : schema.getDescriptors()) {
          field = desc.getName().toString().toUpperCase();
          fieldNames[j] = field;
          if (desc instanceof GeometryDescriptor) {
            type = "SDO_GEOMETRY";
            // Update spatial index
            int srid = getSrid(((GeometryType) desc.getType()));

            spatialIndex
                .append("DELETE FROM user_sdo_geom_metadata WHERE table_name = '")
                .append(tableName)
                .append("'\n");

            spatialIndex
                .append("Insert into user_sdo_geom_metadata ")
                .append("(TABLE_NAME,COLUMN_NAME,DIMINFO,SRID)")
                .append("values ('")
                .append(tableName)
                .append("','")
                .append(field)
                .append("',MDSYS.SDO_DIM_ARRAY(MDSYS.SDO_DIM_ELEMENT('X',140.962,144.909,0.00001),")
                .append("MDSYS.SDO_DIM_ELEMENT('Y',-38.858,-33.98,0.00001)")
                .append( // support 3d index
                    ((GeometryDescriptor) desc).getCoordinateReferenceSystem() != null
                            && ((GeometryDescriptor) desc)
                                    .getCoordinateReferenceSystem()
                                    .getCoordinateSystem()
                                    .getDimension()
                                == 3
                        ? ", MDSYS.SDO_DIM_ELEMENT('Z',-100000, 100000, 1) ),"
                        : "),")
                .append(srid)
                .append(")\n");

            // ensure it's <= 30 characters to avoid Oracle exception
            String indexName =
                (tableName.length() <= 26 ? tableName : tableName.substring(0, 26)) + "_IDX";
            if (spatialIndexCounter > 0) {
              // to avoid duplicate index name when there are > 1 geometry in the same table
              indexName += spatialIndexCounter;
            }

            spatialIndex
                .append("CREATE INDEX \"")
                .append(indexName)
                .append("\" ON \"")
                .append(tableName)
                .append("\"(\"")
                .append(field)
                .append("\") ")
                .append("INDEXTYPE IS \"MDSYS\".\"SPATIAL_INDEX\"\n");
            spatialIndexCounter++;
          } else {
            type = Classes.getShortName(desc.getType().getBinding());
            if (type.equalsIgnoreCase("String")) {
              type = "NVARCHAR2(250)";
            } else if (type.equalsIgnoreCase("Double")) {
              type = "NUMBER";
            }
            // etc. assign as required
          }
          createParams.add(field + " " + type);
          j++;
        }
        // Add numeric PK for sorting
        fieldNames[j] = "PKEY";
        createParams.add("PKEY VARCHAR2(30)");
        buf.append(StringUtils.join(createParams.iterator(), ", "));
        buf.append(")\n");
        buf.append(
            "ALTER TABLE " + tableName + " ADD CONSTRAINT " + tableName + " PRIMARY KEY (PKEY)\n");
        // then insert rows
        SimpleFeature feature;
        FeatureId id;
        while (reader.hasNext()) {
          buf.append("INSERT INTO ").append(tableName).append("(");
          feature = reader.next();
          buf.append(StringUtils.join(fieldNames, ", "));
          buf.append(") ");
          buf.append("VALUES (");
          Collection<Property> properties = feature.getProperties();
          String[] values = new String[size];
          int valueIndex = 0;
          for (Property prop : properties) {
            Object value = prop.getValue();
            if (value instanceof Geometry) {
              // use wkt writer to convert geometry to string, so third dimension can be supported
              // if present.
              Geometry geom = (Geometry) value;
              value = new WKTWriter(geom.getCoordinate().z == Double.NaN ? 2 : 3).write(geom);
            }
            if (value == null || value.toString().equalsIgnoreCase("null")) {
              values[valueIndex] = "null";
            } else if (prop.getType() instanceof GeometryType) {
              int srid = getSrid(((GeometryType) prop.getType()));
              StringBuffer geomValue = new StringBuffer(parser + "('");
              geomValue.append(value).append("'");
              if (srid > -1) {
                // attach srid
                geomValue.append(", ").append(srid);
              }
              geomValue.append(")");
              values[valueIndex] = geomValue.toString();
            } else if (prop.getType().getBinding().getSimpleName().equalsIgnoreCase("DATE")) {
              values[valueIndex] = "TO_DATE('" + value + "', 'yyyy-MM-dd')";
            } else {
              values[valueIndex] = "'" + value + "'";
            }
            valueIndex++;
          }
          id = feature.getIdentifier();
          // insert primary key
          values[valueIndex] = "'" + id.toString() + "'";
          buf.append(StringUtils.join(values, ","));
          buf.append(")\n");
        }
      }
      buf.append(spatialIndex.toString());
      spatialIndex.delete(0, spatialIndex.length());
      if (buf.length() > 0) {
        this.sql = buf.toString();
      }
    }
  }