gt:         if (idx >= 0)
context: package dbfit.environment;
  
  import dbfit.annotations.DatabaseEnvironment;
  import dbfit.api.AbstractDbEnvironment;
  import dbfit.api.DbStoredProcedureCall;
  import dbfit.util.*;
  import dbfit.util.OracleDbParameterAccessor;
  import oracle.jdbc.OracleTypes;
  
  import java.math.BigDecimal;
  import java.sql.*;
  import java.util.*;
  
  import fit.TypeAdapter;
  
  @DatabaseEnvironment(name="Oracle", driver="oracle.jdbc.OracleDriver")
  public class OracleEnvironment extends AbstractDbEnvironment {
      private static String SKIP_ORACLE_SYNONYMS = "SKIPORACLESYNONYMS";
  
      public static class OracleTimestampParser {
          public static Object parse(String s) throws Exception {
              return new oracle.sql.TIMESTAMP(
                      (java.sql.Timestamp) SqlTimestampParseDelegate.parse(s));
          }
      }
  
      private static enum InfoSource {
          DB_DICTIONARY,
          JDBC_RESULT_SET_META_DATA
      }
  
      private static class DbParameterOrColumnInfo {
          String name = null;
          String direction = null;
          String dataType = null;
          String userDefinedTypeName;
          int position = -1;
      }
  
      private static boolean isReturnValueParameter(String paramName) {
          return (paramName == null) || paramName.trim().isEmpty();
      }
  
      static abstract class AbstractParamsOrColumnsIterator implements Iterator<DbParameterOrColumnInfo> {
          protected int position;
          protected DbParameterOrColumnInfo info;
  
          abstract protected boolean readNext() throws SQLException;
  
          protected void incrementPosition() {
              if (!isReturnValueParameter(info.name)) {
                  ++position;
              }
          }
  
          protected boolean fetchNext() throws SQLException {
              if (readNext()) {
                  incrementPosition();
                  return true;
              }
              return false;
          }
  
          @Override
          public boolean hasNext() {
              try {
                  if (info != null) {
                      return true;
                  } else {
                      return fetchNext();
                  }
              } catch (SQLException e) {
                  throw new RuntimeException(e);
              }
          }
  
          @Override
          public DbParameterOrColumnInfo next() {
              if (hasNext()) {
                  DbParameterOrColumnInfo result = info;
                  info = null;
                  return result;
              } else {
                  throw new java.util.NoSuchElementException();
              }
          }
  
          @Override
          public void remove() throws UnsupportedOperationException {
              throw new UnsupportedOperationException();
          }
      }
  
      /**
       * Iterate over ResultSet with db dictionary meta data about parameters or columns
       */
      static class DbDictionaryParamsOrColumnsIterator extends AbstractParamsOrColumnsIterator
              implements Iterator<DbParameterOrColumnInfo> {
      private ResultSet rs;
  
          private DbDictionaryParamsOrColumnsIterator(ResultSet rs) {
              this.rs = rs;
              this.position = 0;
              this.info = null;
          }
  
          public static DbDictionaryParamsOrColumnsIterator newInstance(ResultSet rs) {
              return new DbDictionaryParamsOrColumnsIterator(rs);
          }
  
          @Override
          protected boolean readNext() throws SQLException {
              if (rs.next()) {
                  readToInfo();
                  return true;
              }
  
              return false;
          }
  
          private void readToInfo() throws SQLException {
              info = new DbParameterOrColumnInfo();
              info.name = rs.getString(1);
              info.dataType = rs.getString(2);
              info.direction = rs.getString(4);
              info.position = position;
              info.userDefinedTypeName = rs.getString(6);
          }
      }
  
      /**
       * Iterator over JdbcRsMeta of columns
       */
      static class JdbcRsMetaParamsOrColumnsIterator extends AbstractParamsOrColumnsIterator
              implements Iterator<DbParameterOrColumnInfo> {
          private ResultSetMetaData md;
          private int currentColumn = -1;
          private int columnCount;
  
          private JdbcRsMetaParamsOrColumnsIterator(ResultSetMetaData md) throws SQLException {
              this.md = md;
              this.position = 0;
              this.info = null;
              this.currentColumn = 0;
              this.columnCount = md.getColumnCount();
          }
  
          public static JdbcRsMetaParamsOrColumnsIterator newInstance(ResultSetMetaData md) throws SQLException {
              return new JdbcRsMetaParamsOrColumnsIterator(md);
          }
  
          @Override
          protected boolean readNext() throws SQLException {
              if (currentColumn < columnCount) {
                  readToInfo();
                  ++currentColumn;
                  return true;
              }
  
              return false;
          }
  
          private String getColumnType(int columnID) throws SQLException {
              return md.getColumnTypeName(columnID);
          }
  
          private void readToInfo() throws SQLException {
              info = new DbParameterOrColumnInfo();
  
              int columnIndex = currentColumn + 1;
  
              info.name = md.getColumnName(columnIndex);
              info.dataType = getColumnType(columnIndex);
  
              setUserDefinedTypes(columnIndex);
              info.direction = "IN";
              info.position = position;
          }
  
          private void setUserDefinedTypes(int columnIndex) throws SQLException {
              switch (md.getColumnType(columnIndex)) {
                  case OracleTypes.ARRAY:
                      info.userDefinedTypeName = info.dataType;
                      info.dataType = "TABLE";
                      break;
                  case OracleTypes.STRUCT:
                      info.userDefinedTypeName = info.dataType;
                      info.dataType = "OBJECT";
              }
          }
      }
  
      private static Iterator<DbParameterOrColumnInfo> createParamsOrColumnsIterator(
              ResultSet rs, InfoSource infoSrc) throws SQLException {
  
          switch (infoSrc) {
              case DB_DICTIONARY:
                  return DbDictionaryParamsOrColumnsIterator.newInstance(rs);
              case JDBC_RESULT_SET_META_DATA:
                  return JdbcRsMetaParamsOrColumnsIterator.newInstance(rs.getMetaData());
              default:
                  return null;
          }
      }
  
      @Override
      protected void afterConnectionEstablished() throws SQLException {
          super.afterConnectionEstablished();
          TypeAdapter.registerParseDelegate(java.sql.Struct.class,
                  new OracleObjectTypeParseDelegate(this));
          TypeAdapter.registerParseDelegate(java.sql.Array.class,
                  new OracleObjectTypeParseDelegate(this));
      }
  
      public OracleEnvironment(String driverClassName) {
          super(driverClassName);
          defaultParamPatternString = ":([A-Za-z0-9_]+)";
  
          // TypeAdapter.registerParseDelegate(oracle.sql.TIMESTAMP.class,
          // OracleTimestampParser.class);
          TypeNormaliserFactory.setNormaliser(oracle.sql.TIMESTAMP.class,
                  new OracleTimestampNormaliser());
          TypeNormaliserFactory.setNormaliser(oracle.sql.DATE.class,
                  new OracleDateNormaliser());
          TypeNormaliserFactory.setNormaliser(oracle.jdbc.OracleClob.class,
                  new OracleClobNormaliser());
          TypeNormaliserFactory.setNormaliser(oracle.jdbc.rowset.OracleSerialClob.class,
                  new OracleSerialClobNormaliser());
          TypeNormaliserFactory.setNormaliser(java.sql.Date.class,
                  new SqlDateNormaliser());
          try {
              TypeNormaliserFactory.setNormaliser(java.sql.ResultSet.class,
                      new OracleRefNormaliser());
          } catch (Exception e) {
              throw new Error("Cannot initialise oracle rowset", e);
          }
      }
  
      public boolean supportsOuputOnInsert() {
          return true;
      }
  
      protected String getConnectionString(String dataSource) {
          return "jdbc:oracle:thin:@" + dataSource;
      }
  
      // for oracle, data source has to be host:port
      protected String getConnectionString(String dataSource, String databaseName) {
          if (dataSource.indexOf(":") == -1)
              throw new UnsupportedOperationException(
                      "data source should be in host:port format - " + dataSource
                              + " specified");
          return "jdbc:oracle:thin:@" + dataSource + ":" + databaseName;
      }
  
      public Map<String, DbParameterAccessor> getAllProcedureParameters(
              String procName) throws SQLException {
          String[] qualifiers = NameNormaliser.normaliseName(procName).split(
                  "\\.");
          String cols = " argument_name, data_type, data_length,  IN_OUT, sequence, " +
                  "(case when type_name is null then null else type_owner ||'.'|| type_name end) as type";
          String qry = "select " + cols
                  + "  from all_arguments where data_level=0 and ";
          if (qualifiers.length == 3) {
              qry += " owner=? and package_name=? and object_name=? ";
          } else if (qualifiers.length == 2) {
              qry += " ((owner=? and package_name is null and object_name=?) or "
                      + " (owner=user and package_name=? and object_name=?))";
          } else {
              qry += " (owner=user and package_name is null and object_name=?)";
          }
  
          // map to public synonyms also
          if (qualifiers.length < 3 && (!Options.is(SKIP_ORACLE_SYNONYMS))) {
              qry += " union all "
                      + " select "
                      + cols
                      + " from all_arguments, all_synonyms "
                      + " where data_level=0 and all_synonyms.owner='PUBLIC' and all_arguments.owner=table_owner and ";
              if (qualifiers.length == 2) { // package
                  qry += " package_name=table_name and synonym_name=? and object_name=? ";
              } else {
                  qry += " package_name is null and object_name=table_name and synonym_name=? ";
              }
          }
          qry += " order by sequence ";
          if (qualifiers.length == 2) {
              String[] newQualifiers = new String[6];
              newQualifiers[0] = qualifiers[0];
              newQualifiers[1] = qualifiers[1];
              newQualifiers[2] = qualifiers[0];
              newQualifiers[3] = qualifiers[1];
              newQualifiers[4] = qualifiers[0];
              newQualifiers[5] = qualifiers[1];
              qualifiers = newQualifiers;
          } else if (qualifiers.length == 1) {
              String[] newQualifiers = new String[2];
              newQualifiers[0] = qualifiers[0];
              newQualifiers[1] = qualifiers[0];
              qualifiers = newQualifiers;
          }
          return readIntoParams(qualifiers, qry);
      }
  
      public Map<String, DbParameterAccessor> getAllColumns(String tableOrViewName)
              throws SQLException {
          String query = "select * from " + tableOrViewName + " where 1 = 2";
          return readIntoParams(new String[]{}, query, InfoSource.JDBC_RESULT_SET_META_DATA);
      }
  
      private DbParameterAccessor addSingleParam(Map<String, DbParameterAccessor> allParams,
              DbParameterOrColumnInfo info) {
          DbParameterAccessor dbp = makeSingleParam(info);
  
          Log.log("read out " + dbp.getName() + " of " + info.dataType);
  
          allParams.put(NameNormaliser.normaliseName(dbp.getName()), dbp);
  
          return dbp;
      }
  
      private DbParameterAccessor makeSingleParam(DbParameterOrColumnInfo info) {
          return makeSingleParam(info.name, info.dataType, info.userDefinedTypeName, info.direction, info.position);
      }
  
      private DbParameterAccessor makeSingleParam(
              String paramName, String dataType, String userTypeName, String direction, int position) {
          if (paramName == null)
              paramName = "";
          Direction paramDirection;
          int paramPosition = position;
          if (isReturnValueParameter(paramName)) {
              paramDirection = Direction.RETURN_VALUE;
              paramPosition = -1;
          }
          else {
              paramDirection = getParameterDirection(direction);
          }
  
          DbParameterAccessor dbp = createOracleDbParameterAcccessor(
                  paramName,
                  paramDirection, getSqlType(dataType), getJavaClass(dataType),
                  paramPosition, normaliseTypeName(dataType), userTypeName);
  
          return dbp;
      }
  
      private OracleDbParameterAccessor createOracleDbParameterAcccessor(
              String name, Direction direction,
              int sqlType, Class<?> javaType, int position,
              String originalTypeName, String userTypeName) {
          return new OracleDbParameterAccessor(
                  name, direction,
                  sqlType, javaType, position,
                  dbfitToJdbcTransformerFactory, originalTypeName, userTypeName);
      }
  
      private Map<String, DbParameterAccessor> readIntoParams(
              String[] queryParameters, String query, InfoSource infoSrc) throws SQLException {
  
          try (CallableStatement dc = openDbCallWithParameters(query, queryParameters)) {
              Log.log("executing query");
              ResultSet rs = dc.executeQuery();
              Map<String, DbParameterAccessor> allParams = new HashMap<String, DbParameterAccessor>();
              Iterator<DbParameterOrColumnInfo> iter = createParamsOrColumnsIterator(rs, infoSrc);
  
              while (iter.hasNext()) {
                  addSingleParam(allParams, iter.next());
              }
  
              return allParams;
          }
      }
  
      private Map<String, DbParameterAccessor> readIntoParams(
              String[] queryParameters, String query) throws SQLException {
          return readIntoParams(queryParameters, query, InfoSource.DB_DICTIONARY);
      }
  
      private CallableStatement openDbCallWithParameters(String query,
              String[] queryParameters) throws SQLException {
          Log.log("preparing call " + query, (Object[]) queryParameters);
          CallableStatement dc = currentConnection.prepareCall(query);
          Log.log("setting parameters");
          for (int i = 0; i < queryParameters.length; i++) {
              dc.setString(i + 1, queryParameters[i].toUpperCase());
          }
  
          return dc;
      }
  
  
      // List interface has sequential search, so using list instead of array to
      // map types
      private static List<String> stringTypes = Arrays.asList(new String[] {
              "VARCHAR", "VARCHAR2", "NVARCHAR2", "CHAR", "NCHAR", "CLOB",
              "NCLOB", "ROWID", "BOOLEAN"
          });
      private static List<String> decimalTypes = Arrays.asList(new String[] {
              "BINARY_INTEGER", "NUMBER", "FLOAT" });
      private static List<String> dateTypes = Arrays.asList(new String[] {});
      private static List<String> timestampTypes = Arrays.asList(new String[] {
              "DATE", "TIMESTAMP" });
      private static List<String> refCursorTypes = Arrays
              .asList(new String[] { "REF" });
      private static List<String> objectTypes = Arrays.asList(new String[] {
              "OBJECT", "MDSYS.SDO_GEOMETRY" });
  
      private static List<String> recordTypes = Arrays.asList(new String[] {
              "VARRAY", "TABLE" });
  
      private static String normaliseTypeName(String dataType) {
          dataType = dataType.toUpperCase().trim();
          if (dataType.endsWith("BOOLEAN")) {
              return "BOOLEAN";
          }
  
          int idx = dataType.indexOf(" ");
          if (idx >= 0)
              dataType = dataType.substring(0, idx);
          idx = dataType.indexOf("(");
  