public List<RowMetaAndData> getFeatureSummary() {
    List<RowMetaAndData> list = new ArrayList<>();
    RowMetaAndData r = null;
    final String par = "Parameter";
    final String val = "Value";

    ValueMetaInterface testValue = new ValueMetaString( "FIELD" );
    testValue.setLength( 30 );

    if ( databaseInterface != null ) {
      // Type of database
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Database type" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getPluginId() );
      list.add( r );
      // Type of access
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Access type" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getAccessTypeDesc() );
      list.add( r );
      // Name of database
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Database name" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getDatabaseName() );
      list.add( r );
      // server host name
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Server hostname" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getHostname() );
      list.add( r );
      // Port number
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Service port" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getDatabasePortNumberString() );
      list.add( r );
      // Username
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Username" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getUsername() );
      list.add( r );
      // Informix server
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Informix server name" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getServername() );
      list.add( r );
      // Other properties...
      Enumeration<Object> keys = getAttributes().keys();
      while ( keys.hasMoreElements() ) {
        String key = (String) keys.nextElement();
        String value = getAttributes().getProperty( key );
        r = new RowMetaAndData();
        r.addValue( par, ValueMetaInterface.TYPE_STRING, "Extra attribute [" + key + "]" );
        r.addValue( val, ValueMetaInterface.TYPE_STRING, value );
        list.add( r );
      }

      // driver class
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Driver class" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getDriverClass() );
      list.add( r );
      // URL
      String pwd = getPassword();
      setPassword( "password" ); // Don't give away the password in the URL!
      String url = "";
      try {
        url = getURL();
      } catch ( Exception e ) {
        url = "";
      } // SAP etc.
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "URL" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, url );
      list.add( r );
      setPassword( pwd );
      // SQL: Next sequence value
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "SQL: next sequence value" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getSeqNextvalSQL( "SEQUENCE" ) );
      list.add( r );
      // is set fetch size supported
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "supported: set fetch size" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, isFetchSizeSupported() ? "Y" : "N" );
      list.add( r );
      // needs place holder for auto increment
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "auto increment field needs placeholder" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, needsPlaceHolder() ? "Y" : "N" );
      list.add( r );
      // Sum function
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "SUM aggregate function" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionSum() );
      list.add( r );
      // Avg function
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "AVG aggregate function" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionAverage() );
      list.add( r );
      // Minimum function
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "MIN aggregate function" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionMinimum() );
      list.add( r );
      // Maximum function
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "MAX aggregate function" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionMaximum() );
      list.add( r );
      // Count function
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "COUNT aggregate function" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getFunctionCount() );
      list.add( r );
      // Schema-table combination
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Schema / Table combination" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getQuotedSchemaTableCombination( "SCHEMA", "TABLE" ) );
      list.add( r );
      // Limit clause
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "LIMIT clause for 100 rows" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getLimitClause( 100 ) );
      list.add( r );
      // add column statement
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Add column statement" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getAddColumnStatement(
        "TABLE", testValue, null, false, null, false ) );
      list.add( r );
      // drop column statement
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Drop column statement" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getDropColumnStatement(
        "TABLE", testValue, null, false, null, false ) );
      list.add( r );
      // Modify column statement
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Modify column statement" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getModifyColumnStatement(
        "TABLE", testValue, null, false, null, false ) );
      list.add( r );

      // List of reserved words
      String reserved = "";
      if ( getReservedWords() != null ) {
        for ( int i = 0; i < getReservedWords().length; i++ ) {
          reserved += ( i > 0 ? ", " : "" ) + getReservedWords()[i];
        }
      }
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of reserved words" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, reserved );
      list.add( r );

      // Quote reserved words?
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Quote reserved words?" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, quoteReservedWords() ? "Y" : "N" );
      list.add( r );
      // Start Quote
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "Start quote for reserved words" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getStartQuote() );
      list.add( r );
      // End Quote
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "End quote for reserved words" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getEndQuote() );
      list.add( r );

      // List of table types
      String types = "";
      String[] slist = getTableTypes();
      if ( slist != null ) {
        for ( int i = 0; i < slist.length; i++ ) {
          types += ( i > 0 ? ", " : "" ) + slist[i];
        }
      }
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of JDBC table types" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, types );
      list.add( r );

      // List of view types
      types = "";
      slist = getViewTypes();
      if ( slist != null ) {
        for ( int i = 0; i < slist.length; i++ ) {
          types += ( i > 0 ? ", " : "" ) + slist[i];
        }
      }
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of JDBC view types" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, types );
      list.add( r );

      // List of synonym types
      types = "";
      slist = getSynonymTypes();
      if ( slist != null ) {
        for ( int i = 0; i < slist.length; i++ ) {
          types += ( i > 0 ? ", " : "" ) + slist[i];
        }
      }
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "List of JDBC synonym types" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, types );
      list.add( r );

      // Use schema-name to get list of tables?
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "use schema name to get table list?" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, useSchemaNameForTableList() ? "Y" : "N" );
      list.add( r );
      // supports view?
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports views?" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsViews() ? "Y" : "N" );
      list.add( r );
      // supports synonyms?
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports synonyms?" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsSynonyms() ? "Y" : "N" );
      list.add( r );
      // SQL: get list of procedures?
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "SQL: list of procedures" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, getSQLListOfProcedures() );
      list.add( r );
      // SQL: get truncate table statement?
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "SQL: truncate table" );
      String truncateStatement = getTruncateTableStatement( null, "TABLE" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, truncateStatement != null
        ? truncateStatement : "Not supported by this database type" );
      list.add( r );
      // supports float rounding on update?
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports floating point rounding on update/insert" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsFloatRoundingOnUpdate() ? "Y" : "N" );
      list.add( r );
      // supports time stamp to date conversion
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports timestamp-date conversion" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsTimeStampToDateConversion() ? "Y" : "N" );
      list.add( r );
      // supports batch updates
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports batch updates" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsBatchUpdates() ? "Y" : "N" );
      list.add( r );
      // supports boolean values
      r = new RowMetaAndData();
      r.addValue( par, ValueMetaInterface.TYPE_STRING, "supports boolean data type" );
      r.addValue( val, ValueMetaInterface.TYPE_STRING, supportsBooleanDataType() ? "Y" : "N" );
      list.add( r );
    }

    return list;
  }
@Test
  public void testGetFeatureSummary() throws Exception {
    DatabaseMeta databaseMeta = mock( DatabaseMeta.class );
    OracleDatabaseMeta odbm = new OracleDatabaseMeta();
    doCallRealMethod().when( databaseMeta ).setDatabaseInterface( any( DatabaseInterface.class ) );
    doCallRealMethod().when( databaseMeta ).getFeatureSummary();
    doCallRealMethod().when( databaseMeta ).getAttributes();
    databaseMeta.setDatabaseInterface( odbm );
    List<RowMetaAndData> result = databaseMeta.getFeatureSummary();
    assertNotNull( result );
    for ( RowMetaAndData rmd : result ) {
      assertEquals( 2, rmd.getRowMeta().size() );
      assertEquals( "Parameter", rmd.getRowMeta().getValueMeta( 0 ).getName() );
      assertEquals( ValueMetaInterface.TYPE_STRING, rmd.getRowMeta().getValueMeta( 0 ).getType() );
      assertEquals( "Value", rmd.getRowMeta().getValueMeta( 1 ).getName() );
      assertEquals( ValueMetaInterface.TYPE_STRING, rmd.getRowMeta().getValueMeta( 1 ).getType() );
    }
  }