package net.sf.jpluck.jxl;
import net.sf.jpluck.swing.TableListModel;
import net.sf.jpluck.xml.DOMUtil;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathException;
import org.apache.commons.jxpath.Pointer;
import org.apache.xml.serialize.OutputFormat;
import org.apache.xml.serialize.XMLSerializer;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
public class JXL extends TableListModel {
public static final String PUBLIC_ID = "-//jpluck//DTD JXL 2.0//EN";
public static final String SYSTEM_ID = "http://jpluck.sourceforge.net/jxl/DTD/jxl-2.0.dtd";
public static final String PUBLIC_ID_0_9 = "-//jpluck//DTD JXL 0.9//EN";
private org.w3c.dom.Document root;
private File file;
private JXPathContext context;
protected List documentList = new ArrayList();
private List templateList = new ArrayList();
private String systemId;
private String[] columnNames = { "Name", "Category", "Type", "Last Converted", "Size", "Due" };
public JXL() {
root = DOMUtil.createDocument();
root.appendChild(root.createElement("jxl"));
context = JXPathContext.newContext(root.getDocumentElement());
}
/**
* Creates a DocumentList from the specified InputSource.
*/
public JXL(InputSource inputSource) throws IOException, SAXException {
parse(inputSource, true);
}
public JXL(File file) throws IOException, SAXException {
this(file, true);
}
public JXL(File file, boolean validate) throws IOException, SAXException {
this.file = file;
InputSource inputSource = new InputSource(file.toURI().toString());
inputSource.setByteStream(new FileInputStream(file));
parse(inputSource, validate);
}
public JXL(URL url) throws IOException, SAXException {
InputSource inputSource = new InputSource(url.toString());
inputSource.setByteStream(url.openStream());
parse(inputSource, true);
}
public int getColumnCount() {
return columnNames.length;
}
public String getColumnName(int i) {
return columnNames[i];
}
public Document getDocument(String name) {
for (Iterator iterator = documentList.iterator(); iterator.hasNext();) {
Document document = (Document) iterator.next();
if (document.getName().equalsIgnoreCase(name)) {
return document;
}
}
return null;
}
public Document[] getDocuments() {
return (Document[]) documentList.toArray(new Document[documentList.size()]);
}
public Document[] getDocumentsToConvert() {
List convertList = new ArrayList();
for (Iterator it = documentList.iterator(); it.hasNext();) {
Document document = (Document) it.next();
if (document.shouldConvert()) {
convertList.add(document);
}
}
return (Document[]) convertList.toArray(new Document[convertList.size()]);
}
public Document[] getDueDocuments() {
List dueDocumentList = new ArrayList();
for (Iterator it = documentList.iterator(); it.hasNext();) {
Document document = (Document) it.next();
if (document.isDue()) {
dueDocumentList.add(document);
}
}
return (Document[]) dueDocumentList.toArray(new Document[dueDocumentList.size()]);
}
public Object getElementAt(int index) {
return documentList.get(index);
}
public File getFile() {
return file;
}
public boolean containsImport(String href) {
try {
String value = (String) context.getValue("import/@href[.='" + href + "']");
return (value != null);
} catch (JXPathException e) {
return false;
}
}
public int getSize() {
return documentList.size();
}
public String getSystemId() {
return systemId;
}
public Template getTemplate(String id) {
for (Iterator iterator = templateList.iterator(); iterator.hasNext();) {
Template template = (Template) iterator.next();
if (template.getID().equals(id)) {
return template;
}
}
return null;
}
public Template[] getTemplates() {
return (Template[]) templateList.toArray(new Template[templateList.size()]);
}
public String getTitle() {
try {
Pointer p = context.getPointer("info/title");
return (String) p.getValue();
} catch (JXPathException e) {
return null;
}
}
public Object getValue(Object obj, int index) {
Document document = (Document) obj;
switch (index) {
case 0:
return document.getName();
case 1:
return document.getPrimaryCategory();
case 2:
if (document instanceof Feed) {
return "Feed";
} else {
return "Site";
}
case 3:
Date lastConverted = document.getLastConverted();
if (lastConverted != null) {
return formatDate(lastConverted);
} else {
return null;
}
case 4:
long size = document.getLastSize();
if (size > -1) {
return (((int) Math.ceil(size / 1024d)) + " KB");
} else {
return "";
}
case 5:
Date due = document.getDueDate();
if (due != null) {
return formatDate(due);
} else {
return null;
}
}
return null;
}
public Document add(Element element) {
root.getDocumentElement().appendChild(element);
Document document;
if (element.getNodeName().equals("site")) {
document = new Site(element, this);
} else {
document = new Feed(element, this);
}
documentList.add(document);
setLastEdited(new Date());
fireIntervalAdded(this, documentList.size() - 1, documentList.size() - 1);
return document;
}
public Document addCopyOf(Document document) {
Element element;
if (root != document.element.getOwnerDocument()) {
element = (Element) root.importNode(document.element, true);
} else {
element = (Element) document.element.cloneNode(true);
}
root.getDocumentElement().appendChild(element);
if (document instanceof Site) {
document = new Site(element, this);
} else {
document = new Feed(element, this);
}
documentList.add(document);
fireIntervalAdded(this, documentList.size() - 1, documentList.size() - 1);
return document;
}
public void addCopyOf(Template template) {
HierarchyTraversal hierarchy = new HierarchyTraversal(template);
Template[] parentTemplates = hierarchy.getParentTemplates();
Template[] templates = new Template[1 + parentTemplates.length];
templates[0] = template;
System.arraycopy(parentTemplates, 0, templates, 1, parentTemplates.length);
JXL sourceJXL = template.getJXL();
for (int i = 0; i < templates.length; i++) {
if (!containsTemplate(templates[i].getID())) {
if ((templates[i].getSystemId() == null) ||
((templates[i].getSystemId() != null) &&
templates[i].getSystemId().equals(template.getSystemId()))) {
Element element = (Element) root.importNode(templates[i].element, true);
root.getDocumentElement().appendChild(element);
templateList.add(new Template(element, this));
} else {
String href = templates[i].getSystemId();
if (!containsImport(href)) {
Element imp = root.createElement("import");
imp.setAttribute("href", href);
NodeList nodeList = root.getDocumentElement().getChildNodes();
if (nodeList.getLength() == 0) {
root.getDocumentElement().appendChild(imp);
} else {
root.getDocumentElement().insertBefore(imp, nodeList.item(0));
}
}
}
}
}
}
public Feed addFeed(String template, String name, String uri, int linkDepth, String category) {
Element element = root.createElement("feed");
populateElement(element, template, name, uri, linkDepth, null, category);
root.getDocumentElement().appendChild(element);
Feed feed = new Feed(element, this);
feed.configureParentTemplate();
documentList.add(feed);
return feed;
}
public Site addSite(String template, String name, String uri, int linkDepth, String restrict, String category) {
Element element = root.createElement("site");
populateElement(element, template, name, uri, linkDepth, restrict, category);
root.getDocumentElement().appendChild(element);
Site site = new Site(element, this);
site.configureParentTemplate();
documentList.add(site);
return site;
}
public boolean containsDocument(String name) {
return (getDocument(name) != null);
}
public boolean containsTemplate(String id) {
return (getTemplate(id) != null);
}
public Element createFeedElement() {
return root.createElement("feed");
}
public Element createSiteElement() {
return root.createElement("site");
}
public void edited(Document document) {
int idx = documentList.indexOf(document);
setLastEdited(new Date());
fireContentsChanged(this, idx, idx);
}
public void remove(Document document) {
int idx = documentList.indexOf(document);
documentList.remove(document);
root.getDocumentElement().removeChild(document.element);
setLastEdited(new Date());
fireIntervalRemoved(this, idx, idx);
}
public int removeDocuments(String name) {
int count = 0;
for (Document document; (document = getDocument(name)) != null;) {
root.getDocumentElement().removeChild(document.getElement());
documentList.remove(document);
count++;
}
return count;
}
public String resolve(String uri) {
if (systemId != null) {
return URI.create(systemId).resolve(uri).toString();
} else {
return uri;
}
}
public void save(File file, String encoding) {
try {
Element jxl = root.getDocumentElement();
jxl.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
jxl.setAttribute("xsi:noNamespaceSchemaLocation", "http://jpluck.sourceforge.net/jxl/jxl-2.1.xsd");
File temp = new File(file.getAbsolutePath() + ".tmp");
OutputFormat outputFormat = new OutputFormat("xml", encoding, true);
outputFormat.setLineSeparator(System.getProperty("line.separator"));
FileOutputStream out = new FileOutputStream(temp);
XMLSerializer serializer = new XMLSerializer(out, outputFormat);
serializer.asDOMSerializer().serialize(root);
out.close();
file.delete();
temp.renameTo(file);
this.file = file;
systemId = file.toURI().toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Saves the DocumentList using UTF-8 encoding. UTF-8 is the default encoding for XML documents.
*/
public void save(File file) throws IOException {
save(file, "UTF-8");
}
public synchronized void save() throws IOException {
if (file != null) {
save(file);
}
}
public void sortByColumn(int index, boolean ascending) {
Collections.sort(documentList, new DocumentComparator(index, ascending));
fireContentsChanged(this, 0, documentList.size());
}
static String dateToString(Date date) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
return df.format(date);
}
static Date stringToDate(String value) throws ParseException {
try {
return new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss").parse(value);
} catch (ParseException e) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(value);
} catch (ParseException e2) {
try {
return new SimpleDateFormat("yyyy-MM-dd HH:mm").parse(value);
} catch (ParseException e3) {
return new SimpleDateFormat("yyyy-MM-dd").parse(value);
}
}
}
}
void updated(Document document) {
int idx = documentList.indexOf(document);
fireContentsChanged(this, idx, idx);
}
public static String formatDate(Date date) {
if (date == null) {
return null;
}
Calendar cal = new GregorianCalendar();
cal.setTime(date);
Calendar now = new GregorianCalendar();
int day = cal.get(Calendar.DAY_OF_YEAR) - now.get(Calendar.DAY_OF_YEAR);
String s = null;
if (day == -1) {
s = "Yesterday";
} else if (day == 0) {
s = "Today";
} else if (day == 1) {
s = "Tomorrow";
} else {
s = new SimpleDateFormat("EEE d-MMM", Locale.ENGLISH).format(date);
}
s += (" " + DateFormat.getTimeInstance(DateFormat.SHORT).format(date));
return s;
}
private void importTemplates(DocumentBuilder db, org.w3c.dom.Document document, String systemId)
throws SAXException, IOException {
URI uri = URI.create(systemId);
JXPathContext context = JXPathContext.newContext(document.getDocumentElement());
for (Iterator it = context.iteratePointers("template"); it.hasNext();) {
Pointer pointer = (Pointer) it.next();
Element element = (Element) pointer.getNode();
Template template = new Template(element, this);
template.setSystemId(systemId);
templateList.add(template);
}
for (Iterator it = context.iterate("import/@href"); it.hasNext();) {
String href = (String) it.next();
InputSource importInputSource = new InputSource(uri.resolve(href).toString());
org.w3c.dom.Document importDocument = db.parse(importInputSource);
importTemplates(db, importDocument, importInputSource.getSystemId());
}
}
private void parse(InputSource inputSource, boolean validate)
throws IOException, SAXException {
JXLEntityResolver resolver = new JXLEntityResolver();
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setValidating(validate);
dbf.setNamespaceAware(true);
dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
"http://www.w3.org/2001/XMLSchema");
URL jxlURL = JXL.class.getClassLoader().getResource("net/sf/jpluck/jxl/jxl-2.1.xsd");
dbf.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", jxlURL.toString());
DocumentBuilder db = dbf.newDocumentBuilder();
db.setEntityResolver(resolver);
db.setErrorHandler(new ErrorHandler() {
private SAXException newSAXException(String type, SAXParseException e) {
return new SAXException(type + ": " + e.getMessage() + " (" + e.getLineNumber() + ")");
}
public void warning(SAXParseException exception)
throws SAXException {
//Ignore warnings
}
public void error(SAXParseException exception)
throws SAXException {
throw newSAXException("ERROR", exception);
}
public void fatalError(SAXParseException exception)
throws SAXException {
throw newSAXException("FATAL", exception);
}
});
root = db.parse(inputSource);
systemId = inputSource.getSystemId();
context = JXPathContext.newContext(root.getDocumentElement());
importTemplates(db, root, systemId);
// Check circular template references
for (Iterator iterator = templateList.iterator(); iterator.hasNext();) {
List childList = new ArrayList();
Template template = (Template) iterator.next();
while (true) {
try {
String s = (String) template.getValue("@template");
if (s == null) {
break;
}
Template parent = getTemplate(s);
if (parent == null) {
break;
}
if (childList.contains(parent)) {
throw new RuntimeException("Circular template reference encountered: id=" +
template.getID() + ", template=" + s);
} else {
childList.add(parent);
template = parent;
}
} catch (JXPathException e) {
break;
}
}
}
for (Iterator iterator = templateList.iterator(); iterator.hasNext();) {
Template template = (Template) iterator.next();
template.configureParentTemplate();
}
for (Iterator it = context.iteratePointers("site|feed"); it.hasNext();) {
Pointer pointer = (Pointer) it.next();
Element element = (Element) pointer.getNode();
if (element.getNodeName().equals("site")) {
documentList.add(new Site(element, this));
} else if (element.getNodeName().equals("feed")) {
documentList.add(new Feed(element, this));
}
}
} catch (SAXException e) {
if (resolver.isJXL2()) {
throw new SAXException("This is a JXL 2.0 file. Please convert it to JXL 2.1 first.");
} else {
throw e;
}
} catch (ParserConfigurationException e) {
// Should not occur under normal circumstances.
throw new RuntimeException(e);
}
}
private void populateElement(Element element, String template, String name, String uri, int linkDepth,
String restrict, String category) {
if (template != null) {
element.setAttribute("template", template);
}
Element nameElement = root.createElement("name");
element.appendChild(nameElement);
nameElement.appendChild(root.createTextNode(name));
Element uriElement = root.createElement("uri");
element.appendChild(uriElement);
uriElement.appendChild(root.createTextNode(uri));
uriElement.setAttribute("maxDepth", String.valueOf(linkDepth));
if (restrict != null) {
uriElement.setAttribute("restrict", restrict);
}
if (category != null) {
Element categoryElement = root.createElement("category");
categoryElement.appendChild(root.createTextNode(category));
element.appendChild(categoryElement);
}
}
public Template getDefaultTemplate() {
String id = root.getDocumentElement().getAttribute("defaultTemplate");
return getTemplate(id);
}
public void setLastEdited(Date date) {
if (date != null) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss");
root.getDocumentElement().setAttribute("lastEdited", df.format(date));
} else {
root.getDocumentElement().removeAttribute("lastEdited");
}
}
public Date getLastEdited() {
try {
Pointer p = context.getPointer("@lastEdited");
return JXL.stringToDate((String)p.getValue());
} catch (Exception e) {
return null;
}
}
public static class JXLEntityResolver implements EntityResolver {
private boolean jxl2;
public InputSource resolveEntity(String publicId, String systemId)
throws SAXException, IOException {
if ((publicId != null) && publicId.equals(PUBLIC_ID)) {
jxl2 = true;
return new InputSource(getClass().getClassLoader().getResource("net/sf/jpluck/jxl/jxl-2.0.dtd")
.toString());
} else {
return null;
}
}
public boolean isJXL2() {
return jxl2;
}
}
private static class DocumentComparator implements Comparator {
private boolean ascending;
private int column;
DocumentComparator(int column, boolean ascending) {
this.column = column;
this.ascending = ascending;
}
public int compare(Object o1, Object o2) {
Document d1 = (Document) o1;
Document d2 = (Document) o2;
Date date1;
Date date2;
int result = 0;
switch (column) {
case 0:
result = d1.getRawName().compareToIgnoreCase(d2.getRawName());
break;
case 1:
String c1 = d1.getPrimaryCategory();
if (c1 == null) {
c1 = "";
}
String c2 = d2.getPrimaryCategory();
if (c2 == null) {
c2 = "";
}
result = c1.compareToIgnoreCase(c2);
break;
case 2:
if (d1 instanceof Feed && d2 instanceof Site) {
result = -1;
} else if (d1 instanceof Site && d2 instanceof Feed) {
result = 1;
} else {
result = 0;
}
break;
case 3:
date1 = d1.getLastConverted();
date2 = d2.getLastConverted();
if (date1.before(date2)) {
result = -1;
} else if (date1.after(date2)) {
result = 1;
} else {
return 0;
}
break;
case 4:
result = (int) (d1.getLastSize() - d2.getLastSize());
break;
case 5:
date1 = d1.getDueDate();
date2 = d2.getDueDate();
if (date1.before(date2)) {
result = -1;
} else if (date1.after(date2)) {
result = 1;
} else {
return 0;
}
break;
}
return (ascending ? result : (result * -1));
}
}
}