package eu.irreality.age.swing.newloader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableModel;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import eu.irreality.age.i18n.UIMessages;
import eu.irreality.age.util.xml.XMLfromURL;
/**
* A model for a JTable containing information about AGE worlds.
* @author carlos
*
*/
public class GameTableModel extends AbstractTableModel
{
private Vector columnNames;
private List catalogUrls; //urls to the XML catalog files
private File catalogWritePath; //file to write the global catalog to
private List gameEntries = new ArrayList(); //game entries (the content of the model)
private void initColumnNames()
{
columnNames = new Vector();
columnNames.add( UIMessages.getInstance().getMessage("gameinfo.name") );
columnNames.add( UIMessages.getInstance().getMessage("gameinfo.author") );
columnNames.add( UIMessages.getInstance().getMessage("gameinfo.date") );
columnNames.add( UIMessages.getInstance().getMessage("gameinfo.version") );
columnNames.add( UIMessages.getInstance().getMessage("gameinfo.required") );
columnNames.add( UIMessages.getInstance().getMessage("gameinfo.language") );
columnNames.add( UIMessages.getInstance().getMessage("gameinfo.downloaded") );
columnNames.add( "Local Path" );
columnNames.add( "Remote URL" );
}
public GameTableModel()
{
initColumnNames();
catalogUrls = new ArrayList();
catalogUrls.add ( this.getClass().getClassLoader().getResource("catalog.xml") );
}
/**
* Adds a game entry to the table model.
* If the overwrite parameter is false, it adds the game only if it does not already exist in the model.
* If it is true, then if the game exists, it deletes the old entry and adds the given game entry.
* Returns true if changes were made, false if they weren't (i.e. if overwrite was set to false and entry already existed).
*/
public boolean addGameEntry( GameEntry ge , boolean overwrite )
{
if ( overwrite )
{
if ( gameEntries.contains(ge) )
gameEntries.remove(ge);
gameEntries.add(ge);
Collections.sort(gameEntries); //TODO this may not scale
fireTableDataChanged();
return true;
}
else
{
if ( !gameEntries.contains(ge) ) //equality is by local path and remote url
{
gameEntries.add(ge);
Collections.sort(gameEntries); //TODO this may not scale
fireTableDataChanged();
return true;
}
return false;
}
}
public GameEntry getGameEntry ( int index )
{
return (GameEntry) gameEntries.get(index);
}
/**
* Adds the games contained in the given catalog to the table model, and updates the catalog URLs,
* adding the given URL as a catalog URL to the model.
* @param doc
* @param catalogURL
* @param overwrite
* @throws MalformedGameEntryException
* @return The number of entries that were added or overwritten.
*/
public int addGameCatalog ( Document doc , URL catalogURL , boolean overwrite ) throws MalformedGameEntryException
{
int nGamesAdded = addGamesFromCatalog((Element)doc.getFirstChild(),overwrite);
if ( !catalogUrls.contains(catalogURL) )
{
if ( overwrite )
catalogUrls.add(0,catalogURL); //add at beginning so that it takes larger priority when refresh is called
else
catalogUrls.add(catalogURL); //add at end
}
return nGamesAdded;
}
/**
* Opens a XML catalog from an URL, adds all the games contained in a catalog to the table model, and updates the catalog URLs
* to add the given URL.
* This method is blocking and should be called from the event dispatch thread. Note that this means it is not suitable for remote
* catalogs where getting the XML information from the URL could take significant time.
* @param catalogURL URL where the game catalog in XML can be found.
* @param overwrite If true, the entries from the given catalog overwrite those of the old catalog if they have the same local path and remote URL.
* @throws MalformedGameEntryException
* @throws TransformerFactoryConfigurationError
* @throws TransformerConfigurationException
*/
public void loadGameCatalog ( URL catalogURL , boolean overwrite ) throws IOException, TransformerException, MalformedGameEntryException
{
Document doc = XMLfromURL.getXMLFromURL(catalogURL);
addGameCatalog(doc,catalogURL,overwrite);
}
/**
* Adds all the games described in the game XML elements that are children of the given catalog XML elements.
* If overwrite is true, the existing entries with the same local path and remote URL are overwritten by the new entries. If it's false, they aren't.
* @param e
* @throws MalformedGameEntryException
* @return The number of game entries that were added or overwritten (might be 0 not only if the catalog is empty, but also if
* overwrite is false and all the entries in the catalog were already present in the model).
*/
private int addGamesFromCatalog ( Element e , boolean overwrite ) throws MalformedGameEntryException
{
int nGamesAdded = 0;
NodeList gameList = e.getElementsByTagName("game");
for ( int i = 0 ; i < gameList.getLength() ; i++ )
{
GameEntry ge = new GameEntry();
ge.initFromXML(gameList.item(i));
boolean added = addGameEntry ( ge , overwrite );
if ( added ) nGamesAdded++;
}
return nGamesAdded;
}
/**
* Sets the path to which the combined catalog will be written at the end of execution.
* @param path
*/
public void setCatalogWritePath ( File path )
{
catalogWritePath = path;
}
/**
* Writes the combined, updated catalog to the set path.
* @throws ParserConfigurationException
*/
public void writeCatalog() throws TransformerException, ParserConfigurationException, IOException
{
if ( catalogWritePath == null ) return;
Document d = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
org.w3c.dom.Element catalogElement = d.createElement("catalog");
for ( int i = 0 ; i < gameEntries.size() ; i++ )
{
catalogElement.appendChild( ((GameEntry)gameEntries.get(i)).getXML(d) );
}
d.appendChild(catalogElement);
Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.INDENT,"yes");
Source s = new DOMSource(d);
Result r = new StreamResult(catalogWritePath);
t.transform(s,r);
}
/**
* Empty the table and re-load all the catalogs.
* @throws MalformedGameEntryException
* @throws TransformerException
* @throws IOException
*/
/**
* TODO: At the moment this operation is unused. If we actually want to use it, maybe some refactoring would be good.
* Loading stuff from URLs (especially if they're remote URLs) is not very model-ish. Maybe this should be on the
* game panel class, should get the URLs from the model, and load the catalogs from them (calling addCatalog, loadCatalog,
* etc. on the model when necessary)
*/
public void refreshCatalogs ( ) throws IOException, TransformerException, MalformedGameEntryException
{
gameEntries.clear();
for ( int i = 0 ; i < catalogUrls.size() ; i++ )
{
loadGameCatalog ( (URL)catalogUrls.get(i) , false );
}
fireTableDataChanged();
}
public int getRowCount()
{
return gameEntries.size();
}
public int getColumnCount()
{
return 9;
}
public Object getValueAt(int rowIndex, int columnIndex)
{
Object value;
if ( rowIndex < 0 || rowIndex >= gameEntries.size() )
throw new IndexOutOfBoundsException("Row index out of bounds: " + rowIndex);
GameEntry gameEntry = (GameEntry) gameEntries.get(rowIndex);
switch (columnIndex)
{
case 0:
value = gameEntry.getTitle();
break;
case 1:
value = gameEntry.getAuthor();
break;
case 2:
value = gameEntry.getDate();
break;
case 3:
value = gameEntry.getVersion();
break;
case 4:
value = gameEntry.getAgeVersion();
break;
case 5:
value = gameEntry.getLanguage();
break;
case 6:
value = Boolean.valueOf(gameEntry.isDownloaded());
break;
case 7:
value = gameEntry.getMainResource().getLocalPath();
break;
case 8:
value = gameEntry.getMainResource().getRemoteURL();
break;
default:
throw new IndexOutOfBoundsException("Column index out of bounds: " + columnIndex);
}
return value;
}
public Class getColumnClass ( int columnIndex )
{
Class cl;
switch ( columnIndex )
{
case 0: case 1: case 2: case 3: case 4: case 5: case 7: cl = String.class; break;
case 6: cl = Boolean.class; break;
case 8: cl = URL.class; break;
default: throw new IndexOutOfBoundsException("Column index out of bounds: " + columnIndex);
}
return cl;
}
public String getColumnName ( int columnIndex )
{
return (String) columnNames.get(columnIndex);
}
}