I'm using the foundationDB SQL parser (https://github.com/FoundationDB/sql-parser) to parse a query inside Java, but I'm not very familiar with the visitor design pattern, that is used by the parser to consume a query.
I would like to send a query to the parser like this: "SELECT a, b FROM c WHERE d > 5" and get as a result:
all the fields names in the SELECT clause (accomplished)
the table name into the FROM clause (accomplished)
the columns names, operand and expression into the WHERE clause
Thats the code I'm implementing:
#Override
public QueryDescription parse() throws StandardException {
SQLParser parser = new SQLParser();
StatementNode stmt = parser.parseStatement(sql);
Visitor v = new Visitor() {
#Override
public boolean visitChildrenFirst(Visitable arg0) {
// TODO Auto-generated method stub
return false;
}
#Override
public Visitable visit(Visitable arg0) throws StandardException {
// Temporary stores the QueryDescription parameters
StatementEnum se = null;
String fromTable = null;
String[] fields = null;
if(arg0 instanceof CursorNode) {
CursorNode cn = (CursorNode) arg0;
// print out what statement is been declared in sql query
System.out.println("Statement: " + cn.statementToString());
// temporarly stores the statement
String statement = cn.statementToString();
// creates the right StatementEnum
if(statement == "CREATE TABLE") {
se = StatementEnum.CREATE_TABLE;
} else if(statement == "INSERT") {
se = StatementEnum.INSERT;
} else if(statement == "SELECT") {
se = StatementEnum.SELECT;
} else if(statement == "DROP TABLE") {
se = StatementEnum.DROP_TABLE;
}
}
description = new QueryDescription(se, fromTable, fields);
return arg0;
}
#Override
public boolean stopTraversal() { return false; }
#Override
public boolean skipChildren(Visitable arg0) throws StandardException { return false; }
};
stmt.accept(v);
// TODO remove, only for debug purpose
stmt.treePrint();
return description;
}
And that's the QueryDescription class code:
public class QueryDescription {
/* Member variables: */
private QueryTypeEnum queryType;
private StatementEnum statement;
private String fromTable;
private String[] fields;
/* Constructors: */
/**
*
* #param statement
* #param fromTable
* #param fields
*/
public QueryDescription(StatementEnum statement, String fromTable, String[] fields) {
this.statement = statement;
this.fromTable = fromTable;
this.fields = fields;
}
/* Methods: */
/**
* Analyze which type of query is the one passed by parameter and assigns the right queryTypeEnum
*/
public void assignType() {
switch(statement) {
case CREATE_TABLE:
break;
case SELECT:
if(fields[0] == "allFields")
queryType = QueryTypeEnum.DUMP;
else {
// TODO risolvere questione del WHERE
queryType = QueryTypeEnum.SELECT_FROM;
}
break;
case UPDATE:
break;
case INSERT:
break;
case DROP_TABLE:
break;
}
}
/* Getters and Setter: */
/**
*
* #return the queryType
*/
public QueryTypeEnum getQueryType() {
return queryType;
}
/**
*
* #return the statement
*/
public StatementEnum getStatement() {
return statement;
}
/**
*
* #return the from table
*/
public String getFromTable() {
return fromTable;
}
/**
*
* #return the fields
*/
public String[] getFields() {
return fields;
}
}
Your code does not show what the QueryDescription class does, but I can guess.
In dealing with the where clause, you are looking for three types of nodes:
BinaryLogicalOperatorNode - This has the AND, OR, IS operators that separate the individual clauses in the WHERE clause.
BinaryOperatorNode - This has the individual >, <, and other operations.
The constants and column operators.
In your example, you would visit a BinaryOperatorNode, with a type of LT, and the two children would be ConstantNode with a value of 5, and a ColumnReference node with the value of "d".
Note: the parser does not connect the Column references to the tables in the tables list. This is a separate step in the query processing. So you will have a ColumnReference node, but no link to which table the column is referencing. The reason is the parser does not have enough information to correctly link the "d" column to the correct table.
This is enough to process the simple query you gave in the example. Obviously queries can become much more complex.
The one node I would add to the list of checks in the InListOperatorNode which handles WHERE d IN (1,2,3,4).
EDIT to add:
Keep in mind the Vistor#visit() method gets called for each and every node in the tree created by the query parser. The visit method will need to check, and set your variables properly, for FromBaseTable, ConstantNode, ColumnReference.
Visitor v = new Visitor() {
List<String> fromTable = new ArrayList<String>();
List<String> fields = new ArrayList<String>();
// other Visitor methods go here, not copied for conciseness.
#Override
public Visitable visit(Visitable arg0) throws StandardException {
// other code from your visit() method goes here
//
if (arg0 instanceof FromBaseTable) {
FromBaseTable table = (FromBaseTable)arg0;
fromTable.append(table.getTableName());
} else if (arg0 instanceof ColumnReference) {
ColumnReference column = (ColumnReference) arg0;
fields.append(column.getColumnName())
}
// Remove the call to create QueryDescription
}
public QueryDescription getQueryDescription() {
return new QueryDescription(se, fromTable, fields)
}
}
Then in your main line of code you call:
stmt.accept(v);
QueryDescription description = v.getQueryDescription();
Now if you have other parts of the query you are interested in, you need to add those nodes to the visit method, and capture the part of the node (names, values, etc) you are interested in.
Related
I added an external dependency (open CSV) to my Gradle project. I wanted to override a particular method from that.
I subclassed the required class in a new custom class
But I can not override any methods in the super class. Even though they are public
What will be the reason for this.? The Gradle dependencies exist in Library/Project and External Dependencies.
class CustomMappingStrategy<T> extends ColumnPositionMappingStrategy<T> {
private static final String[] HEADER = new String[]{"TradeID", "GWML GUID", "MXML GUID", "GWML File", "MxML File", "MxML Counterparty", "GWML Counterparty"};
#Override //This is not working
public String[] generateHeader() {
return HEADER;
}
}
The super class looks like:
package com.opencsv.bean;
import com.opencsv.CSVReader;
import com.opencsv.ICSVParser;
import com.opencsv.exceptions.CsvBadConverterException;
import com.opencsv.exceptions.CsvRequiredFieldEmptyException;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*;
/**
* Allows for the mapping of columns with their positions. Using this strategy
* without annotations ({#link com.opencsv.bean.CsvBindByPosition} or
* {#link com.opencsv.bean.CsvCustomBindByPosition}) requires all the columns
* to be present in the CSV file and for them to be in a particular order. Using
* annotations allows one to specify arbitrary zero-based column numbers for
* each bean member variable to be filled. Also this strategy requires that the
* file does NOT have a header. That said, the main use of this strategy is
* files that do not have headers.
*
* #param <T> Type of object that is being processed.
*/
public class ColumnPositionMappingStrategy<T> extends AbstractMappingStrategy<String, Integer, ComplexFieldMapEntry<String, Integer, T>, T> {
/**
* Whether the user has programmatically set the map from column positions
* to field names.
*/
private boolean columnsExplicitlySet = false;
/**
* The map from column position to {#link BeanField}.
*/
private FieldMapByPosition<T> fieldMap;
/**
* Holds a {#link java.util.Comparator} to sort columns on writing.
*/
private Comparator<Integer> writeOrder;
/**
* Used to store a mapping from presumed input column index to desired
* output column index, as determined by applying {#link #writeOrder}.
*/
private Integer[] columnIndexForWriting = null;
/**
* Default constructor.
*/
public ColumnPositionMappingStrategy() {
}
/**
* There is no header per se for this mapping strategy, but this method
* checks the first line to determine how many fields are present and
* adjusts its field map accordingly.
*/
// The rest of the Javadoc is inherited
#Override
public void captureHeader(CSVReader reader) throws IOException {
// Validation
if (type == null) {
throw new IllegalStateException(ResourceBundle
.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale)
.getString("type.unset"));
}
String[] firstLine = reader.peek();
fieldMap.setMaxIndex(firstLine.length - 1);
if (!columnsExplicitlySet) {
headerIndex.clear();
for (FieldMapByPositionEntry entry : fieldMap) {
Field f = entry.getField().getField();
if (f.getAnnotation(CsvCustomBindByPosition.class) != null
|| f.getAnnotation(CsvBindAndSplitByPosition.class) != null
|| f.getAnnotation(CsvBindAndJoinByPosition.class) != null
|| f.getAnnotation(CsvBindByPosition.class) != null) {
headerIndex.put(entry.getPosition(), f.getName().toUpperCase().trim());
}
}
}
}
/**
* #return {#inheritDoc} For this mapping strategy, it's simply
* {#code index} wrapped as an {#link java.lang.Integer}.
*/
// The rest of the Javadoc is inherited
#Override
protected Object chooseMultivaluedFieldIndexFromHeaderIndex(int index) {
return Integer.valueOf(index);
}
#Override
public BeanField<T> findField(int col) {
// If we have a mapping for changing the order of the columns on
// writing, be sure to use it.
if (columnIndexForWriting != null) {
return col < columnIndexForWriting.length ? fieldMap.get(columnIndexForWriting[col]) : null;
}
return fieldMap.get(col);
}
/**
* This method returns an empty array.
* The column position mapping strategy assumes that there is no header, and
* thus it also does not write one, accordingly.
*
* #return An empty array
*/
// The rest of the Javadoc is inherited
#Override
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
String[] h = super.generateHeader(bean);
columnIndexForWriting = new Integer[h.length];
// Once we support Java 8, this might be nicer with Arrays.parallelSetAll().
for (int i = 0; i < columnIndexForWriting.length; i++) {
columnIndexForWriting[i] = i;
}
// Create the mapping for input column index to output column index.
Arrays.sort(columnIndexForWriting, writeOrder);
return ArrayUtils.EMPTY_STRING_ARRAY;
}
/**
* Gets a column name.
*
* #param col Position of the column.
* #return Column name or null if col > number of mappings.
*/
#Override
public String getColumnName(int col) {
return headerIndex.getByPosition(col);
}
/**
* Retrieves the column mappings.
*
* #return String array with the column mappings.
*/
public String[] getColumnMapping() {
return headerIndex.getHeaderIndex();
}
/**
* Setter for the column mapping.
* This mapping is for reading. Use of this method in conjunction with
* writing is undefined.
*
* #param columnMapping Column names to be mapped.
*/
public void setColumnMapping(String... columnMapping) {
if (columnMapping != null) {
headerIndex.initializeHeaderIndex(columnMapping);
} else {
headerIndex.clear();
}
columnsExplicitlySet = true;
}
#Override
protected void loadFieldMap() throws CsvBadConverterException {
boolean required;
fieldMap = new FieldMapByPosition<>(errorLocale);
fieldMap.setColumnOrderOnWrite(writeOrder);
for (Field field : loadFields(getType())) {
String fieldLocale, capture, format;
// Custom converters always have precedence.
if (field.isAnnotationPresent(CsvCustomBindByPosition.class)) {
CsvCustomBindByPosition annotation = field
.getAnnotation(CsvCustomBindByPosition.class);
Class<? extends AbstractBeanField> converter = annotation.converter();
BeanField<T> bean = instantiateCustomConverter(converter);
bean.setField(field);
required = annotation.required();
bean.setRequired(required);
fieldMap.put(annotation.position(), bean);
}
// Then check for a collection
else if (field.isAnnotationPresent(CsvBindAndSplitByPosition.class)) {
CsvBindAndSplitByPosition annotation = field.getAnnotation(CsvBindAndSplitByPosition.class);
required = annotation.required();
fieldLocale = annotation.locale();
String splitOn = annotation.splitOn();
String writeDelimiter = annotation.writeDelimiter();
Class<? extends Collection> collectionType = annotation.collectionType();
Class<?> elementType = annotation.elementType();
Class<? extends AbstractCsvConverter> splitConverter = annotation.converter();
capture = annotation.capture();
format = annotation.format();
CsvConverter converter = determineConverter(field, elementType, fieldLocale, splitConverter);
fieldMap.put(annotation.position(), new BeanFieldSplit<T>(
field, required, errorLocale, converter, splitOn,
writeDelimiter, collectionType, capture, format));
}
// Then check for a multi-column annotation
else if (field.isAnnotationPresent(CsvBindAndJoinByPosition.class)) {
CsvBindAndJoinByPosition annotation = field.getAnnotation(CsvBindAndJoinByPosition.class);
required = annotation.required();
fieldLocale = annotation.locale();
Class<?> elementType = annotation.elementType();
Class<? extends MultiValuedMap> mapType = annotation.mapType();
Class<? extends AbstractCsvConverter> joinConverter = annotation.converter();
capture = annotation.capture();
format = annotation.format();
CsvConverter converter = determineConverter(field, elementType, fieldLocale, joinConverter);
fieldMap.putComplex(annotation.position(), new BeanFieldJoinIntegerIndex<T>(
field, required, errorLocale, converter, mapType, capture, format));
}
// Then it must be a bind by position.
else {
CsvBindByPosition annotation = field.getAnnotation(CsvBindByPosition.class);
required = annotation.required();
fieldLocale = annotation.locale();
capture = annotation.capture();
format = annotation.format();
CsvConverter converter = determineConverter(field, field.getType(), fieldLocale, null);
fieldMap.put(annotation.position(), new BeanFieldSingleValue<T>(
field, required, errorLocale, converter, capture, format));
}
}
}
#Override
public void verifyLineLength(int numberOfFields) throws CsvRequiredFieldEmptyException {
if (!headerIndex.isEmpty()) {
BeanField f;
StringBuilder sb = null;
for (int i = numberOfFields; i <= headerIndex.findMaxIndex(); i++) {
f = findField(i);
if (f != null && f.isRequired()) {
if (sb == null) {
sb = new StringBuilder(ResourceBundle.getBundle(ICSVParser.DEFAULT_BUNDLE_NAME, errorLocale).getString("multiple.required.field.empty"));
}
sb.append(' ');
sb.append(f.getField().getName());
}
}
if (sb != null) {
throw new CsvRequiredFieldEmptyException(type, sb.toString());
}
}
}
private List<Field> loadFields(Class<? extends T> cls) {
List<Field> fields = new LinkedList<>();
for (Field field : FieldUtils.getAllFields(cls)) {
if (field.isAnnotationPresent(CsvBindByPosition.class)
|| field.isAnnotationPresent(CsvCustomBindByPosition.class)
|| field.isAnnotationPresent(CsvBindAndJoinByPosition.class)
|| field.isAnnotationPresent(CsvBindAndSplitByPosition.class)) {
fields.add(field);
}
}
setAnnotationDriven(!fields.isEmpty());
return fields;
}
/**
* Returns the column position for the given column number.
* Yes, they're the same thing. For this mapping strategy, it's a simple
* conversion from an integer to a string.
*/
// The rest of the Javadoc is inherited
#Override
public String findHeader(int col) {
return Integer.toString(col);
}
#Override
protected FieldMap<String, Integer, ? extends ComplexFieldMapEntry<String, Integer, T>, T> getFieldMap() {
return fieldMap;
}
/**
* Sets the {#link java.util.Comparator} to be used to sort columns when
* writing beans to a CSV file.
* Behavior of this method when used on a mapping strategy intended for
* reading data from a CSV source is not defined.
*
* #param writeOrder The {#link java.util.Comparator} to use. May be
* {#code null}, in which case the natural ordering is used.
* #since 4.3
*/
public void setColumnOrderOnWrite(Comparator<Integer> writeOrder) {
this.writeOrder = writeOrder;
if (fieldMap != null) {
fieldMap.setColumnOrderOnWrite(this.writeOrder);
}
}
}
Have you tried
public String[] generateHeader(T bean) throws CsvRequiredFieldEmptyException {
super.generateHeader(bean);
return HEADER;
}
I have UniversalComparator and it does all the task related to sorting, It uses reflection API to recognized the method name and invocatio target.
Now what happened is, I need to sort the sites, previously it was sorted using "name", now what happened is, user need to upload documents in frequency of monthly, quarterly,semi and annually.
REQUIREMENT
If document not uploaded in any of the frequency, that data represent with white color, rest of the blocks are reprsented as red color.
There are six SITES right now,
first site has only one white block
second site has no white block.
third site has no white block
four has three white block
fifth has three white block
sixth has three white block
so the count is
1
0
0
3
3
Now whta I did, I cretaed an ArrayList of Integer and store all the count
now I need to sort the list of SITE Block on behalf of this count so it should be like
0
0
1
3
3
CODE
package com.lear.common.utility;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Comparator;
public class UniversalComparator implements Comparator {
/**
* static final int ASCENDING
*/
public static final int ASCENDING = 1;
/**
* static final int DESCENDING
*/
public static final int DESCENDING = -1;
/*
* int for descAscIndicator
*/
private int descAscIndicator = 1;
/*
* String to store method Name
*/
private String methodName = "toString";
/**
* Constructor for UniversalComparator
*
* #param descAscIndicator
* int to store descAscIndicator.
*/
public UniversalComparator(int descAscIndicator) {
this.descAscIndicator = descAscIndicator;
}
/**
* Constructor for UniversalComparator
*
* #param methodName
* : name of method as criteria
* #param descAscIndicator
* : order of sorting
*/
public UniversalComparator(String methodName, int descAscIndicator) {
this(descAscIndicator);
this.methodName = methodName;
}
/**
* This Method compare Two Objects
*
* #param o1
* : An Instance of Object.
* #param o2
* : An Instance of Object.
* #return int
*/
public int compare(Object o1, Object o2) {
Object comp1 = null;
Object comp2 = null;
try {
Method o1_Method = (o1.getClass()).getMethod(methodName, null);
Method o2_Method = (o2.getClass()).getMethod(methodName, null);
comp1 = o1_Method.invoke(o1, null);
comp2 = o2_Method.invoke(o2, null);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Method does not exist" + e.getMessage());
} catch (IllegalAccessException e) {
throw new RuntimeException("Illegal access" + e.getMessage());
} catch (InvocationTargetException e) {
throw new RuntimeException("InvocationTargetException"
+ e.getMessage());
}
Comparable c1 = (Comparable) comp1;
Comparable c2 = (Comparable) comp2;
return c1.compareTo(c2) * descAscIndicator;
}
/**
* Check for Equality obect
*
* #param obj
* : An Instance of Object.
* #return boolean return true if equal or false if not
*/
public boolean equals(Object obj) {
return this.equals(obj);
}
}
ScoreCardManager.java
List<Integer> naSiteDataList = new ArrayList<Integer>();
public String getComparativeSiteAnalysis(Integer divId, String lang, int selectedYear) {
// PLENTY OF CODE HERE
int annualDataCount = site.getComparativeColorAnnual().equalsIgnoreCase("White") ? 1 : 0;
naSiteDataCount = monthlyDataCount + quaterlyDataCount + semiAnnualDataCount + annualDataCount;
naSiteDataList.add(naSiteDataCount);
naSiteCounter.add(naSiteDataCount);
site.setNaSiteCount(naSiteDataList);
site.setNaSiteCounter(naSiteCounter);
System.out.println("datacount is" + naSiteDataCount);
}
// THIS LINE
Collections.sort(sites, new UniversalComparator("getNaSiteCount", 1));
Site.java
public class Site{
// lot of properties
private List<Integer> naSiteCount;
public List<Integer> getNaSiteCount() {
return naSiteCount;
}
public void setNaSiteCount(List<Integer> naSiteCount) {
this.naSiteCount = naSiteCount;
}
}
Reflection is a really awful approach to this. You have absolutely no type safety in this code. You have no guarantee that a method with the name you supply as a string actually exists. You have no guarantee that it takes no arguments. You have no idea what exceptions it might throw.
If you were working in Java 8 plus, it would be trivial to implement specific comparators:
Comparator<Object> comparator = Comparator.comparing(Object::toString);
but the lack of Java 8 features isn't a reason to do it with reflection.
Define an abstract class:
abstract class AbstractComparator<T, C extends Comparable<? super C>> implements Comparator<T> {
abstract C toComparable(T object);
#Override public int compare(T a, T b) {
return toComparable(a).compareTo(toComparable(b));
}
Comparator<T> reverse() {
return new Comparator<T>() {
#Override public int compare(T a, T b) {
return toComparable(b).compareTo(toComparable(a));
}
}
}
}
and then implement this for your specific cases:
Comparator<Object> comparator = new AbstractComparator<Object, String> {
#Override String toComparable(Object object) { return object.toString(); }
}.reverse();
If you are using Guava or some other library with a Function-like class, or are happy to define it yourself, you can of course take a compositional approach, rather than using inheritance:
Comparator<Object> comparator = new ConcreteComparator<>(new Function<Object, String>() {
#Override public String apply(Object object) {
return object.toString();
}
});
Provided you don't use raw types around either the comparator or the things you are comparing, ClassCastExceptions will now be impossible.
I'm creating a kind of data testing program, and one specific part is giving me a huge amount of trouble. In my main method class there is one section where I need to send over a String of data as a parameter in a method to my methods class (let's call it ValidatorClass) and the idea being that the method will then return any validation errors or if there are none simply an empty String.
This would be fine except that I use "for loops" when going through my data to validate as doing it without is just too clunky. I tried to research about arrays of methods and found plenty of useful things that work with void methods but found nothing on any methods that return variables.
In a nutshell I'm asking: Is it possible to create an array of methods (or implement an array of objects to simulate an array of methods) that return a variable?
Here is some example code, but in the actual program the method's return would actually be used further on:
public class Validation{
public static void main(String args){
ValidatorClass valTest = new ValidatorClass();
String[] dataList = {"Andrew", "Jameson", "Male"}
for(int i = 0; i < dataList.length; i++){
String errors = valTest.testInput(dataList[i], i).validationList[i];
System.out.println(errors);
}
}
}
And in ValidatorClass:
public class ValidatorClass{
public String testInput(String data, int index){
//Tests the data by calling method "index" which corresponds to data type.
//ie. validateName would be index : 1, validateSurname index : 2 etc
String errors = validationMethodList[index](data); //Somehow add data as a parameter to it
return errors;
}
public String validateName(String name){
String errors = "";
if(name.length < 1){
errors += "Name Not Entered";
}
return errors;
}
public String validateSurname(String surname){
String errors = "";
if(surname.length < 1){
errors += "Surame Not Entered";
}
return errors;
}
public String validateGender(String gender){
String errors = "";
if(!gender.equalsIgnoreCase("male") || !gender.equalsIngoreCase("female")){
errors += "Invalid Gender";
}
return errors;
}
}
I imagine that you have something like...
static String validate1(Validatible v) { /* do something */ }
static String validate2(Validatible v) { /* do something else */ }
static String validate3(Validatible v) { /* do something still else */ }
And that you want to execute, in some method...
Validatible v = getValidatible();
System.out.println(validate1(v));
System.out.println(validate2(v));
System.out.println(validate3(v));
Then perhaps you could write an interface:
public interface Validator {
String validate(Validatible v);
}
...and keep them in an array or a list...
private static final List<Validator> validators = Arrays.asList(
new Validator() {
#Override
public String validate() {
/* do something */
}
},
new Validator() {
#Override
public String validate() {
/* do something else */
}
},
new Validator() {
#Override
public String validate() {
/* do something still else */
}
}
);
// Can be written more compactly if in Java 8.
Thereafter, you can call the methods in a for-loop:
Validatible v = getValidatible();
for(Validator validator : validators) {
System.out.println(validator.validate(v));
}
Possible improvements would include using a StringBuilder to build a single String (or using the Stream API and using Collectors.joining) if this fits your purpose better.
Can anyone help me do this a bit better, I have an ArrayList with data been added everytime a question is answered in the session by calling the method saveQuestionToPlayersQuestion(), after all questions are answered the savePlayersQuestionsToDB() meth is called. I Have a Table in the schema for that data.
I have it working and saving to database, but don't think its the correct way of doing it.
Can I just Insert the arraylist at once, meaning instead of calling the
ConnectionClass.createPlaySessionInDB(pQ.getPlayer_id(),
pQ.getQuestion_tbl_id(), pQ.getAns(),
pQ.getPlayer_score(), pQ.getPlay_session_id());
for every object that's in the List, Its OK when only 3 question are answered, But what happens if they have to answer 20 or 30+ question. Is there a better way.
My declared ArrayList
private ArrayList<Play_Questions> playQuestionList;
playQuestionList = new ArrayList<Play_Questions>();
Here is the method I call to save each question answered to playQuestionList and the next method savePlayersQuestionsToDB() is the one called to save all the object to the DB using a enhanced for loop.
/**
* add the question to playQuestionList
*/
public void saveQuestionToPlayersQuestion() {
Play_Questions temp = new Play_Questions(playerId, question_tbl_id,
choosenAnswer, scorePerQuestion, nextPlaySessionId);
playQuestionList.add(temp);
playQuestionList.toString();
}
/**
* save the playQuestion to DataBase
*/
public void savePlayersQuestionsToDB() {
for (Play_Questions pQ : playQuestionList) {
if (pQ == null) {
System.out.println("Play Question List is empty");
} else
try {
ConnectionClass.createPlaySessionInDB(pQ.getPlayer_id(),
pQ.getQuestion_tbl_id(), pQ.getAns(),
pQ.getPlayer_score(), pQ.getPlay_session_id());
System.out.println("Worked check DB --->>");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out
.println("Error with ElbtView savePlayersQuestionsToDB()");
}
}
Here is the method in the Connection Class
public static void createPlaySessionInDB(int player_id,
int question_tbl_id, String ans, int player_score,
int play_session_id) throws SQLException {
String sql = "INSERT INTO player_questions (id, player_id, question_tbl_id, ans, player_score, play_session_id ) VALUES (null,?,?,?,?,?)";
try {
preparedStatement = preparedStatement(sql);
preparedStatement.setInt(1, player_id);
preparedStatement.setInt(2, question_tbl_id);
preparedStatement.setString(3, ans);
preparedStatement.setInt(4, player_score);
preparedStatement.setInt(5, play_session_id);
// execute the SQL statement
preparedStatement.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out
.println("Problem with ConnectionClass createPlaySessionInDB method: "
+ e.getMessage());
} finally {
// close the connection
getConnection().close();
}
}
Here is the Play_Questions class
public class Play_Questions {
private int player_id;
private int question_tbl_id;
private String ans;
private int player_score;
private int play_session_id;
/**
* Default Constructor
*/
public Play_Questions(){
this(0,0,null,0,0);
}
/**
* #param player_id:
* the players id
* #param question_tbl_id:
* the question id from question table
* #param ans:
* the answer selected by player
* #param player_score:
* the score they achieved for answering
* #param play_session_id:
* the play session id
*/
public Play_Questions(int player_id, int question_tbl_id, String ans,
int player_score, int play_session_id) {
this.player_id = player_id;
this.question_tbl_id = question_tbl_id;
this.ans = ans;
this.player_score = player_score;
this.play_session_id = play_session_id;
}
/**
* #return the player_id
*/
public int getPlayer_id() {
return player_id;
}
/**
* #param player_id the player_id to set
*/
public void setPlayer_id(int player_id) {
this.player_id = player_id;
}
/**
* #return the question_tbl_id
*/
public int getQuestion_tbl_id() {
return question_tbl_id;
}
/**
* #param question_tbl_id the question_tbl_id to set
*/
public void setQuestion_tbl_id(int question_tbl_id) {
this.question_tbl_id = question_tbl_id;
}
/**
* #return the ans
*/
public String getAns() {
return ans;
}
/**
* #param ans the ans to set
*/
public void setAns(String ans) {
this.ans = ans;
}
/**
* #return the player_score
*/
public int getPlayer_score() {
return player_score;
}
/**
* #param player_score the player_score to set
*/
public void setPlayer_score(int player_score) {
this.player_score = player_score;
}
/**
* #return the play_session_id
*/
public int getPlay_session_id() {
return play_session_id;
}
/**
* #param play_session_id the play_session_id to set
*/
public void setPlay_session_id(int play_session_id) {
this.play_session_id = play_session_id;
}
Your help in making me code this a bit better will be greatly appreciate.
Gman
This is my approach , its best you use a very transparent approach that models the problem close to a real life scenario .
public class Question
{
private int question_no ;
// The right answer for this question may be a , b or c
private String question_answer ;
private int question_point ;
public Question()
{
}
/**
* #return the question_id
*/
public int getQuestion_id() {
return question_no;
}
/**
* #param question_id the question_id to set
*/
public void setQuestion_id(int question_id) {
this.question_no = question_id;
}
/**
* #return the question_answer
*/
public String getQuestion_answer() {
return question_answer;
}
/**
* #param question_answer the question_answer to set
*/
public void setQuestion_answer(String question_answer) {
this.question_answer = question_answer;
}
/**
* #return the question_point
*/
public int getQuestion_point() {
return question_point;
}
/**
* #param question_point the question_point to set
*/
public void setQuestion_point(int question_point) {
this.question_point = question_point;
}
}
Now the answer class
/**
*
* Track an answer
*/
public class Answer
{
private String answer ;
// correct or failed
private String status ;
public Answer()
{
}
/**
* #return the answer
*/
public String getAnswer() {
return answer;
}
/**
* #param answer the answer to set
*/
public void setAnswer(String answer) {
this.answer = answer;
}
/**
* #return the status
*/
public String getStatus() {
return status;
}
/**
* #param status the status to set
*/
public void setStatus(String status) {
this.status = status;
}
}
Now the player class that encapsulates all the operations for each player after successful login a player object is added to session , it handles operations like marks , save to db ,etc
/**
*
* encapsulates a player
* Class should be placed in session for web applications
*/
public class Player
{
String username ;
String password ;
// holds all the questions arranged for this player without allowing duplicates in questions
Set questions = new HashSet<Question>();
// map this players question and answer
Map question_answers = new HashMap<Question, Answer>();
/**
*
* Allows you to dynamically set questions for players
* #param questions_
*/
public Player(Set questions_ )
{
this.questions = questions_;
}
// if you want the same set of questions for all players
public Player()
{
}
/**
* Question answered for this particular user
* please note that the player object is in session if it is a web application
* #param q
* #param a
*/
public void answerQuestion(Question q , Answer a)
{
question_answers.put(q, a);
}
/**
*
* The user might go back to a previous question to change an answer
* #param q
* #param a
*/
public void updateAnswer(Question q, Answer a)
{
// remove the question and update it with
if(question_answers.containsKey(q))
{
question_answers.remove(q);
}
// add the new q & a
answerQuestion(q, a);
}
/**
*
* finally save the players data
* here your db model counts it would have to cater for
* each players question and answer , send the batch update using batch prepared statements
*/
public void savePlayerData()
{
// db code is commented because i didnt write db codes
// status int the column will stand for correct or fail
// String save_sql =insert into results(player_id , question_id , answer , status) values(?,?,?,?)
// PreparedStatement pstat = dbConnection.prepareStatement(save_sql);
//dbConnection.setAutoCommit(false);
// if automark is enabled
autoMark();
Iterator it = question_answers.values().iterator();
while(it.hasNext())
{
// fetch each question
Question q = (Question)it.next();
// Fetch each answer based on the question
Answer a = (Answer)question_answers.get(q);
int question_id = q.getQuestion_id();
String answer = a.getAnswer();
String answer_status = a.getStatus();
/**
*
*
* commented cause i would have to write db backing code , lol !
*
* pstat.setInt(1, getUsername());
* pstat.setInt(2, question_id);
* pstat.setString(3 , answer);
* pstat.setString(4 , answer_status)
* pstat.addBatch();
* pstat.executeBatch();
*
*/
}
//dbConnection.setAutoCommit(false);
}
/**
*
* This method can allow your program to auto mark if
* the question and answer if it is based on a , b , c
*/
public void autoMark()
{
Iterator it = question_answers.values().iterator();
while(it.hasNext())
{
// fetch each question
Question q = (Question)it.next();
// Fetch each answer based on the question
Answer a = (Answer)question_answers.get(q);
if(q.getQuestion_answer().equalsIgnoreCase(a.getAnswer()))
{
a.setStatus("Correct");
}
else
{
a.setStatus("Failed");
}
updateAnswer(q, a);
}
}
}
So anytime a player answers a question u call
p.answerQuestion(Question q , Answer a)
The question object is the particular question answered and the answer object is created to ,match the question .
U can also track the users current question by adding a value called current_question on the player class with getter and setter(Question Object) to track the current question in cases where the user might go back to a previous question you can then call
p.updateAnswer(Question q, Answer a)
Same thing u pass the particular question an d the new answer object
p.savePlayerData()
Saves data to the db
p.autoMark()
This method is called in the p.savePlayerData() method before the records are saved so the db only holds final assessment & marked records , This can come in handy for intelligent reporting such as who has the highest score . Thats it if you have extra questions you can contact me tyger2007#gmail.com.
Ok I did manage to achieve what I was trying to do, here is the revised code to help others that have the same problem. This is not the definite way to do this just the only way I know at this time to acheive what I need to. If anyone has a nice, faster way of doing this I would love to know. Got some inspiration from this thread here Java: Insert multiple rows into MySQL with PreparedStatement
Here is the method I call to save each question answered to playQuestionList and the next method savePlayersQuestionsToDB(), Instead of doing the work here I pass the Arraylist to the Connection Class and loop through it there.
/**
* add the question to playQuestionList
*/
public void saveQuestionToPlayersQuestion() {
Play_Questions temp = new Play_Questions(playerId, question_tbl_id,
choosenAnswer, scorePerQuestion, nextPlaySessionId);
playQuestionList.add(temp);
}
/**
* save the playQuestion to DataBase
*/
public void savePlayersQuestionsToDB() {
try {
ConnectionClass.savePlaySessionInDB(playQuestionList);
System.out.println("Worked check DB --->>");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out
.println("Error with ElbtView savePlayersQuestionsToDB()");
}
}
and here is the method in the ConnectionClass
/**
* Creates a batch using addBatch()and then executes it by calling
* ececuteBatch()
*
* #param playQuestions
* the ArrayList<T> to be added to batch and then saved to DB
* #throws SQLException
* when executing the the batch
*/
public static void savePlaySessionInDB(
ArrayList<Play_Questions> playQuestions) throws SQLException {
try {
String sql = "INSERT INTO player_questions (id, player_id, question_tbl_id, ans, player_score, play_session_id ) VALUES (null,?,?,?,?,?)";
preparedStatement = preparedStatement(sql);
for (int i = 0; i < playQuestions.size(); i++) {
Play_Questions playQuestion = playQuestions.get(i);
preparedStatement.setInt(1, playQuestion.getPlayer_id());
preparedStatement.setInt(2, playQuestion.getQuestion_tbl_id());
preparedStatement.setString(3, playQuestion.getAns());
preparedStatement.setInt(4, playQuestion.getPlayer_score());
preparedStatement.setInt(5, playQuestion.getPlay_session_id());
preparedStatement.addBatch();
if ((i + 1) % 100 == 0) {
preparedStatement.executeBatch(); // excute every 100 items
}
}
preparedStatement.executeBatch();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
System.out
.println("Problem with ConnectionClass savePlaySessionInDB(ArrayList<Play_Questions> playQuestions) method: "
+ e.getMessage());
} finally {
// close the connection
getConnection().close();
}
}
Defiantly open to a much better suggestion.
Gman
I must say that as this solution actually works perfectly now , in the long hol it might still pose some problems it actually depends on the nature of your application , is it an application that will be deployed and used by a lot of users ? or its just a test application ?, Anyway i do advise that you model your objects properly as this is the most important thing in object oriented programming ,
My suggestions :
Isolate Question , Player , Answer Objects , Having Objects Like Play_Question will make your application difficult to extend dynamically .
Use an Iterator for your loops , its been optimized to perform better than for loops especially in collection.
For me i naturally use a singleton connection class if it is a desktop application & JNDI if its a web application , the connection class uses a connection pool so my application does not open a new connection for every db request , it makes your application super fast in db request [http://jolbox.com/ bonecp connection pooling for Desktop apps if web use JNDI ]
Maps are very easy to use its a case of key/value pairs , All the same nice work and have fun. .
It's ok ,spent quite some time developing applications that generally I always develop to scale ,lol ,I used iterators in the Players class in the method savePlayerData() u can always use it with your collection classes .
I need to fetch many records from an RDBMS in Java (10-20k) my target system expects them to be available as Java List. So I want to implement my code as "Virtual list" where I actually only fetch the records I actually need. I expect SQL like
SELECT * FROM CUSTOMER WHERE COUNTRY="Moldovia"
as parameter and just return what is requested. Most likely the data is requested in batches of 50. Any hints how to do that?
Unless you expect your clients to randomly access the data, you're probably better off returning an Iterator. Also, take a look at ResultSet.setFetchSize: http://java.sun.com/javase/6/docs/api/java/sql/ResultSet.html#setFetchSize(int)
So something like:
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
public class FooResultSetIterator implements Iterator<Foo>
{
private final ResultSet resultSet;
private boolean hasNext;
FooResultSetIterator(final ResultSet resultSet, final int fetchSize) throws SQLException
{
this.resultSet = resultSet;
this.resultSet.setFetchSize(fetchSize);
this.hasNext = resultSet.next();
}
#Override
public boolean hasNext()
{
return hasNext;
}
#Override
public Foo next()
{
final Foo foo = new Foo(resultSet);
try
{
this.hasNext = resultSet.next();
}
catch (final SQLException e)
{
throw new RuntimeException(e);
}
return foo;
}
#Override
public void remove()
{
throw new UnsupportedOperationException("Cannot remove items from a ResultSetIterator");
}
}
class Foo
{
public Foo(ResultSet resultSet)
{
// TODO Auto-generated constructor stub
}
}
Use OFFSET and LIMIT in your query:
SELECT * FROM CUSTOMER WHERE
COUNTRY="Moldovia" LIMIT 50 OFFSET 50
Assuming of course that your SQL dialect allows it. The example will return rows 51-100.