/*
Jreepad - personal information manager.
Copyright (C) 2004-2006 Dan Stowell
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.
The full license can be read online here:
http://www.gnu.org/copyleft/gpl.html
*/
package jreepad.io;
import java.io.IOException;
import java.io.InputStream;
import jreepad.JreepadTreeModel;
import jreepad.ui.PasswordDialog;
/**
* Reads a Jreepad file automatically detecting file type (XML or HJT).
*
* @version $Id$
*/
public class AutoDetectReader
implements JreepadReader
{
XmlReader xmlReader;
TreepadReader treepadReader;
EncryptedReader encryptedReader;
public AutoDetectReader(String encoding, boolean autoDetectHtmlArticles)
{
xmlReader = new XmlReader();
treepadReader = new TreepadReader(encoding, autoDetectHtmlArticles);
encryptedReader = new EncryptedReader(this); // Use this reader as the underlying reader
}
public JreepadTreeModel read(InputStream in)
throws IOException
{
in = new RewindableInputStream(in);
// Read first line
String currentLine = ((RewindableInputStream)in).readLine();
in.reset(); // reset stream, so the specific readers read from the beginning
if (currentLine.startsWith("<?xml"))
{
return xmlReader.read(in);
}
else if ((currentLine.toLowerCase().startsWith("<treepad") && currentLine.endsWith(">")))
{
treepadReader.setFileFormat(1);
return treepadReader.read(in);
}
else if ((currentLine.toLowerCase().startsWith("<hj-treepad") && currentLine.endsWith(">")))
{
treepadReader.setFileFormat(1);
return treepadReader.read(in);
}
else if (currentLine.startsWith(EncryptedWriter.HEADER))
{
String password = PasswordDialog.showPasswordDialog("This file is encrypted. Please enter password:");
if (password == null)
throw new IOException("Could not decrypt. No password entered.");
encryptedReader.setPassword(password);
return encryptedReader.read(in);
}
else
{
System.out.println("First line of file does not indicate a recognised format:\n"
+ currentLine + "\n");
throw new IOException("First line of file does not indicate a recognised format:\n\n"
+ currentLine);
}
}
public boolean isAutoDetectHtmlArticles()
{
return treepadReader.isAutoDetectHtmlArticles();
}
public void setAutoDetectHtmlArticles(boolean autoDetectHtmlArticles)
{
treepadReader.setAutoDetectHtmlArticles(autoDetectHtmlArticles);
}
public String getEncoding()
{
return treepadReader.getEncoding();
}
public void setEncoding(String encoding)
{
treepadReader.setEncoding(encoding);
}
/**
* This class wraps the byte inputstreams we're presented with. We need it because
* java.io.InputStreams don't provide functionality to reread processed bytes, and they have a
* habit of reading more than one character when you call their read() methods. This means that,
* once we discover the true (declared) encoding of a document, we can neither backtrack to read
* the whole doc again nor start reading where we are with a new reader. This class allows
* rewinding an inputStream by allowing a mark to be set, and the stream reset to that position.
* <strong>The class assumes that it needs to read one character per invocation when it's read()
* method is inovked, but uses the underlying InputStream's read(char[], offset length)
* method--it won't buffer data read this way!</strong>
*
* @xerces.internal
* @author Neil Graham, IBM
* @author Glenn Marcy, IBM
*/
protected static class RewindableInputStream
extends InputStream
{
private static int BUFFER_SIZE = 2048;
private InputStream fInputStream;
private byte[] fData;
private int fStartOffset;
private int fEndOffset;
private int fOffset;
private int fLength;
private int fMark;
public RewindableInputStream(InputStream is)
{
fData = new byte[BUFFER_SIZE];
fInputStream = is;
fStartOffset = 0;
fEndOffset = -1;
fOffset = 0;
fLength = 0;
fMark = 0;
}
public void setStartOffset(int offset)
{
fStartOffset = offset;
}
public void rewind()
{
fOffset = fStartOffset;
System.out.println("Rewinding " + fOffset + "/" + fLength + " -> " + fStartOffset
+ "(end=" + fEndOffset + ")");
}
public int read()
throws IOException
{
int b = 0;
if (fOffset < fLength)
{
return fData[fOffset++] & 0xff;
}
if (fOffset == fEndOffset)
{
return -1;
}
if (fOffset == fData.length)
{
byte[] newData = new byte[fOffset << 1];
System.arraycopy(fData, 0, newData, 0, fOffset);
fData = newData;
}
b = fInputStream.read();
if (b == -1)
{
fEndOffset = fOffset;
return -1;
}
fData[fLength++] = (byte)b;
fOffset++;
return b & 0xff;
}
public int read(byte[] b, int off, int len)
throws IOException
{
int bytesLeft = fLength - fOffset;
if (bytesLeft == 0)
{
if (fOffset == fEndOffset)
{
return -1;
}
return fInputStream.read(b, off, len);
}
if (len < bytesLeft)
{
if (len <= 0)
{
return 0;
}
}
else
{
len = bytesLeft;
}
if (b != null)
{
System.arraycopy(fData, fOffset, b, off, len);
}
fOffset += len;
return len;
}
public long skip(long n)
throws IOException
{
int bytesLeft;
if (n <= 0)
{
return 0;
}
bytesLeft = fLength - fOffset;
if (bytesLeft == 0)
{
if (fOffset == fEndOffset)
{
return 0;
}
return fInputStream.skip(n);
}
if (n <= bytesLeft)
{
fOffset += n;
return n;
}
fOffset += bytesLeft;
if (fOffset == fEndOffset)
{
return bytesLeft;
}
n -= bytesLeft;
/*
* In a manner of speaking, when this class isn't permitting more than one byte at a
* time to be read, it is "blocking". The available() method should indicate how much
* can be read without blocking, so while we're in this mode, it should only indicate
* that bytes in its buffer are available; otherwise, the result of available() on the
* underlying InputStream is appropriate.
*/
return fInputStream.skip(n) + bytesLeft;
}
public int available()
throws IOException
{
int bytesLeft = fLength - fOffset;
if (bytesLeft == 0)
{
if (fOffset == fEndOffset)
{
return -1;
}
return fInputStream.available();
}
return bytesLeft;
}
public void mark(int howMuch)
{
fMark = fOffset;
}
public void reset()
{
fOffset = fMark;
}
public boolean markSupported()
{
return true;
}
public void close()
throws IOException
{
if (fInputStream != null)
{
fInputStream.close();
fInputStream = null;
}
}
public String readLine()
throws IOException
{
byte[] bytes = new byte[BUFFER_SIZE];
int len = 0;
while (len < BUFFER_SIZE)
{
int ret = read();
if (ret == -1 || ret == 0x0a || ret == 0x0d)
break;
bytes[len] = (byte)(ret & 0xff);
len++;
}
return new String(bytes, 0, len);
}
} // end of RewindableInputStream class
}