From: Stefan Bund Date: Mon, 20 Sep 2010 11:56:32 +0000 (+0200) Subject: initial commit X-Git-Url: http://g0dil.de/git?p=jpim.git;a=commitdiff_plain;h=4095790c7943e91462bce2072234fe53f610f95b initial commit --- 4095790c7943e91462bce2072234fe53f610f95b diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..0cbf9cd --- /dev/null +++ b/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ce8045 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/bin/ +/config.xml diff --git a/.project b/.project new file mode 100644 index 0000000..fab5f6e --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + PIMStuff + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.springframework.ide.eclipse.core.springbuilder + + + + + + org.springframework.ide.eclipse.core.springnature + org.eclipse.jdt.core.javanature + + diff --git a/.springBeans b/.springBeans new file mode 100644 index 0000000..4dff647 --- /dev/null +++ b/.springBeans @@ -0,0 +1,13 @@ + + + 1 + + + + + + + + + + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..a0c6ac6 --- /dev/null +++ b/build.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/de/j32/avmfritz/FritzBox.java b/src/de/j32/avmfritz/FritzBox.java new file mode 100644 index 0000000..de8e63f --- /dev/null +++ b/src/de/j32/avmfritz/FritzBox.java @@ -0,0 +1,102 @@ +package de.j32.avmfritz; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.xml.sax.SAXException; + +import de.j32.httplib.HttpGETRequest; +import de.j32.httplib.HttpPOSTRequest; +import de.j32.httplib.HttpRequest; +import de.j32.httplib.HttpResponse; +import de.j32.util.Util; + +public class FritzBox +{ + String url_; + String sid_; + + public FritzBox(String password, String url) + throws SAXException, IOException + { + url_ = url; + + HttpResponse response = null; + try { + response = httpGet("cgi-bin/webcm") + .addParameter("getpage", "../html/login_sid.xml") + .execute(); + LoginXML loginxml = new LoginXML(response); + response.close(); + response = null; + + if (loginxml.iswriteaccess()) { + sid_ = loginxml.sid(); + return; + } + + response = httpPost("cgi-bin/webcm") + .addParameter("getpage", "../html/login_sid.xml") + .addParameter("var:lang", "de") + .addParameter("login:command/response", + loginxml.response(password)).execute(); + loginxml = new LoginXML(response); + response.close(); + response = null; + + if (!loginxml.iswriteaccess()) + throw new RuntimeException("FritzBox login failed"); + + sid_ = loginxml.sid(); + } + finally { + Util.nothrowClose(response); + } + + } + + public InputStream exportAddressbook() + throws IOException + { + return httpPostMultipart("cgi-bin/firmwarecfg") + .addParameter("sid", sid_) + .addParameter("PhonebookId", "0") + .addParameter("PhonebookExportName", "Telefonbuch") + .addParameter("PhonebookExport", "").execute(); + } + + public OutputStream importAddressbook() + throws IOException + { + return new ByteArrayOutputStream() { + public void close() + throws IOException + { + System.out.println("sending to fritzbox"); + httpPostMultipart("cgi-bin/firmwarecfg") + .addParameter("sid", sid_) + .addParameter("PhonebookId", "0") + .addParameter("PhonebookImportFile", toByteArray(), "iso-8859-1") + .execute() + .close(); + } + }; + } + + HttpRequest httpGet(String path) + { + return new HttpGETRequest(url_ + "/" + path); + } + + HttpRequest httpPost(String path) + { + return new HttpPOSTRequest(url_ + "/" + path); + } + + HttpRequest httpPostMultipart(String path) + { + return new HttpPOSTRequest(url_ + "/" + path).setMultipart(true); + } +} diff --git a/src/de/j32/avmfritz/LoginXML.java b/src/de/j32/avmfritz/LoginXML.java new file mode 100644 index 0000000..c50f80a --- /dev/null +++ b/src/de/j32/avmfritz/LoginXML.java @@ -0,0 +1,75 @@ +package de.j32.avmfritz; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import javax.xml.bind.annotation.adapters.HexBinaryAdapter; + +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +import de.j32.util.Util; +import de.j32.util.XmlUtil; + +public class LoginXML +{ + Document xml_; + + public LoginXML(InputStream is) + throws SAXException, IOException + { + xml_ = XmlUtil.parse(is); + } + + public boolean iswriteaccess() + throws SAXException + { + try { + return xml_.getElementsByTagName("iswriteaccess").item(0).getTextContent().equals("1"); + } + catch (NullPointerException e) { + throw new SAXException(); + } + } + + public String sid() + throws SAXException + { + try { + return Util.nonnull(xml_.getElementsByTagName("SID").item(0).getTextContent()); + } + catch (NullPointerException e) { + throw new SAXException(); + } + } + + public String challenge() + throws SAXException + { + try { + return Util.nonnull(xml_.getElementsByTagName("Challenge").item(0).getTextContent()); + } + catch (NullPointerException e) { + throw new SAXException(); + } + } + + public String response(String password) + throws SAXException + { + try { + String c = challenge(); + MessageDigest md = MessageDigest.getInstance("MD5"); + md.update((c + "-" + password).getBytes("UTF-16LE")); + return c + "-" + new HexBinaryAdapter().marshal(md.digest()).toLowerCase(); + } catch (NoSuchAlgorithmException e) { + // Is it at all feasible for this to happen ? + throw new RuntimeException("missing MD5 implementation"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("missing UTF-16LE encoding"); + } + } +} diff --git a/src/de/j32/httplib/HttpGETRequest.java b/src/de/j32/httplib/HttpGETRequest.java new file mode 100644 index 0000000..3929df6 --- /dev/null +++ b/src/de/j32/httplib/HttpGETRequest.java @@ -0,0 +1,18 @@ +package de.j32.httplib; + +public class HttpGETRequest + extends HttpRequest +{ + public HttpGETRequest(String url) + { + super(url,"GET"); + } + + @Override + public HttpGETRequest addParameter(String name, byte[] value, String encoding) + { + appendParameter(query(), query().length() == 0, name, value); + return this; + } + +} diff --git a/src/de/j32/httplib/HttpPOSTRequest.java b/src/de/j32/httplib/HttpPOSTRequest.java new file mode 100644 index 0000000..f59134e --- /dev/null +++ b/src/de/j32/httplib/HttpPOSTRequest.java @@ -0,0 +1,70 @@ +package de.j32.httplib; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; + +public class HttpPOSTRequest + extends HttpRequest +{ + boolean multipart_ = false; + static final String separator_ = + "----MultiPartFormData--MultiPartFormData--MultiPartFormData----"; + + public HttpPOSTRequest(String url) + { + super(url, "POST"); + setContentType("application/x-www-form-urlencoded; charset=utf-8"); + } + + public HttpPOSTRequest setMultipart(boolean flag) + { + multipart_ = flag; + if (multipart_) { + setContentType("multipart/form-data; boundary=" + separator_); + try { + OutputStream body = body(); + body.write("--".getBytes()); + body.write(separator_.getBytes()); + body.write("\r\n".getBytes()); + } + catch (IOException e) { + throw new AssertionError("ByteArrayOutputStream throwing IOExcpetion"); + } + } + return this; + } + + @Override + public HttpRequest addParameter(String name, byte[] value, String encoding) + { + try { + if (multipart_) { + OutputStream body = body(); + body.write("Content-Disposition: form-data; name=\"".getBytes()); + body.write(name.getBytes()); + body.write("\"\r\n".getBytes()); + body.write("Content-Type: text/plain; charset=".getBytes()); + body.write(encoding.getBytes()); + body.write("\r\n".getBytes()); + body.write(("Content-Length: " + value.length).getBytes()); + body.write("\r\n\r\n".getBytes()); + body.write(value); + body.write("\r\n--".getBytes()); + body.write(separator_.getBytes()); + body.write("\r\n".getBytes()); + } + else { + // Encoding not really relevant here since url-encoding is plain ASCII + Writer writer = new OutputStreamWriter(body(),"ascii"); + appendParameter(writer, body().size() == 0, name, value); + writer.flush(); + } + } + catch (IOException e) { + throw new AssertionError("ByteArrayOutputStream throwing IOExcpetion"); + } + return this; + } +} diff --git a/src/de/j32/httplib/HttpRequest.java b/src/de/j32/httplib/HttpRequest.java new file mode 100644 index 0000000..7f9c32e --- /dev/null +++ b/src/de/j32/httplib/HttpRequest.java @@ -0,0 +1,102 @@ +package de.j32.httplib; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; + +public abstract class HttpRequest +{ + StringBuffer url_; + StringBuffer query_ = new StringBuffer(); + String method_; + ByteArrayOutputStream body_ = new ByteArrayOutputStream(); + String contentType_; + + public HttpRequest(String url, String method) + { + url_ = new StringBuffer(url); + method_ = method; + } + + protected StringBuffer query() + { + return query_; + } + + protected ByteArrayOutputStream body() + { + return body_; + } + + protected void setContentType(String c) + { + contentType_ = c; + } + + protected static void appendParameter(Appendable buffer, boolean first, String name, byte[] value) + { + try { + if (! first) + buffer.append("&"); + buffer.append(URLEncoder.encode(name,"utf-8")); + buffer.append("="); + // We really would need a URLEncoder for byte[] (pre-encoded or raw date) + buffer.append(URLEncoder.encode(new String(value,"ascii"),"ascii")); + } + catch (UnsupportedEncodingException e) + { + throw new AssertionError("Missing encoding"); + } + catch (IOException e) + { + throw new AssertionError("IOException on buffer-based Appendable"); + } + } + + public HttpResponse execute() + throws MalformedURLException, IOException + { + if (query_.length() > 0) + url_.append("?"); + url_.append(query_); + System.out.println("Opening: " + url_); + HttpURLConnection connection = + (HttpURLConnection) new URL(new String(url_)).openConnection(); + System.out.println("Request method: " + method_); + connection.setRequestMethod(method_); + if (contentType_ != null) { + System.out.println("Content-Type: " + contentType_); + connection.setRequestProperty("Content-Type", contentType_); + connection.setDoOutput(true); + System.out.println("Body size: " + body_.size()); + connection.setFixedLengthStreamingMode(body_.size()); + System.out.println("Body:"); + System.out.println(body_.toString()); + connection.getOutputStream().write(body_.toByteArray()); + System.out.println("done..."); + } + if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) + throw new IOException("HTTP request failed: " + + connection.getResponseCode() + " " + connection.getResponseMessage()); + System.out.println("response ok"); + return new HttpResponse(connection); + } + + public HttpRequest addParameter(String name, String value) + throws UnsupportedEncodingException + { + return addParameter(name, value, "utf-8"); + } + + public HttpRequest addParameter(String name, String value, String encoding) + throws UnsupportedEncodingException + { + return addParameter(name, value.getBytes(encoding), encoding); + } + + abstract public HttpRequest addParameter(String name, byte[] value, String encoding); +} diff --git a/src/de/j32/httplib/HttpResponse.java b/src/de/j32/httplib/HttpResponse.java new file mode 100644 index 0000000..c35adb4 --- /dev/null +++ b/src/de/j32/httplib/HttpResponse.java @@ -0,0 +1,34 @@ +package de.j32.httplib; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; + +public class HttpResponse + extends InputStream +{ + HttpURLConnection connection_; + InputStream stream_; + + public HttpResponse(HttpURLConnection connection) + throws IOException + { + connection_ = connection; + stream_ = connection_.getInputStream(); + } + + @Override + public int read() + throws IOException + { + return stream_.read(); + } + + @Override + public void close() + throws IOException + { + stream_.close(); + } + +} diff --git a/src/de/j32/pimstuff/Main.java b/src/de/j32/pimstuff/Main.java new file mode 100644 index 0000000..173df38 --- /dev/null +++ b/src/de/j32/pimstuff/Main.java @@ -0,0 +1,96 @@ +package de.j32.pimstuff; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Properties; + +import org.xml.sax.SAXException; + +import de.j32.avmfritz.FritzBox; +import de.j32.pimstuff.conduit.FritzAddressbookReader; +import de.j32.pimstuff.conduit.FritzAddressbookWriter; +import de.j32.pimstuff.data.Addressbook; +import de.j32.util.Util; + +public class Main { + + public static void main(String[] args) + { + try { + + System.out.println("Launching pimstuff ..."); + Properties config = new Properties(); + + try { + config.loadFromXML(new FileInputStream("config.xml")); + } + catch (FileNotFoundException e) { + config.setProperty("password", "password"); + config.setProperty("url", "http://fritz.box"); + + config.storeToXML(new FileOutputStream("config.xml"), null, "UTF-8"); + } + + String pw, url; + + try { + pw = Util.nonnull(config.getProperty("password")); + url = Util.nonnull(config.getProperty("url")); + } + catch (NullPointerException e) { + throw new RuntimeException("Missing configuration parameter"); + } + + Addressbook ab = new Addressbook(); + FritzBox fb = new FritzBox(pw, url); + + System.out.println("loading ..."); + // Load Addressbook from FritzBox + InputStream is = null; + FritzAddressbookReader reader = null; + try { + ab = new Addressbook(); + is = fb.exportAddressbook(); + reader = new FritzAddressbookReader(is); + reader.sendTo(ab); + is.close(); + is = null; + reader.close(); + reader = null; + } + finally { + Util.nothrowClose(is); + } + + System.out.println("saving ..."); + // Save Addressbook back to FritzBox + FritzAddressbookWriter writer = null; + OutputStream os = null; + try { + os = fb.importAddressbook(); + writer = new FritzAddressbookWriter(os); + ab.sendTo(writer); + writer.close(); + writer = null; + os.close(); + os = null; + } + finally { + Util.nothrowClose(writer); + Util.nothrowClose(os); + } + + } catch (SAXException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + +} diff --git a/src/de/j32/pimstuff/conduit/Exporter.java b/src/de/j32/pimstuff/conduit/Exporter.java new file mode 100644 index 0000000..9fc72a0 --- /dev/null +++ b/src/de/j32/pimstuff/conduit/Exporter.java @@ -0,0 +1,9 @@ +package de.j32.pimstuff.conduit; + +import java.io.Closeable; + +import de.j32.pimstuff.data.EntryConsumer; + +public interface Exporter + extends Closeable, EntryConsumer +{} diff --git a/src/de/j32/pimstuff/conduit/FritzAddressbookReader.java b/src/de/j32/pimstuff/conduit/FritzAddressbookReader.java new file mode 100644 index 0000000..a00d294 --- /dev/null +++ b/src/de/j32/pimstuff/conduit/FritzAddressbookReader.java @@ -0,0 +1,67 @@ +package de.j32.pimstuff.conduit; + +import java.io.IOException; +import java.io.InputStream; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import de.j32.pimstuff.data.Entry; +import de.j32.pimstuff.data.EntryConsumer; +import de.j32.util.XmlUtil; + +public class FritzAddressbookReader + implements Importer +{ + Document xml_; + + public FritzAddressbookReader(InputStream is) + throws SAXException, IOException + { + xml_ = XmlUtil.parse(is); + } + + @Override + public void sendTo(EntryConsumer consumer) + { + for (Element node : XmlUtil.iterateElements(xml_.getElementsByTagName("contact"))) { + /* subnodes: + * category (unused, always 0) + * person/realName + * person/imageURL + * telephony/number (@prio, @type, @vanity) + * services/email + * mod_time + */ + Entry entry = new Entry(); + + try { + entry.name(node.getElementsByTagName("realName").item(0).getTextContent()); + + for (Element phone : XmlUtil.iterateElements(node.getElementsByTagName("number"))) { + entry.attribute("phone", phone.getAttribute("type"), phone.getTextContent()); + } + + try { + entry.attribute("email", "", + node.getElementsByTagName("email").item(0).getTextContent()); + } + catch (NullPointerException e) {} // ignore missing optional email + } + catch (NullPointerException e) { + // Ignore incomplete entries + entry = null; + } + + if (entry != null) + consumer.consume(entry); + } + } + + @Override + public void close() + throws IOException + {} + +} diff --git a/src/de/j32/pimstuff/conduit/FritzAddressbookWriter.java b/src/de/j32/pimstuff/conduit/FritzAddressbookWriter.java new file mode 100644 index 0000000..361caf8 --- /dev/null +++ b/src/de/j32/pimstuff/conduit/FritzAddressbookWriter.java @@ -0,0 +1,94 @@ +package de.j32.pimstuff.conduit; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; + +import org.xml.sax.SAXException; + +import de.j32.pimstuff.data.Attribute; +import de.j32.pimstuff.data.Entry; +import de.j32.util.SimpleXmlGenerator; +import de.j32.util.Util; + +public class FritzAddressbookWriter + implements Exporter +{ + OutputStream os_; + SimpleXmlGenerator gen_; + + public FritzAddressbookWriter(OutputStream os) + { + try { + os_ = os; + gen_ = new SimpleXmlGenerator(os_, "iso-8859-1"); + gen_.startDocument(); gen_.nl(); + gen_.start("phonebooks"); gen_.nl(); + gen_.start("phonebook"); gen_.nl(); + } + catch (SAXException e) + { + throw new AssertionError("Invalid XML/SAX document generated."); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("Unsopported encoding iso-8859-1 ??"); + } + } + + @Override + public void consume(Entry entry) + { + try { + gen_.start("contact"); gen_.nl(); + + gen_.start("category"); gen_.text("0"); gen_.end(); gen_.nl(); + + gen_.start("person"); gen_.nl(); + gen_.start("realName"); gen_.text(entry.name()); gen_.end(); gen_.nl(); + gen_.empty("imageURL"); gen_.nl(); + gen_.end(); gen_.nl(); + + gen_.start("telephony"); gen_.nl(); + for (Attribute number : entry.attributes("phone")) { + gen_.start("number", + gen_.attribute("prio", number.index > 0 ? "0" : "1") + .attribute("type", number.rel) + .attribute("vanity", "")); + gen_.text(number.value); gen_.end(); gen_.nl(); + } + gen_.end(); gen_.nl(); + + Attribute email = Util.first(entry.attributes("email")); + if (email != null) { + gen_.start("services"); gen_.nl(); + gen_.start("email", gen_.attribute("classifier","private")); + gen_.text(email.value); gen_.end(); gen_.nl(); + gen_.end(); gen_.nl(); + } + else { + gen_.empty("services"); gen_.nl(); + } + + gen_.end(); gen_.nl(); + } + catch (SAXException e) + { + throw new AssertionError("Invalid XML/SAX document generated."); + } + } + + @Override + public void close() + throws IOException + { + try { + gen_.end(); gen_.nl(); + gen_.end(); + gen_.endDocument(); + } + catch (SAXException e) + { + throw new AssertionError("Invalid XML/SAX document generated."); + } + } + +} diff --git a/src/de/j32/pimstuff/conduit/Importer.java b/src/de/j32/pimstuff/conduit/Importer.java new file mode 100644 index 0000000..9f7b4b2 --- /dev/null +++ b/src/de/j32/pimstuff/conduit/Importer.java @@ -0,0 +1,9 @@ +package de.j32.pimstuff.conduit; + +import java.io.Closeable; + +import de.j32.pimstuff.data.EntryProducer; + +public interface Importer + extends Closeable, EntryProducer +{} diff --git a/src/de/j32/pimstuff/data/Addressbook.java b/src/de/j32/pimstuff/data/Addressbook.java new file mode 100644 index 0000000..8c1d4b5 --- /dev/null +++ b/src/de/j32/pimstuff/data/Addressbook.java @@ -0,0 +1,31 @@ +package de.j32.pimstuff.data; + +import java.util.Iterator; +import java.util.LinkedList; + +public class Addressbook + implements EntryConsumer, EntryProducer +{ + LinkedList data_ = new LinkedList(); + + public void add(Entry entry) + { + data_.add(entry); + } + + public Iterator entries() + { + return data_.iterator(); + } + + public void consume(Entry entry) + { + add(entry); + } + + public void sendTo(EntryConsumer consumer) + { + for (Entry entry : data_) + consumer.consume(entry); + } +} diff --git a/src/de/j32/pimstuff/data/Attribute.java b/src/de/j32/pimstuff/data/Attribute.java new file mode 100644 index 0000000..3889ebb --- /dev/null +++ b/src/de/j32/pimstuff/data/Attribute.java @@ -0,0 +1,17 @@ +package de.j32.pimstuff.data; + +public class Attribute +{ + public String type; + public String rel; + public String value; + public int index; + + public Attribute(String type_, String rel_, String value_, int index_) + { + type = type_; + rel = rel_; + value = value_; + index = index_; + } +} diff --git a/src/de/j32/pimstuff/data/Entry.java b/src/de/j32/pimstuff/data/Entry.java new file mode 100644 index 0000000..909cd3b --- /dev/null +++ b/src/de/j32/pimstuff/data/Entry.java @@ -0,0 +1,75 @@ +package de.j32.pimstuff.data; + +import java.util.ArrayList; +import java.util.Iterator; + +import de.j32.util.Filter; +import de.j32.util.FilteredIterator; + +public class Entry +{ + long id_ = 0; + String name_ = ""; + ArrayList attributes_ = new ArrayList(); + + public void name(String name) + { + name_ = name; + } + + public String name() + { + return name_; + } + + public void id(long id) + { + id_ = id; + } + + public long id() + { + return id_; + } + + public void attribute(String type, String rel, String value) + { + attributes_.add(new Attribute(type, rel, value,attributes_.size())); + } + + public Iterable attributes() + { + return attributes_; + } + + public Iterable attributes(final String type) + { + return new Iterable() { + public Iterator iterator() { + return new FilteredIterator( + attributes_.iterator(), + new Filter() { + public boolean match(Attribute element) { + return element.type == type; + } + }); + } + }; + } + + public Iterable attributes(final String type, final String rel) + { + return new Iterable() { + public Iterator iterator() { + return new FilteredIterator( + attributes_.iterator(), + new Filter() { + public boolean match(Attribute element) { + return element.type == type && element.rel == rel; + } + }); + } + }; + } + +} diff --git a/src/de/j32/pimstuff/data/EntryConsumer.java b/src/de/j32/pimstuff/data/EntryConsumer.java new file mode 100644 index 0000000..98f3e07 --- /dev/null +++ b/src/de/j32/pimstuff/data/EntryConsumer.java @@ -0,0 +1,6 @@ +package de.j32.pimstuff.data; + +public interface EntryConsumer +{ + public void consume(Entry entry); +} diff --git a/src/de/j32/pimstuff/data/EntryProducer.java b/src/de/j32/pimstuff/data/EntryProducer.java new file mode 100644 index 0000000..9b79f97 --- /dev/null +++ b/src/de/j32/pimstuff/data/EntryProducer.java @@ -0,0 +1,6 @@ +package de.j32.pimstuff.data; + +public interface EntryProducer +{ + public void sendTo(EntryConsumer consumer); +} diff --git a/src/de/j32/util/Filter.java b/src/de/j32/util/Filter.java new file mode 100644 index 0000000..07f4359 --- /dev/null +++ b/src/de/j32/util/Filter.java @@ -0,0 +1,6 @@ +package de.j32.util; + +public interface Filter +{ + public boolean match(E element); +} diff --git a/src/de/j32/util/FilteredIterator.java b/src/de/j32/util/FilteredIterator.java new file mode 100644 index 0000000..26ad1f0 --- /dev/null +++ b/src/de/j32/util/FilteredIterator.java @@ -0,0 +1,54 @@ +package de.j32.util; + +import java.util.Iterator; +import java.util.NoSuchElementException; + + +public class FilteredIterator + implements Iterator +{ + Iterator base_; + Filter filter_; + E next_; + boolean hasNext_ = true; + + public FilteredIterator(Iterator base, Filter filter) + { + base_ = base; + filter_ = filter; + advance(); + } + + public E next() + { + if (hasNext_) { + E rv = next_; + advance(); + return rv; + } + else + throw new NoSuchElementException(); + } + + public boolean hasNext() + { + return hasNext_; + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + + void advance() + { + while (base_.hasNext()) { + next_ = base_.next(); + if (filter_.match(next_)) + return; + } + hasNext_ = false; + next_ = null; + } + +} diff --git a/src/de/j32/util/SimpleXmlGenerator.java b/src/de/j32/util/SimpleXmlGenerator.java new file mode 100644 index 0000000..cc8356d --- /dev/null +++ b/src/de/j32/util/SimpleXmlGenerator.java @@ -0,0 +1,127 @@ +package de.j32.util; + +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.util.Stack; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; + +public class SimpleXmlGenerator +{ + TransformerHandler handler_; + Stack openElements_ = new Stack(); + + public SimpleXmlGenerator(OutputStream os, String encoding) + throws UnsupportedEncodingException + { + SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance(); + // factory.setAttribute("indent-number", new Integer(2)); + try { + handler_ = factory.newTransformerHandler(); + } + catch (TransformerConfigurationException e) { + throw new RuntimeException("XML/SAX transformer configuration error"); + } + catch (TransformerFactoryConfigurationError e) { + throw new RuntimeException("XML/SAX transformer factory configuration error"); + } + Transformer tf = handler_.getTransformer(); + tf.setOutputProperty(OutputKeys.ENCODING,encoding); + // if (doctype != null) + // tf.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM,doctype); + // tf.setOutputProperty(OutputKeys.INDENT,indent ? "yes" : "no"); + handler_.setResult(new StreamResult(new OutputStreamWriter(os, encoding))); + } + + public void startDocument() + throws SAXException + { + handler_.startDocument(); + } + + public void start(String name) + throws SAXException + { + handler_.startElement("","",name,null); + openElements_.add(name); + } + + public static class Attributes + { + AttributesImpl attributes_ = new AttributesImpl(); + + public Attributes attribute(String name, String value) + { + attributes_.addAttribute("","",name,"CDATA",value); + return this; + } + + AttributesImpl getAttributes() + { + return attributes_; + } + + Attributes() {} + } + + public Attributes attribute(String name, String value) + { + Attributes a = new Attributes(); + return a.attribute(name, value); + } + + public void start(String name, Attributes attrs) + throws SAXException + { + handler_.startElement("","",name, attrs.getAttributes()); + openElements_.push(name); + } + + public void end() + throws SAXException + { + handler_.endElement("","",openElements_.pop()); + } + + public void empty(String name) + throws SAXException + { + start(name); + end(); + } + + public void empty(String name, Attributes attrs) + throws SAXException + { + start(name, attrs); + end(); + } + + public void text(String text) + throws SAXException + { + handler_.characters(text.toCharArray(), 0, text.length()); + } + + public void nl() + throws SAXException + { + text("\n"); + } + + public void endDocument() + throws SAXException + { + handler_.endDocument(); + } +} diff --git a/src/de/j32/util/Util.java b/src/de/j32/util/Util.java new file mode 100644 index 0000000..95a3f78 --- /dev/null +++ b/src/de/j32/util/Util.java @@ -0,0 +1,40 @@ +package de.j32.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; + +public class Util { + + public static E nonnull(E ob) { + if (ob == null) + throw new NullPointerException(); + return ob; + } + + public static void transfer(InputStream is, OutputStream os) + throws IOException { + byte[] buffer = new byte[16384]; + int len = -1; + while ((len = is.read(buffer)) != -1) + os.write(buffer, 0, len); + } + + public static void nothrowClose(Closeable c) { + if (c != null) { + try { + c.close(); + } catch (IOException e) {} + } + } + + public static E first(Iterable i) + { + Iterator it = i.iterator(); + if (it.hasNext()) + return it.next(); + return null; + } +} diff --git a/src/de/j32/util/XmlUtil.java b/src/de/j32/util/XmlUtil.java new file mode 100644 index 0000000..2ec9086 --- /dev/null +++ b/src/de/j32/util/XmlUtil.java @@ -0,0 +1,101 @@ +package de.j32.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.util.Iterator; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.EntityResolver; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class XmlUtil +{ + public static Document parse(InputStream is) + throws SAXException, IOException + { + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setValidating(false); + factory.setExpandEntityReferences(false); + DocumentBuilder builder = factory.newDocumentBuilder(); + builder.setEntityResolver(new EntityResolver() { + public InputSource resolveEntity(String publicId, String systemId) + throws SAXException, IOException + { + return new InputSource(new StringReader("")); + }}); + return builder.parse(is); + } + catch (ParserConfigurationException e) { + throw new RuntimeException("SAX/DOM parser configuration error"); + } + } + + public static Iterable iterateElements(final NodeList nodes) + { + return new Iterable() { + public Iterator iterator() { + return new NodeListIterator(nodes, Element.class); + } + }; + } + + static class NodeListIterator + implements Iterator + { + Class nodeType_; + NodeList nodes_; + int i_ = 0; + E next_; + + public NodeListIterator(NodeList nodes, Class nodeType) + { + nodes_ = nodes; + nodeType_ = nodeType; + advance(); + } + + @Override + public boolean hasNext() + { + return next_ != null; + } + + @Override + public E next() + { + E rv = next_; + advance(); + return rv; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + void advance() + { + while (i_ < nodes_.getLength()) { + Node n = nodes_.item(i_); + ++ i_; + if (nodeType_.isInstance(n)) { + next_ = (E) n; + return; + } + } + next_ = null; + } + } +}