/* * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* * ArffTableModel.java * Copyright (C) 2005 University of Waikato, Hamilton, New Zealand * */ package weka.gui.arffviewer; import weka.core.Attribute; import weka.core.Instance; import weka.core.Instances; import weka.core.Undoable; import weka.core.Utils; import weka.core.converters.AbstractFileLoader; import weka.core.converters.ConverterUtils; import weka.filters.Filter; import weka.filters.unsupervised.attribute.Reorder; import weka.gui.ComponentHelper; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.Vector; import javax.swing.JOptionPane; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableModel; /** * The model for the Arff-Viewer. * * * @author FracPete (fracpete at waikato dot ac dot nz) * @version $Revision: 5987 $ */ public class ArffTableModel implements TableModel, Undoable { /** the listeners */ private HashSet m_Listeners; /** the data */ private Instances m_Data; /** whether notfication is enabled */ private boolean m_NotificationEnabled; /** whether undo is active */ private boolean m_UndoEnabled; /** whether to ignore changes, i.e. not adding to undo history */ private boolean m_IgnoreChanges; /** the undo list (contains temp. filenames) */ private Vector m_UndoList; /** whether the table is read-only */ private boolean m_ReadOnly; /** * performs some initialization */ private ArffTableModel() { super(); m_Listeners = new HashSet(); m_Data = null; m_NotificationEnabled = true; m_UndoList = new Vector(); m_IgnoreChanges = false; m_UndoEnabled = true; m_ReadOnly = false; } /** * initializes the object and loads the given file * * @param filename the file to load */ public ArffTableModel(String filename) { this(); if ( (filename != null) && (!filename.equals("")) ) loadFile(filename); } /** * initializes the model with the given data * * @param data the data to use */ public ArffTableModel(Instances data) { this(); this.m_Data = data; } /** * returns whether the notification of changes is enabled * * @return true if notification of changes is enabled */ public boolean isNotificationEnabled() { return m_NotificationEnabled; } /** * sets whether the notification of changes is enabled * * @param enabled enables/disables the notification */ public void setNotificationEnabled(boolean enabled) { m_NotificationEnabled = enabled; } /** * returns whether undo support is enabled * * @return true if undo support is enabled */ public boolean isUndoEnabled() { return m_UndoEnabled; } /** * sets whether undo support is enabled * * @param enabled whether to enable/disable undo support */ public void setUndoEnabled(boolean enabled) { m_UndoEnabled = enabled; } /** * returns whether the model is read-only * * @return true if model is read-only */ public boolean isReadOnly() { return m_ReadOnly; } /** * sets whether the model is read-only * * @param value if true the model is set to read-only */ public void setReadOnly(boolean value) { m_ReadOnly = value; } /** * loads the specified ARFF file * * @param filename the file to load */ private void loadFile(String filename) { AbstractFileLoader loader; loader = ConverterUtils.getLoaderForFile(filename); if (loader != null) { try { loader.setFile(new File(filename)); m_Data = loader.getDataSet(); } catch (Exception e) { ComponentHelper.showMessageBox( null, "Error loading file...", e.toString(), JOptionPane.OK_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE ); System.out.println(e); m_Data = null; } } } /** * sets the data * * @param data the data to use */ public void setInstances(Instances data) { m_Data = data; } /** * returns the data * * @return the current data */ public Instances getInstances() { return m_Data; } /** * returns the attribute at the given index, can be NULL if not an attribute * column * * @param columnIndex the index of the column * @return the attribute at the position */ public Attribute getAttributeAt(int columnIndex) { if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) return m_Data.attribute(columnIndex - 1); else return null; } /** * returns the TYPE of the attribute at the given position * * @param columnIndex the index of the column * @return the attribute type */ public int getType(int columnIndex) { return getType(0, columnIndex); } /** * returns the TYPE of the attribute at the given position * * @param rowIndex the index of the row * @param columnIndex the index of the column * @return the attribute type */ public int getType(int rowIndex, int columnIndex) { int result; result = Attribute.STRING; if ( (rowIndex >= 0) && (rowIndex < getRowCount()) && (columnIndex > 0) && (columnIndex < getColumnCount()) ) result = m_Data.instance(rowIndex).attribute(columnIndex - 1).type(); return result; } /** * deletes the attribute at the given col index. notifies the listeners. * * @param columnIndex the index of the attribute to delete */ public void deleteAttributeAt(int columnIndex) { deleteAttributeAt(columnIndex, true); } /** * deletes the attribute at the given col index * * @param columnIndex the index of the attribute to delete * @param notify whether to notify the listeners */ public void deleteAttributeAt(int columnIndex, boolean notify) { if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) { if (!m_IgnoreChanges) addUndoPoint(); m_Data.deleteAttributeAt(columnIndex - 1); if (notify) notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW)); } } /** * deletes the attributes at the given indices * * @param columnIndices the column indices */ public void deleteAttributes(int[] columnIndices) { int i; Arrays.sort(columnIndices); addUndoPoint(); m_IgnoreChanges = true; for (i = columnIndices.length - 1; i >= 0; i--) deleteAttributeAt(columnIndices[i], false); m_IgnoreChanges = false; notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW)); } /** * renames the attribute at the given col index * * @param columnIndex the index of the column * @param newName the new name of the attribute */ public void renameAttributeAt(int columnIndex, String newName) { if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) { addUndoPoint(); m_Data.renameAttribute(columnIndex - 1, newName); notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW)); } } /** * sets the attribute at the given col index as the new class attribute, i.e. * it moves it to the end of the attributes * * @param columnIndex the index of the column */ public void attributeAsClassAt(int columnIndex) { Reorder reorder; String order; int i; if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) { addUndoPoint(); try { // build order string (1-based!) order = ""; for (i = 1; i < m_Data.numAttributes() + 1; i++) { // skip new class if (i == columnIndex) continue; if (!order.equals("")) order += ","; order += Integer.toString(i); } if (!order.equals("")) order += ","; order += Integer.toString(columnIndex); // process data reorder = new Reorder(); reorder.setAttributeIndices(order); reorder.setInputFormat(m_Data); m_Data = Filter.useFilter(m_Data, reorder); // set class index m_Data.setClassIndex(m_Data.numAttributes() - 1); } catch (Exception e) { e.printStackTrace(); undo(); } notifyListener(new TableModelEvent(this, TableModelEvent.HEADER_ROW)); } } /** * deletes the instance at the given index * * @param rowIndex the index of the row */ public void deleteInstanceAt(int rowIndex) { deleteInstanceAt(rowIndex, true); } /** * deletes the instance at the given index * * @param rowIndex the index of the row * @param notify whether to notify the listeners */ public void deleteInstanceAt(int rowIndex, boolean notify) { if ( (rowIndex >= 0) && (rowIndex < getRowCount()) ) { if (!m_IgnoreChanges) addUndoPoint(); m_Data.delete(rowIndex); if (notify) notifyListener( new TableModelEvent( this, rowIndex, rowIndex, TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE)); } } /** * deletes the instances at the given positions * * @param rowIndices the indices to delete */ public void deleteInstances(int[] rowIndices) { int i; Arrays.sort(rowIndices); addUndoPoint(); m_IgnoreChanges = true; for (i = rowIndices.length - 1; i >= 0; i--) deleteInstanceAt(rowIndices[i], false); m_IgnoreChanges = false; notifyListener( new TableModelEvent( this, rowIndices[0], rowIndices[rowIndices.length - 1], TableModelEvent.ALL_COLUMNS, TableModelEvent.DELETE)); } /** * sorts the instances via the given attribute * * @param columnIndex the index of the column */ public void sortInstances(int columnIndex) { if ( (columnIndex > 0) && (columnIndex < getColumnCount()) ) { addUndoPoint(); m_Data.sort(columnIndex - 1); notifyListener(new TableModelEvent(this)); } } /** * returns the column of the given attribute name, -1 if not found * * @param name the name of the attribute * @return the column index or -1 if not found */ public int getAttributeColumn(String name) { int i; int result; result = -1; for (i = 0; i < m_Data.numAttributes(); i++) { if (m_Data.attribute(i).name().equals(name)) { result = i + 1; break; } } return result; } /** * returns the most specific superclass for all the cell values in the * column (always String) * * @param columnIndex the column index * @return the class of the column */ public Class getColumnClass(int columnIndex) { Class result; result = null; if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) { if (columnIndex == 0) result = Integer.class; else if (getType(columnIndex) == Attribute.NUMERIC) result = Double.class; else result = String.class; // otherwise no input of "?"!!! } return result; } /** * returns the number of columns in the model * * @return the number of columns */ public int getColumnCount() { int result; result = 1; if (m_Data != null) result += m_Data.numAttributes(); return result; } /** * checks whether the column represents the class or not * * @param columnIndex the index of the column * @return true if the column is the class attribute */ private boolean isClassIndex(int columnIndex) { boolean result; int index; index = m_Data.classIndex(); result = ((index == - 1) && (m_Data.numAttributes() == columnIndex)) || (index == columnIndex - 1); return result; } /** * returns the name of the column at columnIndex * * @param columnIndex the index of the column * @return the name of the column */ public String getColumnName(int columnIndex) { String result; result = ""; if ( (columnIndex >= 0) && (columnIndex < getColumnCount()) ) { if (columnIndex == 0) { result = "