Type safeing my class.

PaulCaswell
PaulCaswell used Ask the Experts™
on
Hi All,

I've put together a class that helps me manipulate database tables quite succinctly and it works quite well. See the <unsafe> code below ...

Note that I have cut down the code for posting purposes to the point where it won't actually work but I hope it demonstrates my problem.

I am now trying to make it type safe and I'm having difficulties typeing the EnumSets. See SafeChangeTable and SafeTable.

In particular I am trying to remove the use of EnumSet. My first attempt was the obvious:

  public String updateQuery ( EnumSet<Column> key, Column[] columns ) {

but this gives me the error:

type parameter Column is not within its bound

A bit of research found one method:

  public String updateQuery (EnumSet<? extends Enum<Column>> key, Column[] columns ) {

which removes the error but then when I try:

  public String updateQuery (EnumSet<? extends Enum<Column>> key) {
    // If columns not supplied, assume all columns.
    return ( updateQuery(key, columns) );
  }

I get the error:

cannot find symbol; symbol  : method updateQuery(java.util.EnumSet<? extends java.lang.Enum<Column>>,Column[]), location: class SafeTable<Column>

Is EnumSet<? extends Enum<Column>> the right way of doing this? If so, how do I avoid the second error?

Thanks in advance.

Paul
// .... <Unsafe version using Enum> ...
 
public class ChangeTable extends Table {
  static final Map<Enum,Field.Type> columnTypes = new HashMap<Enum,Field.Type>();
  
  enum Column {
      Change(Field.Type.String),
      Date(Field.Type.Long);
 
      // Keep track of the column types.
      Column(Field.Type type) {
        columnTypes.put(this, type);
      }
  };
  
  public ChangeTable () {
    super("ChangeTable", Column.values(), columnTypes);
  }
}
 
public class Table {
  final String tableName;
  final Enum[] columns;
  final Map<Enum,Field.Type> types;
  Map<Enum,Object> values = new HashMap<Enum,Object>();
  
  protected Table (String tableName, Enum [] columns, Map<Enum,Field.Type> types) {
    this.tableName = tableName;
    this.columns = columns;
    this.types = types;
  }
 
  // Returns a query that will update all records matching the key.
  private String updateQuery ( EnumSet key, Enum[] columns ) {
    String query = "UPDATE "+tableName+" SET ";
    // SET - All update columns not in key.
    for ( int i = 0; i < columns.length; i++ ) {
      Enum col = columns[i];
      // Not key column.
      if ( !key.contains(col) ) {
        // Add field to query.
        query += col.name() + " = " + values.get(col).toString();
      }
    }
    // WHERE All columns in key.
    query += " WHERE " + keyValues(key);
    return query;
  }
 
  public String updateQuery (EnumSet key) {
    // If columns not supplied, assume all columns.
    return ( updateQuery(key, columns) );
  }
 
  public String updateQuery (EnumSet key, EnumSet updateColumns) {
    // Allow an EnumSet of Columns to update.
    return updateQuery(key, (Enum[]) updateColumns.toArray() );
  }
 
  private String keyValues ( EnumSet key ) {
    String keyValues = "";
    for ( Iterator i = key.iterator(); i.hasNext(); ) {
      // Pull out the column.
      Enum col = (Enum)i.next();
      String val = values.get( col ).toString();
      keyValues += col.name() + " = " + val;
    }
    return keyValues;
  }
 
}
 
public class Field {
  
  enum Type {
    String,
    Long;
  }
  
  public Field () {
  }
}
 
// .... <Attempt at safe version, not using Enum but still using EnumSet> ... 
 
public class SafeChangeTable extends SafeTable<SafeChangeTable.Column> {
  static final Map<Column,Field.Type> columnTypes = new HashMap<Column,Field.Type>();
 
  enum Column {
      Change(Field.Type.String),
      Date(Field.Type.Long);
 
      // Keep track of the column types.
      Column(Field.Type type) {
        columnTypes.put(this, type);
      }
  };
 
  public SafeChangeTable () {
    super("ChangeTable", Column.values(), columnTypes);
  }
}
 
public class SafeTable<Column extends Enum> {
  final String tableName;
  final Column[] columns;
  final Map<Column,Field.Type> types;
  Map<Column,Object> values = new HashMap<Column,Object>();
 
  protected SafeTable (String tableName, Column [] columns, Map<Column,Field.Type> types) {
    this.tableName = tableName;
    this.columns = columns;
    this.types = types;
  }
 
  public String updateQuery ( EnumSet key, Column[] columns ) {
    String query = "UPDATE "+tableName+" SET ";
    // SET - All update columns not in key.
    for ( int i = 0; i < columns.length; i++ ) {
      Column col = columns[i];
      // Not key column.
      if ( !key.contains(col) ) {
        // Add field to query.
        query += col.name() + " = " + values.get(col);
      }
    }
    // WHERE All columns in key.
    query += " WHERE " + keyValues(key);
    return query;
  }
 
  public String updateQuery (EnumSet key) {
    // If columns not supplied, assume all columns.
    return ( updateQuery(key, columns) );
  }
 
  public String updateQuery (EnumSet key, EnumSet updateColumns) {
    // Allow an EnumSet of Columns to update.
    return updateQuery(key, (Column[]) updateColumns.toArray() );
  }
 
  private String keyValues ( EnumSet key ) {
    String keyValues = "";
    for ( Iterator i = key.iterator(); i.hasNext(); ) {
      // Pull out the column.
      Enum col = (Enum)i.next();
      String val = values.get( col ).toString();
      keyValues += col.name() + " = " + val;
    }
    return keyValues;
  }
 
}

Open in new window

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
Top Expert 2016

Commented:
Don't you mean something like this?
    public String updateQuery (EnumSet<? extends Column> key, Column[] columns) {
        return null;
    }   
 
    public String updateQuery (EnumSet<? extends Column> key) {
        // If columns not supplied, assume all columns.
        return ( updateQuery(key, columns) );
    }

Open in new window

Author

Commented:
I think you are right but it still does not work.

>>        return ( updateQuery(key, columns) );

I get:

cannot find symbol; symbol  : method updateQuery(java.util.EnumSet<? extends Column>,Column[]), location: class SafeTable<Column> at line 21 (21:14)

Which stumps me because that is exactly the signature of the method.

Paul
Top Expert 2016

Commented:
Please post your current exact code
OWASP: Forgery and Phishing

Learn the techniques to avoid forgery and phishing attacks and the types of attacks an application or network may face.

Top Expert 2016

Commented:
>>location: class SafeTable<Column> at line 21 (21:14)

That strikes me as a surprisingly low line number for the class you posted. Are you sure you're compiling the right source?
Top Expert 2016

Commented:
Now i'm certain - i've just compiled all your source without error ;-)

Author

Commented:
What IDE are you using? I'm using JBuilder 2006.

I'll try it with NetBeans but I need it to work in JBuilder. I'd better make contact with Borland.

P.S. With the source I uploaded I get:

"SafeTable.java": cannot find symbol; symbol  : method updateQuery(java.util.EnumSet<? extends Column>,Column[]), location: class SafeTable<Column> at line 33, column 14

The error I posted earlier was with your code pasted in above mine with the method names changed.

Paul

Author

Commented:
BTW: Does EnumSet<Column> work for you?

Paul
Top Expert 2016

Commented:
>>What IDE are you using? I'm using JBuilder 2006.

I'm compiling at the command line.

Whatever IDE you use, you must ensure you know where your sources actually are as well as be sure that they are where your IDE expects to find them.

You must also distinguish between compile-time and runtime errors. I suspect that your last one could be a runtime error(?)
Top Expert 2016

Commented:
Try doing a clean then build

Author

Commented:
Its definitely compile-time. I've done a clean/rebuild and even a quit/restart of JBuilder which sometimes clears obscure issues.

I'm downloading the latest JDK1.5 in case that helps. I currently have 1.5.0_03-b07.

Paul

Author

Commented:
Now using 1.5.0_19-b02.

I've switched JBuilder to using javac rather than its own built-in compiler and now the error message reads:

"SafeTable.java": C:\Documents and Settings\Paul\jbproject\test\src\SafeTable.java:33: cannot find symbol at line 33, column 0
symbol  : method updateQuery(java.util.EnumSet<capture of ? extends Column>,Column[])
location: class SafeTable<Column>
    return ( updateQuery(key, columns) );
             ^

What JDK are you compiling against?

Paul
Top Expert 2016

Commented:
Please run the following at the command line, and paste what you see in the code snippet window:


copy "C:\Documents and Settings\Paul\jbproject\test\src\SafeTable.java" "%TEMP%" && notepad "%TEMP%\SafeTable.java"

Open in new window

Top Expert 2016

Commented:
>>in the code snippet window
==
into the code snippet window

Author

Commented:
Done
import java.util.*;
 
public class SafeTable<Column extends Enum> {
  final String tableName;
  final Column[] columns;
  final Map<Column,Field.Type> types;
  Map<Column,Object> values = new HashMap<Column,Object>();
 
  protected SafeTable (String tableName, Column [] columns, Map<Column,Field.Type> types) {
    this.tableName = tableName;
    this.columns = columns;
    this.types = types;
  }
 
  public String updateQuery (EnumSet<? extends Column> key, Column[] columns ) {
    String query = "UPDATE "+tableName+" SET ";
    // SET - All update columns not in key.
    for ( int i = 0; i < columns.length; i++ ) {
      Column col = columns[i];
      // Not key column.
      if ( !key.contains(col) ) {
	// Add field to query.
	query += col.name() + " = " + values.get(col);
      }
    }
    // WHERE All columns in key.
    query += " WHERE " + keyValues(key);
    return query;
  }
 
  public String updateQuery (EnumSet<? extends Column> key) {
    // If columns not supplied, assume all columns.
    return ( updateQuery(key, columns) );
  }
 
  public String updateQuery (EnumSet key, EnumSet updateColumns) {
    // Allow an EnumSet of Columns to update.
    return updateQuery(key, (Column[]) updateColumns.toArray() );
  }
 
  private String keyValues ( EnumSet key ) {
    String keyValues = "";
    for ( Iterator i = key.iterator(); i.hasNext(); ) {
      // Pull out the column.
      Enum col = (Enum)i.next();
      String val = values.get( col ).toString();
      keyValues += col.name() + " = " + val;
    }
    return keyValues;
  }
 
}

Open in new window

Author

Commented:
I've done a bit of Googling on <capture of ? extends ...> and it looks like it actually should not match <? extends ...> but I can't work out why or how to get the problem to go away.

Paul
Top Expert 2016

Commented:
Yes, It's a compiler thing. The error occurs for me with 1.5 but not 1,6

Author

Commented:
Thanks.

Can you think of any other way around this? Perhaps an alternative to the <? extends Column> thing?

This seems to be accepted but it is really messy:

public class SafeTable<Column extends Enum, KeySet extends EnumSet<? extends Column>> {

...

  public String updateQuery (KeySet key) {
    // If columns not supplied, assume all columns.
    return ( updateQuery(key, columns) );
  }

  public String updateQuery (KeySet key, EnumSet updateColumns) {
    // Allow an EnumSet of Columns to update.
    return updateQuery(key, (Column[]) updateColumns.toArray() );
  }

  private String keyValues ( EnumSet key ) {

...

Thanks for all your help.

Paul

Author

Commented:
No it doesn't!!!

Now I get:

  private String keyValues ( KeySet key ) {
    String keyValues = "";
    for ( Iterator<Column> i = key.iterator(); i.hasNext(); ) {

"SafeTable.java": incompatible types; found   : java.util.Iterator<capture of ? extends Column>, required: java.util.Iterator<Column> at line 44, column 36

Makes you wonder whether it is really worth the hassle making your classes typesafe.

Paul
Top Expert 2016
Commented:
Take the easy way out - upgrade to 1.6 ;-)

Author

Commented:

I see what my boss says ;D
Top Expert 2016

Commented:
:-)

Author

Commented:
Hi CEHJ,

I woke up this morning with the answer!!

The problem is that an EnumSet, although abstract, is a concrete class. I was attempting to make use of a concrete collection of a generic class.

The answer is to use Set<Column> instead of trying to use EnumSet<Column> or EnumSet<? extends Column>. I am therefore actually quite surprised that Java 6 is happy with EnumSet<Column>.

I still think Java 6 is a good move. :D

Paul


Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial