/**
* SwingBoxEditorKit.java
* (c) Peter Bielik and Radek Burget, 2011-2012
*
* SwingBox is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SwingBox 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with SwingBox. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.fit.cssbox.swingbox;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import javax.swing.JEditorPane;
import javax.swing.JViewport;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultStyledDocument.ElementSpec;
import javax.swing.text.Document;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.ViewFactory;
import org.apache.commons.io.input.ReaderInputStream;
import org.fit.cssbox.io.DocumentSource;
import org.fit.cssbox.io.StreamDocumentSource;
import org.fit.cssbox.layout.Viewport;
import org.fit.cssbox.swingbox.util.CSSBoxAnalyzer;
import org.fit.cssbox.swingbox.util.Constants;
import org.fit.cssbox.swingbox.util.ContentReader;
import org.fit.cssbox.swingbox.util.ContentWriter;
import org.fit.cssbox.swingbox.util.GeneralEvent;
import org.fit.cssbox.swingbox.util.GeneralEvent.EventType;
import org.fit.cssbox.swingbox.util.MouseController;
/**
* This is custom implementation of EditoKit for (X)HTML with use of CSSBox.
*
* @author Peter Bielik
* @version 1.0
* @since 1.0 - 28.9.2010
*/
public class SwingBoxEditorKit extends StyledEditorKit
{
private static final long serialVersionUID = -2774578978116020429L;
/*private static final Pattern charsetPattern = Pattern
.compile("charset\\s*=[\\s'\"]*([\\-\\.\\:_0-9a-zA-Z]+)[\\s'\\\",;]*");*/
private CSSBoxAnalyzer cbanalyzer;
private ViewFactory vfactory;
private JEditorPane component;
private MouseController mcontroller;
/**
* Instantiates a new swing box editor kit.
*/
public SwingBoxEditorKit()
{
super();
String tmp;
tmp = System.getProperty(Constants.DEFAULT_ANALYZER_PROPERTY,
Constants.PROPERTY_NOT_SET);
if (tmp.equals(Constants.PROPERTY_NOT_SET))
{
// sets property for default analyzer, the fully qualified classname
// which is used to instantiate this class by reflection
System.setProperty(Constants.DEFAULT_ANALYZER_PROPERTY,
"org.fit.cssbox.swingbox.util.DefaultAnalyzer");
}
tmp = System.getProperty(
Constants.DOCUMENT_ASYNCHRONOUS_LOAD_PRIORITY_PROPERTY,
Constants.PROPERTY_NOT_SET);
if (tmp.equals(Constants.PROPERTY_NOT_SET))
{
// property not set, load synchronously !
System.setProperty(
Constants.DOCUMENT_ASYNCHRONOUS_LOAD_PRIORITY_PROPERTY,
"-1");
}
mcontroller = new MouseController();
}
/**
* Instantiates a new swing box editor kit with CSSBoxAnalyzer set.
*
* @param cba
* the CSSBoxAnalyzer to be set
*/
public SwingBoxEditorKit(CSSBoxAnalyzer cba)
{
this();
this.cbanalyzer = cba;
}
@Override
public void install(JEditorPane c)
{
super.install(c);
c.addMouseListener(mcontroller);
c.addMouseMotionListener(mcontroller);
component = c;
}
@Override
public void deinstall(JEditorPane c)
{
super.deinstall(c);
c.removeMouseListener(mcontroller);
c.removeMouseMotionListener(mcontroller);
component = null;
}
@Override
public Document createDefaultDocument()
{
SwingBoxDocument doc = new SwingBoxDocument();
// set asynchronous load priority. If set to -1, load synchronously,
// otherwise load asynchronously, with given priority :)
// this value is stored as internal property under
// AbstractDocument.AsyncLoadPriority key.
int priority = -1;// -1 == synchronously
String tmp = System.getProperty(
Constants.DOCUMENT_ASYNCHRONOUS_LOAD_PRIORITY_PROPERTY,
Constants.PROPERTY_NOT_SET);
if (!tmp.equals(Constants.PROPERTY_NOT_SET))
{
try
{
priority = Integer.parseInt(tmp);
} catch (Exception ignored)
{
}
}
doc.setAsynchronousLoadPriority(priority);
return doc;
}
@Override
public ViewFactory getViewFactory()
{
if (vfactory == null)
{
vfactory = new SwingBoxViewFactory();
}
return vfactory;
}
@Override
public String getContentType()
{
return "text/html";
}
@Override
public Caret createCaret()
{
return null;
}
@Override
public void write(OutputStream out, Document doc, int pos, int len)
throws IOException, BadLocationException
{
// this method closes OutputStream
if (doc instanceof SwingBoxDocument)
{
Writer tmpOut = new BufferedWriter(new OutputStreamWriter(out,
Charset.defaultCharset()), 8 * 1024);
writeImpl(tmpOut, (SwingBoxDocument) doc, pos, len);
tmpOut.flush();
tmpOut.close();
}
else
{
super.write(out, doc, pos, len);
}
}
@Override
public void write(Writer out, Document doc, int pos, int len)
throws IOException, BadLocationException
{
// this method closes OutputStream
if (doc instanceof SwingBoxDocument)
{
Writer tmpOut = new BufferedWriter(out, 8 * 1024);
writeImpl(tmpOut, (SwingBoxDocument) doc, pos, len);
tmpOut.flush();
tmpOut.close();
}
else
{
super.write(out, doc, pos, len);
}
}
@Override
public void read(InputStream in, Document doc, int pos) throws IOException,
BadLocationException
{
if (doc instanceof org.fit.cssbox.swingbox.SwingBoxDocument)
{
readImpl(in, (org.fit.cssbox.swingbox.SwingBoxDocument) doc, pos);
}
else
{
super.read(in, doc, pos);
}
}
@Override
public void read(Reader in, Document doc, int pos) throws IOException,
BadLocationException
{
if (doc instanceof org.fit.cssbox.swingbox.SwingBoxDocument)
{
InputStream is = new ReaderInputStream(in);
readImpl(is, (org.fit.cssbox.swingbox.SwingBoxDocument) doc, pos);
}
else
{
super.read(in, doc, pos);
}
}
/**
* Updates layout, using new dimensions.
*
* @param doc
* the document
* @param root
* the root box
* @param dim
* new dimension
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public void update(SwingBoxDocument doc, Viewport root, Dimension dim)
throws IOException
{
ContentReader rdr = new ContentReader();
List<ElementSpec> elements = rdr.update(root, dim, getCSSBoxAnalyzer());
ElementSpec elementsArray[] = elements.toArray(new ElementSpec[0]);
doc.create(elementsArray);
}
/**
* Allows to set custom CSSBoxAnalyzer
*
* @param cba
* the instance of CSSBoxAnalyzer
* @see CSSBoxAnalyzer
*/
public void setCSSBoxAnalyzer(CSSBoxAnalyzer cba)
{
this.cbanalyzer = cba;
}
/**
* Gets current instance of {@link CSSBoxAnalyzer}
*
* @return the instance of {@link CSSBoxAnalyzer}
*/
public CSSBoxAnalyzer getCSSBoxAnalyzer()
{
if (cbanalyzer == null)
{
cbanalyzer = getDefaultAnalyzer();
}
return cbanalyzer;
}
@SuppressWarnings("rawtypes")
protected CSSBoxAnalyzer getDefaultAnalyzer()
{
// possible to provide custom implementation
CSSBoxAnalyzer cba;
String cname = System.getProperty(Constants.DEFAULT_ANALYZER_PROPERTY,
Constants.PROPERTY_NOT_SET);
if (Constants.PROPERTY_NOT_SET.equals(cname))
{
cba = null;
}
else
{
try
{
Class c;
ClassLoader loader = getClass().getClassLoader();
if (loader != null)
{
c = loader.loadClass(cname);
}
else
{
c = Class.forName(cname);
}
Object o = c.newInstance();
if (o instanceof CSSBoxAnalyzer)
{
cba = (CSSBoxAnalyzer) o;
}
else
{
cba = null;
}
} catch (Exception e)
{
cba = null;
}
}
return cba;
}
private void readImpl(InputStream in, SwingBoxDocument doc, int pos)
throws IOException, BadLocationException
{
if (component == null)
throw new IllegalStateException("Component is null, editor kit is probably deinstalled from a JEditorPane.");
if (pos > doc.getLength() || pos < 0)
{
BadLocationException e = new BadLocationException("Invalid location", pos);
readError(null, e);
throw e;
}
ContentReader rdr = new ContentReader();
URL url = (URL) doc.getProperty(Document.StreamDescriptionProperty);
CSSBoxAnalyzer analyzer = getCSSBoxAnalyzer();
Container parent = component.getParent();
Dimension dim;
if (parent != null && parent instanceof JViewport)
{
dim = ((JViewport) parent).getExtentSize();
}
else
{
dim = component.getBounds().getSize();
}
if (dim.width <= 10)
{
// component might not be initialized, use screen size :)
Dimension tmp = Toolkit.getDefaultToolkit().getScreenSize();
dim.setSize(tmp.width / 2.5, tmp.height / 2.5);
}
// long time = System.currentTimeMillis();
List<ElementSpec> elements;
try
{
String ctype = null;
Object ct = doc.getProperty("Content-Type");
if (ct != null)
{
if (ct instanceof List)
ctype = (String) ((List<?>) ct).get(0);
else
ctype = ct.toString();
}
DocumentSource docSource = new StreamDocumentSource(in, url, ctype);
elements = rdr.read(docSource, analyzer, dim);
String title = analyzer.getDocumentTitle();
if (title == null)
title = "No title";
doc.putProperty(Document.TitleProperty, title);
} catch (IOException e)
{
readError(url, e);
throw e;
}
// System.out.println(System.currentTimeMillis() - time + " ms");
ElementSpec elementsArray[] = elements.toArray(new ElementSpec[0]);
doc.create(elementsArray);
// component.revalidate();
// component.repaint();
// System.out.println(System.currentTimeMillis() - time + " ms");
// Dictionary<Object, Object> dic = doc.getDocumentProperties();
// Enumeration<Object> en = dic.keys();
// while( en.hasMoreElements()) {
// Object k = en.nextElement();
// System.out.println(k + " " + dic.get(k));
// }
readFinish(url);
}
private void readError(URL url, Exception e)
{
if (component instanceof BrowserPane)
{
((BrowserPane) component).fireGeneralEvent(new GeneralEvent(this, EventType.page_loading_error, url, e));
// NodeList nodes =
// analyzer.getDocument().getElementsByTagName("meta");
}
}
private void readFinish(URL url)
{
if (component instanceof BrowserPane)
{
((BrowserPane) component).fireGeneralEvent(new GeneralEvent(this,
EventType.page_loading_end, url, null));
// NodeList nodes =
// analyzer.getDocument().getElementsByTagName("meta");
}
}
private void writeImpl(Writer out, SwingBoxDocument doc, int pos, int len)
throws BadLocationException, IOException
{
if (pos > doc.getLength() || pos < 0) { throw new BadLocationException(
"Invalid location", pos); }
if (len < 0) len = 0;
ContentWriter wrt = new ContentWriter();
StringBuilder sb = wrt.write(getCSSBoxAnalyzer().getDocument());
out.write(sb.toString());
out.flush();
}
}