/*
* Copyright (c) 2013 Patrick Meyer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.itemanalysis.jmetrik.scoring;
import com.itemanalysis.jmetrik.dao.DatabaseAccessObject;
import com.itemanalysis.jmetrik.sql.DataTableName;
import com.itemanalysis.jmetrik.sql.DatabaseName;
import com.itemanalysis.jmetrik.sql.VariableTableName;
import com.itemanalysis.jmetrik.utils.Alphabet;
import com.itemanalysis.jmetrik.workspace.VariableChangeEvent;
import com.itemanalysis.jmetrik.workspace.VariableChangeListener;
import com.itemanalysis.jmetrik.workspace.VariableChangeType;
import com.itemanalysis.psychometrics.data.ItemType;
import com.itemanalysis.psychometrics.data.VariableAttributes;
import org.apache.log4j.Logger;
import javax.swing.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
public class BasicScoringAnalysis extends SwingWorker<String, Void>{
private Connection conn = null;
private DatabaseAccessObject dao = null;
private BasicScoringCommand command = null;
private DatabaseName dbName = null;
private DataTableName tableName = null;
private VariableTableName variableTableName = null;
private ArrayList<String> key = null;
private ArrayList<Integer> ncat = null;
private ArrayList<VariableChangeListener> variableChangeListeners = null;
private ArrayList<VariableAttributes> variables = null;
private Throwable theException = null;
private String omitCode = "";
private String notReachedCode = "";
static Logger logger = Logger.getLogger("jmetrik-logger");
public BasicScoringAnalysis(Connection conn, DatabaseAccessObject dao, BasicScoringCommand command){
this.conn = conn;
this.dao = dao;
this.command = command;
variableChangeListeners = new ArrayList<VariableChangeListener>();
}
private void processCommand(){
dbName = new DatabaseName(command.getPairedOptionList("data").getStringAt("db"));
tableName = new DataTableName(command.getPairedOptionList("data").getStringAt("table"));
variableTableName = new VariableTableName(tableName.toString());
key = command.getFreeOptionList("key").getString();
ncat = command.getFreeOptionList("ncat").getInteger();
omitCode = command.getPairedOptionList("codes").getStringAt("omit");
notReachedCode = command.getPairedOptionList("codes").getStringAt("nr");
}
/**
* The score string is formatted as (A,B,C,D)(0,1,0,0). See DefaultItemScoring
* for the regex and more information.
*
* @param key answer key may be a number or letter
* @param ncat number of response options (number of categories)
* @return
*/
private double maxNumberOfResponseOptions(String key, int ncat){
try{
//key is an integer
double k = Double.parseDouble(key);
return Math.max(k, (double)ncat);
}catch(NumberFormatException ex){
//key is a string
int pos = Alphabet.getNum(key)+1;
return Math.max(pos, ncat);
}
}
private String polytomousScoreString(String key, int ncat)throws NumberFormatException{
boolean ascending = key.startsWith("+");
int startValue = 1;
int complement = ncat;
if(!ascending){
startValue = ncat;
complement = 1;
}
if(key.length()>1){
int iKey = Integer.parseInt(key.substring(1));//will throw NumberFormatException if key.substring(1) is a String.
startValue = Math.abs(iKey);
if(ascending){
complement = ncat+startValue-1;
}else{
complement = startValue+1 - ncat;
complement = Math.max(0, complement);//force non-negative numbers
}
}
String options = "(";
String score = "(";
//key is an integer
if(ascending){
for(int i=0;i<ncat;i++){
options += startValue + ",";
score += startValue + ",";
startValue++;
}
}else{
for(int i=0;i<ncat;i++){
options += Math.max(0, complement+i) + ",";
score += startValue + ",";
startValue--;
startValue = Math.max(0, startValue);//prevent negative score values
}
}
options = options.substring(0, options.lastIndexOf(","));
options+=")";
score = score.substring(0, score.lastIndexOf(","));
score+=")";
return options + score;
}
private String binaryScoreString(String key, int ncat)throws NumberFormatException{
double maxCat = maxNumberOfResponseOptions(key, ncat);
String options = "(";
String score = "(";
try{
double iKey = Double.parseDouble(key);
//key is a double
for(int i=0;i<maxCat;i++){
options += (i+1) + ",";
if(iKey==(double)(i+1)){
score += 1 + ",";
}else{
score += 0 + ",";
}
}
options = options.substring(0, options.lastIndexOf(","));
options+=")";
score = score.substring(0, score.lastIndexOf(","));
score+=")";
}catch(NumberFormatException ex){
//key is a string
boolean lowerCase = Character.isLowerCase(key.charAt(0));
String optionString = "";
for(int i=0;i<maxCat;i++){
optionString = Alphabet.getLetter(i+1, lowerCase);
options += optionString + ",";
if(key.equals(optionString)){
score += 1 + ",";
}else{
score += 0 + ",";
}
}
options = options.substring(0, options.lastIndexOf(","));
options+=")";
score = score.substring(0, score.lastIndexOf(","));
score+=")";
}
return options + score;
}
private String getScoreString(String key, int ncat)throws NumberFormatException{
String trimmedKey = key.trim();
if("".equals(trimmedKey)){
return "";//not an item
}else if(trimmedKey.startsWith("+") || trimmedKey.startsWith("-")){
return polytomousScoreString(key, ncat);
}else{
return binaryScoreString(key, ncat);
}
}
/**
* Converts input information into a scoring string for VariableInfo object.
* Database is updated with the new scoring. The method assumes that the input
* ArrayLists are in teh same order as the variables in the database table.
*
* @throws java.sql.SQLException
*/
private void convertScoring()throws SQLException{
variables = dao.getAllVariables(conn, variableTableName);
//only use the smallest number of variables found in input
int nvar = variables.size();
int nkey = key.size();
int nopt = ncat.size();
nvar = Math.min(nvar, nkey);
nvar = Math.min(nvar, nopt);
String scoreString = "";
VariableAttributes tempVar;
for(int i=0;i<nvar;i++){
scoreString = getScoreString(key.get(i), ncat.get(i));
tempVar = variables.get(i);
tempVar.clearCategory();
tempVar.addAllCategories(scoreString);
if(tempVar.getType().getItemType()!= ItemType.NOT_ITEM){
if(omitCode!=null) tempVar.getSpecialDataCodes().setOmittedCode(omitCode);
if(notReachedCode!=null) tempVar.getSpecialDataCodes().setNotReachedCode(notReachedCode);
}
}
//add item scoring to database
dao.setVariableScoring(conn, variableTableName, variables);
dao.setOmitAndNotReachedCode(conn, variableTableName, variables);
}
@Override
protected String doInBackground()throws Exception{
try{
logger.info(command.paste());
processCommand();
convertScoring();
}catch(Exception ex){
theException = ex;
throw ex;
}
return "";
}
@Override
protected void done(){
try{
if(theException==null){
firePropertyChange("table-updated", null, tableName);//updates display of data table
for(VariableAttributes v : variables){
//updates variables in dialogs
fireVariableChanged(new VariableChangeEvent(this, variableTableName, v, VariableChangeType.VARIABLE_MODIFIED));
}
}else{
logger.fatal(theException.getMessage(), theException);
firePropertyChange("error", "", "Error - Check log for details.");
}
}catch(Exception ex){
logger.fatal(ex.getMessage(), ex);
firePropertyChange("error", "", "Error - Check log for details.");
}
}
//===============================================================================================================
//Handle variable changes here
// -Dialogs will use these methods to add their variable listeners
//===============================================================================================================
public synchronized void addVariableChangeListener(VariableChangeListener l){
variableChangeListeners.add(l);
}
public synchronized void removeVariableChangeListener(VariableChangeListener l){
variableChangeListeners.remove(l);
}
public synchronized void removeAllVariableChangeListeners(){
variableChangeListeners.clear();
}
public void fireVariableChanged(VariableChangeEvent event){
for(VariableChangeListener l : variableChangeListeners){
l.variableChanged(event);
}
}
//===============================================================================================================
}